# kView INVESTOR — Fondation Inertia, Vue 3 et TypeScript

> Statut : fondation phase 2, pilote phase 4 et shell produit phase 5 validés
>
> Date : 18 juillet 2026
>
> Portée : socle technique et première route produit `/search`

## 1. Résultat concret

kView INVESTOR possède maintenant deux moteurs frontend dans des documents séparés :

```text
Routes Blade restantes
Laravel -> vue Blade -> layouts/app.blade.php -> app.js -> JavaScript historique

Routes Inertia authentifiées (`/search` et futures pages migrées)
Laravel -> réponse Inertia -> inertia.blade.php -> inertia.ts
        -> AuthenticatedLayout -> ApplicationShell persistant -> slot page

Spécimens Inertia local/testing
Laravel -> même racine/layout -> pages Foundation dans le même shell
```

La surface locale `GET /__kview/inertia` démontre le nouveau chemin complet :

- rendu d'une page Vue choisie par un contrôleur Laravel ;
- navigation sans rechargement complet du document ;
- historique et état local mémorisé ;
- formulaire envoyé à Laravel ;
- erreur de validation affichée et reliée au champ ;
- props partagées typées ;
- chunk JavaScript distinct par page.

Ces surfaces de démonstration n'existent qu'en environnement `local` et
`testing`. Les deux routes `application-shell` et la route produit `/search`
partagent désormais le même contrat de layout Vue persistant. Les routes métier
non migrées gardent un document et un shell Blade distincts jusqu'à la phase 6.

## 2. Versions du checkpoint

| Brique | Version verrouillée | Rôle |
| --- | --- | --- |
| Laravel | 13.20 | routes, contrôleurs, validation et métier |
| Inertia Laravel | 3.1.1 | protocole serveur |
| Inertia Vue/Vite | 3.6.1 | navigation et résolution des pages |
| Vue | 3.5.40 | rendu et état interactif |
| TypeScript | 5.9.3 | contrats statiques |
| Vite | 8.1.5 | build et découpage par page |
| `vue-tsc` | 3.3.7 | vérification des templates Vue |
| Vitest | 4.1.10 | tests unitaires frontend |
| ESLint | 9.39 | analyse statique frontend |
| Reka UI | 2.10.1 | mécanique interne des seuls `KviewDialog` et `KviewDropdown` |

Les versions exactes restent celles de `composer.lock` et `package-lock.json`.

## 3. Frontière Blade / Vue

### Racine Blade historique

- template : `resources/views/layouts/app.blade.php` ;
- entrée : `resources/js/app.js` ;
- propriétaire de navigation : `resources/js/ui-page-loading.js` ;
- composants : `x-ui.*` et `x-erp.*` ;
- routes : toutes les routes produit historiques non migrées.

### Racine Inertia

- template serveur : `resources/views/inertia.blade.php` ;
- entrée : `resources/js/inertia.ts` ;
- pages : `resources/js/Pages/` ;
- composants applicatifs temporaires : `resources/js/components/` ;
- styles : `resources/css/inertia.css` et modules importés ;
- propriétaire de navigation : Inertia.

Les pages authentifiées migrées, dont `/search`, déclarent
`defineOptions({ layout: AuthenticatedLayout })`. Le layout monte une seule fois
`ApplicationShell` puis Inertia remplace uniquement son slot de page. Les
anciens fichiers `inertia-dashboard.blade.php`, `inertia-dashboard.ts` et
`inertia-dashboard.css` sont retirés et ne constituent plus une architecture
supportée.

### Règles non négociables

1. Une entrée Inertia n'importe jamais `resources/js/app.js`.
2. `ui-page-loading.js` reste le propriétaire des routes Blade uniquement. Une
   racine Vue volontairement embarquée dans un spécimen utilise
   `[data-frontend-owner="vue"]`, mais `/search` n'en dépend plus.
3. Un lien entre deux pages Inertia utilise `Link`.
4. Un lien d'une racine Inertia vers une route Blade utilise une navigation
   complète avec un élément `a` normal.
5. Une route Blade ne devient pas partiellement Vue par montage opportuniste.
   Sa migration adopte la racine Inertia standard et un layout explicite.
6. La suppression d'un initialiseur `kviewInit*` attend la migration de son
   dernier consommateur.
7. `/search` conserve une seule topbar/sidebar Vue. Le shell possède la
   recherche globale et `Search/Index` possède son formulaire, ses erreurs et
   ses résultats locaux.
