Agorastore · Plateforme frontend · Septembre 2026

Les nouveautés
de la stack front.

Query · Marketplace · Entity Details · Hors-ligne — comment on s’en sert.

01 Query
client typé · config · useQuery · mutations · prefetch
02 Market
prefetch en prod · CatalogList · SEO & images OG
03 Admin
Entity Details : loader · config · sections · shell — sélecteurs distants · DataListRoot
04 Inventory
hors-ligne : coquille · référentiel · brouillons
01

Query

Le client typé · l’utiliser · le configurer · le consommer · muter · précharger.

1.1le client typé

Rien ne change dans l’api-sdk. Tout le client en sort.

À gauche : ce qu’on écrivait déjà. À droite : ce que createApiOperationClient en génère, pour les 44 contrôleurs.

$client
├─ items
│  ├─ getFull    query
│  ├─ search     query
│  ├─ update     mutation
│  └─ deleteMany mutation
├─ organisations get · search · getHierarchy · update …
├─ bidding getProxyBids · queryBids …
├─ categories · characteristics · contracts
└─ 44 contrôleurs · types inférés des DTO Zod
query → .queryOptions() · .call() · .queryKey() · .key()mutation → .mutationOptions() · .call() · .mutationKey()useNuxtApp().$client
1.2utiliser le client

Quatre méthodes par query, une clé derrière.

La clé est toujours ['api', controller, operation, input] : même input, même entrée de cache — quel que soit l’endroit du code.

$client.items.getFull ['api','items','getFull', input] .queryOptions({ input, select?, enabled? }) → useQuery · tables · formulaires · SSR · prefetch .call(input) lecture impérative, hors cache, typée .queryKey(input) la clé exacte d’une entrée — setQueryData ciblé .key() le préfixe de l’opération — invalidation large
1.3configuration globale

defineQueryConfig — une fois par app, pour toutes les queries.

Le moteur est commun ; chaque app décide fraîcheur, retry, invalidation, préchauffage et persistance. Les écrans n’en savent rien.

1
mutationInvalidationaprès une mutation, quelles queries deviennent périmées — par défaut le contrôleur, avec exceptions.
2
queryClient · defaultOptionsle TanStack classique : staleTime, gcTime, focus, retry par type d’erreur.
3
warmupqueries lancées après la première page, en priorité basse — les référentiels sont chauds quand un formulaire s’ouvre.
4
persistencebrancher un persister (IndexedDB) et une liste blanche — le cache survit à la fermeture.
1.3configuration globale · les quatre apps

Même contrat, quatre politiques.

Ce que chaque config/query.ts a choisi, et pourquoi.

customer-front
Transactionnel authentifié : rafraîchir vite, sans churn au focus.
staleTime15 s
gcTime15 min
focus refetchnon
retry1
invalidationcontroller
admin-front
Poste de travail dense : revenir sur l’onglet montre l’état récent.
staleTime5 s
gcTime10 min
focus refetchoui
retry≥ 500 · 1×
warmup3 référentiels · 30 min
market-front
Public, SSR : ne jamais rejouer une 404, ne pas refetch au remontage.
staleTime30 s
gcTime∞ serveur · 30 min
retryOnMountnon
retry408 · 429 · ≥ 500
prefetchplans de route
inventory-front
PWA terrain : référentiel disponible 24 h sans réseau.
staleTime24 h
reconnexionrefetch
retry1 · mutations 3
persistanceIndexedDB
scopesession × shop
1.3configuration globale · intégrations

Les intégrations sont des capacités, pas des plugins.

Désactivé par défaut dans la couche partagée. Une app active ; le plugin propriétaire lit la config avant d’injecter le moindre script.

capacitécustomeradminmarketinventory
googleTagManager
didomi · consentement
recaptcha
googlePlaces
markerIo · feedback

0coût nul pour ce qu’on n’active pas

Inventory ne paie ni analytics, ni consentement, ni Places, ni feedback — et garde reCAPTCHA. Étendre la couche partagée ne veut pas dire charger tout ce qu’elle contient.

