Audits · septembre 2026Audit du chat et de sa toolbar : 17 septembre 2026

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'item ai-platform/2707 ; l'i18n residuelle reste dans ai-platform/0568, la question AI SDK 7 dans ai-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.

PaquetInstalleDernierEcartVerdict
ai6.0.277 → 6.0.2856.0.285 / 7.0.105patch pris / majeure (2026-06-25)la 7 : tenue, decision ecrite dans l'ADR 0019 (item 2708)
@ai-sdk/react3.0.2803.0.288 / 4.0.108idemidem (suit ai)
@ai-sdk/anthropic3.0.1163.0.118 / 4.0.56idemidem
@ai-sdk/gateway3.0.1893.0.196 / 4.0.85idemidem
next16.2.1116.3.5mineure (Instant Navigations, cache disque au build, streams Node natifs, TS 7)PR dediee, elle touche le build Vercel
react19.2.419.3.0mineureavec Next 16.3
streamdown2.5.0 → retire2.6.0n/aretire avec ses quatre plugins et MessageResponse (ADR 0019)
use-stick-to-bottom1.1.3 → 1.1.61.1.6patchpris
@base-ui/react1.0.01.8.0mineuresPR design-system, avec pnpm ui:diff
motion12.3613.4majeurePR dediee
sonner1.7.42.0.8majeurePR dediee
zod3.25.764.6.5majeurePR dediee (les schemas d'outils AI SDK en dependent)
lucide-react0.4541.46majeure (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 (ComposerSnapshot dans chat-transport.ts). Sept chemins envoyaient sans model : 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 sans type, que le serveur ignorait.
  • La file se vide aussi sur error, et son bandeau (avec « Clear ») s'affiche pendant submitted.
  • 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 transmet upgradeTo / upgradeLimit.
  • L'hydratation garde les parts : use-chat-branches.ts redeclarait un convertisseur qui aplatissait chaque message en une part texte. persistedToUIMessage est la seule copie et porte metadata + createdAt, donc le pied de message (cout, modele) et l'horodatage survivent au reload.
  • Faye s'allume. delegateTo_intelligence → ag-spec-finance par la table du roster (specialistIdForToolHandle), plus par chirurgie de chaine.
  • use-chat-resume.ts ne se reabonne plus a chaque token.
  • Le useCallback du 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, desormais disabled sans credits (au lieu d'un lien qui ne faisait rien).
  • submitted compte 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 (preventDefault honore par submitViaForm).
  • 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 (ToolbarVoiceBar a 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 compact dans un panneau dense, comme le picker de modele a cote ; squelette de chargement dans les deux branches ; role="status" au lieu d'un aria-hidden qui annulait l'aria-label.
  • Prop t morte retiree de ComposerPlusMenu, canUseChat mort retire de useChatVoice, model trompeur retire de la ligne atlas-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-expanded sur le toggle, pluriel ICU, revokeObjectURL sur « Clear ».

Rendu

  • dynamic-tool rend (tout appel MCP ne rendait rien) : guards, regroupement, arbre des etapes, phase, approbations.
  • Parts file : url + filename (le data lu n'existe pas), rendues par le composant Attachment, plus en double via les extras.
  • La carte d'approbation peut s'afficher (approval + state passes) et ses boutons appellent addToolApprovalResponse. Un outil en approval-requested / output-denied s'ouvre par defaut et n'est jamais plie dans « 3 steps ».
  • Pas de bloc « Result » vide pendant l'appel ; sortie JSON en CodeBlock et 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 a https:// ; memo de Response sensible a messageId.
  • 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 disabled sans credits.
  • Un seul SyntaxHighlighter par bloc de code (la copie dark:hidden ne rendait jamais) ; Conversation mesure 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

  • handleSwitchBranch remplace toute la serie de messages assistant d'un tour (jusqu'au prochain message utilisateur), plus seulement le premier.
  • Message.model stocke le modele qui a RÉELLEMENT tourne (metadata.model du tour), plus le sentinel atlas-auto.
  • Les trois 403 du handler sans code en 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.*).
  • ChatRequest dit lesquels de ses champs aucun client n'ecrit.
  • Loader / Task* / InlineCitation* restent sous le plafond de chat-elements-mounted.test.ts (composants vendus, sans cout) ; MessageResponse / StreamingCaret sont retires avec Streamdown (ADR 0019).
  • La faute de syntaxe de main (run-algorithm-button.tsx, \' dans un attribut JSX) est portee ici pour que Typecheck passe.
  • i18n : le reliquat de 0568 est livre, l'item est archive.

Ce qui reste, et pourquoi

ConstatPourquoi pas ici
metadata.plan n'a aucun ecrivain serveur : la carte Plan des extras ne rend jamaistranche le 2026-09-17 : retiree (composant plan.tsx, exports, cles, PlanMetadata). Le PlannerView d'execution couvre le cas utile
Migration AI SDK 7ADR 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 sur main, admin marketplace + status).
  • pnpm lint sur 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-branch 47 → 45, ai-elements-local-divergence). Deux echecs pre-existants sur main hors perimetre : bulletin/health.test.ts et l'item platform-ops/2696 marque done hors archive.
  • pnpm i18n:parity vert ; client-namespaces.generated.ts re-derive.