8. La sidebar desktop est ouverte à `240px` ou cachée à `0`, jamais compacte.
9. Un layout persistant est déclaré au niveau de la page ; la page ne remonte
   jamais son propre shell.
10. Une transition vers une route utilisant une autre racine reste native. Une
    visite entre `/search` et une future page Inertia partage le shell ; une
    sortie vers une route Blade recharge le document.
11. Logout utilise `POST`. Une session expirée ou un logout Inertia quitte la
    racine Vue par une redirection externe vers la page Blade `/login`.
12. La recherche globale Vue consomme le même endpoint que Blade, mais possède
    son propre Teleport et son propre cycle de vie. Elle ne rend aucun hook
    vanilla. Elle suit `item.inertia` seulement si l'hôte active `useInertia` :
    `/search` est same-root, les destinations Blade restent natives.
13. Settings consomme un payload typé issu du même contexte Laravel que Blade.
    Un clic ordinaire ouvre le dialog Vue ; l'ancre réelle reste un fallback et
    `/preferences?tab=...` ouvre explicitement la version Blade de comparaison.
14. Les pages ne chargent jamais Reka directement. Seuls `KviewDialog` et
    `KviewDropdown`, importés depuis `@kview/ui-core/vue`, peuvent l'utiliser
    derrière leur API et leurs styles kView.
15. `#application-content` reste le `scroll-region` Inertia. Le shell expose
    `aria-busy` immédiatement ; son skeleton contenu attend `120 ms` et ne
    s'affiche que lors d'un changement de pathname. Les recherches same-path,
    prefetch et visites sans progression ne le déclenchent pas.
16. Le composant `prepaint-preferences` est exécuté avant les assets dans les
    deux racines. Il restaure la préférence desktop sans transformer le drawer
    mobile transitoire en préférence persistante.

`FrontendMigrationGuardrailTest` vérifie la séparation des deux racines et
l'absence des anciennes entrées hybrides.

## 4. Contrat Laravel vers Vue

`App\Http\Middleware\HandleInertiaRequests` partage les données transversales.
Le miroir TypeScript se trouve dans `resources/js/types/inertia.ts`.

```text
auth.user
  id, name, email ou null

auth.permissions
  admin

flash
  success, error, status

preferences
  targetCurrency, itemsPerPage, amountScaling, decimalSeparator,
  dateFormatView, dateFormatList

locale
  locale Laravel active

ui
  messages, resourceIcons

navigation
  brand, URLs, sections, permissions, active state, link mode,
  Settings data URL + Blade comparison URL
```

Les permissions et validations réelles restent côté Laravel. Le type
TypeScript prévient les incohérences pendant le développement ; il ne remplace
pas la sécurité serveur.

Le middleware lit une éventuelle ligne `user_config` sans la créer. Une visite
de page ne produit donc pas d'écriture implicite en base. Les props `ui` et
`navigation`, ajoutées par les phases 3 et 3.5, passent respectivement par
`App\Support\KviewUiProps` et `App\Support\ApplicationNavigation`.

## 5. Découpage du code

L'entrée appelle `createInertiaApp` avec une résolution lazy du dossier
`Pages`. Vite a produit lors du checkpoint :

| Asset | Taille | gzip |
| --- | ---: | ---: |
| socle Inertia/Vue | 175,24 kB | 59,94 kB |
| CSS Inertia + kView Core | 45,95 kB | 8,89 kB |
| page Index | 4,89 kB | 1,97 kB |
| page Navigation | 2,88 kB | 1,27 kB |
| shell partagé | 1,65 kB | 0,83 kB |

Ces tailles sont des mesures locales de build, pas des objectifs de données
terrain. Le point important est que les pages sont séparées et que le bundle
historique `app.js` n'est pas chargé dans cette racine.

## 6. Pourquoi Reka reste interne, sans Pinia ni migration Tailwind globale

Le socle continue de privilégier les contrats kView :

- Reka UI est installé uniquement pour les comportements accessibles complexes
  de `KviewDialog` et `KviewDropdown`. Les écrans importent les composants
  kView, conservent leurs classes, tokens, dimensions et apparence, et ne voient
  aucune API Reka ;
- Pinia n'est pas installé, car les props Inertia, l'état local Vue et
  `useRemember` couvrent le besoin démontré ;
- le SSR est désactivé, car kView INVESTOR est un SaaS authentifié et aucun bénéfice
  mesuré ne le justifie actuellement ;
