# Feature — Le dossier de projet   ·   Module : core (projets, chat, documents)   ·   Statut : cadré

> Cadré le 2026-08-11 avec le user. **Recentre `../suite/suite.md`**, qui reste la vision longue
> mais dont la majeure partie est parkée — voir « Ce qui sort du périmètre, et pourquoi ».
> Le contenant d'affichage est spécifié à part : [`screen.md`](screen.md).
> **Complété le 2026-08-15** — décisions 13 et 14, ouvertes par le premier test de charge réel.
> **Complété le 2026-08-18** — décision 17, **en révision de la décision 7** : le chat modifie le
> document sans geste quand il sait nommer sa cible. Ouverte par l'usage réel, pas par une idée.
> **Implémentée le même jour**, en trois étapes ; ce qui a divergé de l'écrit est noté dans la
> décision, sous « Ce qui a été construit ».
> **Complété le 2026-08-17** — décisions 15 et 16, et l'amendement de 6 et 13. **B9 est absorbé
> dans B6** : on unifie les deux natures d'objet *avant* d'écrire le sommaire, au lieu de l'écrire
> contre deux natures puis de le simplifier. Le surcoût que la roadmap avait noté et accepté
> disparaît. Motif du user : *« on se retrouve ici avec une dette de l'ancien système, et c'est pour
> ça qu'il y a deux entrées »* — B6 sur le projet d'origine, B9 sur le bug découvert au test.

## Intention

**Un projet est un dossier.** Tout ce qu'on rassemble sur un sujet y va — conversations,
recherches, pages, documents, schémas — et **le chat connaît le dossier**.

Ce n'est pas une suite bureautique. C'est un **dossier de travail** dont l'assistant a lu le
sommaire.

## Le recentrage, en une phrase

`suite.md` (26/01) décrit une suite bureautique souveraine qui se compare à Google Workspace :
versioning pseudo-git, partage entre utilisateurs, collaboration temps réel, hébergement de sites,
tableurs. Ce qui naît réellement avec le moteur de recherche est **plus petit et plus défendable** :
le dossier de travail sur un sujet, et un chat qui le connaît. Aucune des cinq briques ci-dessus
n'est nécessaire à ça — elles étaient nécessaires à « remplacer Google Workspace ».

⚠️ **Le Drive n'est pas à construire, il est à révéler.** Un projet agrège déjà des conversations,
des recherches enregistrées, des instructions et des fichiers joints. Ce qui manque n'est pas un
système de fichiers : c'est que **le document et le schéma deviennent rattachables** comme le reste,
et une vue qui montre le dossier.

## Les décisions (2026-08-11)

### 1. Un projet est un dossier du Drive

Une seule arborescence, pas deux à réconcilier. Cela répond d'un coup à **la même question posée
deux fois à sept mois d'écart** : `suite.md` demandait « un projet = un dossier Workspace
automatique, ou deux concepts séparés ? », le handoff de design demandait « le Drive reste-t-il un
outil de premier niveau, ou un onglet dans le projet ? ».

### 2. Tout porte un projet — le neutre est un projet

Sur le modèle du conteneur système « Conversations rapides » : un document, un schéma, une
recherche ont **toujours** un projet ; soit le neutre, soit un vrai. Plus de branche `NULL`, plus
de cas particulier dans les requêtes.

Cela **corrige une divergence déjà installée** : aujourd'hui une conversation sans projet vit dans
un conteneur système, pendant qu'une recherche sans projet a un `project_id` à `NULL` filtré par
`?project=none`. Deux mécaniques pour un même besoin.

> ⚠️ **Le neutre doit être exempté de toute garde commerciale, explicitement et par un test.**
> Le piège s'est déjà refermé le 2026-08-10 : *aucun compte du plan Gratuit ne pouvait démarrer une
> conversation*, la garde sur le nombre de projets s'appliquant aussi au conteneur système.
> Invisible depuis n'importe quel compte de développement, qui a un plan payant. Si **tout** passe
> désormais par un projet, la même erreur toucherait tout le produit et non plus le seul chat.

### 3. Ce qu'est un Document : un nom, une place, une sortie

La définition d'usage **est** la définition système, et il ne faut pas en chercher une autre. Un
document s'appelle quelque chose, il est rangé quelque part, et il peut quitter le produit
(imprimé, exporté, envoyé).

Ce qui dit aussi ce qui n'en est **pas** un : une lettre dans une bulle de chat n'a ni nom, ni
place hors du fil, et pour la sortir il faut la sélectionner à la souris. C'est un paragraphe de
conversation.

### 4. Le Document est un état, pas un type de contenu

Le même contenu existe en trois états :

| État | Où | Ce qui existe |
|---|---|---|
| **Dit** | dans la conversation | un message, qui vit avec le fil |
| **Ouvert** | dans le Screen | un rendu — **rien n'est créé** |
| **Gardé** | nommé, dans un projet | un document, retrouvable et réexportable |

**Ouvrir, c'est regarder. Toucher, c'est garder.** Le document naît au premier geste qui l'engage —
modifier, renommer, exporter — et pas avant. Zéro clic pour lire, zéro déchet dans le Drive, et
l'utilisateur n'a jamais à se demander si c'est enregistré. C'est le comportement d'un brouillon de
mail : il n'existe pas tant qu'on n'a rien tapé.

Conséquence structurelle : **tout ce qui s'ouvre dans le Screen peut devenir un document**, par
construction et non par une liste de types à maintenir. Le jour où un tableur s'y ouvre, il le peut
sans qu'on ait rien à décider.

### 5. Pas d'ouverture automatique du Screen — le ménage se fait à l'extraction

**Arbitrage du user : pour l'instant, non.** La réponse arrive dans le chat comme aujourd'hui ; un
bouton « éditer / ouvrir » la porte vers le Screen. **Et c'est au clic qu'on fait le ménage** : on
ne garde que le texte de la lettre ou du mail, pas ce qui l'entoure.

**Ce choix est meilleur que l'alternative sur l'axe du risque, et il faut savoir pourquoi.** La
voie concurrente était de décider en amont, dans `IntentClassifier`, que la demande produit un
artefact — ce qui aurait eu un bénéfice réel (une lettre écrite pour être imprimée n'a pas de
préambule *par construction*, ce qui attaquait enfin « le préambule qui résiste »). Mais c'est le
prompt le plus sensible du produit, et le risque y est la **dilution** : ajouter une demande dégrade
les autres, et aucun test unitaire ne le voit. Le ménage à l'extraction obtient le même résultat
visible **sans y toucher**.

> La voie du classifieur reste ouverte, et elle vaudra le coup le jour où on voudra que la réponse
> *elle-même* soit plus propre, pas seulement le document qu'on en tire. À ce moment-là : campagne
> `--group live` avant et après, 28 cas de référence, non négociable.

### 6. Le contexte : un sommaire tenu à l'écriture, et la lecture à la demande

Le projet donne du contexte au chat par un **catalogue** : à chaque nouvelle entrée dans le dossier,
on écrit une ligne. Le chat reçoit le catalogue, **pas les contenus**, et va lire ce dont il a
besoin (« va lire mon chien.docx »).

- **Le coût est amorti** : le résumé s'écrit **une fois, à l'ajout**, jamais à chaque tour. Même
  mécanique que `ConversationSummary` pour la compaction et que le corpus L'dicO qui écrit à
  l'ingestion et laisse le cron enrichir. Rien ne s'ajoute au chemin critique d'un tour.
- **Le sommaire porte de quoi décider, pas de quoi répondre** : un titre, une nature, une date, deux
  lignes de sujet, un identifiant lisible. Un catalogue de bibliothèque, pas un digest.

> **Amendé le 2026-08-17.** Trois points que la décision laissait implicites, et dont deux
> auraient reproduit le défaut qu'elle corrige.
>
> **Le catalogue et les résumés ne voyagent pas par le même chemin.** Le catalogue — nom, nature,
> date, identifiant — est borné et sert à décider : il peut rester dans le prompt système. Les
> **résumés n'y ont pas leur place** : ce qui transite par le socle entre dans les *incompressibles*
> de `TurnPayloadBuilder`, où rien n'est arbitré ni compacté. Un résumé par pièce est vingt fois plus
> léger qu'un texte intégral, mais **de même nature** — à cent pièces, le mur revient, simplement
> plus tard. Les résumés sont donc une **source arbitrée sous budget**, à côté de l'historique et de
> la recherche. ⚠️ **C'est le point où le lot 5a renaîtrait une troisième fois.**
>
> **Le résumé des pièces déjà présentes s'écrit paresseusement**, à la première utilisation du
> projet, et non par une commande de reprise lancée au déploiement. Motif : il coûte un appel Small
> par pièce, et une reprise en masse qui appelle une API externe échoue au premier délai dépassé.
> Entre-temps la pièce **est au sommaire avec son nom**, et l'absence de résumé est **dite** — le
> comportement existe déjà et il est délibéré ([`DocumentContext::resume()`](../../../src/Service/Context/DocumentContext.php)) :
> une troncature muette fait conclure le modèle sur du partiel, ce qui est pire qu'une absence.
> Le coût est imputé au **quota de l'utilisateur**, comme tout appel fait pour lui.
>
> **Une image se résume par l'OCR, puis par la vision si l'OCR ne rend rien.** L'OCR ne lit que du
> texte : il sert un scan, une capture ou un tableau blanc, et rend le vide sur une photo de
> chantier, un graphique ou un logo. Le produit a déjà les deux chemins — `OcrService` et Pixtral,
> qui porte son propre quota. L'ordre est celui du coût : l'OCR d'abord, la vision seulement quand
> il n'y a rien à garder.
>
> ⚠️ **L'OCR distant n'est pas configuré**, et ce n'est pas un défaut de code : `OcrService` porte
> bien ses deux modes et le distant prime dès que l'URL et la clé existent, mais `OCR_REMOTE_URL`
> est vide et Tesseract n'est installé que dans le conteneur Docker. **Ça marche en local et pas sur
> le mutualisé.** Bloquant pour livrer, pas pour construire — inscrit à D8 avec ImageMagick.

