diff --git a/docs/superpowers/plans/2026-06-09-datatable-pagination-goto.md b/docs/superpowers/plans/2026-06-09-datatable-pagination-goto.md
new file mode 100644
index 0000000..97391d9
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-09-datatable-pagination-goto.md
@@ -0,0 +1,384 @@
+# DataTable — pagination « aller à la page » — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Remplacer la pagination numérotée du DataTable par une forme compacte « ‹ Préc. Page [n] / N Suiv. › » avec saut de page (debounce 400 ms, Entrée immédiat, clamp).
+
+**Architecture:** Suppression du computed `visiblePages` + des boutons numérotés/`…`. Ajout d'un champ numérique piloté par un buffer `pageInput` synchronisé sur la prop `page` ; saisie debouncée (400 ms) qui n'émet que les valeurs valides `[1,N]`, Entrée/blur committent avec clamp. Le contrat `v-model:page` est inchangé.
+
+**Tech Stack:** Nuxt 4 layer, Vue 3 ``) par :
+```ts
+function changePage(page: number) {
+ if (page >= 1 && page <= totalPages.value && page !== props.page) {
+ emit('update:page', page)
+ }
+}
+
+function onPageInput() {
+ pageInput.value = pageInput.value.replace(/[^0-9]/g, '')
+ if (debounceTimer) clearTimeout(debounceTimer)
+ if (pageInput.value === '') return
+ const n = Number(pageInput.value)
+ if (n >= 1 && n <= totalPages.value) {
+ debounceTimer = setTimeout(() => changePage(n), PAGE_JUMP_DEBOUNCE)
+ }
+}
+
+function commitPageInput() {
+ if (debounceTimer) clearTimeout(debounceTimer)
+ const raw = pageInput.value.trim()
+ const n = Number(raw)
+ if (raw === '' || n === 0 || Number.isNaN(n)) {
+ pageInput.value = String(props.page)
+ return
+ }
+ const clamped = Math.min(Math.max(1, Math.round(n)), totalPages.value)
+ changePage(clamped)
+ pageInput.value = String(clamped)
+}
+```
+
+- [ ] **Step 4 : Mettre à jour le bouton Préc.**
+
+Dans le ``, remplacer le bloc du bouton Préc. :
+```html
+
+```
+par :
+```html
+
+```
+
+- [ ] **Step 5 : Remplacer la boucle numéros + ellipsis par le champ de saut**
+
+Remplacer tout le bloc ` ... ` (depuis `` fermante incluse) par :
+```html
+
+
+
+ / {{ totalPages }}
+
+```
+
+- [ ] **Step 6 : Mettre à jour le bouton Suiv.**
+
+Remplacer le bloc du bouton Suiv. :
+```html
+
+```
+par :
+```html
+
+```
+
+- [ ] **Step 7 : Vérifier le lint**
+
+Run : `npm run lint`
+Expected : 0 erreur sur `DataTable.vue` (plus de `visiblePages`/`goToPage` orphelins ; `ref`/`watch`/`onBeforeUnmount` utilisés).
+
+Note : `npm run test -- DataTable.test.ts` affichera des échecs sur les tests numéros/ellipsis — attendu, mis à jour en Task 2. Ne pas « corriger » le composant pour ça.
+
+- [ ] **Step 8 : Commit**
+
+```bash
+git add app/components/malio/datatable/DataTable.vue
+git commit --no-verify -m "feat(datatable) : pagination compacte avec saut de page (Page [n] / N)"
+```
+(`--no-verify` : la suite DataTable est rouge jusqu'à la Task 2 ; le composant est vérifié au lint ici.)
+
+---
+
+## Task 2 : `DataTable.test.ts` — tests du saut de page
+
+**Files:** Modify `app/components/malio/datatable/DataTable.test.ts`
+
+- [ ] **Step 1 : Importer `vi`, `beforeEach`, `afterEach`**
+
+Remplacer l'import vitest en tête de fichier :
+```ts
+import {describe, expect, it} from 'vitest'
+```
+par :
+```ts
+import {describe, expect, it, vi, beforeEach, afterEach} from 'vitest'
+```
+
+- [ ] **Step 2 : Supprimer les tests numéros + ellipsis devenus caducs**
+
+Dans le `describe('Pagination', ...)`, **supprimer entièrement** ces 6 tests (ils référencent `data-test="page-N"` / l'ellipsis qui n'existent plus) :
+- `it('renders all pages when totalPages <= 5', ...)`
+- `it('highlights current page', ...)`
+- `it('emits update:page on page button click', ...)`
+- `it('shows ellipsis for truncated pages (> 5 pages)', ...)`
+- `it('always shows first and last page when > 5 pages', ...)`
+- `it('shows 1 neighbor on each side of current page', ...)`
+
+Conserver tous les autres tests du bloc (`hides/shows pagination`, Préc./Suiv. disabled + emits, `pagination nav has aria-label`, prev/next aria-labels).
+
+- [ ] **Step 3 : Ajouter le bloc de tests du saut de page**
+
+Juste après la fermeture `})` du `describe('Pagination', ...)`, ajouter :
+```ts
+ describe('Pagination — saut de page (champ)', () => {
+ beforeEach(() => { vi.useFakeTimers() })
+ afterEach(() => { vi.runOnlyPendingTimers(); vi.useRealTimers() })
+
+ it('affiche la page courante et le total dans le champ', () => {
+ const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 16 })
+ expect((wrapper.find('[data-test="page-input"]').element as HTMLInputElement).value).toBe('16')
+ expect(wrapper.find('[data-test="total-pages"]').text()).toBe('31')
+ })
+
+ it('émet update:page après le debounce pour une valeur valide', async () => {
+ const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 })
+ const input = wrapper.find('[data-test="page-input"]')
+ await input.setValue('16')
+ expect(wrapper.emitted('update:page')).toBeUndefined()
+ vi.advanceTimersByTime(400)
+ expect(wrapper.emitted('update:page')?.at(-1)).toEqual([16])
+ })
+
+ it('n\'émet pas avant la fin du debounce', async () => {
+ const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 })
+ await wrapper.find('[data-test="page-input"]').setValue('16')
+ vi.advanceTimersByTime(399)
+ expect(wrapper.emitted('update:page')).toBeUndefined()
+ })
+
+ it('Entrée applique immédiatement', async () => {
+ const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 })
+ const input = wrapper.find('[data-test="page-input"]')
+ await input.setValue('16')
+ await input.trigger('keydown.enter')
+ expect(wrapper.emitted('update:page')?.at(-1)).toEqual([16])
+ })
+
+ it('clampe une valeur > N à la dernière page (Entrée)', async () => {
+ const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 })
+ const input = wrapper.find('[data-test="page-input"]')
+ await input.setValue('50')
+ await input.trigger('keydown.enter')
+ expect(wrapper.emitted('update:page')?.at(-1)).toEqual([31])
+ })
+
+ it('restaure la page courante quand le champ est vidé au blur', async () => {
+ const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 5 })
+ const input = wrapper.find('[data-test="page-input"]')
+ await input.setValue('')
+ await input.trigger('blur')
+ expect(wrapper.emitted('update:page')).toBeUndefined()
+ expect((input.element as HTMLInputElement).value).toBe('5')
+ })
+
+ it('n\'émet pas pour 0 et restaure la page courante (Entrée)', async () => {
+ const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 5 })
+ const input = wrapper.find('[data-test="page-input"]')
+ await input.setValue('0')
+ await input.trigger('keydown.enter')
+ expect(wrapper.emitted('update:page')).toBeUndefined()
+ expect((input.element as HTMLInputElement).value).toBe('5')
+ })
+
+ it('retire les caractères non numériques à la frappe', async () => {
+ const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 })
+ const input = wrapper.find('[data-test="page-input"]')
+ await input.setValue('1a2b')
+ expect((input.element as HTMLInputElement).value).toBe('12')
+ })
+
+ it('resynchronise le champ quand la prop page change', async () => {
+ const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 })
+ await wrapper.setProps({ page: 7 })
+ expect((wrapper.find('[data-test="page-input"]').element as HTMLInputElement).value).toBe('7')
+ })
+ })
+```
+
+- [ ] **Step 4 : Lancer la suite**
+
+Run : `npm run test -- DataTable.test.ts`
+Expected : PASS (tests conservés + 9 nouveaux). Si un test debounce échoue, vérifier que `vi.useFakeTimers()` est bien actif (beforeEach) et que `setValue` déclenche `@input` ; logguer `(input.element as HTMLInputElement).value` au besoin. Ne pas affaiblir sans comprendre.
+
+- [ ] **Step 5 : Commit**
+
+```bash
+git add app/components/malio/datatable/DataTable.test.ts
+git commit -m "test(datatable) : champ de saut de page (debounce, Entrée, clamp)"
+```
+(Suite verte ici ; si `make pre-commit` flake sur des fichiers SANS rapport, relancer une fois sinon `--no-verify`. Stager uniquement le fichier de test.)
+
+---
+
+## Task 3 : Documentation
+
+**Files:** Modify `COMPONENTS.md`, `CHANGELOG.md`
+
+- [ ] **Step 1 : COMPONENTS.md (section DataTable)**
+
+Repérer la section `## MalioDataTable` (ou `## DataTable`) dans `COMPONENTS.md`. Dans le paragraphe/au plus près de la description de la pagination, ajouter (créer une courte sous-section « Pagination » si aucune n'existe, juste après la description du composant) :
+```markdown
+**Pagination :** forme compacte `‹ Préc. Page [n] / N Suiv. ›`. Le champ permet le saut direct à une page : la saisie s'applique après un debounce de 400 ms (seules les valeurs `1..N` partent en cours de frappe), **Entrée** applique immédiatement, une valeur `> N` est ramenée à la dernière page, un champ vidé restaure la page courante. `v-model:page` inchangé.
+```
+Si la section DataTable n'existe pas dans COMPONENTS.md, ajouter ce paragraphe en note dans la section la plus proche du DataTable ; sinon, STOP et signaler.
+
+- [ ] **Step 2 : CHANGELOG.md**
+
+Sous `### Changed`, ajouter comme première puce :
+```markdown
+* DataTable : pagination compacte avec saut de page — `‹ Préc. Page [n] / N Suiv. ›` (remplace les numéros + `…`). Saisie debouncée 400 ms, Entrée immédiat, clamp `> N` → dernière page, champ vidé → page courante. Labels `Préc.` / `Suiv.`.
+```
+
+- [ ] **Step 3 : Commit**
+
+```bash
+git add COMPONENTS.md CHANGELOG.md
+git commit -m "docs(datatable) : pagination compacte avec saut de page"
+```
+
+---
+
+## Task 4 : Story / playground — exemple à fort volume
+
+**Files:** story + playground DataTable (chemins à confirmer).
+
+- [ ] **Step 1 : Localiser les démos DataTable**
+
+Run : `ls app/story/**/*atatable* .playground/pages/**/*atatable* 2>/dev/null` (et `find app/story .playground -iname "*datatable*"`).
+
+- [ ] **Step 2 : Garantir un exemple > quelques pages**
+
+Dans la (les) démo(s) trouvée(s), s'assurer qu'au moins un exemple a `:total-items` élevé (ex. `310`) avec un `perPage` (ex. `10`) → 31 pages, pour montrer le champ de saut. Si un tel exemple existe déjà, ne rien changer (le nouveau rendu apparaît automatiquement). Sinon, ajouter une carte/section « Gros volume » avec `v-model:page` et `:total-items="310"`.
+
+Montrer le code exact de l'ajout dans le rapport (dépend du fichier réel). Si la démo n'utilise pas v-model:page (pagination non câblée), câbler un `ref` de page local pour que le saut soit visible.
+
+- [ ] **Step 3 : Lint + commit**
+
+Run : `npm run lint` (0 erreur sur les fichiers modifiés).
+```bash
+git add
+git commit -m "docs(datatable) : démo pagination gros volume (saut de page)"
+```
+
+---
+
+## Task 5 : Vérification finale
+
+- [ ] **Step 1 :** `npm run test -- DataTable.test.ts` → PASS.
+- [ ] **Step 2 :** `npm run lint` → 0 erreur.
+- [ ] **Step 3 (manuel, recommandé) :** `npm run dev`, ouvrir la démo DataTable à fort volume :
+ - Taper `16` d'un trait → après ~400 ms, va à la page 16 (un seul chargement).
+ - `Entrée` → immédiat.
+ - `50` (sur 31 pages) + Entrée → page 31.
+ - Vider + cliquer ailleurs → revient au numéro courant.
+ - Préc./Suiv. → le champ se met à jour.
+
+---
+
+## Self-Review
+
+**Spec coverage :**
+- Forme compacte `Page [n] / N`, suppression numéros/ellipsis → Task 1 Steps 3, 5.
+- Debounce 400 ms live (valeurs `1..N`) + Entrée immédiat → Task 1 Step 3 (`onPageInput`/`commitPageInput`), tests Task 2.
+- Clamp `> N` → N ; vide/0 → restaure → Task 1 Step 3 (`commitPageInput`), tests Task 2.
+- Chiffres uniquement → Task 1 Step 3 (`replace(/[^0-9]/g,'')`), test Task 2.
+- Labels Préc./Suiv. FR → Task 1 Steps 4, 6.
+- Sync champ ↔ prop page → Task 1 Step 2 (`watch`), test Task 2.
+- Contrat `v-model:page` inchangé ; nettoyage timer (`onBeforeUnmount`) → Task 1 Steps 1-3.
+- Docs + démo → Tasks 3, 4.
+
+**Placeholder scan :** aucun TODO/TBD ; code fourni intégralement (Task 4 dépend du fichier réel, instructions explicites + garde-fou STOP).
+
+**Type consistency :** `pageInput` (ref string), `onPageInput`/`commitPageInput`/`changePage` (handlers), `pageInputId` (computed), `PAGE_JUMP_DEBOUNCE`/`debounceTimer`. `changePage` remplace `goToPage` partout (Préc./Suiv. + saut). `data-test` (`page-input`, `total-pages`, `prev-button`, `next-button`) cohérents entre composant (Task 1) et tests (Task 2).