- la page technique consomme directement les tokens et styles sémantiques
  `kview/ui-core`, afin de vérifier la continuité visuelle avant de créer les
  adaptateurs Vue ;
- Tailwind reste disponible pour la composition future, mais sa migration ou
  son extension n'est pas mélangée au changement de moteur.

La phase 3 a confirmé ce choix : les primitives Vue réutilisent le CSS
sémantique kView, tandis que le shell et les compositions applicatives gardent
des modules CSS ciblés. Tailwind reste disponible sans devenir une condition de
la migration.

## 7. Ajouter une page Inertia

1. Créer la page sous `resources/js/Pages/<Domaine>/<Page>.vue`.
2. Définir ses props dans un type qui étend `PageProps`.
3. Retourner `Inertia::render('<Domaine>/<Page>', $props)` depuis Laravel avec
   la racine standard `inertia`.
4. Placer la route dans le groupe `web` normal avec ses permissions.
5. Utiliser des composants kView Vue ; ne pas importer Reka depuis la page.
6. Ajouter `defineOptions({ layout: AuthenticatedLayout })` à toute route
   authentifiée qui rejoint le shell persistant.
7. Utiliser `Link` entre pages Inertia partageant la même racine et un lien
   complet vers Blade ou vers une autre architecture de racine.
8. Ajouter un test Laravel du contrat, un test Vue du composant principal et
   un parcours Playwright.
9. Vérifier clavier, focus, validation, mobile, axe et absence d'erreur console.
10. Comparer les mesures avec la route Blade avant de supprimer celle-ci.

Une route produit ne doit pas être migrée avant les primitives kView Vue dont
elle dépend. `/search` applique désormais ce contrat comme premier exemple :
payload `SearchPageProps` minimal, page `Search/Index`, composition kView,
historique Inertia et sorties natives vers les destinations encore Blade.

## 8. Commandes de contrôle

```bash
php artisan test
vendor/bin/pint --test <fichiers PHP modifiés>
vendor/bin/phpstan analyse --no-progress --memory-limit=512M
npm run typecheck
npm run lint:frontend
npm run test:frontend
npm run build
npx playwright test tests/e2e/inertia-foundation.spec.ts
npx playwright test tests/e2e/search-pilot.spec.ts
```

Le test Playwright s'exécute sur les projets Chromium desktop et mobile. Il
contrôle la conservation du document, la navigation, l'erreur Laravel, le lien
ARIA du champ, l'absence de débordement horizontal et les violations axe
critiques.

## 9. Résultats historiques du checkpoint initial de phase 2

| Contrôle | Résultat |
| --- | --- |
| PHPUnit | 117 tests, 818 assertions |
| nouveaux tests Inertia Laravel | 5 réussis |
| garde-fous d'architecture | 2 réussis |
| Vitest / Vue Test Utils | 2 tests réussis |
| TypeScript / `vue-tsc` | réussi en mode strict |
| ESLint | réussi |
| Playwright dédié | 2 parcours réussis, desktop et mobile |
| axe | aucune violation critique |
| débordement horizontal | aucun |
| build Vite production | réussi, pages découpées |
| audit npm | 0 vulnérabilité |
| audit Composer | aucune vulnérabilité connue |
| PHPStan | 137 erreurs historiques ; 0 dans les nouveaux fichiers |

## 10. Étapes 3, 3.5, 4 et clôture de phase 5

La phase 3 a livré vingt-deux adaptateurs UI, un provider typé, un catalogue
d'icônes JSON commun à PHP et TypeScript, ainsi qu'un dictionnaire Laravel vers
Vue. La phase 3.5 a ajouté la navigation serveur partagée, `AppLink`, un shell
Vue authentifié local et le composant `SearchResultGroup`.

Le détail, les frontières et les vérifications sont dans
`dev_guide/frontend-kview-vue-foundation.md`.

La phase 4 a migré la carte `/search` vers `Search/Index`. À ce checkpoint,
Laravel fournissait
`query`, `searchUrl` et les groupes déjà normalisés ; la page ne reçoit aucun
modèle Eloquent brut. `inertia-dashboard.blade.php` l'embarquait dans le dashboard
Blade historique : il restait donc une seule topbar/sidebar classique, tandis que
la racine `[data-frontend-owner="vue"]` isolait la carte du moteur de navigation
partielle. Les liens des résultats restent natifs vers GP, fonds et
investisseurs encore Blade. L'ancienne vue Blade a été supprimée après
validation ; sa baseline reste reproductible au commit `b276b8a`.