### 7. Le Screen est atteignable par le chat — la prise

> ✅ **Livrée le 2026-08-15/16** pour l'**éditeur TipTap** (le texte) et pour la **forme**
> du document (`core/gabarits.md`, décision 16). Le schéma et l'image restent à faire :
> pour un schéma, la prise coûte un aller-retour de dépôt d'image (décision #0009).
>
> **Le geste est explicite pour le texte** (arbitrage du user, 2026-08-15) : sélection dans
> l'éditeur, sixième entrée du menu bulle, puis la consigne s'écrit **dans le chat**. La
> détection d'intention a été écartée — le jour où elle se trompe, elle n'affiche pas une
> réponse à côté, elle **écrit dans le document**.
>
> ⚠️ **DESSERRÉ le 2026-08-18 — voir la décision 17.** Le geste n'est plus la seule porte : le
> chat modifie aussi le document quand il sait **nommer sa cible** (un titre, ou un passage que
> l'utilisateur colle). Ce qui a été mesuré à l'usage : le geste n'est pas découvrable, et même
> celui qui l'a commandé ne s'en sert pas. Et l'analyse d'origine avait un angle mort — le geste
> ne servait pas à autoriser, il servait à **localiser**.
>
> ⚠️ **La roadmap dit que B6 dépend du tool calling : c'est vrai de la lecture à la demande,
> pas de la prise.** Modifier un passage affiché ne demande qu'un tour spécialisé qui rend
> un texte de remplacement. Les deux moitiés sont séparables.
>
> **Le chemin dangereux est le RETOUR**, et c'est lui qui porte les gardes : les positions
> TipTap sont revérifiées avant d'écrire (le passage a pu bouger pendant le tour), une
> réponse vide est refusée (ce serait une suppression), et l'insertion est **une seule
> transaction** — `Ctrl+Z` la défait d'un coup, marques comprises.
>
> ⚠️ **Le passage part en Markdown, jamais en texte plat**, et ça s'est joué à deux détails
> (`sourceDeLaSelection`) : un fragment de texte nu perd ses marques, mais `doc.cut()` en
> garde trop — une puce sortait avec les marqueurs des listes qui l'entouraient.
>
> **Un tour de prise ne paie pas ce qu'il n'utilise pas** : une retouche (≤ 600 caractères,
> sans titre) part en `small` et **sans le fil**, une réécriture garde les deux. Voir
> `Service\Turn\PriseDuTour`.

Ce qui est affiché dans le Screen doit pouvoir être **désigné** et **modifié** depuis le chat : je
sélectionne un passage et je demande « reformule », la réponse remplace le passage. Je dis « change
le nom de la bulle *chien* en *chat* », le schéma se met à jour.

**C'est la symétrie du Screen, et elle est gratuite.** Chaque moteur qu'on ajoute apporte deux
choses : une **sortie** (afficher) et une **prise** (modifier). TipTap a un modèle de document avec
des positions ; Mermaid est du texte, donc un schéma se modifie en modifiant sa source ; Univer
aura son API le jour venu. La difficulté n'est pas dans le Screen, elle est dans le tour de chat —
voir les contraintes.

**Et la boucle se ferme avec la décision 4** : reformuler un passage, c'est *toucher*. La première
modification **crée** le document. Aucune règle supplémentaire à écrire.

### 8. Trois portes, trois questions — la colonne ne change pas

Le Drive **reste** un outil de la colonne, l'outil Documents **reste**, et la vue Projets reste.
Ce ne sont pas trois accès au même endroit : ce sont trois questions différentes, et aucune vue ne
sait répondre à la place d'une autre.

| Porte | Question | Tri | Fréquence du geste |
|---|---|---|---|
| **Documents** | *« je reprends mon travail »* | **dernière modification** | quotidienne |
| **Drive** | *« où est ce truc ? »* — toutes natures confondues | date, type | occasionnelle |
| **Projet** | *« de quoi je dispose sur ce sujet ? »* | par thème | contextuelle |

C'est le motif de la boîte de réception et des dossiers, de la pellicule et des albums. Personne
n'y voit un doublon.

> ⚠️ **Le Drive affiche des DOSSIERS, pas une liste** — précisé le 2026-08-12 après une première
> implémentation fautive. La décision 1 dit « un projet **est** un dossier du Drive » ; la première
> version a implémenté la phrase sans son intention et a livré un listing à plat. **Un Drive qui
> déverse tout à plat n'est pas un rangement, c'est un journal.**
>
> La vue **Dossiers** est donc la vue par défaut ; la liste à plat devient un second mode, utile
> quand on ne sait plus dans quel dossier chercher — ce qui est exactement la question de cet outil.
> On y crée un dossier, et un dossier ouvert mène à **la fiche du projet**, sa vraie page : en
> réimplémenter une ici la ferait diverger.
>
> **« Sans projet » y figure comme dossier mais n'est pas un projet.** Sans lui, tout ce qui est
> gardé avant d'avoir créé un projet serait invisible — le cas de tout le monde au départ. Il ne
> compte pas dans le compteur.
>
> **Le compteur du Drive compte les dossiers**, pas les éléments (arbitrage du 2026-08-12) :
> compter le contenu total lui ferait répéter la somme des compteurs au-dessus, ce qui n'apprend
> rien.

**Deux arguments décident, et le second est plus fort que le premier.** D'abord l'usage de reprise :
un étudiant ne *cherche* pas son rapport, il le *reprend* — le faire passer par un filtre ou par un
projet serait lui faire chercher ce qu'il sait déjà où il est. Ensuite le neutre : avec la
décision 2, **la plupart des comptes n'auront aucun projet pendant longtemps**. Si le seul chemin
passait par la vue Projets, on leur demanderait d'entrer dans un conteneur système qu'ils n'ont pas
créé pour retrouver leurs propres fichiers.

> ⚠️ **L'outil Documents change de nature, il ne disparaît pas.** Le design le dessinait comme un
> **éditeur** — `panneau-sortie.md` avait déjà noté que ce serait faux. Mais la conclusion n'est pas
> qu'il est inutile : il passe de « là où je crée » à **« là où je reprends »**. D'où le tri par
> dernière modification, qui se coderait par défaut à l'envers.

### 9. Il n'y a pas de page blanche

> ✅ **Branchée le 2026-08-19** — et elle ne l'était pas depuis le 2026-08-11. Le bouton
> « Nouveau document » appelait `navigate(tool.path)` depuis `/documents`, c'est-à-dire la page
> où l'on se trouvait déjà : rien ne se passait. ⚠️ **Ce n'était pas un manque de spec, c'était
> une spec non tenue** — la roadmap l'avait classé « hors périmètre, en attente », et l'écran,
> lui, montrait un bouton bleu avec son raccourci. Ce que le bouton DIT en arrivant relève de
> B10, et fait l'objet de la décision 18.

L'action primaire de l'outil Documents n'ouvre pas un éditeur vide : **elle ouvre un chat qui
demande ce qu'on veut créer.** Un document naît toujours d'une conversation — sans exception, ce
qui évite d'avoir à maintenir un second chemin de création.

C'est une position de produit, pas un détail d'interface : Word vous laisse seul devant une page
blanche, LintellO vous demande ce que vous voulez faire. Et mécaniquement, le chemin existe déjà —
`pendingMessage` sert déjà à « Discuter dans le chat » depuis l'écran de recherche.

**Les gabarits (« templates ») viennent par là**, et il faut distinguer deux choses qui n'ont pas
le même coût :

- **Le modèle *est* le gabarit.** « Fais-moi un rapport de stage » produit déjà la structure, et il
  l'adapte au cas de la personne — ce qu'un gabarit figé ne fait pas. Coût : zéro, c'est le chat.
- **Une galerie de gabarits** (objets stockés, choisissables) apporte autre chose : la
  **découverte**, pour qui ne sait pas quoi demander. C'est un vrai bénéfice, mais c'est du
  stockage, une surface et de l'entretien.

**Recommandation** : commencer par des **suggestions** dans l'écran de départ — « un rapport ? une
lettre ? un CV ? » — qui ne sont que des amorces de conversation. On obtient la découverte sans
créer d'objet. Si l'usage montre que trois d'entre elles reviennent tout le temps, elles pourront
devenir de vrais gabarits.

⚠️ **Ne pas promettre le « à la Word » sur la mise en page.** Un gabarit Word est largement une
affaire d'**apparence** — marges, en-tête, papier à en-tête, polices. La chaîne actuelle
(TipTap headless → Markdown → DOCX) rend de la **structure**, pas une mise en page paginée. Des
gabarits de structure sont à portée ; la fidélité visuelle à l'impression est un autre sujet, et
il n'est pas ouvert.

### 10. Un document appartient à un seul projet

Pas de document partagé entre deux dossiers. **Arbitrage du user : le besoin n'est pas visible
aujourd'hui, on verra à l'usage.** Noté ici pour que ce soit une décision et non un oubli : c'est
l'appartenance simple qui permet à la corbeille, à la purge et au sommaire de rester triviales.

### 11. La corbeille — un document n'est pas une page web

> **Motif du user : *« un doc n'est pas une page web, il faut une sécurité »*.**

Une page web dérivée se repurge sans conséquence : la source est dehors, elle se relit. **Un
document, non — il n'existe nulle part ailleurs.** Le supprimer, c'est le perdre.

**Deux niveaux, jamais un seul :**

1. **Mise en corbeille** — réversible. Le document ou le projet quitte la vue courante et vit dans
   la corbeille.
2. **Suppression définitive** — explicite, confirmée, et seulement depuis la corbeille.

**Ce que la règle emporte :**

- **Rien n'expire tout seul.** Ce qui est en corbeille y reste jusqu'à ce que l'utilisateur décide.
  Pas de purge automatique à 30 jours : une donnée qui disparaît sans qu'on l'ait demandé est le
  contraire d'une sécurité.
- **Mettre un projet en corbeille y emmène son contenu**, visible et restaurable **d'un bloc**.
  Sinon on aurait des documents orphelins dans un dossier qui n'existe plus.
- **Restaurer remet à sa place d'origine.** Si le projet d'origine a été supprimé définitivement,
  le document repart au projet neutre (décision 2) — jamais perdu, jamais bloqué.
- ⚠️ **La purge des pages web dérivées se fait à la suppression définitive, pas à la mise en
  corbeille.** L'argument juridique du 2026-08-07 dit « purgé **avec le projet** » ; tant qu'il est
  en corbeille, le projet existe encore. La mise en corbeille n'est pas une suppression.
- ⚠️ **La suppression d'un compte purge la corbeille**, sans étape intermédiaire. C'est la condition
  pour que le droit à l'effacement reste vrai — et pour que la promesse « suppression = suppression
  réelle » ne devienne pas une rétention silencieuse. **Une corbeille est une sécurité pour
  l'utilisateur, jamais un stock pour nous.**
- **Ce qui est en corbeille sort du sommaire du projet** : le chat ne doit pas travailler sur ce que
  l'utilisateur a jeté.

**Ce qui est en corbeille compte dans le quota du plan** (tranché le 2026-08-11). Ce n'est pas une
punition, c'est le **mécanisme qui fait vider la corbeille** : sans pression de quota, rien
n'inciterait jamais à la purger, et puisqu'on a écarté toute expiration automatique, il fallait bien
que quelque chose déclenche le geste. À dire dans l'interface plutôt qu'à cacher — « votre corbeille
occupe X » est une information utile, pas un reproche.

**L'accès à la corbeille se décidera au design** (arbitrage du user) : sixième porte de la colonne,
ou endroit atteint depuis le Drive. La spec n'en dépend pas.

### 12. L'enrichissement de l'éditeur — ce qui est prévu, et ce qui ne le sera pas

> Cadré le 2026-08-12, après mesure. Rien n'est codé.

**Le format décide, pas le goût.** Le document est stocké en Markdown, donc une extension
d'éditeur n'est acceptable que si elle fait l'aller-retour. La mesure a été faite avec la
configuration réelle (`tiptap-markdown`) :

| En `html: false` (notre config) | En `html: true` |
|---|---|
| `<u>` → `&lt;u&gt;` **visible en clair** dans le document | `<u>` survit **tant qu'aucune extension ne le revendique** |
| idem pour `<mark>`, `<span style>`, `<sup>` | dès qu'on ajoute `Underline`/`Highlight`/`Color`, le balisage **disparaît en gardant le texte** |

Mesuré : `Un <mark>surligne</mark> la.` ressort `Un surligne la.` Le mot reste, la mise en forme
s'évapore — sans erreur.

**Donc couleur, surlignage, souligné, exposant et alignement ne sont pas une question de bouton.**
Ils supposent de changer le format de stockage, ce qui touche la décision 6 (le chat lit les
documents). ⚠️ **Ne pas les rouvrir pour de la décoration.**

**Ce qui est prévu, par rapport qualité / coût :**

1. **Typography** — transforme des *caractères*, donc la sortie reste du texte pur : guillemets
   français, apostrophes typographiques, tirets cadratins, `…`, `×`. Un texte français bien
   composé est ce qui distingue un document d'un copier-coller. Une dépendance, zéro risque.
2. **Menu contextuel à la sélection** (BubbleMenu) — **déjà dans `@tiptap/react`**, aucune
   dépendance. Le plus gros gain d'usage de la liste.
3. **Placeholder** — un document vide sans repère a l'air cassé.
4. **Menu slash** (`/titre`, `/tableau`) — purement d'interface, donc sans risque de format ; plus
   de travail, mais c'est le geste que les gens connaissent.
5. **Coloration syntaxique des blocs de code** — Markdown-sûre (```` ```lang ````), et **elle
   recouvre une partie de B1** : à faire ensemble plutôt que deux fois.
6. **Insertion d'image** — l'extension est installée et `![]()` survit, mais il faut d'abord
   trancher *d'où vient l'image* : une URL collée, ou un envoi de fichier ? La seconde est un
   chantier, pas un bouton.

**Écartés** : blocs dépliants et vidéo (HTML), et la poignée de déplacement — qui relève, sauf
vérification contraire, de l'offre payante de TipTap.

### 13. Un fichier téléversé est un document — le troisième silo disparaît

> Tranché le 2026-08-15, après le premier test de charge réel. Les constats mesurés qui l'ont
> ouvert vivent dans `../../../STATE.md`.

La décision 1 dit « un projet est un dossier du Drive » et la décision 3 définit un document par son
usage — **un nom, une place, une sortie**. Un référentiel déposé dans un projet a les trois. Il n'y
avait donc jamais eu de raison d'en faire une nature à part, et pourtant `ProjectFile` en est une :
invisible du Drive, invisible de la fiche du projet, sans résumé, et **seule à ne pas passer par le
sommaire**.

**Un fichier téléversé devient un `Document`** — `kind: text`, rangé dans le projet, ouvert par le
Screen au clic. Le silo ne se branche pas au Drive : **il est absorbé.** C'est l'avertissement de
l'intention — *« le Drive n'est pas à construire, il est à révéler »* — appliqué à l'objet qui avait
été oublié de sa liste : elle nommait le document et le schéma, jamais le fichier téléversé.

⚠️ **Le motif compte plus que la décision, parce que l'objection est naturelle et fausse.**
« En réécrivant tout en Markdown, ne perd-on pas des informations ? » suppose qu'on conserve
aujourd'hui davantage. C'est l'inverse : l'extraction produit du **texte plat**, plus pauvre que du
Markdown. Elle écrit déjà les barres verticales d'un tableau et les puces d'une liste, et **jette le
niveau des titres**. Passer au Markdown ne perd rien — **il récupère**.

**Conséquence à tenir au moment de coder : l'extraction doit *produire* du Markdown**, pas du texte
plat. C'est déterministe et quasi gratuit sur Word/ODT, où la structure est disponible et écartée à
la dernière marche. C'est **hors de portée sur un PDF**, dont on n'extrait aucune structure — ne pas
promettre là ce que seul un modèle pourrait deviner, car il l'inventerait.

Ce que le Markdown ne portera jamais — pagination, couleur, souligné, surlignage, formules — est
déjà écarté par la décision 12, mesures à l'appui. **Aucun renoncement nouveau ici.**

> **Amendé le 2026-08-17 — ce qu'une pièce importée devient, et ce que deviennent celles déjà là.**
>
> **Une pièce importée est un document en lecture seule** : le Markdown extrait, non éditable, cité
> comme source. Arbitrage du user : *« du texte en MD non éditable, c'est tout — normalement ils
> n'iront pas »*. Ce n'est pas un verrou technique contre l'utilisateur, c'est le refus de
> promettre une édition dont la sortie ne pourrait pas revenir dans le fichier d'origine, lequel
> n'existe plus (décision 14). L'ouvrir plus tard reste possible et ne casse rien.
>
> **Les pièces déjà déposées sont reprises, une par une, et sans rien perdre.** Ce qui vit en base
> pour chaque `ProjectFile` — le nom d'origine, le type, la taille, le **texte extrait** chiffré —
> suffit à recréer un document par pièce, avec son nom. Ce qui manque est le binaire, et la lecture
> seule le rend sans objet.
>
> **Une image se reconstitue entièrement**, ce qui n'était pas prévu : son `extractedText` porte
> **l'image complète en base64**, jamais tronquée ([`FileService`](../../../src/Service/FileService.php),
> le préfixe `[IMAGE_BASE64]`). Le fichier se réécrit sur le disque et redevient un `DocumentImage`
> de plein droit, avec son texte OCR déjà stocké à côté.
>
> ⚠️ **Le détecteur maison ne doit pas servir à ça.** `FileService::isImageBase64()` teste que la
> chaîne *commence* par le préfixe. Or, quand l'OCR a réussi, le texte stocké est
> `[OCR]…[FIN_OCR]` **puis** `[IMAGE_BASE64]…` : la méthode rend donc **faux pour toute image dont
> l'OCR a réussi**. S'y fier ferait passer ces images pour des documents texte et écrirait des
> mégaoctets de base64 dans le Drive. **La détection se fait sur le type MIME**, comme le fait déjà
> `PromptAssembler::projet()`.
>
> **Un filet, et il doit rester rare** : une ligne qu'on ne sait pas replacer va dans un document
> nommé **« Anciens contexte projet »**, au pied du dossier, plutôt que d'être perdue en silence.
> Le cas normal ne l'emprunte pas.
>
> **L'interrupteur `isActive` est abandonné** (arbitrage du user : *« on ne perd pas d'intérêt »*).
> Conséquence assumée : une pièce qu'un utilisateur avait désactivée **revient dans le contexte**
> après la reprise. C'est le seul effet visible de la bascule, et il est silencieux — donc écrit ici.

### 14. Le fichier d'origine n'est pas conservé — et c'est une décision

> Tranchée le 2026-08-15. Inscrite pour que ce soit un choix et non un oubli.

**LintellO conserve du texte, pas des fichiers.** C'est déjà vrai partout : le binaire est détruit
après extraction pour une pièce jointe de conversation, et un fichier de projet n'est même jamais
écrit sur le disque. « Stocké dans le Drive » ne décrit donc l'état d'aucun fichier aujourd'hui.

Cet état de fait est **reconduit volontairement**. Le seul cas où l'original a une valeur propre est
celui où **le fichier fait foi** — un contrat signé, un acte, un document officiel qu'il faut
pouvoir ressortir intact. Un référentiel déposé pour que l'assistant le lise n'en est pas : c'est
son texte qui vaut, pas son fichier.

⚠️ **La seule chose à ne pas faire : écrire du code qui présume l'existence de l'original.** À cette
condition, conserver les binaires reste un **ajout** et jamais une reprise. Ce qu'il emporterait
alors n'est pas gratuit et se décidera à ce moment-là : stockage, quota, purge, et une ligne dans la
grille — la dette « le stockage n'a pas d'instrument » de `STATE.md` tient déjà le raisonnement de
coût, et sa conclusion vaut ici. **Le giga-octet est le mauvais compteur : ce qui coûte, c'est
l'entrée.**

> **Reconduite le 2026-08-17, et la question est refermée.** Elle avait été rouverte par la
> lecture seule : « source non éditable » pouvait vouloir dire « l'original consultable », ce qui
> aurait imposé de conserver les binaires — faisable, `DocumentImage` le fait déjà avec sa colonne
> `chemin`. Le user a tranché l'autre sens : la lecture seule porte sur **le Markdown extrait**.
> Il n'y a donc plus aucun motif de garder les originaux, et la décision 14 tient sans amendement.

### 15. Le chat va chercher une pièce — l'outil de lecture

> Tranché le 2026-08-17. **C'est la seule brique réellement neuve de tout l'ensemble** : LintellO
> n'a jamais fait de *tool calling* — vérifié, zéro occurrence de `tools` dans `Service\Mistral`.

Le sommaire dit ce que le dossier contient ; l'outil est ce qui permet d'en **ouvrir une pièce**.
Sans lui, le catalogue annonce des documents que personne ne peut lire — les deux moitiés de la
décision 6 ne se livrent pas séparément.

**L'adresse d'une pièce sert au modèle, pas à l'humain** (arbitrage du user) : l'humain sait où la
trouver, il passe par le Drive. Une adresse donnée à un modèle sans outil pour l'appeler n'est
qu'une chaîne de caractères qu'il récitera ou inventera.

Quatre gardes, et elles se décident **avant** d'écrire la première ligne :

- **Ce que l'outil rend est borné.** C'est le risque principal, et il est structurel :
  `TurnPayloadBuilder` calcule l'enveloppe **une fois**, avant l'appel, et il *écrit* — il compacte
  ce qui déborde. Un contenu qui arrive au milieu du tour, en réponse à l'outil, **n'a traversé aucun
  budget**. Le tour part en `small` (arbitrage du user), donc dans une fenêtre de 32 000 tokens :
  une pièce rendue en entier fait franchir le mur, et ce n'est pas une dégradation, c'est un
  **échec de requête**. L'outil rend un extrait ou une tranche, avec un plafond, et il **dit** qu'il
  a tronqué — même règle que le résumé absent de la décision 6.
- **L'identifiant demandé doit appartenir au compte.** Reprise telle quelle de la garde de B8 sur
  les images : ce n'est pas l'adresse écrite quelque part qui fait foi, c'est la ligne en base et son
  propriétaire.
- **Le nombre d'allers-retours est plafonné à trois.** Rappeler l'outil est un comportement normal,
  pas une erreur — une pièce lue en cite une autre. Au-delà du plafond, le modèle répond avec ce
  qu'il a **et le dit**. C'est un chiffre, pas une architecture : il se règle à l'usage.
- **Le quota se vérifie avant d'engager le premier aller-retour.** Les exceptions « plus de quota »
  et « pas assez de quota → passez au plan supérieur » existent pour ça. Vérifier en cours de route
  livrerait un **demi-tour** : une réponse commencée, coupée en plein milieu par un mur de
  facturation.

**Et le comptage se branche le premier jour.** Un tour avec outil, c'est deux appels Mistral ou
plus. ⚠️ La leçon du lot 6b est fraîche : Flow imputait au **quota Large** les appels Small de ses
étapes, et un Étudiant épuisait ses tokens avec des appels qui n'en étaient pas. Un chemin non
compté est un poste de coût invisible, pas un poste gratuit.

**Ce que l'utilisateur voit pendant ce temps.** Un aller-retour d'outil est un moment où le modèle
ne produit pas de texte : sans rien, c'est un blanc. Le spinner du chat existe déjà — il **nomme la
pièce** (« je lis *référentiel-bac-pro.pdf* ») plutôt que d'afficher un texte aléatoire. C'est le
seul moment du produit où l'attente a une raison précise, donc affichable.

⚠️ **Un risque qu'on peut écarter, et il vaut mieux l'écrire que le redouter** : l'outil se déclare
sur l'appel qui **répond**, jamais sur `IntentClassifier`. Le prompt du classifieur n'est pas touché,
et la campagne `--group live` n'est pas en jeu.

> **Livré le 2026-08-17, et la forme retenue n'est pas celle que cette décision décrivait.**
>
> La décision annonçait « un aller-retour **au milieu** » du tour. L'implémentation le place
> **avant** : un appel `small` dédié décide s'il faut ouvrir une pièce, puis le tour de réponse
> part normalement, en streaming, avec ce qui a été lu. Ce qui a été échangé :
>
> - **le flux du chat n'est pas touché.** Gérer des `tool_calls` dans un flux SSE demande
>   d'accumuler des fragments d'arguments JSON répartis sur plusieurs deltas, d'interrompre le
>   flux puis de le reprendre — sur le chemin le plus critique du produit ;
> - **le modèle ne répond qu'une fois.** Une boucle d'outils non streamée qui finirait par une
>   réponse ferait payer cette réponse deux fois : une pour la lire, une pour la streamer.
>
> **Ce que ça ne change pas** : le modèle voit un vrai outil, l'appelle vraiment, et reçoit
> vraiment ce qu'il a demandé. ⚠️ **Ce que ça change** : la décision de lire se prend **avant**
> d'avoir commencé à répondre — le modèle ne peut pas s'apercevoir en cours de rédaction qu'il
> lui manque une pièce. C'est le prix, et il est payé sciemment.
>
> **Effet de bord heureux, qui lève la crainte principale de cette décision** : la lecture ayant
> lieu avant `TurnPayloadBuilder::construire()`, ce qu'elle rend **traverse le budget** comme
> l'historique et la recherche (`ContextPriority::PIECE_LUE`). Le plafond d'entrée reste
> nécessaire — il empêche une pièce entière d'écraser l'enveloppe avant tout arbitrage — mais le
> contenu n'entre plus « sans avoir été arbitré ».
>
> **Deux gardes de plus, trouvées au clavier** :
>
> - **la pièce doit être dans CE projet**, pas seulement appartenir au compte. Sans cette seconde
>   condition, une conversation d'un projet lirait les pièces d'un autre — ce que rien dans
>   l'interface ne laisse supposer ;
> - **un projet sans dossier ne paie aucun appel.** C'est ce qui rend la fonctionnalité gratuite
>   pour l'immense majorité des conversations ; sans elle, chaque tour du produit paierait un
>   `small` de plus pour se voir proposer de lire un dossier vide.
>
> **`tool_choice` vaut `auto`, jamais `any`** : `any` force un appel, donc le modèle inventerait
> une pièce à lire même quand la question n'en demande aucune. Le droit de ne rien demander est la
> moitié de l'intérêt de l'outil.
>
> **Vérifié sur l'API réelle** (`mistral-small-latest`, 2026-08-17), et pas seulement en tests
> doublés : le modèle rend bien des `tool_calls`, **ses arguments arrivent en JSON dans une
> chaîne** et non en objet, il extrait l'identifiant écrit entre crochets du sommaire, et il
> cesse d'appeler l'outil une fois le contenu reçu. Le premier appel a coûté 215 tokens d'entrée
> pour 54 de sortie.

### 16. Les plafonds — ce qui tombe, et ce qui ne le remplace pas encore

> Tranché le 2026-08-17.

**Deux digues tombent avec ce lot**, et non une seule : le plafond de **50 000 caractères par
projet**, et le plafond de **10 fichiers par projet**
([`ProjectFileController`](../../../src/Controller/Api/ProjectFileController.php)) — le second
n'avait jamais été relevé dans les constats. Toutes deux protégeaient l'injection intégrale ;
l'injection disparaît, elles n'ont plus rien à retenir.

**Ce qui les remplace n'est pas de même nature.** Le budget de contexte prend le premier rôle : il
**arbitre** au lieu de refuser, et c'est tout le progrès du lot. Mais il ne protège pas le
*stockage*, et il n'a jamais eu à le faire.

⚠️ **Le quota de stockage n'existe pas** — il n'est pas trop bas, il est **absent**. Le code le dit
lui-même : *« aucune garde de quota ici »*
([`DocumentImageController`](../../../src/Controller/Api/DocumentImageController.php), et la même
note dans `DocumentController`). Ce qui existe aujourd'hui plafonne **par fichier** et **par unité**,
jamais un total : taille par fichier et par image selon le plan, texte extrait de 50 000 à 200 000
caractères selon le plan ([`FileService`](../../../src/Service/FileService.php)). La **mesure**,
elle, existe déjà (`poidsTotal`).

**En attendant, on applique les plafonds par plan existants dans leur version la plus favorable à
l'utilisateur** (arbitrage du user). Conséquence assumée et écrite pour qu'elle soit choisie : entre
maintenant et la pose du quota, **le stockage n'a plus de plafond total**. En bêta fermée c'est sans
conséquence — c'est une porte laissée ouverte sciemment, pas un oubli.

### 17. Le chat modifie le document sans geste — quand il sait nommer sa cible

> Tranché le 2026-08-18, **en révision de la décision 7**. Motif : l'usage réel.
> **Livré le 2026-08-18**, en trois étapes — voir « Ce qui a été construit » en fin de décision.

#### Ce que l'usage a montré, et pourquoi c'est un défaut

Le user, qui **a commandé** le geste explicite et sait qu'il existe, ne s'en sert pas. Il ouvre un
document, il écrit dans le chat *« le tableau des viandes a disparu, tu peux le réintégrer dans le
document ? »*, et il attend que ça se fasse. Le chat a répondu à côté ; rien n'a bougé.

> *« Au final dans l'utilisation, j'ai plutôt tendance à me servir du chat pour modifier le doc,
> pas de l'outil car je ne sais pas qu'il existe — moi je le sais mais pas mon user lambda. Donc
> ce sera considéré comme un bug. »*

⚠️ **Le geste n'est pas découvrable, par construction.** La sixième entrée du menu bulle
n'apparaît qu'**après** avoir sélectionné du texte : elle est invisible à qui n'a pas déjà eu
l'idée de sélectionner. Une fonctionnalité qu'il faut connaître pour la trouver n'existe que pour
ceux qui l'ont écrite.

#### Le retournement : le geste donnait l'ADRESSE, pas la permission

C'est l'erreur d'analyse de la décision 7, et elle est instructive. On croyait que le geste servait
à **autoriser** — « l'utilisateur a désigné, donc il voulait ». Il servait surtout à **localiser** :
des positions TipTap exactes.

Une modification demande trois choses. Le chat en avait toujours deux :

| | D'où ça vient |
|---|---|
| **Quoi** changer | la phrase de l'utilisateur — le chat l'a toujours eue |
| **Où** l'écrire | le geste, **et rien d'autre** — c'était le manque |
| **Avec quoi** | le fil de la conversation — le chat l'a toujours eu |

Dans l'exemple ci-dessus, les trois étaient disponibles : la demande, le titre « Viandes », et le
contenu qui se trouvait plus haut dans le fil. **Il ne manquait que la carte du document.**

#### La règle

**LintellO agit quand il sait nommer sa cible sans ambiguïté. Sinon il le dit et propose une
sortie.**

L'adresse a trois sources, et **aucune n'est devinée** — les trois se vérifient :

| Source | Résolution |
|---|---|
| **Un titre** — « la section Viandes » | comparaison sur le sommaire du document |
| **Un passage collé** — l'utilisateur colle le texte | recherche **exacte** dans le document |
| **Le geste** — sélection dans l'éditeur | positions TipTap, exactes par construction |

Et la résolution décide, pas la confiance du modèle :

- **une seule correspondance** → il agit ;
- **aucune** → il demande. Il n'invente pas une place ;
- **plusieurs** → il demande laquelle.

⚠️ **Le critère n'est pas « le modèle est-il sûr ».** Un modèle est toujours sûr. Le critère est
la résolution de l'adresse, qui est déterministe et coûte une comparaison de chaînes. C'est ce qui
permet d'écrire **sans clic de validation** — arbitrage du user : une confirmation à chaque
modification est précisément le mauvais UX qu'on répare.

#### Le droit de ne pas savoir, et les deux sorties

> *« LintellO a le droit de ne pas savoir et il doit le dire et proposer une alternative ! »*

- l'endroit reste introuvable → *« sélectionnez le passage et demandez-moi depuis là »* ;
- la cible est trop fine pour un titre → *« collez-moi le texte à changer, je le retrouverai »*.

⚠️ **C'est ce qui rend le geste enfin découvrable** : c'est le chat qui l'apprend à l'utilisateur,
au moment exact où il sert. Le menu bulle ne disparaît pas — il cesse d'être la seule porte pour
devenir **le chemin précis**.

**Le collage referme le trou que les titres laissent.** Une phrase, une ligne de tableau, un
paragraphe n'ont pas de titre pour cible ; collés, ils se retrouvent au caractère près. Le
périmètre de « agir » n'est donc pas une liste d'actions autorisées : c'est **tout ce dont il sait
nommer la cible**.

#### Ce que ça demande, et c'est peu

Le tour reçoit aujourd'hui l'**identifiant** du document ouvert, et rien d'autre — il ne pouvait
donc pas trouver « Viandes » même en le voulant. Il lui faut le **sommaire du document** : ses
titres, avec leur niveau et leur rang.

⚠️ **Les titres, pas le contenu** — exactement la règle du sommaire de projet (décision 6) : *de
quoi décider, pas de quoi répondre*. Un document de cent pages a vingt titres.

#### Ce que l'utilisateur voit

Le document change **sous ses yeux**, et le fil dit ce qui a changé — comme le réglage de forme,
qui annonce déjà « les titres sont passés en rouge ». `Ctrl+Z` défait en une fois.

⚠️ **Il n'y a pas d'historique de document, et c'est un choix.** L'annulation vit dans l'éditeur :
elle part avec lui. Le versionnage est parké dans `../suite/suite.md`. Le user a tranché en
connaissance de cause :

> *« Ce risque de perte, les humains vivent avec. Quand Windows plante et qu'OpenOffice est
> ouvert, tu perds. Moi humain je sais qu'on peut perdre des données informatiques, et je vis
> avec. »*

Et l'objection pratique qui va avec : on ne ferme pas un document qu'on est en train d'éditer.

#### Ce qui a été construit, et ce qui a divergé de l'écrit

> Livré le 2026-08-18. Ce paragraphe existe parce que trois choix d'implémentation ne se
> déduisaient pas de la décision, et qu'un lecteur qui ne les trouverait pas ici les prendrait
> pour des accidents.

| Étape | Ce qu'elle a produit |
|---|---|
| **1 — le silence** | un bloc de prompt émis dès qu'un document est ouvert **et** que le tour n'est pas dirigé : il dit les deux sorties |
| **2 — le plan** | `SommaireDuDocument` — les titres, avec leur rang et leur niveau, jamais le contenu |
| **3 — l'outil** | `modifier_le_document_ouvert(cible, remplacement)`, résolu par `CibleDuDocument`, appliqué par l'éditeur |

**Le plan part par le prompt système, pas par le message.** C'est l'inverse du passage d'une prise
(décision 7), et pour une raison précise : le message composé est **persisté**. Un plan poussé
dedans s'afficherait dans le fil, y resterait, et repartirait en historique à chaque tour suivant —
la maladie du lot 5a, en plus petit. Le passage d'une prise, lui, a une raison d'être persisté : le
fil garde la trace de ce qui a été modifié. Un plan n'en a aucune.

**Le serveur ne réécrit pas le document.** Il rend le couple *ancien / nouveau* ; l'éditeur
applique, en une transaction. C'est ce qui tient la contrainte « toute modification est
annulable » — l'annulation vit dans TipTap, et un `Ctrl+Z` défait d'un coup. Écrire en base
ouvrirait une course avec l'enregistrement différé, et perdrait l'annulation.

⚠️ **La conséquence à connaître** : le serveur a résolu contre la version *enregistrée*, l'éditeur
tient la version *vivante*. Le navigateur revérifie donc avant d'écrire, avec la même
normalisation, et **refuse s'il ne retrouve pas exactement une occurrence**. Un refus à ce
moment-là est visible — le fil vient d'annoncer la modification — d'où un bandeau dans l'éditeur.

**Rien ne déclenche l'étape 1 par reconnaissance de vocabulaire.** Le lexique de la modification de
contenu (« ajoute », « corrige », « complète ») est celui du chat ordinaire, et le cas d'origine —
*« le tableau des viandes a disparu »* — n'en contient aucun mot. Un faux négatif ramènerait
exactement le silence qu'on répare.

#### La comparaison est normalisée — sur les espaces et les apostrophes

> Tranché par le user le 2026-08-18, à l'ouverture de l'étape 3.

Une comparaison stricte échouerait sur les deux écarts systématiques entre le Markdown stocké et ce
qui traverse l'éditeur, et **aucun des deux ne porte de sens** :

- **les apostrophes** — `’` typographique contre `'` droite. Une sur deux dans un texte français,
  et l'utilisateur qui colle ne peut ni la voir ni la corriger ;
- **les espaces** — insécables, fines, tabulations, et surtout les retours à la ligne : le Markdown
  coupe ses paragraphes où il veut, un copier-coller les rapporte autrement.

⚠️ **Rien d'autre n'est normalisé. Ni la casse, ni les accents, ni la ponctuation** — ceux-là
portent du sens. « Viandes » et « viandes » peuvent être deux sections, et confondre deux cibles
est précisément le risque contre lequel le comptage des correspondances existe.

⚠️ **Ce qui est remplacé reste le texte d'origine**, jamais sa version normalisée : on normalise
pour *comparer*, jamais pour réécrire. Sinon la modification repeindrait le document en
apostrophes droites et effacerait ses retours à la ligne.

#### Ce que la décision ne couvre pas, et qui a été borné à l'implémentation

- **Une seule modification par tour.** Contrairement à la lecture, où rappeler l'outil est normal,
  chaque appel ici **écrit** : boucler enchaînerait des modifications que l'utilisateur n'a pas
  vues passer, sur un document dont l'état a déjà changé sous la première.
- **Flow reçoit le plan, pas l'outil.** Une étape intermédiaire ne doit pas modifier le document
  sous une hypothèse que la suivante démentira. Dans Flow, le bloc du plan continue de dire les
  deux sorties.
- **Un schéma est refusé par cette porte.** Sa source demande une validation `mermaid.parse` avant
  application — c'est une contrainte de cette spec — et cette validation vit sur le chemin du
  geste.
- **Le coût.** Un tour avec un document ouvert paie un appel `small` de plus ; s'il a aussi un
  dossier garni, la lecture à la demande en ajoute un autre. C'est la conséquence directe du choix
  de la décision 15 — ne pas mettre de `tool_calls` dans le flux SSE — et c'est à relire avec
  `ModelDetectionConfig`.

### 18. Ce que le produit sait faire s'énonce — premier morceau de B10

> Ouverte et livrée le 2026-08-19. Elle tranche **un cas**, pas la question générale : B10 reste
> à cadrer. Ce qu'elle fixe, c'est la **forme** d'un énoncé de capacité, sur le premier écran qui
> en demandait un.

**Le cas.** Arriver par « Nouveau document » ouvre le chat sur une question — *« Que voulez-vous
créer ? »* — suivie de ce qu'on peut demander et d'une ligne sur la façon dont un document naît.
Sans cette dernière ligne, l'utilisateur cherche l'éditeur vide que la décision 9 a supprimé ; le
produit le laisserait chercher, ce que B10 appelle *« un mode d'emploi pour faire à la main »*.

**La forme, et c'est elle qui vaut au-delà du cas :**

- **Un texte écrit par nous, versionné, testé.** Pas une prose qu'un modèle réinvente à chaque
  passage, pas une spec qu'on lui ferait lire — 56 Ko pour celle-ci seule, et elle décrit des
  arbitrages, dont ce qui est parké ou refusé, qu'un modèle lirait comme des capacités.
- ⚠️ **Et pas un appel de modèle.** Ce texte ne dépend d'aucune donnée. Le faire rédiger coûterait
  un appel, un délai et une variation, pour rendre à chaque fois la même phrase en moins sûr.
- ⚠️ **Il ne nomme que ce qui existe, et un test le vérifie contre le produit.** Les formes
  annoncées sont exactement les gabarits que le serveur sait rendre ; un gabarit ajouté fait
  tomber le test. C'est le point qui manquait à la description de `lire_une_piece_du_dossier`,
  qui a affirmé *« le sommaire suffit souvent »* longtemps après que ce fut devenu faux.
- **Il pose une question, il ne remplit pas le champ.** Pré-écrire la demande reviendrait à
  décider de ce que l'utilisateur veut créer, et il faudrait l'effacer avant de commencer.

**Précédent invoqué**, et c'est celui que B10 veut généraliser :
`SommaireDeProjet::catalogue($projet, lecturePossible:)` n'annonce l'ouverture d'une pièce que là
où elle est possible — *une promesse tenue par personne est pire qu'un silence*.

⚠️ **Ce qui n'est PAS tranché ici** : l'énoncé général des capacités injecté dans les tours, et la
règle partagée de la façon dont chaque outil raconte ce qu'il vient de faire. Les deux sont le
cœur de B10 et attendent son cadrage.

### 19. Un bloc de prompt déclare de quoi il parle — le mécanisme de B10

> Décidée et implémentée le 2026-08-19, après l'inventaire des ~37 émetteurs d'instructions.

**Le prompt système est la garantie de fonctionnement du produit.** On n'en sort donc rien : on y
fait rentrer proprement ce que la phase B a laissé dehors (arbitrage du user).

**Le diagnostic.** `PromptBlock` posait deux questions dans un seul champ — *qui a autorité*
(`level`, qui marche) et *de quoi ça parle* (`dimension`). Or `PromptDimension` n'est pas un enum
de sujets : c'est un enum de **préférences que l'utilisateur peut arbitrer**. Déclarer une
dimension donnait l'exclusivité **et** exposait à être éteint par un niveau plus bas. Un bloc
décrivant une *situation* renonçait donc à la dimension — et perdait l'exclusivité. **Quatorze sur
vingt.** Le mille-feuille n'était pas un relâchement : c'était la seule sortie que le mécanisme
laissait.

**La règle.** Un troisième axe, `sujet`, indépendant des deux autres : un seul bloc retenu par
état du monde. La supersession se **déclare** (`remplace`), elle ne se devine pas. Et une
collision non déclarée **ne se résout pas en silence** — elle est enregistrée, et une matrice de
tests la refuse.

⚠️ **L'invariant qui empêche que ça repousse : tout bloc déclare une dimension, un sujet, ou les
deux — jamais rien.** C'est ce qui fait qu'un outil ajouté demain ne peut plus se glisser hors du
mécanisme.

**Trois leçons de l'application, qui valent plus que la règle :**

- ⚠️ **Découper les sujets par DÉCISION, jamais par émetteur.** Mes premiers sujets suivaient les
  émetteurs : la matrice n'a alors rien vu d'une contradiction franche entre le bloc du schéma
  ouvert et celui du placement. Un sujet doit répondre à une question, et à une seule.
- ⚠️ **Chercher si deux blocs mélangent des décisions AVANT de déclarer une supersession.**
  `forme.document_ouvert` en portait trois ; le remplacer en bloc aurait retiré le plan **et** la
  garde anti-hallucination sur le tour où le chat vient d'écrire. Une fois séparés, il n'y avait
  plus rien à arbitrer : la collision venait d'une **condition d'émission fausse**.
- ⚠️ **Une matrice vide ne prouve pas la cohérence.** Le mécanisme attrape les collisions de même
  sujet, pas les contradictions entre sujets voisins.

**Et ce qui se dit à plusieurs endroits ne s'écrit qu'une fois** (`EnoncesPartages`) : les
descriptions d'outils partent dans la même requête que les consignes de décision, et disaient la
même chose en trois rédactions. Un énoncé n'entre dans ce registre que s'il a **deux
consommateurs réels**.

### 20. Le chat sait ce que l'utilisateur a sous les yeux

> Décidée et implémentée le 2026-08-19. Elle répond à la première des deux causes du ton
> générique nommées par le user : *« le chat ne sait pas où il est »*.

⚠️ **Le tuyau évident était vide.** L'outil courant se dérive de l'URL, mais on ne peut taper dans
le chat que depuis la route du chat : le signal aurait valu « chat » à peu près toujours.

**Le vrai « où » est à côté du chat.** Le Screen porte **cinq** natures — page, document, schéma,
code, image — et deux seulement remontaient. Une page rapatriée d'une recherche s'affichait à côté
du chat sans que le modèle le sache : *« résume-moi ça »* tombait dans le vide.

**`SurfaceOuverte`** porte ce qui est ouvert, et **remplace** les deux paramètres qui décrivaient
la même chose au lieu de s'y ajouter — l'accrétion étant précisément ce qui fabrique un
mille-feuille.

- ⚠️ **Le titre et l'adresse, jamais le corps.** Une page pèse des milliers de mots : l'envoyer à
  chaque tour referait le pansement du lot 5a. Le prompt annonce la **présence** et dit que le
  modèle ne l'a pas lue.
- ⚠️ **Un document n'est jamais déclaré par le navigateur** : il se prouve par son identifiant,
  vérifié en base contre le compte. Seules `page`, `code` et `image` sont acceptées sur
  déclaration, avec un titre borné et encadré.

**Mesuré** par la campagne des refus : 5/5 — il dit ne pas avoir lu sur les trois questions qui
portent sur la page, et répond normalement sur les deux autres **sans jamais la mentionner**.

### 21. L'éditeur du Screen — dix-sept boutons deviennent six contrôles

> Handoff #4, implémenté le 2026-08-19. Il répond point par point à l'inventaire UX du même
> jour : dix commandes d'en-tête au même poids, dix-sept boutons de barre sans libellé, un
> sous-menu Tableau qui débordait, et un menu de sélection invisible avant sélection.

**L'en-tête se lit en trois familles**, dans cet ordre spatial : identité (icône, titre) →
assistance (« Demander à LintellO ») → sortie (Gabarit, Exporter) → cycle de vie (`⋯`) → confort
(plein écran, fermer). Les familles se séparent par l'ordre, sans couleur ni fond ajoutés.

- **Le titre EST le bouton de renommage**, soulignement pointillé **permanent**. Une affordance
  qui n'apparaît qu'au survol ne se découvre pas, elle se retrouve.
- **L'état d'enregistrement descend au pied du panneau**, au ras du texte : il vivait à 700 px du
  point de frappe. Le projet et le compteur le suivent — ils *décrivent* le document, ils ne le
  commandent pas.
- **Jeter quitte la rangée** pour le menu `⋯`, détaché par un filet. Il était à 24 px d'Exporter :
  l'un produit le livrable, l'autre le fait disparaître. La distance suit la gravité du geste.
- ⚠️ **Un seul point d'entrée vers le chat, et il est nommé.** Deux « O » cohabitaient sans faire
  la même chose. C'est aussi la réponse au menu de sélection invisible : le bouton est permanent,
  donc le prompt système n'a plus à décrire le geste à la place de l'interface.

**La barre d'outils devient quatre menus et quatre icônes.** `Style` (ce qui structure — et que
le chat lit comme plan), les quatre marques en icônes, `Liste`, `Insérer`. L'historique est
repoussé à l'extrémité droite : annuler n'écrit rien, c'est une commande sur le passé.

- Chaque style s'affiche **dans sa propre taille** : on choisit un titre en le voyant.
- Chaque entrée de menu dit **avant le clic** ce qu'elle cache — « 4 types », « Ctrl K ».
- ⚠️ **La couleur dit à quoi sert le groupe** : bleu pour ce qui structure, violet pour ce qui
  insère, sarcelle pour les marques, gris pour l'historique. Cette règle préexistait ; les
  nouveaux menus doivent la suivre, sous peine de se lire comme des champs de formulaire.

**Le repli se mesure sur la largeur du PANNEAU, jamais sur celle de l'écran.** Le Screen partage
60/40, bascule en recouvrement sous 1160 px et se redimensionne à la main : un même écran donne
un panneau de 870 px ou de 470 px. Une règle sur le viewport se trompe la moitié du temps, **et
en silence** — c'est ainsi que le sous-menu Tableau débordait. Trois paliers, mesurés en direct :

| Palier | Largeur | Ce qui tombe |
|---|---|---|
| 1 | ≥ 780 px | rien |
| 2 | 560–780 px | les libellés de Gabarit et Exporter |
| 3 | < 560 px | ceux de Liste, Insérer et du sous-menu Tableau |

⚠️ **« Demander à LintellO » garde son mot jusqu'au palier 3, et la valeur de `Style` ne tombe
jamais.** Une *capacité* doit rester lisible plus longtemps qu'un réglage ; et un *état* n'est
pas une étiquette — on peut oublier ce qu'ouvre un bouton, pas savoir si l'on écrit dans un
titre.

**Quand le dessin et le code divergent, le code fait foi** (arbitrage du user). Le handoff
nommait trois gabarits — Document, Note, Rapport — là où le produit en a quatre. Suivre le dessin
aurait effacé Lettre et Protocole de l'interface alors que le serveur sait les rendre : c'est la
faute inverse de celle qu'on corrige partout, ne nommer que ce qui existe.

## Contraintes (non-négociables)

- **Le sommaire porte des liens, jamais du contenu.** C'est le garde-fou contre le retour de
  `findLastMessagesWithFiles` — ce pansement retiré au lot 5a qui réinjectait le texte intégral de
  toutes les pièces jointes à chaque tour, indéfiniment et hors budget. **C'était le vrai poste de
  coût du produit.** Un dossier de projet est exactement la situation qui le ferait renaître.
- **Le neutre est exempté des gardes commerciales**, avec un test qui le fige (voir décision 2).
- **Un tour de modification n'est pas un tour de conversation.** Il reçoit une sélection et une
  consigne, il rend **le texte de remplacement et rien d'autre**. Cela relève de la hiérarchie de
  prompt existante (une dimension de forme, niveau 6), pas d'un chemin parallèle.
- **Le retour d'une modification est brut, ou il est rejeté.** Si le modèle répond « Voici la
  reformulation : », on injecterait ça dans le document. Extraction, ou refus — jamais d'injection
  aveugle.
- **Toute modification est annulable.** TipTap le donne nativement ; pour un schéma, il faut
  conserver la source précédente. ⚠️ **C'est ce qui interdit d'écrire le document côté serveur**
  (décision 17) : l'annulation vit dans l'éditeur, donc c'est l'éditeur qui applique — en une
  transaction, pour que `Ctrl+Z` défasse d'un coup et non paragraphe par paragraphe.
- **Une modification de schéma qui ne se parse plus n'est pas appliquée.** La validation est
  gratuite (`mermaid.parse`) et évite qu'une consigne mal comprise détruise un diagramme.
- **Réinjecter suppose un document gardé.** Cohérent avec la décision 4, et à ne pas contourner :
  modifier un contenu qui n'existe nulle part n'aurait rien à mettre à jour.
- **Aucun contenu de fichier ne part par le prompt système.** La contrainte du sommaire ci-dessus dit
  *quoi* envoyer ; celle-ci dit *par où*. Ce qui transite par le socle échappe à l'arbitrage sous
  budget — il n'est ni compacté, ni tronqué, ni classé par priorité. C'est ce détail de plomberie,
  et non le volume, qui a fait renaître le pansement du lot 5a un cran plus haut.
- **L'extraction écrit du Markdown, pas du texte plat** (décision 13). Le seul instant où l'on tient
  la structure d'un fichier est celui où on le tient encore lui-même.
- **Aucun code ne présume que le fichier d'origine existe** (décision 14). C'est ce qui garde la
  décision réversible.
- **Ce que rend l'outil de lecture est borné, et il dit qu'il a tronqué** (décision 15). Ce contenu
  arrive après le calcul du budget : c'est la seule matière du produit qui entre dans un tour sans
  avoir été arbitrée.
- **Le quota se vérifie avant le premier aller-retour d'outil**, jamais pendant. Un tour coupé en
  son milieu par un mur de facturation est pire qu'un tour refusé.
- **Une image se détecte sur son type MIME, jamais sur le préfixe de son texte extrait**
  (décision 13). `isImageBase64()` rend faux dès que l'OCR a réussi.
- **Une modification sans cible résolue ne s'applique pas** (décision 17). Zéro correspondance ou
  plusieurs : on demande, on n'approche pas. C'est la seule garde de cette décision, et elle
  remplace le clic de validation qu'on a écarté.
- **La cible se compte DEUX FOIS, et des deux côtés** (décision 17). Le serveur résout contre la
  version enregistrée, le navigateur revérifie contre la version vivante avant d'écrire. Entre les
  deux il y a l'enregistrement différé de l'éditeur, donc quelques secondes de frappe possibles :
  sans la seconde vérification, une modification s'écrirait au mauvais endroit **en silence**.
- **La normalisation porte sur les espaces et les apostrophes, jamais au-delà** (décision 17). La
  casse, les accents et la ponctuation portent du sens : les effacer confondrait deux cibles, ce
  que le comptage des correspondances existe précisément pour empêcher. Et l'on normalise pour
  *comparer*, jamais pour réécrire — c'est le texte d'origine qui est remplacé.
- **Le sommaire du document ouvert porte ses titres, jamais son contenu** (décision 17). Même
  règle que le sommaire de projet, et pour la même raison.
- **Une réponse qui ne résout pas de cible reste une réponse.** Elle ne touche à rien et **dit
  comment faire** — sélectionner, ou coller le passage. Un refus muet est le défaut qu'on répare.
- **Un échec d'application se voit** (décision 17). Le fil vient d'annoncer la modification : si
  le navigateur refuse de l'appliquer, se taire ferait lire « c'est fait » devant un document
  inchangé — le défaut de la décision 17, retourné contre nous.

## Périmètre

**Dans le périmètre**

- Le projet comme dossier : le document et le schéma deviennent rattachables, comme la recherche
  l'est déjà.
- **L'absorption de `ProjectFile` dans `Document`** : un fichier téléversé cesse d'être une nature à
  part, et l'extraction produit du Markdown (décision 13). **La reprise des pièces déjà déposées en
  fait partie** — un projet existant ne perd pas son contexte à la bascule.
- **L'outil de lecture** et ses quatre gardes (décision 15), plus la chute des deux plafonds
  (décision 16).
- Le projet neutre, et l'alignement des recherches dessus.
- L'objet Document : nom, projet, contenu, sorties existantes (Markdown, DOCX, PDF).
- Le sommaire de projet, écrit à l'entrée.
- La lecture à la demande d'une pièce du dossier par le chat — **la seule brique réellement neuve**,
  et c'est la phase 2 de `suite.md`, la seule de ses six phases qui *gagne* au recentrage.
- La prise du chat sur le contenu du Screen.
- **La modification du document sans geste** (décision 17) : le plan du document ouvert, l'outil
  d'écriture, et la résolution de cible qui décide s'il agit ou s'il demande.
- Le ménage à l'extraction (décision 5).

**Ce qui sort du périmètre, et pourquoi**

| Brique de `suite.md` | Statut | Motif |
|---|---|---|
| Versioning pseudo-git (`WorkspaceVersion`) | ⏸ parké | Servait la suite bureautique. Un dossier de travail n'en a pas besoin pour exister |
| Partage entre utilisateurs (`WorkspaceShare`) | ⏸ parké | Aucun usage collaboratif dans le produit aujourd'hui |
| Collaboration temps réel (Yjs) | ⏸ parké | Suppose le partage, qui est parké |
| Tableurs (Univer) | ⏸ parké | Brique entière. **Le Screen la rend indolore plus tard** : un moteur de plus derrière une porte ouverte |
| Hébergement de sites (LintellO Web) | ⏸ parké | Un autre produit, pas une phase de celui-ci |
| Présentations | ⏸ parké | Déjà « priorité basse » dans `suite.md` |
| Grille tarifaire de `suite.md` | ❌ **périmée** | C1 a arbitré la grille le 2026-08-11 sur 50 % de marge en pire cas. **Ne pas rouvrir le prix depuis un document de janvier** |

## Pistes d'implémentation (indicatives)

- **Les sorties existent déjà.** TipTap tourne en headless comme passerelle Markdown → DOCX,
  l'export Word fonctionne, les gabarits PDF sont côté serveur. Ce qui manque au Document, ce n'est
  pas la sortie — c'est le nom et la place.
- **La prise, par moteur** : TipTap remplace sur un intervalle (`from`/`to`) et gère l'annulation ;
  un schéma se modifie par sa source puis se re-rend, avec validation de parse avant application ;
  une page web est en lecture, donc désignable mais non modifiable — et c'est cohérent.
- **La lecture à la demande suppose du tool calling**, que LintellO n'a jamais fait. Mistral le
  supporte. C'est le seul morceau non trivial de toute cette spec, et il change la forme d'un tour
  (un aller-retour au milieu). Les gardes sont arrêtées en décision 15 ; ce qui reste au clavier,
  c'est le plafond de ce que l'outil rend et l'état affiché pendant l'attente.
- **La modification par le chat est un OUTIL, pas un contrat de sortie** (décision 17). C'est la
  différence avec la prise et le réglage de forme, qui imposent au modèle la forme de sa réponse.
  Ici il doit pouvoir faire **deux choses différentes** — agir, ou répondre qu'il ne sait pas où —
  et un contrat qui exige du JSON ne laisse pas cette liberté. Un appel d'outil, si : il l'appelle
  ou il ne l'appelle pas. La plomberie existe depuis la lecture à la demande (décision 15), gardes
  comprises : plafond d'allers-retours, propriété vérifiée, quota vérifié avant.
- **La résolution de la cible se fait CHEZ NOUS, jamais dans le modèle.** Il nomme un titre ou rend
  un passage ; c'est le code qui compte les correspondances et tranche. Lui demander « es-tu sûr ? »
  reviendrait à confier la garde à celui contre qui elle protège.
- **La reprise des pièces existantes est une conversion, pas une commande de résumé.** Elle recrée
  des documents à partir de texte déjà extrait — aucun appel externe, donc aucun délai à dépasser.
  Les résumés viennent ensuite, à l'usage (décision 6). ⚠️ Elle passe par **PHP et non par du SQL** :
  les contenus sont chiffrés des deux côtés (`encrypted_text`), et les images demandent d'écrire un
  fichier sur le disque.
- **Le sommaire grandit avec le dossier.** À dix entrées c'est gratuit, à deux cents il faudra le
  hiérarchiser — par nature, ou en résumant le sommaire. Pas un problème maintenant ; à ne pas
  découvrir en butant dedans.

## Critères de « fait »

1. Un document et un schéma se rattachent à un projet, comme une recherche aujourd'hui.
2. Tout objet a un projet ; le neutre en est un, et **un compte du plan le plus bas peut y créer**.
3. Ouvrir un contenu dans le Screen ne crée rien en base. Le modifier, le renommer ou l'exporter
   crée le document.
4. Le chat d'un projet connaît le sommaire du dossier et peut demander à lire une pièce nommée.
5. Le sommaire ne contient aucun contenu de fichier.
6. Une sélection dans le Screen plus une consigne remplace le passage désigné, et le remplacement
   s'annule.
7. Une consigne qui casserait un schéma ne l'applique pas.
8. L'outil Documents liste par **dernière modification**, et un document repris est atteint en deux
   clics depuis la colonne.
9. L'action primaire de Documents ouvre une conversation qui demande quoi créer — **aucun écran du
   produit n'ouvre un éditeur vide**.
10. **Aucun geste unique ne détruit un document.** Supprimer met en corbeille ; détruire se fait
    depuis la corbeille, et se confirme.
11. Un projet mis en corbeille y emmène son contenu, et se restaure d'un bloc.
12. Ce qui est en corbeille n'apparaît pas dans le sommaire donné au chat.
13. La suppression d'un compte ne laisse rien dans la corbeille.
14. Le Drive s'ouvre sur ses **dossiers**, on peut en créer un, et ouvrir un dossier mène à la
    fiche du projet.
15. **La fiche d'un projet liste ses documents.** Sans cela le dossier n'existe que dans la base.
16. Depuis le Screen, un document se **renomme** et se **range** — y compris pour ressortir d'un
    dossier, qui doit toujours rester possible.
17. Un fichier téléversé dans un projet **apparaît dans le Drive et dans la fiche du projet**, et
    s'ouvre dans le Screen au clic — comme n'importe quel document.
18. **Le prompt système ne contient le contenu d'aucun fichier.** Ce qui part à chaque tour est le
    sommaire, et il se dispute le budget avec les autres sources.
19. Un document Word déposé **conserve le niveau de ses titres**. (Aucun équivalent n'est promis
    pour un PDF : il n'en porte pas.)
20. Déposer un fichier volumineux dans un projet **réussit**. Le refus au dépôt était la digue posée
    devant l'injection intégrale ; l'injection disparaissant, la digue n'a plus rien à retenir.
21. **Déposer un onzième fichier dans un projet réussit aussi** — la seconde digue tombe avec la
    première (décision 16).
22. **Un projet existant garde son contexte après la bascule** : ses pièces sont là, sous leurs
    noms, et le chat y répond comme avant. Une image d'un ancien projet **redevient une image**, pas
    un document rempli de base64.
23. Une pièce importée s'ouvre dans le Screen et **ne se modifie pas**.
24. Le chat nomme une pièce, l'outil la lit, et la réponse s'appuie dessus. Quand la pièce dépasse
    ce que l'outil peut rendre, **le tour le dit** au lieu de conclure sur un fragment.
25. Un tour qui appelle l'outil **apparaît dans `usage_log`** avec ses allers-retours, et il est
    refusé **avant de commencer** quand le quota n'y suffit pas.
26. **« Réintègre le tableau des viandes » modifie le document**, sans avoir rien sélectionné —
    le cas exact du 2026-08-18, qui a ouvert la décision 17.
27. **Un passage collé dans le chat est retrouvé dans le document** et remplacé.
28. **Une cible introuvable ou ambiguë ne modifie RIEN**, et la réponse dit laquelle des deux
    sorties emprunter — sélectionner, ou coller le passage.
29. Une modification faite par le chat **s'annule d'un seul `Ctrl+Z`**, comme celle du menu bulle.
30. Le fil **dit ce qui a changé**, en français, sans afficher de JSON — la leçon du réglage de
    forme, apprise le 2026-08-16.
31. **Le contrôle négatif** : une question ordinaire, avec le même document ouvert, ne modifie
    **rien**. Un outil disponible est un outil appelé ; ici le prix d'un appel de trop n'est pas
    quelques tokens, c'est du texte écrit dans le document de quelqu'un.
32. **Taper dans l'éditeur pendant que le tour part ne fait pas écrire au mauvais endroit** : le
    navigateur refuse d'appliquer et **le signale**, alors même que le fil vient d'annoncer la
    modification.

## Ouvert

- **L'ouverture automatique du Screen** — écartée pour l'instant (décision 5), à rediscuter quand
  l'usage aura montré si le bouton suffit.
- ~~**Le Drive comme cinquième outil**~~ — **tranché** (décision 8) : il reste, la colonne ne change
  pas, et l'outil Documents change de nature au lieu de disparaître.
- **La galerie de gabarits** — suggestions d'abord, objets ensuite si l'usage le demande
  (décision 9). Ce qui reste vraiment ouvert : la **fidélité visuelle à l'impression**, hors
  périmètre aujourd'hui.
- ~~**Un même document dans deux projets**~~ — **tranché** (décision 10) : un seul projet, à
  revoir à l'usage.
- ~~**Ce que devient un document quand son projet est supprimé**~~ — **tranché** (décision 11) :
  corbeille en deux temps, retour au neutre si le projet d'origine a disparu.
- **L'accès à la corbeille** — sixième porte ou endroit du Drive : **renvoyé au design** par le
  user. Rien d'autre n'en dépend.
- ~~**Le quota compte-t-il ce qui est en corbeille ?**~~ — **tranché** : oui, et c'est ce qui la
  fera vider (décision 11).
- ~~**Ce que devient un fichier téléversé**~~ — **tranché le 2026-08-15** (décision 13) : c'est un
  document, et l'extraction produit du Markdown.
- ~~**Garde-t-on le fichier d'origine ?**~~ — **tranché le 2026-08-15** (décision 14) : non. Reste
  ouvert, et **seulement** ouvert : le jour où un document devra *faire foi*, il faudra un
  archivage — avec son stockage, son quota et sa purge.
- **La restructuration d'un PDF par un modèle** — écartée par la décision 13 pour ce lot (il
  inventerait). À rouvrir seulement si l'usage montre que le texte plat d'un PDF ne suffit pas.
- **Le quota de stockage** — reporté par le user (*« quand ce sera le moment »*). C'est le seul
  point de ce lot qui laisse une porte ouverte, et il est écrit en décision 16 pour que la
  réouverture n'ait pas à redécouvrir que **le quota n'existait pas**, plutôt qu'il était trop bas.
- **L'édition d'une pièce importée** — fermée pour ce lot (décision 13, lecture seule), et le user
  l'a posée comme telle : *« on verra plus tard si on édite »*. Rien dans le modèle de données ne
  l'empêche.
- ~~**« Lecture seule » veut-il dire l'original consultable ?**~~ — **tranché le 2026-08-17** : non,
  c'est le Markdown extrait. La décision 14 est donc reconduite sans amendement.
- ~~**Le chat peut-il modifier le document sans geste ?**~~ — **tranché le 2026-08-18**
  (décision 17) : oui, quand il sait nommer sa cible. La décision 7 est desserrée. **Implémenté le
  même jour**, en trois étapes.
- **Plusieurs modifications en un seul tour** — borné à UNE à l'implémentation (décision 17), et
  ce n'est pas un oubli : chaque appel écrit, donc boucler enchaînerait des changements que
  l'utilisateur n'a pas vus passer, sur un document dont l'état a déjà bougé sous le premier. À
  rouvrir seulement si l'usage montre qu'on redemande souvent « et pareil dans l'autre section ».
- **Le coût d'un tour à deux appels d'outil** — un document ouvert dans un projet garni paie
  aujourd'hui la lecture *et* la modification, avant même de répondre. C'est la conséquence du
  choix de la décision 15 (pas de `tool_calls` dans le flux SSE), pas un défaut de la 17. À
  reprendre avec `ModelDetectionConfig`, qui est le levier qualité/coût et reste hors service
  depuis le lot 3.
- **Un filet qui survivrait à la fermeture de l'éditeur** — écarté pour l'instant, et le motif est
  du user : *« ce risque de perte, les humains vivent avec »*. Si ça manque un jour, le moins cher
  n'est **pas** le versionnage : c'est garder **une** version précédente au moment d'une
  modification par le chat, écrasée à la suivante — un « annuler la dernière modification de
  LintellO » qui survit à la fermeture. Écrit ici pour que la piste ne soit pas à retrouver.
- **Jusqu'où le chat modifie-t-il ?** Les sections et les passages collés sont bordés. « Change ce
  mot partout » ne l'est pas — plusieurs correspondances, donc refus par la règle. À rouvrir si
  l'usage le réclame ; le refus est correct en attendant, il n'est pas satisfaisant.

## Décisions liées : #0006 (une recherche = une requête) · candidates à formaliser : « un projet est
un dossier », « ouvrir c'est regarder, toucher c'est garder »
