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

11 KiB
Raw Blame History

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.tsxresolveGateways() 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 terminalterminal.ts openTerminal/reattach : Channel<number[]>, octets bruts → term.write. Cœur de F3 (xterm sur WS).
  2. PTY agentagent.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 ticketticket.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).