Le contrat de phase 4 couvre historique précédent/suivant, scroll, chargement,
erreur réseau et relance de la carte, focus après rendu, double soumission
rapide, clavier, axe, une seule topbar, indépendance entre le menu global et le
champ local, isolation par le marqueur Vue, absence de chevauchement de la
topbar à 1 024 px et débordement horizontal aux cinq largeurs contrôlées :
1 440, 1 024, 919, 768 et 390 px. Le parcours Playwright réécrit passe sur
desktop et mobile ; les largeurs intermédiaires ne sont exécutées qu'une fois,
côté desktop.

Après vidage explicite du cache par CDP, le checkpoint desktop local final
retient la médiane de trois chargements cold : responseEnd `25 ms`, DCL
`63 ms`, load `119 ms`, ressources transférées brutes `641 468 B`, ressources
décodées `686 411 B`, LCP `108 ms`, CLS `0` et aucune tâche d'au moins `50 ms`.
Le serveur PHP local ne compresse pas les assets ; le budget cold porte donc
sur le payload brut hybride Blade + Vue et autorise au maximum `700 000 B`.

À chaud, la visite Inertia naturelle desktop a une médiane de `76 ms`, une
requête, `4,2–7,7 kB` transférés, un INP observé de `16–24 ms`, CLS `0` et
aucune longue tâche. Le délai artificiel de `250 ms` reste réservé au test de
chargement et n'entre jamais dans les mesures naturelles. Ces observations
locales ne sont ni des percentiles production ni comparables à un run servi
avec compression.

Le parcours authentifié nécessite
`php artisan db:seed --class=CrudDemoSeeder`, `E2E_EMAIL` et `E2E_PASSWORD` ;
leur absence fait échouer la CI, même si le test peut être ignoré localement.

La phase 5.1 ajoute `AuthenticatedLayout` et une seconde page technique. Les
tests navigateur prouvent que l'instance DOM du shell et son état de sidebar
survivent à l'aller-retour. Logout est désormais `POST` dans Blade et Vue ; un
logout Inertia ou une session expirée produit une redirection externe vers le
login Blade, tandis que le flux natif conserve la redirection Laravel.

À ce checkpoint, la phase 5.2 ajoutait `ApplicationGlobalSearch` à ce layout uniquement.
`GlobalSearchConfiguration` fournit à la fois les actions de
`SearchController` et les valeurs par défaut partagées par
`ApplicationNavigation`. Le frontend reçoit un contrat typé et réutilise
`/search/suggest` avec debounce, abort immédiat, protection contre les réponses
désordonnées, clavier combobox/listbox et panneau téléporté/collisionné. La
topbar produit Blade restait inchangée.

Le checkpoint complet réussit `153` tests PHP / `1 797` assertions, `13`
fichiers Vitest / `60` tests et `11` passages Playwright pour `3` skips
intentionnels. PHPStan ciblé est vert sur les fichiers 5.1–5.2 ; sa dernière
mesure globale, effectuée au checkpoint 5.1, comptait `136` erreurs historiques.

La phase 5.3 ajoute au même layout `KviewToastProvider`, le propriétaire Excel
Login et le compte complet. `ApplicationNavigation.account` fournit les URLs
User Settings, Data Status, Workspace et Excel ; `auth.user.avatarUrl` fournit
l'image privée cache-bustée. Le menu s'appuie sur `KviewPopover` et
`KviewAvatar`, tandis que les flashes et Excel utilisent `useKviewToast`.
Chaque surface Vue Teleport directement dans `body` et ne partage aucun hook
avec le runtime Blade.

La caractérisation Playwright du shell historique couvre toujours Settings,
toasts, Excel, Escape et restitution du focus. Le parcours équivalent du shell
technique vérifie le protocole mocké, l'absence de vol de focus, les routes du
compte, le popover ouvert sous axe et l'absence de hooks legacy. Le checkpoint
réussit `156` tests PHP / `1 869` assertions et `17` fichiers Vitest / `75`
tests, en plus du typecheck, d'ESLint, du build, de Pint et de PHPStan ciblé.
La matrice Playwright complète est verte avec `24` passages et `6` skips
intentionnels ; les archives du package passent dans des applications propres
Laravel 11, 12 et 13 et leur manifeste de release est cohérent.

