diff --git a/docs/superpowers/specs/2026-06-09-inputamount-separateurs-milliers-design.md b/docs/superpowers/specs/2026-06-09-inputamount-separateurs-milliers-design.md
new file mode 100644
index 0000000..59074ec
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-09-inputamount-separateurs-milliers-design.md
@@ -0,0 +1,117 @@
+# MalioInputAmount — séparateurs de milliers (affichage groupé FR)
+
+**Date :** 2026-06-09
+**Statut :** Validé, prêt pour plan d'implémentation
+**Périmètre :** `MalioInputAmount` uniquement.
+
+## Objectif
+
+Afficher les montants avec des **séparateurs de milliers** (espaces) et une **virgule décimale**, à la française : `1 234 567,89`. Demande du client ERP. Le formatage est **purement visuel** ; la valeur émise reste une chaîne numérique propre.
+
+## Décisions validées
+
+| Sujet | Décision |
+|-------|----------|
+| Valeur émise (`modelValue`) | Reste **propre** : point décimal, sans espaces (`'1234567.89'`). Contrat consommateur inchangé. Les séparateurs ne sont qu'à l'affichage. |
+| Moment du formatage | **Temps réel** (à la frappe), avec gestion du curseur. |
+| Format affiché | Français : **espace** pour les milliers, **virgule** pour les décimales (`1 234 567,89`). |
+| Activation | **Par défaut sur tous** les `MalioInputAmount` (pas de prop opt-in). |
+| `maxLength` | S'applique à la **longueur du `modelValue`** (chaîne propre), **pas** à l'affichage. Le `maxlength` natif (qui compterait les espaces) est retiré ; le plafond est appliqué en JS. |
+| Extraction | Les fonctions pures vont dans `app/components/malio/input/composables/amountFormat.ts` (testables en isolation). |
+
+## Conception détaillée
+
+### 1. Contrat de valeur & flux de données
+
+- **Modèle (émis)** : sortie inchangée de `normalizeAmount` — point décimal, max 2 décimales, zéros de tête retirés, `''` si vide. Ex. `1234567.89`.
+- **Affichage** : `formatGroupedAmount(model)` groupe la partie entière par 3 avec des espaces et remplace le point par une virgule. Ex. `1 234 567,89`. Si le modèle finit par `.` (décimale en cours, ex. `12.`), l'affichage finit par `,` (`12,`).
+- **Binding** : l'input affiche `formatGroupedAmount(currentValue)` au lieu de `currentValue`.
+- **Parse** : à la frappe, le texte saisi (avec espaces/virgule) repasse par `normalizeAmount` → modèle propre → émis.
+
+### 2. Temps réel & gestion du curseur
+
+À chaque `@input` :
+1. Lire `rawText = target.value` et `caret = target.selectionStart`.
+2. `model = normalizeAmount(rawText)`.
+3. **Plafond `maxLength`** (cf. §3) : si dépassement, ignorer le keystroke (restaurer l'affichage précédent, ne pas émettre).
+4. Sinon : `display = formatGroupedAmount(model)` ; écrire `target.value = display` ; **émettre** `update:modelValue(model)`.
+5. **Repositionner le curseur** : compter les caractères significatifs (tout sauf les espaces de groupement) à gauche du curseur dans `rawText`, puis placer le curseur après ce même nombre de caractères significatifs dans `display`.
+
+Helpers curseur (purs) :
+```ts
+export const countSignificant = (str: string, upTo: number): number =>
+ str.slice(0, upTo).replace(/ /g, '').length
+
+export const caretFromSignificant = (display: string, sig: number): number => {
+ let seen = 0
+ for (let i = 0; i < display.length; i++) {
+ if (display[i] !== ' ') seen++
+ if (seen >= sig) return i + 1
+ }
+ return display.length
+}
+```
+
+`` supporte l'API de sélection, donc `setSelectionRange` fonctionne directement (pas de try/catch nécessaire).
+
+### 3. `maxLength` sur le modèle
+
+- On **retire** le binding `:maxlength="maxLength"` natif de l'`` (il compterait les espaces de l'affichage).
+- Dans `onInput`, après `model = normalizeAmount(rawText)` : si `props.maxLength != null` **et** `model.length > Number(props.maxLength)`, on **ignore** le keystroke :
+ - on restaure `target.value = formatGroupedAmount(currentValue)` (modèle précédent),
+ - on replace le curseur à `max(0, caret - 1)` (le caractère refusé n'est pas inséré),
+ - on **n'émet pas**.
+- `maxLength` borne donc la **longueur de la chaîne `modelValue`** (point décimal inclus). Ce point est documenté explicitement.
+- `minLength` : laissé tel quel (attribut natif de validation). Connu : il s'évalue sur le texte affiché ; hors périmètre de cette évolution.
+
+### 4. Helpers extraits — `composables/amountFormat.ts`
+
+- `normalizeAmount(value: string): string` — **déplacé tel quel** depuis le composant (parse).
+- `formatGroupedAmount(model: string): string` — nouveau (format groupé FR). Algorithme :
+ - Si `model === ''` → `''`.
+ - Séparer sur `.` → `integerPart`, `decimalPart` (présent ssi le modèle contient `.`).
+ - Grouper `integerPart` par paquets de 3 depuis la droite avec une espace `' '`.
+ - Si le modèle contient `.` → `groupedInteger + ',' + decimalPart` (decimalPart éventuellement vide).
+ - Sinon → `groupedInteger`.
+- `countSignificant`, `caretFromSignificant` — helpers curseur (purs).
+
+Le composant importe ces helpers ; la logique DOM (lecture `target.value`, `setSelectionRange`) reste dans `InputAmount.vue`.
+
+### 5. Table de vérité (format/parse)
+
+| Saisie utilisateur | `modelValue` émis | Affichage (`formatGroupedAmount`) |
+|---|---|---|
+| `1234567` | `1234567` | `1 234 567` |
+| `1234,56` ou `1234.56` | `1234.56` | `1 234,56` |
+| `12.` (décimale en cours) | `12.` | `12,` |
+| `,5` | `0.5` | `0,5` |
+| `0012345abc` | `12345` | `12 345` |
+| `1234.567` (3 décimales) | `1234.56` | `1 234,56` |
+| `` (vide) | `` | `` |
+| `0` | `0` | `0` |
+
+## Tests
+
+**`composables/amountFormat.test.ts`** (nouveau) :
+- `normalizeAmount` : reprise des cas existants (espaces, virgule→point, zéros de tête, 2 décimales max, vide, décimale en tête).
+- `formatGroupedAmount` : table §5 (groupement par 3, virgule décimale, `12.`→`12,`, vide→vide, nombres < 1000 inchangés).
+- `countSignificant` / `caretFromSignificant` : positions de curseur clés (avant/après un espace inséré, en fin de chaîne).
+
+**`InputAmount.test.ts`** (mis à jour) :
+- Les assertions `input.element.value` passent de la valeur brute (`1234.56`) à la valeur groupée (`1 234,56`).
+- Les assertions d'émission `update:modelValue` restent **inchangées** (modèle propre : `'1234.56'`, `'0.5'`, `''`…).
+- Nouveaux tests : groupement à la frappe d'un grand montant (`1234567` → affichage `1 234 567`, émis `1234567`) ; `maxLength` plafonne le modèle (un keystroke au-delà est ignoré, pas d'émission supplémentaire) ; position du curseur après insertion d'un séparateur.
+
+## Livrables documentaires
+
+- `COMPONENTS.md` : note dans la section `## MalioInputAmount` — affichage groupé FR (`1 234 567,89`), `modelValue` reste propre (`'1234567.89'`), `maxLength` borne la longueur du modèle.
+- `CHANGELOG.md` : entrée sous `### Added` / `### Changed`.
+- Story `app/story/input/inputAmount.story.vue` : exemple grand montant montrant les séparateurs.
+- Playground `.playground/pages/composant/input/inputAmount.vue` : exemple grand montant + affichage de la valeur ISO/propre émise.
+
+## Hors périmètre
+
+- Internationalisation configurable (autres locales / séparateurs paramétrables) — on fige le format FR.
+- `minLength` sur le modèle (reste natif sur l'affichage).
+- Passage à `maska` en mode number (approche écartée au profit du `normalizeAmount` existant).
+- Devises / symboles dynamiques (l'icône € existante est conservée telle quelle).