1.4consommer · useQuery

Notre useQuery : TanStack, plus ce qui manquait.

Un getter d’options, un second argument, un retour étendu — la même signature dans les quatre apps.

getterinput ou enabled changent → autre clé, autre requête. Pas de watch, pas de refetch().
defaultValue · pageContexttype sans undefined ; l’entité alimente fil d’Ariane et titre.
onData · onErrordéclenchés une fois par mise à jour du cache — pas à chaque render, pas au remontage sur données fraîches.
retour étendupatchData, setData, suspense… visent l’entrée exacte qu’on lit — la clé n’est jamais recalculée.
1.4consommer · useQuery · en situation

Six choses qu’on n’écrit plus à la main.

Extraits réels des apps — un onglet par capacité. Chacun remplace un watch, un store ou un refetch d’autrefois.

1
onDatadériver un état local, une fois par donnée — plus de watch à protéger.
2
onErrordéconnecter, capturer dans l’error boundary.
3
pageContextfil d’Ariane et titre dérivés de l’entité.
4
patchDatapousser le résultat d’une écriture — plus de commit + rechargement.
5
data en écritureun live update écrit dans le cache — tous les observateurs suivent.
6
suspense()attendre la 1ʳᵉ résolution — SSR, préchargement hors-ligne.
1.4consommer · tables, formulaires, catalogue

Un queryOptions, six consommateurs.

Partout où une donnée entre, c’est la même déclaration — donc la même clé, le même cache, la même invalidation.

1.5mutations

Écrire, puis laisser invalider.

Avant : le code appelant décide quoi recharger. Après : le contrôleur de la mutation devient périmé, selon la politique de l’app.

1.6prefetch intelligent

La page déclare ses données. Le lien les charge.

Au survol d’un lien, Nuxt exécute le plan de la page destination dans le même cache que useQuery lira à l’arrivée.

survol · focus · viewport d’un <NuxtLink> hook link:prefetch chunk de la route le JS de la destination plan de données étapes en série queries en parallèle QueryClient · ['api','items','getFull', id] ensureQueryData · revalidateIfStale clic → useQuery lit la même clé → hit survols répétés : une seule promesse · plan en échec : navigation intacte accueil → produit : 417 ms → 77 ms
1.6prefetch intelligent · les formes

Une option, une liste, ou un plan.

Le nom de route type les params ; le contexte donne client et route (plus prismic sur market-front).

dédoublonné par fullPathchunk et données en parallèleun plan en échec n’empêche jamais la navigation
02

Marketplace

Ce qui n’existe que sur market-front.

2.1prefetch · en production

Chaque page déclare son plan. Chaque lien le lance.

Le résultat mesuré en production, et un plan de page réel : entité + catalogue préparés ensemble.

accueil → produit V1 · 417 ms V2 · 77 ms · ×5,4 produit → accueil V1 · 623 ms V2 · 138 ms · ×4,5 benchmark du 21/07/2026 · 5 cycles à chaud · survol 800 ms chrono arrêté quand le contenu de destination est visible
index · catalogue · catégorie · séance · vendeur · produit · Prismictoutes les pages ont un plan
2.2CatalogList

Un DataList pour la découverte : la même famille, d’autres clés.

Ce que chaque zone de la page catalogue doit à une clé de defineCatalogSchema. En bleu : ce qui n’existe pas côté DataList.

1
Catalogue1 284 résultatsTrier : fin d’enchère ↑
2
Catégorie
Manutention 312
Machines-outils 208
Véhicules 144
Prix
Localisation
France 1 102
Belgique 182
3
Chariot élévateur Toyota6 200 €
Presse hydraulique 40 t1 450 €
Tour CNC Haas ST-1018 900 €
Compresseur Atlas Copco2 300 €
Nacelle Haulotte7 800 €
Pelle Kubota U2715 200 €
4
12354/catalogue/page2
5
clé du schéma → zone
1#top · #toolbar
slots : titre, total, tri, sauvegarder la recherche
2filters + source.facets
les filtres, alimentés par les agrégats de la recherche
3renderItem · gridSize
({ row }) => <ItemCard item={row} /> — une carte par ligne, pas de colonnes
4pagination.route
segment de chemin indexable, pas de query string
5context · runtimeContextType
vendeur / catégorie résolus avant la requête · contexte d’écran typé
behavior.onFilterChange
renvoyer une route — changer de catégorie ramène au catalogue
source.query · filters · sort · searchQuery · staticFilters · persistency
identiques au DataList
2.3CatalogList