La phase 5.4 ajoute `ApplicationSettingsDialog` au même shell technique. Le
service frontend demande paresseusement un `SettingsPayload` typé au contrôleur
historique avec les en-têtes JSON/modale, puis réutilise les endpoints Laravel
existants pour les formulaires, erreurs `422`, avatar multipart, préférences et
configuration dynamique. `App\Support\ApplicationSettings` construit une
seule fois les onglets, permissions, options, statuts et URLs : ce contexte est
passé tel quel à la vue Blade ou sérialisé sans internals de modèles pour Vue.

`KviewDialog` et `KviewDropdown` sont les deux nouvelles primitives publiques
du package. Leur moteur Reka reste interne ; `ApplicationSettingsDialog` et ses
panels n'importent que `@kview/ui-core/vue`. Le clic ordinaire sur Settings dans
le shell technique ouvre la modale Vue sans démonter la page. Les vraies ancres
restent disponibles pour les navigations natives/modifiées, et l'action
« Compare Blade » ouvre `/preferences?tab=<onglet>` dans un nouvel onglet afin
de comparer les deux implémentations pendant la migration.

La suite PHP complète réussit `158` tests / `1 919` assertions. Typecheck,
ESLint, la suite Vitest complète (`19` fichiers / `81` tests), le build Vite,
`composer validate`, `test:ui-release` et `test:ui-consumer` sont verts. Le
parcours Settings Playwright passe sur desktop et mobile : il vérifie le
payload JSON Vue, la comparaison Blade HTML, la restitution du focus et `Ctrl/Cmd+K`, le dropdown,
la confirmation imbriquée et axe sans violation sur les surfaces contrôlées.

Le premier consommateur d'archive propre a révélé que les peers de l'entrée Vue
devaient être résolus explicitement hors du monorepo. `kview/ui-core` déclare
désormais `vue` et `reka-ui` en `peerDependencies`, et le fixture Vite aliasé
les résout depuis le host. Les consommateurs Laravel 11, 12 et 13 sont verts.
Un consommateur Blade-only n'importe pas cette entrée facultative et n'a besoin
ni d'installer ni de livrer Vue ou Reka au navigateur.

Le checkpoint de parité visuelle post-5.4 aligne ensuite le shell Vue sur les
mesures calculées du shell Blade : rail `3px`, sidebar `240px`/`0`, topbar
`50px`, logo `28px`, recherche `520 × 38px`, contrôles `36px` et navigation
`42px`. Les graisses historiques restent explicites (`750` pour kView INVESTOR,
`550` pour la byline et `600` pour les libellés de navigation) avec la même
famille `DM Sans`. Le pictogramme Excel vient du même registre kView dans les
deux moteurs. Un test Playwright dédié compare ces géométries à `±1px` sur
desktop/mobile et vérifie que la recherche compacte mobile s'ouvre au premier
clic.

La clôture de phase 5 adopte ensuite ce layout sur `/search`. Le contrôleur
utilise la racine standard, `Search/Index` déclare `AuthenticatedLayout` et les
trois fichiers `inertia-dashboard.*` disparaissent. Le même bootstrap de
pré-paint est inclus avant assets dans les deux racines ; il restaure la
préférence desktop, tandis que le drawer mobile reste transitoire.

Le shell produit active `useInertia` pour que l'action globale `/search`
respecte son flag `inertia: true`. Le panneau téléporté se ferme avant la
visite ; quick-create et résultats restent natifs vers Blade. Le contenu
principal devient le `scroll-region` Inertia. `aria-busy` est immédiat, le
skeleton contenu attend `120 ms` et seulement un changement de pathname, les
visites same-path conservent les résultats, et la view transition n'anime pas
le shell ni ne crée un second progress bar.

La validation de clôture compte `159` tests PHP / `1 960` assertions, `19`
fichiers Vitest / `86` tests et `29` passages Playwright pour `7` skips
intentionnels sur les projets desktop/mobile. Typecheck, ESLint, build Vite,
PHPStan ciblé, Composer, manifeste de release et consommateurs Laravel 11, 12
et 13 sont verts ; axe ne remonte aucune violation sur les surfaces contrôlées.
La médiane cold locale du root persistant est `526 709 B`, avec LCP
`84 ms` desktop / `72 ms` mobile, CLS `0` et aucune longue tâche.

Les composants complexes encore absents, la visualisation et `ui-data` restent
différés jusqu'à leur écran consommateur ; Reka ne doit s'étendre qu'au sein
d'une future primitive kView dont le contrat le justifie. Les routes métier
restent Blade jusqu'à leur migration en phase 6.
