Feat/board customization#13
Merged
Merged
Conversation
Composant réutilisable <ModuleHelp> : petit bouton (i) près du titre d'un module ouvrant un popover (aide contextuelle courte + lien « En savoir plus » vers la doc publique). URL surchargeable via NEXT_PUBLIC_DOCS_URL. Posé sur Dashboard (prise en main), Settings → Providers, Settings → Connecteurs, Documents et l'écran « nouvelle conversation » du chat. Ajoute deux guides utilisateur : prise en main (onboarding 5 étapes) et travailler par projet. Le bouton de la page Projets suivra (fichier en cours de refonte).
Systematic audit-driven fixes across 7 module groups (UI surface), verified with tsc + lint + production build (all green). Criticals: - Chat: scope the streaming live-region (was re-announcing every token) - Board: gate infinite edge animation + node pulse behind reduced-motion; add text/icon status badges (was color-only) - Settings: drop invalid CutoutCard role="button" wrapping nested interactives; add explicit "Configurer" trigger + hoist delete dialog Systemic: - aria-current on active nav (sidebar / settings / admin) - Spinner primitive: role=status on wrapper, sr-only label, motion-safe - form helper text wired via aria-describedby - citation highlight -> --highlight token (was hardcoded yellow) - list/table semantics: ul/li, scope, caption, role=progressbar, dl metrics - reduced-motion gating on spring/infinite animations - dead "Bientot" controls removed; emoji -> @tabler icons - print footer contrast; admin stats no longer hidden on mobile Bugs: - broken oklch(oklch()) vignette gradient (rendered nothing) -> color-mix - hardcoded black MiniMap maskColor -> token (dark-mode correct) Perf: - React.memo on assistant markdown rows via stable onOpenDoc, so historical messages stop re-rendering/re-linkifying on every streamed token
Choix du dossier de stockage à la création d'un projet ; en chat-projet le RAG ne voit que les documents du sous-arbre du dossier + l'historique des conversations du projet. Table message_chunks (embeddings 1024d, index hnsw), projects.folderId, lib/projects/scope. Note: le câblage chat/orchestrateur/outils qui consomme ce scope (route.ts, orchestrator, tools.ts, upload) est intriqué avec le sprint prod-ready et committé avec les phases P1/P3 sur ces fichiers partagés.
…y P1) - R1 usage réel agrégé par message (somme tous agents) + message-metadata émise → pill coût, page usage, quota et URL ?id= fonctionnels - R2 abortSignal req.signal → streamText : « Stop » coupe le pipeline serveur ; withRetry ignore AbortError ; pas de sauvegarde de réponse partielle - H8 estimation coût/appels au point de dépense (composer + CTA Essayer), helper estimateCalls/estimateRunCost centralisé, mode-bar refactorée - H9 agent_runs rattachés au message (messageId) avec modelId/providerType par agent - H22 quota mensuel visible au membre (page usage + dashboard) via getMonthlySpendCents partagé avec l'enforcement - tests cost-estimate + non-retry AbortError (vitest 77 → 91)
- R9 $count scopé userId/projet : plus de fuite cross-tenant de search_documents - R7 purge des chunks des versions périmées au remplacement : l'IA ne cite plus de texte obsolète - H17 détection PDF scanné → extractionStatus=failed + message OCR clair - R6 correction des copies « RAG arrive en v0.3 » (le RAG est en production)
…eady P3 R6) - badge par document : indexé (N segments) / non indexé / clé Mistral manquante - action « Réindexer » par document (recovery après ajout de clé ou échec) - bouton « Réindexer tout » dans l'en-tête - helper réutilisable reindexDocument (ownership, idempotent)
…t projet réel, cascade (P3 🟠) - H16 bouton Importer multi-fichiers + rejets drag-drop (type/taille) surfacés au lieu d'être ignorés silencieusement - H18 « Déplacer vers projet » déplace réellement le document dans le dossier du projet (entre dans le périmètre RAG), plus seulement le projectId d'affichage - H20 le dialog de suppression de dossier avertit de la cascade des sous-dossiers (compte récursif)
…ady P2 R3) Les résultats legifrance_search / pappers_search / pappers_get s'affichent en cartes citation cliquables (titre + source + lien externe, rel=noopener) au lieu d'une pill grise « Terminé » qui jetait les URLs. Re-rendu à l'identique au reload (les tool-results sont déjà persistés). Tient la promesse « Louis cite ses sources ». Sécurité (défense en profondeur) : les URLs proviennent d'API externes (PISTE/Pappers) → validation du schéma http(s) via safeHttpUrl avant tout href ; fallback non-cliquable sinon (jamais de javascript:/data: dans un href).
…3a/H3b/H5) - H3a persiste les data parts du trail (agent-event/output/retry + skills) dans messages.parts → le théâtre, les badges d'étapes et les compétences survivent au reload ; output capé à 12k/agent - H3b getConversationAuditTrail (ownership) : runs groupés par message (messageId rattaché via P1) - H5 export du trail en JSON (par message : agents, rôles, modèles, tokens, latence, statut, erreurs) depuis le menu de conversation
Consomme la part data-skills-detected (live + persistée via H3a) et affiche des pills « Compétences appliquées : X » au-dessus du composer. Mapping slug→libellé fourni par page.tsx (getEnabledSkills). Rien affiché si aucune skill détectée.
…cées, notif de fin (P4) - H14 export CSV (route GET ownership-checkée, BOM UTF-8 + séparateur ; + échappement, Content-Disposition attachment) + item « Exporter en CSV » dans le menu - H15-e le format de colonne (date/money/boolean/liste) est désormais injecté dans la consigne d'extraction - H15-f les lignes « running » abandonnées (after() interrompu) au-delà de 5 min sont requalifiées et redeviennent relançables - H15-d toast « Extraction terminée » (transition running→fini, une seule fois, sans faux positif au chargement) + annonce aria-live
…nts (P4 H15-a/b/c) - H15-a rerunReviewRow : ré-extraire une ligne (toutes colonnes) depuis la grille (desktop + mobile) - H15-b rerunReviewColumn : « Ré-extraire » dans le popover colonne après édition du prompt ; extractRow merge désormais (préserve les autres colonnes) - H15-c addReviewDocuments + dialog : ajouter des documents à une analyse existante (docs indexables pas déjà présents), promesse « en ajouter plus tard » tenue
- R4 PISTE n'annonce comme « débloqué » que Légifrance ; Judilibre/JADE/INPI/BODACC déclassés en « à venir » (carte + dialog) - H23 la bibliothèque de modèles affiche le prix entrée/sortie par M de tokens (« prix inconnu » à défaut, jamais de faux gratuit)
…elf-host (P5 R9'/R5) - R9' checklist de mise en route STATEFUL (provider→modèle→connecteur opt.→chat), reflète l'état réel ; remplace la bannière basée sur le nombre de conversations - R5 (providers) « Tester la connexion » est activé dès qu'un baseUrl est configuré (Scaleway/OVH/Albert/OpenAI-compatible self-host), plus de menu grisé muet — l'action passait déjà le baseUrl de la clé
Action testConnectorKey + fonctions testPisteConnection (OAuth) / testPappersConnection (requête minimale). Item « Tester la connexion » dans la carte connecteur + toast (connecté / auth refusée / non configuré / injoignable) + badge « Dernier test » persistant (lastTestStatus). Comble le trou : un cabinet entrait ses identifiants PISTE sans aucun feedback jusqu'ici.
…/H24) - H26 OVH ne renvoie plus une erreur 501 dans la bibliothèque : retourne la liste curée locale (plus de cul-de-sac), hint « liste curée » - H24 un serveur MCP est synchronisé automatiquement à la création (best-effort, n'échoue pas la création) + la carte affiche la liste nominative des outils découverts, pas seulement le compte
…alette Cmd+K (P7 R8/H27) - R8 ajoute les filets manquants : skeleton de chargement (motion-safe), error boundary brandée avec reset, 404 FR brandée, global-error (use client + html/body, styles inline). Plus d'écran Next brut. - H27 DialogTitle/Description déplacés DANS DialogContent → la palette Cmd+K a un nom accessible (aria-labelledby) et ne déclenche plus le warning Radix
… /board dans la palette (P7 VOCAB/H29) - Décision #8 : libellés UI seulement (sidebar, palette, titres de page, menus composer, panel live, copies models). Routes /board et /workflows inchangées (pas de lien cassé). - H29 (partiel) : Board ajouté au groupe Navigation de la palette Cmd+K.
Décision #6 : DraftingAgent (génère/édite actes & mémoires) et LegifranceAgent (sourcing Légifrance) sont implémentés (prompts + allowlist d'outils) et enregistrés dans AGENT_REGISTRY. Les rôles drafting/legifrance sont désormais proposables dans l'add-dialog (plus de retombée silencieuse sur DefaultAgent). Test du registre mis à jour.
…équentiel only (P7 H7) Les poignées de connexion (qui ne connectent rien, nodesConnectable=false) sont rendues invisibles/non-interactives (conservées pour l'ancrage des edges). Le drag ne ré-ordonne plus qu'en mode séquentiel — en council/parallel la position n'a aucun effet sur l'exécution, le drag y était trompeur. Reste (H7) : vue-liste verticale sur mobile (décision #5).
… export CSV/JSON (P7 H21) - labels d'action centralisés (lib/audit/labels) et réutilisés sur la page audit ET la fiche utilisateur (plus de slug brut) - filtres action + plage de dates + réinitialisation, pagination (50/page), total réel (plus de cap dur à 200) - colonne meta (IP/user-agent/motif…) désormais rendue - export CSV (BOM + anti-injection) et JSON via /api/admin/audit/export (requireAdmin catché → 403, pas 500 ; filtres respectés)
…le=status) (P7 H28/A11Y) - harmonise les focus-ring des inputs/boutons custom sur ring-3 (cohérent avec les primitives shadcn) — 9 occurrences - le feedback transitoire de la fiche utilisateur admin passe en role=status aria-live=polite (annoncé aux lecteurs d'écran)
Remplace le champ texte libre (où une typo donnait un agent sans outil, silencieusement) par un sélecteur : « Tous les outils » (null) vs « Sélection » (cases à cocher des outils réellement disponibles — connecteurs actifs + génération docs + RAG + MCP synchronisés, calculés côté serveur). Un outil d'une allowlist héritée mais indisponible est listé et signalé « indisponible ». « Aucun » = sélection vide.
- src/lib/format/time.ts : formatRelativeFr unique (avant : deux copies divergentes dashboard/admin), nullLabel paramétrable. - src/components/empty-state.tsx : EmptyState réutilisable (carte pointillée + titre + corps + action), uniformise les traitements ad hoc. - Câblage : dashboard, admin/users, admin/audit, workflows ; suppression des fonctions/JSX locaux dupliqués.
Les labels à text-[9px] cumulaient une taille minuscule ET un affaiblissement de contraste (opacity-70 sur du muted-foreground hérité, text-foreground/50, text-destructive/70) → sous le seuil WCAG AA 4.5:1. - admin/users : StatCell + « Ce mois » → 10px, muted-foreground franc (suppression du double-mute opacity-70). - chat/model-picker : label souveraineté → 10px, foreground/70. - chat/chat-shell : labels Avant/Après du diff → 10px, couleur pleine.
Les switches actif/inactif (connecteur, provider, MCP) appelaient l'action sans await ni vérification : un échec serveur (ligne disparue, erreur DB) laissait le toggle muet — l'utilisateur croyait avoir activé une intégration qui restait inactive. - Les trois toggle*Active renvoient désormais ActionResult (erreur si introuvable, try/catch sur l'update) au lieu de void. - Côté client : await + toast.error(message) en cas d'échec. Le Switch étant piloté par l'état serveur, il ne bascule que sur succès+revalidate (pas de faux positif optimiste à annuler).
La barre latérale et la palette de commandes maintenaient deux listes parallèles des mêmes destinations — chaque renommage VOCAB (« Bureau » → « Board », « Workflows » → « Trames »…) devait être répliqué et finissait par diverger en libellé, ordre et icône. - src/lib/navigation.ts : PRIMARY_NAV (source unique) + type NavItem. - sidebar-content : consomme PRIMARY_NAV (suppression du tableau local et des 7 imports d'icônes devenus inutiles). - command-palette : PAGES = [...PRIMARY_NAV, ...SETTINGS_PAGES] ; les réglages granulaires propres à la palette restent locaux. - mobile-nav réutilisait déjà SidebarContent — couvert sans changement.
Diff ligne-à-ligne du texte extrait entre une version antérieure et la version courante, accessible depuis l'historique d'un document. - src/lib/diff/line-diff.ts : LCS maison (sans dépendance) avec trim préfixe/suffixe commun, plafond DP (bloc remplacé au-delà), repli des plages identiques en marqueurs « gap ». 12 tests unitaires. - getDocumentVersionDiff : action sécurisée (même propriétaire + même famille de versions), payload borné (MAX_DIFF_OPS). - version-diff-dialog : bouton « Comparer » par version antérieure + rendu vert/rouge avec compteur +/− et bannière de troncature.
Trois manques du panneau live d'un conseil multi-agents : 1. Fallback synthèse — si le synthétiseur échoue, l'orchestrateur émettait emitError + throw : l'utilisateur perdait TOUTE la délibération. Désormais on sert les positions brutes (avec avertissement « non arbitrées / non vérifiées ») via de vraies parts texte — la seule voie effectivement rendue ET persistée par route.ts (data-final-text n'est consommé nulle part). Couvre council ET parallel. Test unitaire : synthé qui throw → pas de throw, texte de repli non vide contenant les positions. 2. Conscience des tours — le panneau affiche « Tour N/M » en council multi-tours (round déjà présent dans les events, désormais consommé). 3. Retry reflété — une carte d'agent relancé passe en « nouvelle tentative N… » dans le panneau flottant (déjà le cas dans le badge).⚠️ Vérifié statiquement (tsc/lint/tests/build + test orchestrateur du fallback). Le rendu SSE live (texte de repli, libellé de tour qui avance en cours de run) reste à confirmer sur un run réel.
Le canvas React Flow est inutilisable au doigt sous 640px. On ressuscite PipelineBoard (vue verticale : agent terminal mis en avant, collaborateurs empilés en une colonne) en fallback mobile, et on réserve le canvas au desktop (sm+). - pipeline-board : props enabledModels + availableTools transmises à l'AgentEditSheet (édition d'agent fonctionnelle sur mobile aussi), team en colonne unique. - board/[id]/page : swap responsive (hidden sm:block / sm:hidden) + AddAgentDialog au positionnement responsive (statique sous la liste en mobile, flottant sur le canvas en desktop).
Chaque agent d'une pipeline peut désormais avoir sa propre portée documentaire RAG, indépendante du périmètre de la conversation. - schéma : colonne pipeline_agents.rag_scope (jsonb, null = inherit → zéro régression) + type AgentRagScope (inherit/none/project/folders/ documents ; folders/documents câblés en Lot 1b). - resolveAgentRag (agents/rag-scope.ts) : résout la portée d'un agent à partir de sa ragScope et du périmètre conversation, SANS toucher buildToolsForUser ni search.ts (on leur passe un ToolScope déjà restreint). Invariant : intersection avec le périmètre conversation, jamais extension. Mode none → documentIds vidés + masquage des outils de lecture documentaire (couvre aussi la conversation globale). - câblage base.ts + default.ts ; mapping repository ; action updatePipelineAgent (zod discriminatedUnion). - UI : section « Sources documentaires (RAG) » dans agent-edit-sheet (toggle Périmètre conversation / Aucun document). - 7 tests resolver (inherit/project/none, projet vs global, omit tools). Migration : via db:push (convention du repo) — colonne rag_scope nullable. Vérifié : tsc, lint, 112 tests, build prod OK.
- resolveAgentRag : modes folders/documents par INTERSECTION stricte avec le périmètre projet de la conversation (jamais d'extension). documents = intersection pure (0 requête) ; folders = getDocsInFolders (sous-arbres récursifs, filtré userId) puis intersection. Hors conversation projet : repli global sûr (borné par le filtre userId de search.ts). - getDocsInFolders + getAgentSourceOptions dans projects/scope.ts (dossiers en ordre DFS avec profondeur, documents avec flag indexé via chunks). - UI : section « Sources » à 4 modes (Select) — Périmètre conversation / Aucun / Dossiers choisis (cases indentées) / Documents choisis (cases + badge « non indexé »). Texte d'aide rappelant l'intersection projet. - Plomberie availableFolders/availableDocuments : page → PipelineWorkflow + PipelineBoard (mobile) → AgentEditSheet. - 5 tests resolver supplémentaires (intersection, doc hors projet, global). Vérifié : tsc, lint, 117 tests, build prod OK.
Le nœud d'agent signale sa portée RAG quand elle est restreinte : « RAG désactivé » (none), « lit : N dossiers » (folders), « lit : N docs » (documents). Aucun badge pour le cas par défaut (hérite du périmètre) afin de garder le canvas lisible. → Lot 1 (RAG par agent) complet. Vérifié : tsc, lint, 117 tests, build prod OK.
Avant, le rôle d'un agent était figé (« supprimer + recréer » pour en changer). Désormais un Select de rôle dans la Sheet permet de basculer Assistant/Recherche/Légifrance/Citateur/Rédacteur/Relecteur/Maestro sans détruire l'agent (label, modèle, prompt, outils, portée RAG conservés). - agent-role-meta : export AGENT_ROLES (liste ordonnée). - updatePipelineAgent : accepte role (z.enum des rôles connus). - Sheet : Select de rôle en tête, méta (icône/pitch/header) réactive au rôle choisi, avertissement quand le rôle change (prompt factory + outils par défaut affectés, prompt perso conservé). Vérifié : tsc, lint, 117 tests, build prod OK.
Chaque agent peut fixer sa propre température d'échantillonnage (ex. Relecteur factuel à 0.2, Rédacteur créatif à 0.8), ou hériter du défaut du provider. - schéma : colonne pipeline_agents.temperature (double precision, null = défaut provider). AgentDefinition.temperature + mapping repo. - streamText (base.ts + default.ts) : temperature: def.temperature ?? undefined (undefined = défaut provider, comportement inchangé). - updatePipelineAgent : temperature (z.number 0–2, nullable). - UI Sheet : toggle Défaut / Personnalisée + slider natif 0–2 (libellés précis/créatif). Migration : via db:push (colonne nullable). Vérifié : tsc, lint, 117 tests, build prod OK.
L'ordre ne se réglait que par drag horizontal sur le canvas, en mode sequential uniquement, et l'agent terminal n'était jamais désigné clairement (juste « le dernier par position »). - execution-order-panel : liste numérotée réordonnable via flèches haut/bas (clavier-accessible), indépendante de la géométrie du canvas, affichée dans les TROIS modes. Badge « répond en dernier » sur l'agent terminal. Texte expliquant la sémantique du mode (chaîne / conseil / parallèle). Persiste via reorderPipelineAgents ; resync optimiste. - Le drag canvas (sequential) reste disponible en complément. Vérifié : tsc, lint, 117 tests, build prod OK.
…essage Le théâtre n'était ouvrable que depuis le panneau live transitoire : une fois la réponse finie (ou au message suivant), le panneau disparaissait et la délibération devenait inaccessible — alors que les données sont persistées dans les parts du message. - Le théâtre est désormais piloté par theatreMessageId (l'id du message à afficher) → ouvrable pour N'IMPORTE quel message multi-agents passé, pas seulement le dernier. theatreTurns calculé pour le message ciblé. - Bouton « Voir le débat » permanent sous le bandeau d'agents de chaque message multi-agents terminé (en plus du panneau live pendant le run). Vérifié : tsc, lint, 117 tests, build prod OK.
…tomization Conflits résolus en faveur de notre branche (sur-ensemble) : elle contient déjà le travail onboarding/a11y que dataring a squashé (#4/#12), plus le sprint et la personnalisation du Board. Aucune fonctionnalité dataring perdue (les lignes divergentes étaient les versions antérieures de lignes que nos commits ont fait évoluer : focus-ring ring-2→ring-3, copie « v0.3 » corrigée, FolderRow/auto-refresh enrichis). Vérifié post-merge : tsc, lint, 117 tests, build prod OK.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Titre :
feat: fiabilité (coûts, citations, RAG, audit, a11y) + Board personnalisable par agent
Description :
Contexte
mainest à#12(passe a11y/theming/perf). Ce PR apporte deux blocscohérents construits par-dessus l'ancêtre commun : la fiabilisation de
l'app (coûts, sources, intégrité documentaire, audit, robustesse), puis la
personnalisation du Board. Les versions non-squashées d'onboarding/a11y
déjà présentes dans
mainse réconcilient via l'ancêtre commun.Partie 1 — Fiabilité, sources & robustesse
message-metadata,Stop qui coupe vraiment l'appel LLM côté serveur (
abortSignal),estimation coût/appels au point de dépense,
agent_runsrattachés aumessage avec modelId par agent, quota mensuel visible.
(durcissement scheme http(s)), trail multi-agents persisté dans
messages.parts(théâtre/badges/skills survivent au reload), export JSONdu trail, pills « compétences appliquées ».
par utilisateur/projet, détection PDF scanné, transparence
indexé/non-indexé + réindexation, import multi-fichiers, diff de versions
de document (LCS maison).
ré-extraction ciblée, ajout de documents, format de colonne, timeout.
connexion providers/connecteurs, checklist de configuration, prix des
modèles, auto-sync MCP, feedback des toggles, fallback OVH.
(handles masqués, drag séquentiel only), allowlist d'outils en multi-select,
live panel conscient des tours + fallback synthèse (positions brutes
si le synthétiseur échoue), vue-liste mobile.
journal d'audit exploitable (filtres/pagination/export CSV+JSON), palette
⌘K accessible, focus-ring harmonisé, source de nav unique, primitives
EmptyState/temps relatif partagées, contraste des micro-labels.Partie 2 — Board personnalisable
pipeline_agents.rag_scope(null = hérite →zéro régression).
resolveAgentRagpasse unToolScopedéjà restreint àbuildToolsForUsersans modifier buildToolsForUser ni search.ts. Règlede sécurité intersection, jamais union. Modes hérite / aucun / projet /
dossiers / documents, section « Sources » dans la Sheet + badge sur le nœud.
clavier-accessibles) + badge « répond en dernier », dans les 3 modes.
Migration BDD
Après merge :
npm run db:push(convention du repo). Ajoute deux colonnesnullable sur
pipeline_agents:rag_scope(jsonb) ettemperature(double precision). Aucune donnée à backfiller, zéro régression.
Vérification
tsc --noEmit✓ ·npm run lint0 ·vitest run117/117 (dont 12 surle resolver RAG + le fallback synthèse) ·
npm run build✓.Réserve — validation live
Validé en live : RAG par agent (un agent scopé sur un dossier ne lit que ce
périmètre), citation de source. Restent à confirmer sur un vrai run
multi-tours : le rendu SSE du fallback synthèse et le libellé de tour qui avance.