Un schéma, trois consommateurs.

Le composant, le SSR et le prefetch lisent la même définition — un filtre change une fois.

itemCatalogSchema() source · context · filtres · tri · rendu <CatalogList> rendu client filtres, tri, pagination, URL SSR suspense même requête, hydratée prefetchCatalog context → source + facets depuis l’URL un filtre change dans le schéma → les trois changent
2.4SEO

Deux composables. Tout dérive de l’entité.

useMarketPageSeo pour une page, useMarketItemSeo pour une fiche : six sorties réactives.

item + marché locale · shop · marque · preview title · descriptionuseSeoMeta canonical · hreflang5 locales · URL par marque robotsnoindex si non publié image OG 1200×630defineOgImage Schema.orgProduct · Breadcrumb · WebPage OG · Twitterunfurl Slack, LinkedIn…
réactif : le prix change → l’offre Schema.org et la carte OG changent
2.5images OG

Une image de partage = un composant Vue.

defineOgImage reçoit des props réactives ; le composant Takumi est rendu côté serveur en JPG.

AGORASTORE
Manutention · Chariots élévateurs
Chariot élévateur électrique Toyota 8FBE18, 2019
6 200 €enchère en cours
rendu serveur → JPG 1200×630cache 7 joursMarket.takumi pour les pages, Product.takumi pour les fichesnouveau type de page = un composant + un defineOgImage
2.6modules Nuxt

Un module par corvée. On configure, on ne code pas.

Ce que chaque module sert en plus de la page — robots, sitemaps, images OG, JSON-LD, llms.txt — sans une ligne de plomberie dans market-front.

nuxt.config.ts · modules: [ '@nuxtjs/i18n', '@nuxtjs/seo', 'nuxt-ai-ready', 'nuxt-vitalizer', '@radya/nuxt-dompurify', 'nuxt-lazytube', 'vue3-carousel-nuxt' ]
seo
@nuxtjs/seo

Le bundle SEO, piloté par la config

Identité multi-tenant selon l’hôte. Robots : filtres et pagination exclus, crawlers d’entraînement bloqués. Sitemaps produits par mois. Schema.org : defineProduct, defineBreadcrumb, defineEvent.

/robots.txt/sitemap_index.xmlproducts-2026-09.xmlld+jsoncanonical
og
@nuxtjs/seo · og-image

Une image de partage = un composant

defineOgImage('Product.takumi', props) — rendu serveur en JPG 1200×630, cache 7 jours, images restreintes à l’origine. Un nouveau type de page : un composant Vue de plus.

og:imagetwitter:cardProduct.takumi.vueMarket.takumi.vue
ai
nuxt-ai-ready

Lisible par les agents IA — à nos conditions

Content-Signal : recherche et usage à la demande autorisés, entraînement refusé. Un llms.txt par tenant — catalogue, catégories, index complet, et la règle : « la page fait foi ».

/llms.txt/llms-full.txtContent-Signal: ai-train=no
i18n
@nuxtjs/i18n

Cinq langues, plusieurs domaines, une route

Locales par domaine, préfixe de langue, chemins traduits : /fr/produit, /nl/product, /de/produkt. Mode strictSeo : hreflang et canonical toujours cohérents.

hreflangx-defaultdefineI18nRoute()
nuxt-vitalizer

Moins de préchargements, meilleur LCP

disablePreloadLinks : plus de cascade de <link rel=preload> qui concurrence l’image principale. Combiné au prefetch des liens à l’interaction plutôt qu’à la visibilité.

