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>
11 KiB
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.tsdéclare 21 gateways (interfaces TS) décrivant ce dont l'UI a besoin, sans aucune référence à Tauri. Toute la couchefeatures/+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 horssrc/adaptersimporte@tauri-apps/apiou appelleinvoke(. 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()choisitcreateTauriGateways()vscreateMockGateways()selonVITE_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) :
- PTY terminal —
terminal.tsopenTerminal/reattach:Channel<number[]>, octets bruts →term.write. Cœur de F3 (xterm sur WS). - PTY agent —
agent.tslaunchAgent/reattach: mêmeChannel<number[]>, réutilisemakeTerminalHandle. Même transport que le PTY terminal (le carnet dit « agents via WS PTY », lot B6). - Chat assistant ticket —
ticket.tssendTicketChat: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+onDomainEventsur WS. Suffit pour « ouvrir un projet + état read-only ». - F3 (xterm sur WS) : adapter WS pour
terminal(+ réécriture demakeTerminalHandleen variante socket).TerminalView.tsxne change pas : il ne connaît que le port et unopen/reattachinjecté. - B6/F(agents) :
agent.launchAgent/reattachsur le même transport WS PTY. - Chat ticket :
ticket.sendTicketChatsur 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 :
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.window.ts(détacher un panneau en fenêtre OS) : concept purement desktop (TauriWebviewWindow). Équivalent web = nouvel onglet/window.opennavigateur, 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).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 /BroadcastChannelsi 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 surattach_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). - ⚠️
pickFolderest 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).