# 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).