prefetchOn: interaction0 preload chunks
ui
dompurify · lazytube · carousel

Le reste du poids lourd

v-sanitize-html sur le HTML venu des traductions et du CMS. <LazyYoutube> : la vidéo de la galerie ne charge rien avant le clic. Carrousels prêts à l’emploi sur huit écrans.

v-sanitize-html<LazyYoutube><Carousel>
03

Entity Details

La fiche entité : ce que c’est, comment elle s’orchestre, comment on en écrit une. Puis deux briques de plus : sélecteurs distants, table en pièces détachées.

3.1qu’est-ce que c’est

Une fiche = un en-tête, des sections, un rail.

Organisation, item, séance, contrat, facture, utilisateur : la même structure, opérée par un composant partagé — <EntityDetailShell>.

1
Ateliers Roux
ACTIVEORG-4821Vendeur · Acheteurcontact@ateliers-roux.fr
RafraîchirActions ▾
édition
2
Identité
Ateliers Roux
FR 42 812 345 678
SAS
contact@ateliers-roux.fr
3
ContactsModifications non enregistrées
+33 4 72 00 00 01
ateliers-roux.fr
M. Roux
fr
AnnulerEnregistrer
données
4
Utilisateurs 8Vue complète →
NomEmailRôle
M. Rouxm.roux@ateliers-roux.frADMIN
5
Items 37différée · chargée à l’approche
6
Édition
Identité
Contacts
Rôles & hiérarchie
Données
Utilisateurs 8
Contrats 5
Items 37
Enchères 112
Suivi
Commentaires 14
Audit
1
En-têteidentité · meta · stats · notices · menu d’actions
2
Section formulaireun schéma FormRenderer, lié à l’entité
3
État modifiébordure, libellé, pied Annuler / Enregistrer — par section
4
Table liéeun DataList embarqué, compteur, vue complète
5
Chargement différérien ne part avant que la section approche
6
Railscrollspy · compteurs · point « modifié »
3.2répartition des rôles

Vous déclarez trois choses. Le shell fait le reste.

Le code métier dit quoi charger, comment la fiche est structurée, et ce qu’un enregistrement fait. Tout le cycle de vie est partagé.

VOUS ÉCRIVEZ defineEntityLoader quoi charger, à quel moment essential.query · background.count · deferred.table entities/organisations/detail/loader.ts defineEntityDetails la structure : en-tête, groupes → sections header · form · table · editor · slot · stats · notices components/entities/organisations/detail/OrganisationDetails.vue submit(formData) → boolean ce qu’un enregistrement fait mutation · erreurs métier · transaction dans le loader LE SHELL OPÈRE renduen-tête · sections · rail · étatsskeleton · erreur · vide chargementessential → background → deferredviewport 320 px · clic rail · refresh navigationrail · scrollspy · compteurspoints « modifié » · alertes liaisonentité → input du formulairebaseline = valeur serveur état modifiédétection · bordure · libellépied Annuler / Enregistrer commitsubmit → invalidation → refetchpuis reset de la baseline verrouune section enregistre à la foisles autres pieds désactivés garde de sortieconfirmation si modificationsun brouillon n’est jamais écrasé tables liéesDataList embarqué · compteurvue complète · scroll maîtrisé packages/shared-ui/lib/entity-details — un seul composant pour les six entités, testé une fois
3.3orchestration du chargement

L’essentiel d’abord, le reste ensuite, les tables à la demande.

Chronologie d’une fiche organisation : ce qui part à l’ouverture, ce qui attend ready, ce qui attend la section.

t0 · montage ready rail complet scroll · clic rail essentialbloque le rendu organisations.get shops.list organisations.getHierarchy en-tête et formulaires rendus backgrounddémarre à ready · ne bloque pas count items · size 1 count bids · users · contracts… logo signed URL pastilles du rail : 37 · 112 · 8 deferredattend la section items contracts users à moins de 320 px du viewport, ou ciblée depuis le rail
un compteur = une requête size: 1, indépendante de la table · rechercher dans la table ne change pas la pastille
3.4partie 1 · le loader

