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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<kind, Renderer>` + `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.
|
||||
Reference in New Issue
Block a user