feat(web): add route-addressable Evidence Desk - #5
Merged
Conversation
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.
Intent
Build Huddle's Variant B Evidence Desk for the bounded synthetic quick-demo: a route-addressable ranked rail and evidence workspace with truthful loading, denied, unavailable, not-built, empty, stale, partial-narration, and refresh states; deterministic priority bands separate from evidence confidence; progressive complete dominant and additional-cause evidence; recovery on pre-open supersession; keyboard/mobile/focus restoration behavior; and deterministic narration provenance. Implement the framework- and database-neutral EvidenceReader visible-open boundary using injected ports, a server-only signed five-minute grant and bounded fifteen-minute same-opening renewal with key rotation/constant-time verification, and an injected atomic nonce-ledger contract. Keep the environment one synthetic guide only; do not add migrations, concrete DB acknowledgement storage, import/refresh implementation, notifications, public APIs, real data, parent/coach portals, or model calls. Operations/integration owns concrete BoardReader and acknowledgment persistence composition, so this branch must fail safely without inventing a production persistence fallback until those ports land.
What Changed
Risk Assessment
✅ Low: The bounded remediation durably closes the reveal-supersession race through an injected atomic lease contract and provides a focusable successful-empty fallback without adding forbidden concrete persistence or broader product scope.
Testing
No baseline command results were supplied; focused automated tests, a real
/boardfail-safe request, scope inspection, and a self-contained production-component HTML render all passed. A screenshot was not possible because no local or connected browser backend was available, so reviewer-visible responsive HTML was captured instead.Evidence: Self-contained responsive Evidence Desk render using the production component and CSS
/var/folders/nt/rdk7cjs538l8zphln24q2k900000gn/T/no-mistakes-evidence/01KYQNFBSKTDJ9XTF0BPSBG7NM/board-denied.html)Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 8 issues found → auto-fixed (4) ✅
apps/web/app/board/evidence-desk.tsx:38- The required “progressive complete dominant and additional-cause evidence” is incomplete and sometimes altered: durations are rounded from stored milliseconds, while attempt/session IDs, ordinal, timing profile, hints, misconception, confidence breakdown, and several computed/derived values are omitted. The same lossy disclosure is reused for additional causes; render exact stored or bundle-precomputed values and expose all causal support.apps/web/app/board/visible-open-acknowledgment.tsx:27- The visible-open boundary equates React effect execution with visibility. A background tab can acknowledge an unseen report, and because the grant expiry is discarded before rendering, a response delayed beyond five minutes renders and renews an opening that should be refreshed before visibility. Gate evidence exposure and submission on actual visibility, retainexpiresAt, and repeatopenEntrywhen already expired.packages/application/src/evidence-reader-implementation.ts:192- Grant expiry is classified before entering the ledger transaction. A grant verified just before expiry can expire in flight yet still useconsumeVisibleGrant, so the required “bounded fifteen-minute same-opening renewal” and “injected atomic nonce-ledger contract” do not hold atomically. Move consume-versus-renew selection to one ledger operation that owns the transactional time/nonce transition.apps/web/app/board/evidence-desk.tsx:377- The required truthful refresh states are not all represented:not-builtignores its retained refresh value, and queued/running are collapsed elsewhere. A first-build failure, queued build, and running build therefore show the same instruction. Render the concrete idle/queued/running/succeeded/failed state, including first-build failures.apps/web/app/board/evidence-desk.tsx:60- The required truthful partial-narration state and deterministic narration provenance are incomplete.Freshnessignoresboard.narration.status/degradedCount, while the report provenance omitsrenderVersion; a partially narrated board appears simply ready and renderer versions under one catalog are indistinguishable.apps/web/app/board/visible-open-acknowledgment.tsx:31- The action result’s immutableacknowledgedAtis discarded and every success displays “Seen just now.” Reopening an unchanged finding or renewing after supersession therefore misreports an older first-open timestamp; preserve and display the returned timestamp.apps/web/app/board/lib/evidence-desk-state.ts:64- EveryopenEntrynot-found result is labeledboard-updatedafter rereading, even if the same run and entry remain current. A missing evidence record consequently produces a false supersession message; compare the reread head and use a truthful unavailable state when it did not change.apps/web/lib/acknowledgment-tokens.ts:30- Signature decoding is permissive: Node’s base64url decoder ignores invalid characters, so noncanonical variants of a valid signature can verify as the same bytes. Reject non-base64url syntax and noncanonical encodings before the constant-time comparison.🔧 Fix: Fix Evidence Desk truthfulness and visible-open atomicity
4 issues (2 errors, 2 warnings) still open:
apps/web/app/board/lib/evidence-desk-state.ts:48- The required “recovery on pre-open supersession” is bypassed when the new head issuccessful-empty. If the URL selects run A/entry E and refresh promotes empty run B, this early return shows the ordinary empty state before comparing the selected IDs, omitting the non-disclosingboard-updatedrecovery. Classify selection mismatch before returning the empty board.apps/web/app/board/visible-open-acknowledgment.tsx:91- The signed five-minute visible-open invariant still has a TOCTOU path: expiry is checked while only the placeholder is visible, thensetExposed(true)renders asynchronously. The grant can expire—or already be server-expired because the client clock is slow—before the evidence is painted, after which it is handled as post-visibility renewal. Make the reveal authorization server-authoritative or recheck before paint with a safe expiry window.apps/web/app/board/evidence-desk.tsx:60- The truthful refresh presentation invents a start time:RefreshViewprovidesrequestedAt, but the running state says “running since” that timestamp. A request queued at 08:00 and claimed at 08:05 is therefore reported as running since 08:00. Label it as requested time or add an authoritative start timestamp to the owning contract.apps/web/app/board/lib/evidence-desk-state.ts:51- A URL containing onlyrunor onlyentryis labeledboard-updatedeven when the board head is unchanged. Treat incomplete selection parameters as an invalid/unavailable route state rather than claiming supersession.🔧 Fix: Fix supersession routing and pre-paint reveal authorization
3 issues (2 errors, 1 warning) still open:
packages/application/src/evidence-reader-implementation.ts:171- The required “recovery on pre-open supersession” remains bypassable after server render. IfopenEntryauthorizes run A, run B becomes current before the hidden/off-screen report becomes visible,authorizeVisibleOpenverifies only the signed credentials and exposes A without rechecking that it remains evidence-readable. Revalidate the signed run/entry throughEvidenceReadPortat this actual visibility boundary and compare the finding fingerprint before authorizing reveal.apps/web/app/board/rail-navigation.tsx:26- The required “keyboard/mobile/focus restoration behavior” fails across refresh supersession because the saved focus target uses the run-scopedtriageEntryId. After run B replaces run A, returning searches the new rail for A’s entry ID; even if the same student remains ranked, the immutable run has a different entry ID, so focus is not restored. Persist the stable student identity and resolve its current row, with a current-rail fallback when it disappears.apps/web/app/board/rail-navigation.tsx:39- Malformed or stale JSON in the mutablesessionStorageentry throws from the mount effect because parsing hasfinallybut nocatch, potentially breaking the rail instead of merely abandoning restoration. Catch parse errors, validatefocusIdandscrollY, and retain unconditional key cleanup.🔧 Fix: Close reveal supersession and restore stable rail focus
2 errors still open:
packages/application/src/evidence-reader-implementation.ts:194- The durable “recovery on pre-open supersession” fix still has a narrower TOCTOU path: run A can pass thisvisible-reveallookup, then run B can be promoted before the response reaches the client and A is painted. A was therefore superseded before visibility but is still revealed. The evidence-selection owner needs an atomic reveal lease or equivalent semantic handoff; another point-in-time lookup cannot close this interval.apps/web/app/board/rail-navigation.tsx:16- The required focus fallback still returns no target when refresh promotes asuccessful-emptyboard. Returning from run A mountsRestoreRailPosition, but the empty branch renders neither a row nor#evidence-rail, sofindRailFocusTargetreturns null and focus is not restored. Provide a focusable current-state fallback such as the successful-empty heading or workspace container.🔧 Fix: Add atomic reveal lease and empty-state focus fallback
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
npx vitest run apps/web/test/evidence-desk-rendering.test.ts apps/web/test/evidence-desk-state.test.ts apps/web/test/rail-navigation.test.ts apps/web/test/acknowledgment-tokens.test.ts packages/application/test/evidence-reader-implementation.test.ts apps/web/test/board-without-narrator.test.tsnpm run dev -w apps/web -- -p 4180followed bycurl --fail-with-body http://127.0.0.1:4180/boardto capture the real route’s non-disclosing denied statevite build --config vite.config.mjsandnode make-portable.mjsin the evidence directory to bundle the production EvidenceDesk component and exact CSS into self-contained responsive HTMLVerified the portable artifact contains ranked priority/confidence, stale and failed-refresh status, partial narration, route addressing, dominant/additional evidence, and visible-open surfaces; verified its inline JavaScript parsesgit diff --name-only d1924922413d8fbf5bb1b2c4432922ce0ecbfee4..cfdf75a0bdb758c6523fa31399b2389b443b06cdscope audit confirmed no migrations, concrete DB persistence, ingest/refresh implementation, notifications, portals, public APIs, or narrator/model changesAttempted Playwright, Chrome DevTools, and the connected browser for screenshots; no browser backend is installed or connected in this environment✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.