defineEntityLoader — chaque ressource nourrit une partie de la fiche.

Un composable par entité. Trois étages, quatre méthodes. À droite : où chaque déclaration atterrit.

Ateliers Roux essential
ACTIVEORG-4821
logo background
Identité essential
Ateliers Roux
Agorastore shops
Items 37 deferred.table
Contrats 5 deferred.table
Données
Items 37
Contrats 5
Utilisateurs 8
③ pastilles = background.count
indépendantes des tables
3.4partie 1 · le loader · les étages

Trois étages, un compteur.

Quand chaque étage part, ce qu’il bloque, et la déclaration qui va avec.

essential

Sans quoi la fiche n’a pas de sens

L’entité, et ce que les formulaires doivent connaître.

Charge → skeleton. Erreur → la fiche l’affiche.

background

Utile tout de suite, jamais bloquant

Compteurs, URL signées, données secondaires.

Démarre au ready. Un échec ici n’empêche rien.

deferred

Cher, donc à la demande

Les tables de relations.

Aucune requête avant que la section soit proche du viewport ou ciblée depuis le rail.

count

Le plus petit fait utile

size: 1, select: total.

Attaché à une table → pastille et titre de section, sans dépendre d’elle.

.query · .count · .table · .resource sur chaque étageretour : ready · error · refreshing · refresh({ resources? })
3.4partie 1 · le loader · prefetch

Le loader est aussi le plan de prefetch.

Survoler une ligne de la liste charge l’essentiel de la fiche — dérivé du loader, jamais réécrit. Six lignes par page, une fois.

survol · focus d’une ligne de la liste <NuxtLink> · prefetchOn: interaction defineQueryPrefetch('organisations-id', …) le plan déclaré par la page destination prefetchEntityLoader(useOrganisationDetail) le loader tourne en mode capture : ✓ essential.query → étages inférés – background · count dormants – deferred · table dormants aucune requête déclarée deux fois au clic : fiche prête mêmes clés ['api', …] identité, hiérarchie, boutiques déjà en cache → « prête » immédiat rail, compteurs et tables suivent comme d’habitude étages inférés étage 1 · queries résolubles organisation · shops · hierarchy étage 2 · celles qui lisent une donnée de l’étage 1 enabled: false → ignorée

=une seule source

Le plan est le loader. Ajouter une query essentielle l’ajoute au prefetch.

!l’essentiel, pas tout

Ce qui conditionne « prête ». Le reste garde ses étages à l’arrivée.

3.5partie 2 · la configuration

defineEntityDetails — des groupes, des sections, quatre types.

La config se lit comme le plan de la fiche. Chaque section a un type ; le shell sait rendre chacun.

Identité
AnnulerEnregistrer

form

schéma + submit → formulaire, état modifié, commit

Items · 37
RA-20481Chariot élévateurOPEN
RA-20488Presse hydrauliquePAID
RA-20495Tour CNCCLOSED
Vue complète →

table

ressource du loader → DataList embarqué, compteur, vue complète

Photos
AnnulerEnregistrer

editor

éditeur maison — même pied, mêmes verrous

Documents
<template #documents>

slot

vous rendez le contenu ; le shell gère rail et activation

3.5partie 2 · l’en-tête

L’en-tête est de la donnée, pas du template.

Nature, référence, titre, statut, métadonnées : cinq clés dans la config. Le shell les rend, gère le chargement, les liens et les valeurs absentes.

Organisation · ORG-7F3K21
Ateliers Roux SAS ACTIVE2
Entreprise · vendeur
▣ Groupe Roux Industrie ↗
Agorastore
contact@ateliers-roux.fr
3
RafraîchirActions ▾
1
kind · referencela nature et l’identifiant, en petit
2
title · statusun VNodeChild chacun — le statut réutilise le rendu de la liste
3
meta[]label, valeur, icône, to et condition ; une valeur undefined n’apparaît pas
3.5partie 2 · le template

