Files
IdeA/docs/ticket13-f0-frontend-transport-inventory.md
Blomios 5505acc1f6 docs(ticket13): inventaire transport backend et frontend (B0)
Lot B0 du chantier server/client mode : cartographie des surfaces de
transport existantes, préalable à l'extraction du cœur backend commun.

- docs/ticket13-b0-backend-transport-inventory.md : inventaire backend.
- docs/ticket13-f0-frontend-transport-inventory.md : inventaire frontend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:50:10 +02:00

159 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Ticket #13 — Lot B0/F0 : inventaire du transport frontend (TS/React)
> Livrable d'inventaire. **Aucun code de feature modifié.** Miroir frontend de
> l'inventaire backend (`ticket13-b0-backend-transport-inventory.md`).
> Objectif : cartographier tous les accès `@tauri-apps/api`, les classer par nature
> (request/response · stream · event · desktop-only), et localiser la frontière
> « gateways TS transport-neutres » du lot F1 (adapter Tauri desktop | adapter
> HTTP+WS web).
## 0. Constat majeur — la frontière F1 existe déjà (à ~90 %)
Le frontend est **déjà** structuré exactement comme le plan cible le demande :
- **Ports transport-neutres** : `src/ports/index.ts` déclare 21 gateways (interfaces
TS) décrivant *ce dont l'UI a besoin*, sans aucune référence à Tauri. Toute la
couche `features/` + `app/` dépend de ces ports via DI (`useGateways()`).
- **Adapters = seul lieu Tauri** : `src/adapters/*` implémente les ports via
`@tauri-apps/api`. C'est le **seul** répertoire autorisé à importer Tauri.
- **Garde d'architecture L1 active** : `src/app/no-direct-invoke.test.ts` échoue en CI
si un fichier hors `src/adapters` importe `@tauri-apps/api` **ou** appelle `invoke(`.
Cette garde est notre filet de sécurité pour F1 : elle garantit qu'aucun composant
n'appelle Tauri en dur — donc **aucun composant métier à réécrire** pour le web.
- **Composition déjà bifurquée** : `src/app/di.tsx``resolveGateways()` choisit
`createTauriGateways()` vs `createMockGateways()` selon `VITE_USE_MOCK`. F1 ajoute
simplement une **3ᵉ implémentation** (`createHttpWsGateways()`) au même seam.
**Conséquence sur le plan de lots** : F1 n'est *pas* une extraction de frontière
(elle est faite), mais l'écriture d'un **second jeu d'adapters** derrière des ports
inchangés. Le risque est concentré dans **3 adapters à flux (Channel)** et **3 usages
desktop-only**, pas dans les 15 gateways request/response (mécaniques).
## 1. Inventaire exhaustif des accès `@tauri-apps/api`
Aucun accès hors `src/adapters`. Détail par fichier adapter :
| Adapter | API Tauri utilisée | Nature | Commentaire transport |
|---|---|---|---|
| `system.ts` | `invoke` (`health`) | request/response | trivial HTTP |
| `system.ts` | `listen("domain://event")` | **event** | bus d'événements domaine global → **WS** (fan-out serveur→client) |
| `system.ts` | `open()` de `@tauri-apps/plugin-dialog` | **desktop-only** | picker dossier natif — **pas de sens sur web** (voir §4) |
| `agent.ts` | `invoke` (list/create/change/read/update/delete/…) | request/response | HTTP |
| `agent.ts` | `Channel<number[]>` (`launch_agent`, `reattach_terminal`) | **stream** | flux PTY agent → **WS** (contrat PTY du carnet) |
| `terminal.ts` | `Channel<number[]>` (`open_terminal`, `reattach_terminal`) | **stream** | flux PTY terminal → **WS** (cœur de F3) |
| `terminal.ts` | `invoke` (`write_/resize_/close_terminal`) | request/response (contrôle PTY) | messages WS client→serveur (input/resize/close) |
| `ticket.ts` | `Channel<ReplyChunk>` (`agent_send`) | **stream** | flux de réponse chat assistant ticket → **WS** |
| `ticket.ts` | `invoke` (~20 commandes `ticket_*`) | request/response | HTTP |
| `window.ts` | `invoke` (`open_/close_/list_view_window`) | **desktop-only** | cycle de vie fenêtre OS (WebviewWindow) — voir §4 |
| `window.ts` | `listen("view-window://lifecycle")` | **desktop-only / event** | fermeture fenêtre OS — voir §4 |
| `focusedProject.ts` | `invoke` + `listen("focused-project://changed")` | **desktop-only / event** | canal fenêtre principale ⇄ fenêtre détachée — voir §4 |
| `project.ts`, `layout.ts`, `git.ts`, `profile.ts`, `modelServer.ts`, `template.ts`, `skill.ts`, `memory.ts`, `embedder.ts`, `permission.ts`, `workState.ts`, `conversation.ts`, `input.ts` | `invoke` seul | request/response | HTTP mécanique |
| `uiPreferences.ts` | *(aucune)*`localStorage` | frontend-pur | déjà transport-neutre, marche tel quel sur web |
## 2. Classement par nature de transport
### 2a. `request/response` (le gros du volume — mapping HTTP direct)
Tous les `invoke()` non-Channel. 15 gateways sur 21 sont **exclusivement**
request/response : `project, layout, git, profile, modelServer, template, skill,
memory, embedder, permission, workState, conversation, input`, + le `health` de
`system` et les ~20 commandes `ticket_*`.
**Aucune décision d'archi** : un adapter HTTP générique (POST `{command, args}` ou
route par commande) suffit. C'est le socle du **premier livrable B4/F2** (web ouvre un
projet + read-only), qui n'a besoin d'aucun stream.
### 2b. `stream` (Channel Tauri → WebSocket) — **le vrai chantier F1/F3**
Trois flux, tous modélisés côté port par un **callback `onData`/`onChunk`** (le port
ne connaît jamais `Channel`) :
1. **PTY terminal**`terminal.ts` `openTerminal`/`reattach` : `Channel<number[]>`,
octets bruts → `term.write`. **Cœur de F3 (xterm sur WS).**
2. **PTY agent**`agent.ts` `launchAgent`/`reattach` : même `Channel<number[]>`,
réutilise `makeTerminalHandle`. Même transport que le PTY terminal (le carnet dit
« agents via WS PTY », lot B6).
3. **Chat assistant ticket**`ticket.ts` `sendTicketChat` : `Channel<ReplyChunk>`,
flux de réponse LLM structuré (`agent_send`). Stream applicatif, pas PTY.
Point de contrat clé : le **handle** (`TerminalHandle`) porte déjà `write/resize/
detach/close` **et** la sémantique detach≠close (détacher la vue sans tuer le PTY).
Cette sémantique **correspond exactement** au contrat PTY WebSocket du carnet
(`attach_terminal`/`detach`, PTY autorité serveur, scrollback borné). L'adapter WS
implémentera `makeTerminalHandle` en émettant `input/resize/detach/close` sur la socket
et en poussant `output(seq,bytes)` dans `onData`. **Le port n'a pas à changer.**
### 2c. `event` (listen Tauri → WebSocket serveur→client)
Un seul événement *portable* : `system.ts` `onDomainEvent("domain://event")` — le bus
d'événements domaine (agent launched, rate-limited, backgroundTaskChanged, issueDeleted,
progress modèle…). Sur web ⇒ **canal WS serveur→client** (ou multiplexé sur la même
socket que le PTY selon l'arbitrage HTTP+WS séparés vs WS unique, laissé à Architect).
Les deux autres `listen` (`view-window://lifecycle`, `focused-project://changed`) sont
**desktop-only** (§4), pas des events métier.
### 2d. `desktop-only` (pas de portage web direct — voir §4)
`system.pickFolder` (dialog natif), tout `window.ts` (WebviewWindow OS),
`focusedProject.ts` (coordination multi-fenêtres OS).
## 3. Frontière F1 : où placer les deux implémentations
**La frontière est déjà `src/ports/` ⇄ `src/adapters/`.** F1 = ajouter un dossier
`src/adapters/http/` (ou `webws/`) avec une implémentation par port, et un
`createHttpWsGateways()` branché dans `resolveGateways()`.
Découpage recommandé des adapters web par priorité (aligné sur les lots du carnet) :
- **F2 (premier livrable, request/response only)** : adapters HTTP pour `system.health`,
`project`, `layout` (read), `workState`, `ticket` (read), `agent.listAgents` +
`onDomainEvent` sur WS. Suffit pour « ouvrir un projet + état read-only ».
- **F3 (xterm sur WS)** : adapter WS pour `terminal` (+ réécriture de
`makeTerminalHandle` en variante socket). `TerminalView.tsx` **ne change pas** : il ne
connaît que le port et un `open/reattach` injecté.
- **B6/F(agents)** : `agent.launchAgent/reattach` sur le même transport WS PTY.
- **Chat ticket** : `ticket.sendTicketChat` sur WS applicatif.
- **desktop-only** : `window`, `focusedProject`, `pickFolder` → implémentations web
dégradées/alternatives (§4).
**Aucun composant métier (`features/`, `app/`) n'appelle Tauri** — garanti par la garde
L1. Donc F1 ne touche **que** `src/adapters/`, `src/app/di.tsx` (3-way resolve) et
`vite`/build (cible web sans Tauri). C'est la bonne nouvelle du plan.
## 4. Points desktop-only — décisions à cadrer (Architect)
Ces trois surfaces n'ont pas d'équivalent WS trivial ; elles ne bloquent **pas** le
premier livrable (read-only) mais doivent être tranchées avant les lots concernés :
1. **`system.pickFolder`** (créer/ouvrir un projet par un dossier local). Sur le
serveur, le « dossier » est **sur la machine serveur**, pas sur le client web. ⇒ Il
faut un **file-picker serveur** (browse arborescence serveur via HTTP) au lieu du
dialog natif OS. Impacte la création/ouverture de projet côté web. **À cadrer.**
2. **`window.ts` (détacher un panneau en fenêtre OS)** : concept purement desktop
(Tauri `WebviewWindow`). Équivalent web = nouvel **onglet/`window.open`** navigateur,
sémantique différente (pas de registre anti-doublon OS). V1 web : dégrader en
**no-op** ou en onglet navigateur, la garde L1 permet une impl web distincte sans
toucher les composants. **À cadrer (probablement hors-scope V1 web).**
3. **`focusedProject.ts`** : coordination fenêtre principale ⇄ fenêtres détachées. Sans
fenêtres détachées (point 2), le canal se réduit à un état local. V1 web :
implémentation triviale in-memory / `BroadcastChannel` si multi-onglets. **À cadrer.**
## 5. Signaux pour le plan de lots / contrat de transport
-**Rien ne remet en cause le plan.** La frontière gateways transport-neutres du
carnet est **déjà réalisée et testée** (garde L1). F1 est un ajout d'adapters, pas un
refactor de composants. Gain de risque majeur.
-**Le contrat PTY WebSocket du carnet colle au port existant** : `TerminalHandle`
(write/resize/detach/close, detach≠close, scrollback au reattach) mappe 1-pour-1 sur
`attach_terminal/input/resize/detach/close` + `attached(scrollback)/output(seq)`.
Aucune évolution de port nécessaire pour F3.
- ⚠️ **3 flux Channel** (PTY terminal, PTY agent, chat ticket) partagent la même
mécanique `onData`/callback — un **transport WS commun** peut les servir tous les
trois. À confirmer avec le choix « HTTP+WS séparés vs WS multiplexé » (délégué
Architect).
- ⚠️ **`pickFolder`** est le seul point request/response qui **change de sémantique** sur
web (dossier serveur ≠ dossier client). À arbitrer avant le lot création/ouverture de
projet web.
- ⚠️ **`window`/`focusedProject`** (multi-fenêtres OS) : probablement **hors V1 web** ;
à confirmer pour ne pas gonfler le périmètre.
- **`uiPreferences`** (localStorage) marche déjà tel quel sur web — aucun adapter à
écrire.
- **Convention DTO** : commandes snake_case, payloads camelCase, souvent enveloppés
dans `{ request: {...} }`. L'adapter HTTP doit préserver cette enveloppe pour parler au
même cœur backend (le miroir backend confirme ce point de contrat).