Audit du chat et de sa toolbar : 17 septembre 2026
Perimetre : le composeur (features/ai/chat/runtime/), le transport et les hooks d'envoi, le rendu des messages (components/patterns/ai-elements/chat/), l'i18n de ces surfaces, et l'ecart entre les versions installees et…
Perimetre : le composeur (
features/ai/chat/runtime/), le transport et les hooks d'envoi, le rendu des messages (components/patterns/ai-elements/chat/), l'i18n de ces surfaces, et l'ecart entre les versions installees et la documentation amont (AI SDK, AI Elements, shadcn, Next, Streamdown). Quatre passes de lecture croisees avec la doc Context7 et le web, chaque constat verifie dans le code avant d'etre retenu. Livre par l'itemai-platform/2707; l'i18n residuelle reste dansai-platform/0568, la question AI SDK 7 dansai-platform/2708.
Ce qui a ete trouve, en une phrase
Le composeur etait coherent avec lui-meme et incoherent avec tout ce qui n'etait pas lui : un seul chemin d'envoi sur huit composait un corps de requete complet, un seul chemin d'hydratation sur deux gardait les parts, et un seul des deux 429 du serveur etait celui que le client croyait recevoir. Le second defaut, plus visible, est une famille de controles qui rendaient un etat et en executaient un autre (le X « reessayer » qui lancait la dictee, la pilule « Upgrade » qui soumettait aussi le formulaire, le bouton d'appel actif sans credits qui ne faisait rien).
Mises a jour : verdict
Versions lues dans le lockfile, dernieres versions lues sur npm le 2026-09-17, changements lus dans les docs de migration.
| Paquet | Installe | Dernier | Ecart | Verdict |
|---|---|---|---|---|
ai | 6.0.277 → 6.0.285 | 6.0.285 / 7.0.105 | patch pris / majeure (2026-06-25) | la 7 : tenue, decision ecrite dans l'ADR 0019 (item 2708) |
@ai-sdk/react | 3.0.280 | 3.0.288 / 4.0.108 | idem | idem (suit ai) |
@ai-sdk/anthropic | 3.0.116 | 3.0.118 / 4.0.56 | idem | idem |
@ai-sdk/gateway | 3.0.189 | 3.0.196 / 4.0.85 | idem | idem |
next | 16.2.11 | 16.3.5 | mineure (Instant Navigations, cache disque au build, streams Node natifs, TS 7) | PR dediee, elle touche le build Vercel |
react | 19.2.4 | 19.3.0 | mineure | avec Next 16.3 |
streamdown | 2.5.0 → retire | 2.6.0 | n/a | retire avec ses quatre plugins et MessageResponse (ADR 0019) |
use-stick-to-bottom | 1.1.3 → 1.1.6 | 1.1.6 | patch | pris |
@base-ui/react | 1.0.0 | 1.8.0 | mineures | PR design-system, avec pnpm ui:diff |
motion | 12.36 | 13.4 | majeure | PR dediee |
sonner | 1.7.4 | 2.0.8 | majeure | PR dediee |
zod | 3.25.76 | 4.6.5 | majeure | PR dediee (les schemas d'outils AI SDK en dependent) |
lucide-react | 0.454 | 1.46 | majeure (renommages d'icones) | PR dediee |
AI SDK 7, ce qui change pour ce depot : system: → instructions:
(12 sites, alias deprecie encore accepte), CallSettings scinde, un
message system dans messages[] refuse par defaut, Node 22 (ok) et
ESM seulement. Codemod officiel npx @ai-sdk/codemod v7. Rien n'oblige
a migrer cette semaine ; ce qui oblige, c'est d'ecrire si on migre.
AI Elements : notre prompt-input.tsx a diverge depuis longtemps de
l'amont (PromptInputHeader / Body / Footer, usePromptInputAttachments,
PromptInputActionMenu) ; il est localise et porte le mode compact.
Ce n'est pas un retard, c'est une divergence declaree
(ai-elements-local-divergence.test.ts). Rien a rattraper tant que le
composant fait ce que l'amont fait.
Deux moteurs markdown dans un meme chat. Response rend avec
react-markdown + harden-react-markdown ; MessageResponse rendait avec
Streamdown et ses quatre plugins (@streamdown/{cjk,code,math,mermaid}),
et n'avait aucun appelant. Retires, avec StreamingCaret :
docs/decisions/0019.
Ce qui a ete corrige (item 2707)
Moteur
- Le corps de requete est stampe par le transport, plus par le bouton
(
ComposerSnapshotdanschat-transport.ts). Sept chemins envoyaient sansmodel: regenerate, message edite, transcript vocal, reprise apres auth, autosubmit, file d'attente, wizards store / brand. Le serveur retombait sur le tier par defaut et remettait l'autonomie a « approval ». - Une seule conversion composeur → fil (
chat-wire-attachments.ts) : la file envoyait des pieces jointes sanstype, que le serveur ignorait. - La file se vide aussi sur
error, et son bandeau (avec « Clear ») s'affiche pendantsubmitted. - Un 429
RATE_LIMITED(20 messages / minute,withSessionAuth) n'est plus lu comme le plafond de concurrence du plan : toast « ralentis », pas banniere « upgrade ». Le vrai 429 transmetupgradeTo/upgradeLimit. - L'hydratation garde les parts :
use-chat-branches.tsredeclarait un convertisseur qui aplatissait chaque message en une part texte.persistedToUIMessageest la seule copie et portemetadata+createdAt, donc le pied de message (cout, modele) et l'horodatage survivent au reload. - Faye s'allume.
delegateTo_intelligence→ag-spec-financepar la table du roster (specialistIdForToolHandle), plus par chirurgie de chaine. use-chat-resume.tsne se reabonne plus a chaque token.- Le
useCallbackdu submit voit les pieces jointes : un drop puis Entree envoyait la liste du rendu precedent.
Toolbar
- Le bouton d'envoi n'a plus d'etat
error: il rendait un X « reessayer » dont le clic lancait la dictee ou un nouvel envoi. Retry vit dans le bandeau d'erreur, desormaisdisabledsans credits (au lieu d'un lien qui ne faisait rien). submittedcompte comme « en cours » partout (isInFlight) : bouton Queue / Stop, bandeau, geometrie des bandes.- Entree pendant un tour met en file, comme le bouton le promet ; Entree de composition IME n'envoie pas (textarea et chips URL).
- La pilule Upgrade ne soumet plus le formulaire en plus
(
preventDefaulthonore parsubmitViaForm). - Le bouton d'appel respecte
uiLocked(sauf pour raccrocher), et sur le hero un operateur sans credits est redirige vers le dashboard au lieu de cliquer dans le vide. - Le mode voix reste dans la barre compacte (
ToolbarVoiceBara la place du champ) au lieu de basculer tout le composeur en pile complete dans un panneau de 290 px. - Le picker de boutique est
compactdans un panneau dense, comme le picker de modele a cote ; squelette de chargement dans les deux branches ;role="status"au lieu d'unaria-hiddenqui annulait l'aria-label. - Prop
tmorte retiree deComposerPlusMenu,canUseChatmort retire deuseChatVoice,modeltrompeur retire de la ligneatlas-auto, logo Figma retire de la ligne « Create brand kit », cartes de modele ouvertes au clavier (focus), prix Studio au meme format que le picker. attachments-bar:aria-label+aria-expandedsur le toggle, pluriel ICU,revokeObjectURLsur « Clear ».
Rendu
dynamic-toolrend (tout appel MCP ne rendait rien) : guards, regroupement, arbre des etapes, phase, approbations.- Parts
file:url+filename(ledatalu n'existe pas), rendues par le composantAttachment, plus en double via les extras. - La carte d'approbation peut s'afficher (
approval+statepasses) et ses boutons appellentaddToolApprovalResponse. Un outil enapproval-requested/output-denieds'ouvre par defaut et n'est jamais plie dans « 3 steps ». - Pas de bloc « Result » vide pendant l'appel ; sortie JSON en
CodeBlocket plus en markdown (GFM mangeait*et_). isStreamingTail(et non le statut global) pilote le raisonnement et les runs : chaque nouveau tour rouvrait tous les blocs de l'historique. Le bloc de raisonnement ne se rouvre plus si on le ferme en cours.- Placeholder « thinking » seulement tant qu'aucun message assistant n'existe : plus de double annonce.
- Listes a puces en
list-disc(elles rendaient numerotees) ; images markdown limitees ahttps://; memo deResponsesensible amessageId. - Sources dedupliquees par URL, et la MEME liste numerote les
[N]en ligne et la legende. - Copier / partager prennent le message entier et attrapent un refus du
presse-papier ; Retry est
disabledsans credits. - Un seul
SyntaxHighlighterpar bloc de code (la copiedark:hiddenne rendait jamais) ;Conversationmesure son propre scroller.
i18n
Voix, appel, toasts micro / upload, compteur de pieces jointes,
« Stores », Step, « Error / Result », « Reasoning », sources,
approbations : six locales, pnpm i18n:parity vert, DIVERGENCES
mis a jour. Le reste est liste dans 0568.
Seconde passe, le meme jour : ce qui etait differe est livre
handleSwitchBranchremplace toute la serie de messages assistant d'un tour (jusqu'au prochain message utilisateur), plus seulement le premier.Message.modelstocke le modele qui a RÉELLEMENT tourne (metadata.modeldu tour), plus le sentinelatlas-auto.- Les trois 403 du handler sans
codeen ont un (NOT_A_MEMBER,STORE_NOT_IN_ORG,AI_USE_DENIED), le transport les reconnait et la surface les dit dans la langue de l'operateur (chat.forbidden.*). ChatRequestdit lesquels de ses champs aucun client n'ecrit.Loader/Task*/InlineCitation*restent sous le plafond dechat-elements-mounted.test.ts(composants vendus, sans cout) ;MessageResponse/StreamingCaretsont retires avec Streamdown (ADR 0019).- La faute de syntaxe de
main(run-algorithm-button.tsx,\'dans un attribut JSX) est portee ici pour queTypecheckpasse. - i18n : le reliquat de
0568est livre, l'item est archive.
Ce qui reste, et pourquoi
| Constat | Pourquoi pas ici |
|---|---|
metadata.plan n'a aucun ecrivain serveur : la carte Plan des extras ne rend jamais | tranche le 2026-09-17 : retiree (composant plan.tsx, exports, cles, PlanMetadata). Le PlannerView d'execution couvre le cas utile |
| Migration AI SDK 7 | ADR 0019 : tenue en 6, PR dediee avec evals ; migration demandee le 2026-09-17, voir la PR de mise a jour |
Comment c'est verifie
pnpm typecheck: zero erreur nouvelle (trois pre-existantes surmain, admin marketplace + status).pnpm lintsur le perimetre : zero warning.pnpm test: les gardes qui encodaient l'ancienne forme ont ete mises a jour, pas retirees (chat-general-scope,composer-send-button-modes,composer-store-switcher,pending-chat-intent-handoff,one-status-line-per-turn,dark-variant-is-a-dead-branch47 → 45,ai-elements-local-divergence). Deux echecs pre-existants surmainhors perimetre :bulletin/health.test.tset l'itemplatform-ops/2696marquedonehors archive.pnpm i18n:parityvert ;client-namespaces.generated.tsre-derive.