Commit Graph
122 Commits
Author SHA1 Message Date
marcopanandClaude Opus 4.8 f59d6d1478 fix(backend): allow PUT in CORS methods so browser can save settings
Found via live browser test: PUT /settings preflight was rejected because
@fastify/cors default methods omit PUT. GET/POST were unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:54:30 +02:00
marcopanandClaude Opus 4.8 abfee5a6fe feat(frontend): add Settings menu item under Nuova domanda
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:30:40 +02:00
marcopanandClaude Opus 4.8 5d79437daf fix(frontend): read MSW request body once in SettingsDialog test (pristine output)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:29:01 +02:00
marcopanandClaude Opus 4.8 29b7e85510 feat(frontend): SettingsDialog with workspace/provider/model/thinking selects
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:26:32 +02:00
marcopanandClaude Opus 4.8 32d963b43d feat(frontend): NewSessionDialog reduced to question-only
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>
2026-06-28 16:22:57 +02:00
marcopanandClaude Opus 4.8 fa0f878c97 feat(frontend): settings api, typed PiModel, question-only createSession
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:19:51 +02:00
marcopanandClaude Opus 4.8 ec36ee421a feat(backend): POST /sessions applies global settings, body is question-only
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>
2026-06-28 16:16:08 +02:00
marcopanandClaude Opus 4.8 098915515f fix(backend): deterministic /models fallback test + merge duplicate import
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:12:18 +02:00
marcopanandClaude Opus 4.8 bfbb017413 feat(backend): /settings GET+PUT, /models PiModel shape, app wiring
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:09:04 +02:00
marcopanandClaude Opus 4.8 c4b599ec00 feat(backend): ephemeral Pi model lister (get_available_models) with TTL cache
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:04:33 +02:00
marcopanandClaude Opus 4.8 6b2883a467 feat(backend): persistent settings store + settingsFile config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:01:22 +02:00
marcopanandClaude Opus 4.8 0cd14b5b55 docs: settings-menu design spec + implementation plan
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:59:47 +02:00
marcopanandClaude Opus 4.8 a9b9ce297a chore: gitignore .playwright-mcp run artifacts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:07:09 +02:00
marcopanandClaude Opus 4.8 091a055bf0 fix(backend): bridge maps real Pi message_update->text_delta for FE streaming
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>
2026-06-28 15:03:13 +02:00
marcopanandClaude Opus 4.8 1ee692cdb1 fix(backend): tht --config is per-command, append after subcommand (not global -c)
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>
2026-06-28 14:52:39 +02:00
marcopanandClaude Opus 4.8 18843d7421 chore: run-stack.sh — avvia i 3 layer reali per validazione end-to-end
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:45:07 +02:00
marcopanandClaude Opus 4.8 5295e34898 fix(frontend): wire currentPhase to WorkflowBar (normalized) + WidgetHost error handling
- 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>
2026-06-28 13:40:33 +02:00
marcopanandClaude Opus 4.8 ddbbe4d1ac test(frontend): unit tests exercise named SSE events (fidelity) + cleanup
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>
2026-06-28 13:33:50 +02:00
marcopanandClaude Sonnet 4.6 1171181f9a test(frontend): Playwright e2e F1 vs backend+fake-pi (hermetic)
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>
2026-06-28 13:27:00 +02:00
marcopanandClaude Opus 4.8 3dec4c3af0 test(frontend): cover NewSessionDialog model degradation + invalidate sessions on resume
- 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>
2026-06-28 13:11:40 +02:00
marcopanandClaude Sonnet 4.6 544882f95b feat(frontend): sessions list/create + steering + resume + selectors
- 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>
2026-06-28 13:06:01 +02:00
marcopanandClaude Opus 4.8 3ecfed5d3d fix(frontend): valid scalar markup + selector/truncated coverage in ResultsPanel
- 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>
2026-06-28 13:00:50 +02:00
marcopanandClaude Sonnet 4.6 b4ab7b005a feat(frontend): results panel (AGGrid + preview/export)
Add ResultsPanel component with AGGrid v36 for tabular preview results,
bold scalar display for 1×1 results, limit selector (10 / tutti), and
Esporta CSV button delegating export to the backend. MSW-tested with 3
passing scenarios (grid cells, scalar, export endpoint call).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 12:57:08 +02:00
marcopanandClaude Sonnet 4.6 38032877e9 test(frontend): settle async highlight in SqlViewer test (pristine output) + assert toggle flip
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>
2026-06-28 12:53:59 +02:00
marcopanandClaude Sonnet 4.6 954f701c3f feat(frontend): SQL/CTE viewer with shiki highlighting
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>
2026-06-28 12:49:58 +02:00
marcopanandClaude Opus 4.8 eb552331a6 fix(frontend): cap schema-linking graph on promoted count + signals in table Perché
- 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>
2026-06-28 12:47:29 +02:00
marcopanandClaude Sonnet 4.6 2a23de6825 feat(frontend): schema-linking viewer (mermaid+table) + markdown view
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>
2026-06-28 12:43:11 +02:00
marcopan f933b544ff test(frontend): cover ArtifactWidget view-only + multiselect allow_empty gate 2026-06-28 12:40:09 +02:00
marcopan be9e761e0a feat(frontend): multiselect/artifact-gate/artifact widgets + linkage + no-limbo 2026-06-28 12:36:13 +02:00
marcopanandClaude Sonnet 4.6 2b55e96608 feat(frontend): 4-zone shell + F1 loop end-to-end (MSW)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 12:30:35 +02:00
marcopan a267754f8f feat(frontend): F1 widgets (select/info/freetext) + reserved controls 2026-06-28 12:25:50 +02:00
marcopan e9aca179d7 feat(frontend): widget registry + universal fallback 2026-06-28 12:22:37 +02:00
marcopan d238ddd395 feat(frontend): useSessionStream (EventSource -> store) 2026-06-28 12:19:55 +02:00
marcopan ebd3988634 feat(frontend): Zustand session store + applyEvent 2026-06-28 12:17:16 +02:00
marcopanandClaude Sonnet 4.6 913ac30137 feat(frontend): contract types + REST client (MSW-tested)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 12:13:50 +02:00
marcopanandClaude Sonnet 4.6 7e42ac11ef chore(frontend): gitignore tsbuildinfo + shadcn to devDeps
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>
2026-06-28 12:11:16 +02:00
marcopanandClaude Sonnet 4.6 662d944267 feat(frontend): scaffold Vite+React+TS+Tailwind/ShadCn + Vitest
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>
2026-06-28 12:08:22 +02:00
marcopanandClaude Opus 4.8 cabff8e77f docs(plans): piano di implementazione del frontend (13 task, F1 first)
Vite+React SPA: scaffold, api/types+client, store Zustand, useSessionStream (SSE),
widget registry+fallback, widget F1 (select/info/freetext), shell 4-zone + loop F1,
widget restanti+linkage, viewer (schema-linking/sql/results), sessioni+steering+resume,
Playwright e2e F1 vs backend+fake-pi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:02:21 +02:00
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
marcopanandClaude Opus 4.8 7efa88aaca docs: stato del progetto e guida alla ripresa (harness+backend su main)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:02:35 +02:00
marcopanandClaude Opus 4.8 b909f3e571 chore(backend): commit package-lock.json for reproducible installs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:59:59 +02:00
marcopanandClaude Sonnet 4.6 c63b2bd126 fix(backend): idempotent spawnFor + identity-checked exit + unified SSE re-emit shape
- 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>
2026-06-27 21:53:12 +02:00
marcopanandClaude Sonnet 4.6 b16c94e30b feat(backend): resume + venv PATH + end-to-end F1 smoke (fake-pi-rpc)
- 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>
2026-06-27 21:37:24 +02:00
marcopanandClaude Sonnet 4.6 d630cac3ab feat(task-10): SQL routes + /workspaces + /models + fix sql preview path bug
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>
2026-06-27 21:31:35 +02:00
marcopanandClaude Sonnet 4.6 061002f85c fix(backend): forward workspace to tht config selection (MVP backend-side)
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>
2026-06-27 21:26:09 +02:00
marcopanandClaude Sonnet 4.6 b4b26e313b feat(backend): session routes + SSE + response/steer wiring
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>
2026-06-27 21:19:19 +02:00
marcopanandClaude Opus 4.8 a58fb19340 feat(backend): pluggable auth (none/mock/oidc seam)
- authPreHandler(mode) returns Fastify preHandler that sets req.user={id}
- none: uses dev@local
- mock: reads x-mock-user header
- oidc: MVP stub returns 501 + throws
- getUser(req) returns user object

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:13:26 +02:00
marcopan 39eb35116e feat(backend): SSE hub with pending-widget re-emit on (re)subscribe 2026-06-27 21:10:48 +02:00
marcopanandClaude Opus 4.8 f4c6126270 refactor(backend): drop dead SpawnFn alias + test Pi exit-handler cleanup
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:09:49 +02:00
marcopanandClaude Sonnet 4.6 de7200f7dd feat(backend): PiProcessManager (one Pi per session, cap, set_model/thinking)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 21:05:03 +02:00