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>
159 lines
11 KiB
Markdown
159 lines
11 KiB
Markdown
# 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).
|