Removes workspace/model/provider/thinking fields. Dialog now posts only { question } to createSession and calls onCreated on success.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
workspace/provider/model/thinking now come from getSettings() injected into
sessionRoutes; the request body supplies only question+name. Also teaches
fake_pi_rpc to respond to set_model and set_thinking_level RPC commands so
tests that pass real model settings don't hang.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live end-to-end against real pi --mode rpc revealed Pi streams assistant text as
top-level message_update events whose nested assistantMessageEvent carries the
incremental delta — not the top-level text_delta the fake-pi-rpc emits. The bridge
now maps message_update(assistantMessageEvent.text_delta).delta -> FE text_delta,
so the chat shows the model's output during a real session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live end-to-end surfaced a 500 on every tht call: ThtRunner prepended
'-c <config>' before the subcommand, but tht has no global -c option
('No such option: -c'). --config/-c is a per-command option, so it must be
appended AFTER the subcommand. Extracted buildArgv() and fixed the unit test
that had codified the wrong (prepended) order.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- sessionStore.applyEvent: on ui_request set currentPhase normalized from the
descriptor's phase to its short id (e.g. "F4_schema_linking" -> "F4"), so
WorkflowBar actually highlights the active phase; falls back to the existing
phase when the descriptor has none. setPhase kept for resume/getSession.
- Add pushToast store action; WidgetHost wraps postResponse in try/catch,
pushes an error toast and keeps the widget pending on failure (clearPending
only on success) so the user can retry.
- Tests: store currentPhase normalization + no-phase passthrough + pushToast;
new WorkflowBar.test.tsx asserting the matching phase is highlighted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FakeEventSource.addEventListener was a no-op, so emit() only drove onmessage.
Production relies on addEventListener for the backend's NAMED events
(event: ui_request), so the unit tests could pass while prod silently broke.
- fakeEventSource: store named handlers in a Map<string,Set>; add emitNamed()
that dispatches to them; keep emit() for the unnamed/default onmessage path
- useSessionStream.test: ui_request now driven via emitNamed (production path);
add a separate test for the unnamed text_delta path via plain emit
- f1-loop.test: widget emission switched to emitNamed("ui_request", ...)
- verified: tests FAIL if addEventListener wiring is removed from
useSessionStream (then restored)
- cleanup: fake-pi.mjs drops unused execFileSync import, uses static spawnSync
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a real-browser Playwright e2e of the full F1 disambiguation loop,
driven against the real backend + fake binaries (no VPN, no real Pi, no Python).
- frontend/e2e/fixtures/fake-tht.mjs: stubs tht CLI (session new/list/show)
- frontend/e2e/fixtures/fake-pi.mjs: wraps harness fake-pi-rpc with f1_disambiguation.json
- frontend/playwright.config.ts: two webServer entries (backend:8799, frontend:5199)
- frontend/e2e/f1.spec.ts: open app → create session → wait for SelectWidget → respond
Bug fixes discovered during e2e:
- useSessionStream: add addEventListener for named SSE events (backend sends
'event: ui_request' etc.; onmessage only fires for unnamed 'event: message')
- FakeEventSource: add no-op addEventListener/removeEventListener stubs
- backend SSE route: add CORS headers manually in writeHead() since
reply.raw bypasses the @fastify/cors onSend hook
- backend: install and register @fastify/cors for all non-SSE routes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add NewSessionDialog.test.tsx covering §9 graceful degradation:
empty models -> free-text input; non-empty models -> dropdown; plus a
createSession body-keys assertion
- Normalize model entries to {value,label} so object-shaped models
({provider,id}) render correctly in the dropdown (was assuming strings)
- NavSessions: invalidate the ["sessions"] query after a successful
resumeSession so the list/status refreshes immediately
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- NavSessions: left nav listing sessions via listSessions (TanStack Query),
clicking resumes via resumeSession and sets active; active item highlighted
- NewSessionDialog: ShadCn dialog with question textarea, workspace native
select (from listWorkspaces), graceful model field (dropdown when models
list is non-empty, free-text input when empty per §9 degradation),
optional thinking/provider fields; try/catch surfaces errors inline
- SteerInput: text input + button POSTs to postSteer, clears on send,
supports Enter key; only shown when a session is active
- WorkflowBar: renders 8 phases (F1..F8) with data-active highlighting
driven by useSessionStore.currentPhase (new store field + setPhase action)
- AppShell: wires all four components; right artifact sidebar now collapsible
via a toggle button (useState sidebarOpen); replaces old "Nuova domanda"
button with NewSessionDialog trigger
- f1-loop.test: updated to drive through the new dialog flow (open → fill
question → submit → wait for SSE → respond)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace invalid role="strong" with data-testid="scalar-value"
- Add test (d): selecting "tutti" re-fetches preview with the large limit
- Render "(risultati troncati)" indicator when truncated; assert in test (a)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Test (a) now drains pending highlightSql microtasks inside act() via
waitFor before synchronous header assertions, eliminating the React
act() warning. Test (c) asserts the layout toggle label flips
Orizzontale -> Verticale rather than merely existing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TDD: SqlViewer renders collapsible CTE blocks with name/field-count/
test-status badges, shiki-highlighted SQL body (dangerouslySetInnerHTML),
per-field comments, and a vertical/horizontal layout toggle.
highlight.ts wraps shiki as a lazy singleton; tests mock it for
determinism (pattern mirrors mermaid.ts).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cap counts promoted candidates only (was all candidates); reuse promoted list (DRY)
- New test: 10 promoted + 50 excluded must not cap, graph stays available
- Extract reasonFor() helper; table Perché falls back to signal keys like the flowchart
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TDD: SchemaLinkingViewer with mocked renderMermaid, toggle flowchart↔table,
≤45 element cap with Italian notice. MarkdownView renders mermaid fences.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Untrack tsconfig.tsbuildinfo (TS build artifact) and add *.tsbuildinfo
to .gitignore. Move shadcn from dependencies to devDependencies (CLI
generator, not runtime). npm test passes; npm run build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TDD: App-renders-ThothII test RED then GREEN. ShadCn v4 (base-nova style)
init + button/checkbox/radio-group/textarea/card/dialog/badge/sonner added.
tailwind.config.ts extended with oklch CSS-variable color mapping required
by ShadCn v4 @apply directives. npm test passes; npm run build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- spawnFor now tears down any existing runtime for the same session id before
the cap check, so resume/respawn neither leaks the old child nor falsely hits
maxPiProcesses
- exit handler is identity-checked (captures rt) so a stale child's late exit
cannot evict a newer runtime
- SSE pending re-emit now sends the full ClientEvent shape
{ type: "ui_request", ui_request } to match hub.publish live events
- tests: same-id respawn replaces runtime (count 1); old child exit does not
evict new runtime; sse-hub re-emit asserts unified shape
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add PiProcessManager.resume(sessionId, tht): reads provider/model/thinking
from tht.sessionShow() and calls spawnFor with those values
- Add POST /sessions/:id/resume route: calls mgr.resume then re-wires
bridge.onClientEvent → hub.publish
- Confirm venv PATH already present in real spawn (no change needed)
- Add e2e test: POST /sessions → SSE receives ui_request via pendingWidget
re-emit → POST /sessions/s1/response → 204 (all over real HTTP against
fake-pi-rpc)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Harness:
- preview_cmd FILE positional arg made optional; when omitted with --session,
path is derived via _session_sql_file (mirrors export_cmd) — fixes the
deferred Task-5 bug where the backend passed sessions/<id>/sql_final.sql
relative to harnessDir, which broke for workspace-dependent paths.
- New pytest: test_preview_session_no_file_resolves_sql_final
Backend:
- ThtRunner.sqlPreview: drop positional file arg; use --session only
- New routes/sql.ts: POST /sessions/:id/sql/preview + /export
- New routes/meta.ts: GET /workspaces (yaml scan) + GET /models (injectable
seam + graceful fallback to {models:[]})
- app.ts: register sqlRoutes + metaRoutes; add listModels to BuildAppDeps
- tht-runner.test.ts: add sqlPreview argv assertion (no file path)
- test/routes-sql-meta.test.ts: 9 tests (sql preview/export + meta routes)
Tests: harness 233 passed; backend 29 passed; build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /sessions no longer silently drops `workspace`. ThtRunner.run/json
take an optional workspace; configArg() selects workspaces/<ws>.yaml when
it exists under harnessDir, else falls back to default configPath.
sessionNew threads workspace through. Route forwards b.workspace with an
MVP note (gate/Pi side still single-workspace via symlinked config/tht.yaml).
19/19 tests pass, tsc clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Make buildApp injectable (deps.thtRunner + deps.spawnFn); create
src/routes/sessions.ts with all 7 session routes under authPreHandler;
wire bridge.onClientEvent→hub.publish before returning {id} from POST
/sessions; SSE subscribes with pendingWidget safety net.
18/18 tests pass, tsc clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Decodes native extension_ui_request (method:input, title=JSON) into
ui_request ClientEvent; encodes respond() as extension_ui_response with
value payload; tracks pending widget; handles notify→info and steer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>