From a93ad6a09c0fb5f4ca9323e61cc5bd8efb26c19d Mon Sep 17 00:00:00 2001 From: Eugene Blikh Date: Sun, 26 Apr 2026 07:16:15 +0300 Subject: [PATCH] docs(lethe-web-ui-foundation): record execute deviations across all 6 phases All six phases implemented, signed, and tested green. Backend aggregates (Phase 1) ship a strict superset of the prior /api/v1/sessions response. Web SPA (Phases 2-6) is embedded into the Go binary; vite dev mode proxies the API with a Remote-User header injection so forward-auth passes without a real reverse proxy. Status flipped to "Execute (verify pending)" pending up:uverify. --- docs/tasks/lethe-web-ui-foundation.md | 32 ++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/tasks/lethe-web-ui-foundation.md b/docs/tasks/lethe-web-ui-foundation.md index c75e9118b740ee2ff2c0f6fe2ede52f2229da7b4..d054ee8ae98afd5aa26d32fe8785c2268a92b159 100644 --- a/docs/tasks/lethe-web-ui-foundation.md +++ b/docs/tasks/lethe-web-ui-foundation.md @@ -1,6 +1,6 @@ # lethe-web-ui-foundation -**Status:** Design +**Status:** Execute (verify pending) **Module:** `sourcecraft.dev/bigbes/lethe` **Branch:** master **Worktree:** none @@ -295,3 +295,33 @@ Approach: six commits — Phase 1 ships the backend aggregates Home depends on ( ### Backwards-compat check 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. + +## Conclusion + +### Deviations from plan + +- **Phase 2 — vite outDir**: set to `../internal/server/web/dist/` so vite output lands directly in the directory the Go embed will read in Phase 3. Plan implied a `web/dist/` location with an unspecified copy step; collapsing to a single dist location removes that step entirely. Root `.gitignore` updated accordingly (`internal/server/web/dist/*` with `.gitkeep` exception). +- **Phase 2 — eslint v8 (vs v9)**: ESLint v9 dropped `.eslintrc.cjs` support in favor of flat config; downgrade to `^8.57.1` keeps the plan's config format. Acceptable until a future cleanup migrates to flat config. +- **Phase 2 — separate `web/vitest.config.ts`**: the TanStack Router vite plugin fails at config-resolve when `src/routes/` is missing or empty; vitest uses its own config that omits the Router plugin so `npm test` runs in this phase before routes exist. Phase 4 will populate routes; the separate file remains useful for test-only config divergence. +- **Phase 2 — `web/src/routes/__root.tsx` placeholder**: required to satisfy the TanStack Router plugin scan at build time; minimal stub that Phase 4 replaces with the real shell-mounted root. +- **Phase 2 — `web/src/test-setup.ts`**: implied by the testing infrastructure; sets up `@testing-library/jest-dom` matchers. + +- **Phase 3 — `r.Get("/*", …)` vs `r.Handle("/*", …)`**: plan said `Handle` (any-method); implementation uses `Get` (and HEAD). Reason: `Handle` made `POST /healthz` return 200 (SPA HTML) instead of 405 problem+json, regressing the existing `TestRouter_MethodNotAllowedReturnsProblemJSON` invariant. SPA only needs GET (browser navigation); restricting the catch-all is more correct and preserves the 405 behavior for non-GET on unknown paths. +- **Phase 3 — `TestRouter_NotFoundReturnsProblemJSON` retargeted**: with `/*` mounted, chi returns 405 (not 404) for non-GET on unknown paths because `/*` matches the path. The existing `notFoundHandler` is still wired (in case chi reaches it) but the routing path that previously hit it no longer does. The test now invokes `notFoundHandler` directly to assert its output. +- **Phase 5 — keyboard `cursor` wiring (retroactive Phase 4 amendment)**: Phase 4 left a static no-op `cursor: { move, activate }` in `__root.tsx`'s keyboard controller. Phase 5 added `cursorRef` + `KeyboardCursorContext` so route-local components can register their cursor and the controller delegates through the ref. This is the pattern the dispatcher's Phase 5 brief instructed; recording so it isn't conflated with Phase 4's omission. +- **Phase 5 — `useHomeCursor.jumpTo(i)`**: plan specified `move`/`activate` only; `jumpTo` added so `SessionsTable.onCursor` can set the cursor on mouse hover/click. Pure additive surface; no plan invariant violated. +- **Phase 5 — `FilterChips` "active filter" set is component-local**: plan said filters are URL-driven. The actual filter values (`since`, `tool`, `host`) ARE URL-driven; what's local is "which chips are shown" (e.g. `+ filter` adds `host` to the displayed chip list). No URL representation was specified for that and adding one is gratuitous. +- **Phase 5 — `ToolDot` tool prop widened from literal union to string**: real ingest data may include tools outside the prototype's five. CSS class still falls through cleanly. +- **Phase 5 — `session.$tool.$host.$id.tsx` placeholder created**: needed so TanStack Router types accept `Link` navigation from Home. Phase 6 replaces the placeholder with the real Session view. +- **Phase 6 — `SessionWithTurns extends Omit`**: `Session.turns` is the aggregate count (number) from Phase 5; `SessionWithTurns.turns` is `Turn[]`. `Omit` resolves the conflict cleanly. SubBar turn-count display reads `session.turns.length`. + +### Known minor gaps + +- **Turn meta line lacks timestamp**: the `Turn` TS interface in the plan's 6.1 spec did not include `timestamp`, so the meta line shows `# seq · model · tokens-in→tokens-out` only. The DTO has it; adding the field to `Turn` and the meta line is a 5-line follow-up if desired (deferred). +- **Manual interactive smoke not run** (Phases 4–6): the implementers had no display environment for `vite dev` browser checks. `up:uverify` will cover it. +- **Docker image not built** (Phase 3): the multi-stage Dockerfile change compiles in principle but was not exercised. `docker build .` smoke recommended before deploy. + +### Deferred (needs user input) + +- **Phase 3 — CI configuration**: the plan calls for `.github/workflows/*` or `.sourcecraft/ci.yml`; neither directory exists in the repo. The CI job (`npm ci && npm run build && npm test && npm run lint`) is not implemented in this phase. To resume: either confirm sourcecraft.dev's CI file format and create `.sourcecraft/ci.yml`, or add a `.github/workflows/web.yml` if GitHub mirroring is in scope. +- **Phase 3 — Dockerfile not docker-built**: the multi-stage `node:20-alpine` builder + `COPY --from=web-builder /internal/server/web/dist /src/internal/server/web/dist` was added but not exercised via `docker build`. Recommend a docker-build smoke before the binary is deployed.