Le shell, plus un slot par section sur mesure.

Le template ne contient que les sections qu’aucun type générique ne couvre — ici une table embarquée qui a besoin de son bouton.

props & slots du shell
:details
la config defineEntityDetails
:actions
menu d’en-tête (MenuOption[]) ; rafraîchir est ajouté par le shell
header dans la config
identité de l’entité — kind · reference · title · status · meta[], rendue dès ready
#actions { refresh, refreshing, submitting }
actions d’en-tête personnalisées
#<key> { section, goTo }
un slot par section sans type — goTo('items') pour naviguer
au niveau de la config
stats
defineEntityDetailStats({ cols, stats: [{ key, label, value, tone?, visual? }] })
notices
bandeaux info / warning / error avec condition
3.6partie 3 · sections formulaire

Une section formulaire, quatre états — tous automatiques.

Vous fournissez un schéma et un submit. La liaison, la détection, le commit et la remise à zéro sont le travail du shell.

1liée

Contacts
+33 4 72 00 00 00
ateliers-roux.fr

input = l’entité chargée. Baseline = valeur serveur. Pas de pied.

2modifiée

ContactsModifications non enregistrées
+33 4 72 00 00 01
ateliers-roux.fr
AnnulerEnregistrer

Chemins modifiés exacts. Bordure, libellé, point du rail, pied. Quitter la page demande confirmation.

3en cours

ContactsEnregistrement…
+33 4 72 00 00 01
ateliers-roux.fr
AnnulerEnregistrer…

submit(formData) sous verrou : les autres sections ne peuvent pas enregistrer en même temps.

4propre à nouveau

Contacts
+33 4 72 00 00 01
ateliers-roux.fr

true → invalidation → refetch → baseline = nouvelle valeur. false → retour à l’état 2, erreurs sur les champs.

3.6partie 3 · sections formulaire · code

Quatre clés, un contrat d’édition.

Avant : panneaux, flags de dirty, barre globale, rechargements. Après : une déclaration et une fonction submit qui renvoie un booléen.

options : input() · disabled() · pending() · reset() · notice(ctx) · headerEnd(ctx)un même submit sert plusieurs sections
3.7partie 4 · données liées

Une relation = le même schéma que la liste, en compact.

Trois déclarations : la table différée + son compteur dans le loader, la section dans la config, les colonnes compactes dans le schéma d’entité.

Contrats 5Vue complète →
RéférenceNomStatut
C-2024-118Cadre annuelACTIF
C-2024-097Vente aux enchèresACTIF
C-2023-311Dépôt-venteCLOS
5 résultats · page 1/1recherche · colonnes · taille de page

différée

0 requête avant que la section soit proche ou ciblée

#compteur indépendant

total de la relation, insensible à la recherche

vue complète

la liste entière, même filtre dans l’URL

défilement maîtrisé

la table rend la main à la page en bout de course

3.8partie 5 · le rail

Le rail sait tout avant les tables.

Compteurs, position, sections modifiées, alertes : le rail dérive de la config et des ressources — rien à câbler.

Édition
Identité
Contacts
Rôles & hiérarchie
Commercial
Données
Utilisateurs 8
KYB 3
Adresses 2
Contrats 5
Items 37
Enchères 112
Suivi
Commentaires 14
Audit
groupes et sectionsdirectement la config : groups[].sections[]. Une section condition: false ou sans droit n’apparaît pas.
37
compteursle count de la ressource (ou count: () => … sur la section). Skeleton tant que non résolu — jamais un faux zéro.
section activescrollspy sur le document. Un clic → activation de la ressource différée + défilement.
point « modifié » · alertele dirty des sections formulaire / éditeur ; alert: () => boolean pour signaler une action requise.
3.9partie 6 · éditeurs sur mesure

Le même contrat, sans formulaire.

Photos d’un item, ressources d’une séance : un composant maison enregistre trois choses et reçoit pied, verrou et garde.

