From 0fe2f2139e40bb18e43164874f8c1cbae8bf0bb6 Mon Sep 17 00:00:00 2001 From: mptyl Date: Sun, 28 Jun 2026 11:51:14 +0200 Subject: [PATCH] docs(frontend): design del frontend ThothII (brainstorming) Decisioni FE-1..FE-6: Vite+React SPA (no Next), TanStack Query+Zustand+hook SSE, EventSource nativo (MVP auth=none), widget registry+fallback, test Vitest+RTL+MSW + Playwright e2e F1, build a slice con F1 come primo loop chiuso. Co-Authored-By: Claude Opus 4.8 --- .../specs/2026-06-27-frontend-design.md | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-27-frontend-design.md diff --git a/docs/superpowers/specs/2026-06-27-frontend-design.md b/docs/superpowers/specs/2026-06-27-frontend-design.md new file mode 100644 index 00000000..324cbc26 --- /dev/null +++ b/docs/superpowers/specs/2026-06-27-frontend-design.md @@ -0,0 +1,160 @@ +# ThothII — Design del frontend + +**Data:** 2026-06-27 +**Stato:** Draft, in attesa di review +**Fonti:** `docs/superpowers/specs/2026-06-25-thothii-architecture-design.md` (§6 UI, §4 widget-descriptor), `docs/superpowers/specs/2026-06-27-backend-design.md` (contratto REST+SSE), backend implementato in `backend/` (route reali). + +--- + +## 1. Obiettivo e contesto + +Terzo e ultimo progetto di ThothII (harness → backend → **frontend**). È l'interfaccia React che sostituisce il TUI di ChironeWp3: presenta il workflow NL→SQL human-in-the-loop a 8 fasi, renderizzando i **widget-descriptor** emessi dall'harness e instradati dal backend via SSE, e raccogliendo le decisioni del revisore via REST. + +Il backend è già completo e ne espone un contratto stabile e testato. Il frontend **consuma solo** quel contratto (REST + SSE su localhost); non parla mai direttamente con Pi, l'harness o il DB. + +**Contratto del backend (reale, implementato):** +- REST: `GET /workspaces`, `GET /models`, `GET /sessions`, `GET /sessions/:id`, `POST /sessions`, `POST /sessions/:id/response`, `POST /sessions/:id/steer`, `POST /sessions/:id/sql/preview`, `POST /sessions/:id/sql/export`, `POST /sessions/:id/close`, `POST /sessions/:id/resume`. +- SSE: `GET /sessions/:id/events` → eventi `{type:"ui_request", ui_request}`, `{type:"info", level, text}`, `{type:"text_delta", text}`, `{type:"system_event", ...}`. +- Widget-descriptor (architettura §4): 6 `kind` (`info`, `select`, `multiselect`, `freetext`, `artifact-gate`, `artifact`) + fallback per kind sconosciuti; opzioni riservate (`back`/`exit`/`other`), `recommended`, linkage `option.opens`, invariante no-limbo. + +--- + +## 2. Decisioni del frontend (locked) + +Decise durante il brainstorming del 2026-06-27. + +**FE-1 — Vite + React + TypeScript SPA.** +App single-page client-only su localhost; niente SSR/route server. ShadCn per i componenti UI, AGGrid (community) per le tabelle risultati. +Perché: il frontend è un client che consuma un backend Fastify separato; SSR e routing server di Next.js non servono. Vite dà build/dev più semplici e un solo runtime. Deviazione consapevole dall'architettura (che indicava Next.js), coerente con YAGNI; il contratto FE↔BE non cambia. + +**FE-2 — Dati: TanStack Query (REST) + Zustand (live) + hook SSE.** +TanStack Query per le chiamate REST (cache, loading/error, refetch di lista sessioni e artefatti). Uno store Zustand per lo stato live della sessione (widget pendente, trascritto chat, info/toast, fase corrente). Un hook `useSessionStream` apre l'SSE e alimenta lo store. +Perché: separazione pulita REST (cacheable) vs live (streaming), poca boilerplate per un'app mono-operatore. + +**FE-3 — SSE via `EventSource` nativo nell'MVP.** +`EventSource` (GET, nessun header) basta con auth=`none` su localhost. +Perché: minimale e nativo. Quando si abiliterà auth con header (mock/oidc) servirà un SSE basato su `fetch` (es. `@microsoft/fetch-event-source`) — registrato come item futuro, non MVP. + +**FE-4 — Widget registry + fallback universale.** +Un registry mappa `widget.kind` → componente renderer; un fallback mostra il payload JSON in un box "Widget non supportato (kind: X) — rispondi manualmente". Aggiungere un widget = registrare un nuovo renderer, senza toccare l'infrastruttura (forwarding, correlazione `id`, store). +Perché: rispecchia la flessibilità del contratto (architettura §4, `kind` aperto); isola ogni widget come unità testabile. + +**FE-5 — Strategia di test: Vitest+RTL+MSW (unit/integration) + Playwright e2e (F1).** +Vitest + React Testing Library per renderer, hook e store; MSW mocka le REST e un mock di `EventSource` simula l'SSE (deterministico, senza backend). Un test Playwright e2e del loop F1 contro il backend reale avviato col `fake-pi-rpc` (analogo all'e2e del backend). +Perché: i renderer si testano in isolamento in modo deterministico; l'e2e prova il rendering reale e il loop end-to-end. CI-friendly. + +**FE-6 — Build a vertical slice, F1 come primo loop chiuso.** +Si costruisce per slice (vedi §7), chiudendo il loop F1 end-to-end il prima possibile, poi estendendo widget e viewer. +Perché: feedback continuo, coerente con la strategia D9 dell'architettura. + +--- + +## 3. Architettura e flusso dei dati + +``` +┌─ FRONTEND (frontend/) — Vite + React SPA, localhost ───────────────────┐ +│ app/ provider (QueryClient), composizione, routing minimale │ +│ shell/ layout a 4 zone │ +│ widgets/ registry kind→renderer (6 + fallback) │ +│ viewers/ schema-linking · cte/sql · risultati · markdown-mermaid │ +│ store/ sessionStore (Zustand): widget pendente, chat, info, fase │ +│ stream/ useSessionStream (EventSource → store) │ +│ api/ client REST tipato + tipi del contratto widget-descriptor │ +└───────────────▲───────────────────────────────┬───────────────────────┘ + SSE (EventSource) REST (fetch / TanStack Query) + │ │ + GET /sessions/:id/events POST /sessions/:id/response, /steer, + (ui_request|info| /sql/preview, /sql/export, /close, + text_delta|system_event) /resume · GET /sessions, /:id, ... + └───────────────► BACKEND (Fastify, :8787) ◄─────────────┘ +``` + +**Flusso di una decisione (es. F4 promozione tabelle):** +1. L'utente apre una sessione → `useSessionStream` apre l'SSE. +2. Arriva `{type:"ui_request", ui_request:{id, widget:"multiselect", ...}}` → store salva il widget pendente. +3. Il registry renderizza il `multiselect`; l'utente seleziona e conferma. +4. `POST /sessions/:id/response` con `{ui_response:{id, choices, decision}}`. +5. Il backend instrada a Pi; arrivano nuovi `text_delta` / il prossimo `ui_request`. + +**Riconnessione/resume:** alla (ri)apertura dell'SSE il backend ri-emette il widget pendente (già implementato lato BE-3); lo store ricostruisce lista/artefatti via REST. Lo steering ambientale (`!`) → `POST /sessions/:id/steer`. + +--- + +## 4. Componenti e confini + +### 4.1 `api/` — client REST + tipi +Un modulo per gruppo (`sessions.ts`, `workspaces.ts`, `models.ts`, `sql.ts`) con funzioni tipate che chiamano il backend. `types.ts` definisce i tipi condivisi del contratto: `UiRequest`, `UiResponse`, `WidgetDescriptor` (union sui 6 kind), `SessionSummary`, `SessionDetail`, `PreviewResult`. È l'unico confine verso il backend. + +### 4.2 `store/sessionStore.ts` — stato live (Zustand) +Tiene: `pendingWidget: WidgetDescriptor | null`, `transcript: ChatEntry[]` (accumulo dei `text_delta`), `toasts: Info[]`, `currentPhase`, `connectionState`. Azioni: `applyEvent(event)` (dispatch per tipo), `clearPending()`, reset all'apertura sessione. Nessuna logica di rete qui (solo stato). + +### 4.3 `stream/useSessionStream.ts` +Hook che, dato `sessionId`, apre `EventSource(/sessions/:id/events)`, fa il parse di ogni messaggio e chiama `store.applyEvent`. Gestisce open/error/retry e chiude alla smontatura. Testabile con un mock di `EventSource`. + +### 4.4 `widgets/` — registry + renderer +- `registry.ts`: `Record` + `resolve(kind)` con fallback. +- Un file per renderer. Ogni renderer riceve `{descriptor, onRespond(uiResponse)}` e si occupa solo del suo rendering + raccolta risposta. Le opzioni riservate (`back`/`exit`/`other`) e `recommended` sono rese in modo uniforme da un sotto-componente condiviso (`ReservedControls`). Il linkage `option.opens` è gestito da un wrapper che apre il widget figlio e combina le risposte. Invariante no-limbo: nessun renderer può "chiudere senza rispondere" (Esc/cancel ripresenta). + +### 4.5 `viewers/` — artefatti (architettura §6) +- `SchemaLinkingViewer`: Mermaid flowchart verticale (default) + tabella gerarchica via toggle (≤45 elementi), commento "perché" inline. +- `SqlViewer`: code block collassabili con header (nome, n° campi, stato test), evidenziazione sintattica via **shiki**, commenti per campo; SQL finale = stesso componente. +- `ResultsPanel`: contestuale — numero in grassetto; lista in **AGGrid** con paginazione (`POST /sql/preview` limit/offset) + export CSV (`POST /sql/export`); selettore `[10 ▾ / tutti]`. +- `MarkdownView`: markdown "mermaid-enhanced" (formattazione + rendering mermaid inline) per testi lunghi, in box scrollabile. + +### 4.6 `shell/` — layout a 4 zone (architettura §6) +Nav sinistra (funzioni + lista sessioni) | workflow bar orizzontale (fasi) sopra la chat | chat+input al centro | sidebar destra collassabile con artefatti/COT/thinking. Il widget pendente compare nella zona centrale sotto la chat; gli artefatti referenziati aprono i viewer nella sidebar destra. + +--- + +## 5. Gestione errori ed edge case + +- **Kind widget sconosciuto** → fallback renderer (FE-4), il revisore risponde manualmente; nessun crash. +- **SSE disconnesso** → `useSessionStream` ritenta; alla riconnessione il widget pendente viene ri-emesso dal backend (BE-3). I `text_delta` persi nel buco non si recuperano (accettabile: conta l'artefatto/widget finale). +- **Backend non raggiungibile** → stato di connessione visibile (banner), retry sulle query REST (TanStack Query). +- **`POST /response` su sessione non attiva** → il backend risponde 404; la UI invita a riprendere la sessione (`POST /resume`). +- **Preview/export SQL in errore** → il backend ritorna errore (stderr/exit); il `ResultsPanel` mostra il messaggio senza rompere la vista. + +--- + +## 6. Test (FE-5) + +- **Unit/component (Vitest + RTL):** ogni renderer widget (incl. fallback, reserved controls, linkage, no-limbo); `sessionStore.applyEvent` per ogni tipo di evento; `useSessionStream` con mock `EventSource`; i viewer con artefatti di esempio. +- **Integration (MSW):** flusso REST (lista/crea sessione, preview/export) mockato a livello rete; SSE mockato. Verifica del ciclo "ui_request renderizzato → risposta → POST corretto". +- **E2E (Playwright):** un test del loop F1 contro il backend reale avviato col `fake-pi-rpc` (`harness/tests/fake_pi/`): crea sessione → riceve il widget F1 via SSE → risponde → avanza. Non in CI deterministica se richiede orchestrazione; documentato come l'analogo dell'e2e del backend. + +--- + +## 7. Strategia di implementazione (slice verticali, FE-6) + +1. **Scaffold** Vite+React+TS + Tailwind/ShadCn + QueryClient provider; health check verso il backend. +2. **`api/` + tipi** del contratto widget-descriptor. +3. **`store/` + `stream/`** (SSE) con mock `EventSource`. +4. **`shell/`** a 4 zone (statica, dati finti). +5. **Widget registry + `select`/`info`/`freetext`** → **chiude il loop F1 end-to-end** (crea sessione → riceve widget → risponde). +6. **`multiselect` + `artifact-gate` + `artifact`** + linkage + no-limbo. +7. **Viewer**: schema-linking (mermaid+tabella), poi CTE/SQL (shiki), poi risultati (AGGrid). +8. **Steering, riconnessione, resume, lista/creazione sessioni** complete; selezione model/thinking/provider (da `GET /models`) e workspace (da `GET /workspaces`) alla creazione. +9. **Playwright e2e F1.** + +--- + +## 8. Fuori scope (MVP) + +- Auth con header nel browser (MVP=`none`; SSE via `fetch` quando servirà — FE-3). +- UI completa del datamart (azione separata con conferma di pseudoanonimizzazione; la generazione è lato harness). +- Temi multipli, configurabilità del layout, supporto mobile. +- Web app centrale multi-utente (modello A dell'architettura): i contratti non cambiano, è un riposizionamento di deployment. + +--- + +## 9. Rischi aperti + +- **Fedeltà del mock SSE/REST vs backend reale.** Mitigazione: i tipi in `api/types.ts` derivano dal contratto reale; il Playwright e2e contro backend+fake-pi cattura le derive. +- **Rendering Mermaid/shiki nel browser** (peso bundle, performance su artefatti grandi). Mitigazione: vincolo ≤45 elementi (schema-linking), lazy-load dei viewer pesanti. +- **`GET /models` lato backend torna `[]` di default** (seam non ancora cablato): la tendina model potrebbe essere vuota finché il backend non espone i modelli — il FE deve degradare con grazia (campo libero / default). + +--- + +## 10. Nota sul piano di implementazione + +Questo documento è il design del frontend. La fase di `writing-plans` produrrà il piano di implementazione (uno, come da architettura §11), costruito a slice (vedi §7) con F1 come primo loop chiuso e testato contro il contratto del backend (MSW/mock) e, per l'e2e, contro backend+fake-pi.