Files
ThothII/docs/superpowers/specs/2026-06-27-frontend-design.md
T
marcopanandClaude Opus 4.8 0fe2f2139e 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>
2026-06-28 11:51:14 +02:00

12 KiB

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.