useEntityDetailEditor({ dirty, reset, submit })
dirty
votre détection de changement
reset()
bouton Annuler du pied
submit() → boolean
bouton Enregistrer, sous le verrou
→ disabled
à propager à vos contrôles
3.10exemple

Hiérarchie & procurations : trois surfaces → une section.

Un cas métier riche qui n’a rien demandé au shell : parent et enfants sont des champs, la matrice est un champ custom, la transaction vit dans submit.

V1 formulaire · champ parent+ organisation-children.vue : dialogue d’ajout, délier /organisations/:id/hierarchy · autre routearbre en lecture seule, nouvel onglet action « Procuration » · modale 600 pxbascule donner / recevoir · arbre · confirmation l’administrateur assemble le modèle dans sa tête

champs ordinaires

parent et enfants sont des selects distants

champ custom

la matrice dépend d’eux et prévisualise le graphe

txsubmit = transaction

cycles · ordre des arêtes · compensation · refresh

3.11en pratique

Ajouter une fiche, ou une section.

Quatre fichiers, dans cet ordre.

1

Le loader

entities/<x>/detail/loader.ts

entité → essential · compteurs → background · tables → deferred · fonctions submit

2

Les schémas

entities/<x>/detail/schema.tsx

formulaires de section · pour les relations : le <y>TableSchema({ <x>ShortId }) existant + son embed

3

Config + shell

components/entities/<x>/detail/<X>Details.vue

defineEntityDetails (header · groupes) · <EntityDetailShell> · un slot par section sur mesure

4

La route

pages/<x>/[id].vue

definePageMeta — accès, fil d’Ariane via ctx.detailLabel — et le composant. Rien d’autre.

déjà sur ce modèle : contrats · factures · items · organisations · séances · utilisateurstests montés : packages/shared-ui/test/entity-details
3.11en pratique · où va le code

Un dossier par entité. Un fichier par rôle.

Jamais pour un seul appelant : pas d’helper, de constante ou de type créé pour un consommateur unique. Une déclaration réutilisable vit chez son propriétaire.

entities/organisations/
├── schema.tsxliste + bloc embed
├── action.tsopérations déclenchées par l’utilisateur
├── render.tsxcellules, statuts
├── filter.tsfiltres du domaine
├── utils.ts · constants.ts · types.tsvaleurs et types canoniques
└── detail/
├── schema.tsxsections éditables
├── action.tsactions de la fiche
├── render.tsxadaptateurs de champs custom
└── loader.tsle loader étagé — et le plan de prefetch

?« où est-ce que ça va ? »

Une colonne → render.tsx. Un filtre → filter.ts. Une action de ligne → action.ts. Une section de fiche → detail/schema.tsx. Un chargement → detail/loader.ts.

corriger le propriétaire

Un comportement faux dans une table embarquée se corrige dans le schéma canonique — la projection suit. Idem pour un champ distant : dans shared-business/entities/…/fields.tsx.

28dossiers d’entité en admin

Le même plan pour chacun. Les sous-dossiers (create/, merge/, users/…) tiennent les écrans satellites.

3.12autres briques · options distantes du moteur de formulaire

Un champ qui cherche dans toute la base.

L’admin opère sur tout le jeu de données : les sélecteurs paginent côté serveur. Le champ déclare deux requêtes, le moteur fait le reste.

Organisation *
Mairie de Lyon ×mai4
mai2 caractères min · 250 ms1
Mairie de Lyon69001 · 3 filiales
Mairie de Marseille13001
Mairie de Nantes44000 · 1 filiale
Mairie de Toulouse31000
Maison Départementale…75012
page 2 · 25 suivants2
1
recherchedebounce, longueur min, réponses périmées rejetées
2
paginationpage ou curseur, chargée au défilement, accumulée
3
dépendancesrefreshOn recharge, clearOnInvalid vide si la valeur n’existe plus
4
hydratationla valeur enregistrée est résolue par resolveSelected, même hors de la page courante
3.12autres briques · options distantes · arbre & sélecteurs

Même contrat, en arbre — et un sélecteur par entité.

