SOW-0001 Chunk 14: frontend scaffolding (Vite + React + TS SPA)#16
Merged
Conversation
Creates frontend/ — the React/TypeScript single-page app served same-origin by ai-viewer-serve. Foundation only; Phase-1 pages (SessionsList, SessionDetail Overview/Logs, Sources) land next, Phase-2 routes are ComingSoon placeholders. - Stack (pinned latest-stable-compatible): React 19, react-router 7, TanStack Query 5, Vite 8, TypeScript 6 (strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes), Vitest 4 + RTL, Playwright (config), ESLint 9 + typescript-eslint (ESLint held at 9.x for eslint-plugin-react peer support). - Theme: dark default + OS prefers-color-scheme + persisted manual override, pure resolveTheme + data-theme on <html> + no-flash inline script + aria-live. - Filters: URL-synced via React Router search params (useFilters); FilterBar. - API: typed client over relative /api (error-envelope -> ApiError, AbortSignal, HEAD/empty-body); types mirror the real backend wire JSON; TanStack Query hooks (useSessions/useSessionDetail/useStats[cross-session]/useSources/useHealth). - SSE: EventSource wrapper — POST /api/subscriptions -> /api/events?sub= -> parse the 5 frames -> query invalidation -> close()/DELETE; abort/unmount-safe (no leaked subscription or EventSource at any timing); malformed frames surfaced. Same-origin (relative /api; no host hardcoded). Gates: tsc 0, eslint 0, 130 vitest tests passing (sse.ts 98.68%, ~97.6% lines overall), vite build 84.56 KB gzipped main chunk (budget 500 KB). Specs (frontend-architecture.md, ui-pages.md) updated in lockstep; converged across 3 external-review rounds.
The CI frontend job runs `npm run e2e` whenever package.json defines an `e2e` script; the scaffold defined one, but there are no E2E specs yet and the playwright webServer (vite preview, with no prior build) timed out at 120s. Remove the `e2e` script so CI skips the Playwright step until Chunk 18 adds real specs + a webServer that boots the serve binary. Trim playwright.config.ts to a skeleton (no webServer; baseURL :7710). Other frontend gates unchanged (lint/typecheck/test/build green).
ktsaou
added a commit
that referenced
this pull request
May 30, 2026
…, EndTs
Round-3 fixes completing the partially-fixed exec/patch enrichment and
web_search pairing plus two correctness gaps, all verified against the
real ~/.codex wire shapes.
- exec_command_end exit_code is now authoritative for op status in BOTH
orders: exec-first applies at finalize; output-first emits a correcting
OpFinalized(failed,command_failed) via the finalizedOps lookup (a
non-zero exit no longer leaves a failed command marked completed).
- patch_apply_end is now order-independent (finalizedOps path) and merges
patch_success/patch_status into op Extras; success=false -> failed.
- exec_duration_ms is now emitted (real duration is {secs,nanos} ->
secs*1000 + nanos/1e6).
- web_search pairing uses a per-turn FIFO queue of open searches (oldest
pairs with each web_search_end) so interleaved searches don't mis-pair;
the end event's action object is now decoded and emitted alongside query.
- NativeID is taken from the authoritative session_meta.payload.id (the
UUID parent_thread_id/forked_from_id reference); the filename UUID is
only a fallback.
- old-format turn_context-only sessions now finalize their EOF turn at the
turn's last-activity timestamp (deterministic), not the file mtime, so
the golden is stable across runs; the new-format stale crash finalize
still uses the stale mtime.
New fixtures l_exec_failed / n_patch_apply / m_multi_web_search /
o_payload_id_filename + regenerated f_exec_truncated / b_old_turncontext /
k_web_search, each line-checked against the spec and byte-identical across
repeated -update-golden runs. Spec pinned the order-independence + the
{secs,nanos} and {patch_success,patch_status} shapes (rules #14/#16/#23).
Gates green: golangci(0)/gosec(0)/vet; race 13/13; codex coverage 92.6%;
FuzzParseLine 0 crashes; secret + AI-attribution scans clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Creates
frontend/— the React/TypeScript SPA served same-origin byai-viewer-serve. Foundation only; Phase-1 pages (SessionsList, SessionDetail Overview/Logs, Sources) land next; Phase-2 routes areComingSoonplaceholders.noUncheckedIndexedAccess+exactOptionalPropertyTypes), Vitest 4 + RTL, Playwright (config), ESLint 9 + typescript-eslint (ESLint held at 9.x foreslint-plugin-reactpeer support — documented).prefers-color-scheme+ persisted manual override; pureresolveTheme+data-theme+ no-flash inline script +aria-live.useFilters) +FilterBar./api(error-envelope →ApiError, AbortSignal,HEAD/empty-body);types.tsmirrors the real backend wire JSON; TanStack Query hooks.EventSourcewrapper —POST /api/subscriptions→/api/events?sub=→ parse 5 frames → query invalidation →close()/DELETE; abort/unmount-safe at every timing (no leaked subscription or EventSource); malformed frames surfaced (no silent drop).Same-origin (relative
/api, no host hardcoded).Review
Converged across 3 external-review rounds (codex + glm + minimax). Notable catches fixed: a
connectSseunmount/abort leak race; the SSE client was untested and excluded from coverage (now tested at 98.68% and in the gate); anduseStats(sessionId)hit/api/stats?session_idwhich the backend ignores (now cross-session-only; per-session aggregates come from/api/sessions/:id;ui-pages.mdcorrected).Test plan
tsc --noEmit— 0 errors (strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes)eslint . --max-warnings 0— 0 warningsvitest run --coverage— 130 tests pass; ~97.6% lines / 93.3% branches (sse.ts98.68%,stats.ts100%)vite build— main chunk 84.56 KB gzipped (budget ≤500 KB)frontend/(relative/api)