@@ 1,6 1,6 @@
# lethe-web-ui-foundation
-**Status:** Execute (verify pending)
+**Status:** Verified (review pending)
**Module:** `sourcecraft.dev/bigbes/lethe`
**Branch:** master
**Worktree:** none
@@ 296,6 296,45 @@ Approach: six commits — Phase 1 ships the backend aggregates Home depends on (
Restating from Design: only `GET /api/v1/sessions` changes shape, additively. The five new fields (`summary`, `turn_count`, `tokens_in_total`, `tokens_out_total`, `model`) are added; nothing is removed or renamed. No client today depends on the response shape (only consumer is the integration test, which we update). No schema migrations; no wire-type changes; no config-key changes.
+## Verify
+
+**Result:** passed
+
+Date: 2026-04-26. Run against `master` HEAD (a93ad6a).
+
+Positive:
+- `go test ./... -race -count=1` → green (11 packages, no skips)
+- `npm test` (vitest) → 32 passed across 3 files (`adapters`, `theme`, `keyboard`)
+- `npm run typecheck`, `npm run lint`, `npm run build` → all clean; build emits `internal/server/web/dist/index.html` + 463.84 KB JS / 144.17 KB gzip
+- Built binary serves SPA at `/` (`text/html`); `/healthz` 200, `/readyz` 200 with `{"checks":{"database":"ok"}}`
+- `GET /api/v1/sessions` (auth) → 200 with new aggregate fields populated (`summary`, `turn_count`, `tokens_in_total`, `tokens_out_total`, `model` when present)
+- `GET /api/v1/sessions/{tool}/{host}/{id}` (auth) → 200 with `turns[]` in seq order
+- Browser smoke (Chrome via vite dev proxy): Home renders 2 ingested rows with mono columns, sans summary, `1.5k` token formatting, ToolDot colors flipping per tool, lime cursor row; click row → Session view with role-coded turns and react-markdown code-block rendering; ⌘K → palette opens with JUMP items + footer hints; tab click `Stats` → `/stats` stub renders `EmptyState`
+
+Negative:
+- `GET /api/v1/sessions` no auth → 401 `application/problem+json`
+- `GET /no-such-spa-path` → 200 SPA fallback HTML (client-routed)
+- `POST /healthz` → 405 `application/problem+json` (proves SPA `/*` is GET-only)
+- `GET /api/v1/sessions/foo/bar/nope` (auth) → 404 `application/problem+json` (API path bypasses SPA)
+
+Invariants:
+- `internal/shared/wire/` untouched in this task (no commits in `1af5bcb..HEAD` affect it)
+- `internal/server/auth/` untouched in this task (forward-auth code unchanged)
+- `web/dist/` has zero committed files; `internal/server/web/dist/` committed = `.gitkeep` + placeholder `index.html` only
+- No `tweaks-panel` or runtime accent toggle ported (`grep -rn 'tweak\|Tweak' web/src` empty)
+- Only one raw `fetch(` in `web/src` and it's in `api/client.ts` (the central wrapper)
+- `/api/v1/sessions` response is a strict superset (existing nine fields all present alongside the five new aggregate fields)
+- Body class `density-compact` is the default in `web/index.html`
+
+Smoke: `./tmp/lethe -config tmp/lethe.yaml` + `npm run dev` → end-to-end Home + Session + Palette + tab nav verified visually. Three screenshots captured and reviewed.
+
+Notes:
+
+- **`g`-leader keyboard chord could not be smoke-tested via Chrome DevTools MCP**: each `press_key` call carries hundreds of milliseconds of MCP serialization latency, exceeding the controller's 800 ms `g`-pending timeout. Verified by running an isolated minimal chord handler in the page that reproduced the same null result. Production code is unaffected (real human typing is sub-100ms between keys); unit tests in `web/src/lib/keyboard.test.ts` cover the timing logic with `vi.useFakeTimers()`.
+- **Composite-id-in-URL** (functional but ugly): clicking a Home row navigates to `/session/$tool/$host/<encoded-composite>` because the `Link` in `SessionsTable` passes `params.id = session.id` (which is the full composite `${tool}/${host}/${session_id}`). Phase 6's `useSession` adapter splits the composite back, so the Session view loads correctly. Fix is a one-liner (pass the bare `session_id` instead) — deferred as a follow-up since it's cosmetic, not functional.
+- **`Session` aggregate fields on the Get endpoint return zeros**: `GET /api/v1/sessions/{tool}/{host}/{id}` reuses the `Session` Go struct via `SessionWithTurns`, so the new `summary`, `turn_count`, `tokens_in_total`, `tokens_out_total` fields appear in JSON as zero values. By design (Plan 1.2 said the Get path is unchanged); the SPA's Session view computes from `turns[]` directly, so this is invisible to the UI. Worth flagging because a future API consumer reading `summary` from a Get response would see an empty string.
+- **`go test ./...` walks `web/node_modules/flatted/golang/pkg/flatted`**: a stray Go package shipped inside an npm dep. Reports `[no test files]`, no failure. Cleanup options: gitignore the path or use `go test ./internal/...` in CI. Not a verify failure.
+
## Conclusion
### Deviations from plan