Racines paginées, branches chargées à l’ouverture, chemin complet reconstitué pour la valeur enregistrée.

Organisation parente
Groupe Hospitalier Sud racine · page 1
CHU de Bordeaux
Pôle logistique valeur enregistrée
Pharmacie centrale
CH de Libourne chargé à l’ouverture
Métropole de Lille
Région Occitanie
… 22 racines suivantes

un sélecteur par entité

organisationsutilisateursitemsséancesfacturesfabricants · modèles

Chaque schéma admin qui pointe une autre entité utilise ce mécanisme — même debounce, même pagination, même hydratation.

3.13autres briques · DataListRoot

La table en pièces détachées.

Même schéma, même moteur — mais vous posez chaque pièce où vous voulez. Pour les écrans admin qui ne rentrent pas dans le DataList standard.

Documents 12🔍 rechercher…+ Demander12
DocumentTypeDéposé leStatut
Carte d’identité — recto.pdfIdentité18/05/2026VALIDÉ
Carte d’identité — verso.pdfIdentité18/05/2026VALIDÉ
Mandat de l’organisation.pdfMandat19/05/2026À REVOIR
3
1–3 sur 12 · Vue complète ↗12344
1
DataListHeaderslots #title · #actions · #band
2
contrôlesSearch · Columns · Refresh · Sort · FilterPanel — et vos boutons
3
DataListTable · DataListGridou DataListContent avec votre rendu des lignes
4
ResultCount · Pagination · PageSizerendu par défaut, ou le vôtre via le slot
04

Inventory

Hors-ligne complet, sans second chemin de données.

4.1le modèle

Trois couches. Deux sont de la configuration.

Coquille pré-cachée · référentiel = cache Query persisté · brouillons locaux publiés plus tard.

1La coquille

@vite-pwa/nuxt · Workbox

HTML, JS, CSS, polices pré-cachés au premier chargement.

navigateFallback: '/' — toute route s’ouvre hors-ligne.

Mise à jour proposée (registerType: 'prompt'), vérifiée toutes les 60 s.

2Le référentiel

cache TanStack Query → IndexedDB

Catégories, caractéristiques, canvases, fabricants, modèles, organisations, boutique.

Liste blanche d’opérations · scope session × shop · 24 h.

Préchargé au login · restauré au démarrage · rafraîchi au retour du réseau.

→ les écrans ne changent pas

3Les brouillons

store Pinia + idb-keyval

Écrits en local d’abord, scopés par personne et boutique.

local → publishing → published, progression d’upload persistée.

Fabricants / modèles créés hors-ligne, réconciliés à la publication.

les mutations restent en ligne — le brouillon local est la file d’attente

4.2le référentiel

Quoi persister, comment, pour qui.

Une liste blanche d’opérations, un persister IndexedDB scopé, un préchargement au login.

4.3les brouillons

Local d’abord. Publication reprise.

Le cycle d’un brouillon, et ce qu’un écran en voit.

local draftStore.create / patchDraft IndexedDB · drafts:person:shop photos incluses · sans réseau fabricant / modèle inconnu → créé en local publish() publishing 1 · brouillon serveur créé 2 · ressources uploadées une à une 3 · références locales → serveur publishProgress · uploadedResourceKeys persistés published l’item existe côté serveur le brouillon local est conservé comme trace app fermée · réseau coupé → au retour : publishing ⇒ local, les ressources déjà envoyées ne sont pas renvoyées ce que l’écran voit onlineManager.isOnline() draft.status draft.publishProgress pastille hors-ligne · barre de progression
Le fil rouge

Le code métier déclare.
La plateforme opère.

contrat · contrôleurschéma de table · de formulaire · de catalogueloader · sections · submitplan de prefetchsource d’options distantesconfig d’app
Query
identité de cache · invalidation · SSR · prefetch
Market
catalogue · SEO · images OG · découverte
Admin
chargement étagé · état modifié · verrous · rail · sélecteurs · pièces de table
Inventory
coquille · référentiel · brouillons