fix: dashboard UI remaining TODOs + review-applied fixes #81

Merged
jesse merged 6 commits from fix/dashboard-ui-remaining-todos into main 2026-05-16 20:50:41 +02:00
Owner

Summary

Finishes the remaining dashboard UI TODOs and folds in the fixes surfaced by /review (multi-agent review of the branch). Two distinct bodies of work:

1. Dashboard UI TODOs (pre-existing branch work):

  • Extracted status panel into its own status.js module (124 lines, was inline in app.js).
  • Extracted polling/panel CSS into panels.css (was inline in styles.css).
  • New dashboard-config.js centralising polling constants (STATS_POLL_MS, HEALTH_POLL_MS, SERVER_HEALTH_POLL_MS).
  • Hardened auth.rs SessionMetadata RBAC branch for the new metadata tool wiring.

2. Review-applied fixes (5 commits from review team dashboard-review-fixes):

Commit What
b569291 get_session response now additively emits both id and session_id (production/mock parity, dashboard prefers session_id).
4ce4c7d SessionMetadata RBAC: deny on Absent+no-bound; deny on Absent+bound-but-no-role with session_id audit; malformed UUID returns JSON-RPC -32602 invalid_params instead of 403 (was info-leaky). 4 canonical reasons extracted as pub(super) const.
c950102 E2E: shared mcpCallLog race fixed via snapshot-and-compare; tightened permission-denied assertion to full canonical message + session header; armed waitForRequest before improve auth-refresh assertion. Mock get_session mirrors prod's additive response.
67435d0 Frontend config expanded (3 polling constants + readPollingConfig() helper that copies into a local frozen object rather than freezing the caller-owned window.__COGNIX_TEST_POLLING__); status.js gets per-fn AbortController to cancel stale in-flight polls; first-render gate fixed so one side recovering re-renders; graph.js accepts { shouldRender } to short-circuit before DOM mutation. New asset tests pin MIME/bytes and constants.
2d14d72 Preserves bootstrap RPC id ordering after the AbortController refactor (the new signal wrapper added a microtask hop that let list_sessions steal id=1). Fix: only pass signal when there's a prior controller to abort.

22 of 29 review findings applied; 10 deferred (cosmetic/structural — listed in branch fix report).

Test Plan

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --features ui,sse --all-targets -- -D warnings -D clippy::unwrap_used -D clippy::panic -D clippy::dbg_macro -D clippy::print_stdout -D clippy::print_stderr -D clippy::wildcard_imports — clean
  • cargo test -p cognix-mcp --lib — 282 passed (baseline 278; +4 new RBAC tests)
  • cargo test -p cognix-server --lib — 327 passed (baseline 327)
  • cargo test -p cognix-server --features ui,sse --test ui_tool_schema_shapes — 6 passed
  • cargo test -p cognix-server --lib --features ui,sse — 410 passed, 8 failed (8 failures all pre-existing on c8cc3ec checkpoint; flagged for separate triage in transport::ui::tests / transport::sse::tests returning HTTP 500)
  • pnpm playwright test e2e/dashboard/ — 39/39 passed
  • node --check on every modified asset

Self-Review Checklist

  • No hardcoded secrets
  • No unwrap() in library code
  • No println! / dbg! / print_stderr
  • Fail-open degradation preserved (RBAC denials use permission_denied_response; new -32602 path is for malformed input only)
  • Tests cover happy path AND error cases (Invalid UUID, Absent+no-bound, Absent+bound-ungranted, SessionScoped positive regression)
  • No thought content in errors or logs (RBAC audit logs reference session_id/agent_id only)
  • All SQL queries use prepared statements (no new SQL surface in this PR)
  • Input limits enforced (UUID parsing rejects malformed input at the boundary)
  • cargo audit and cargo deny check will run in CI

Known follow-ups (not in this PR)

  • 8 pre-existing --features ui,sse test failures in transport::ui::tests / transport::sse::tests (HTTP 500 from router — likely shared init issue). Verified pre-existing on c8cc3ec.
  • Deferred structural refactors (full module-state factory for app.js; 124-line initStatus() split) — tracked in branch fix report.
## Summary Finishes the remaining dashboard UI TODOs and folds in the fixes surfaced by `/review` (multi-agent review of the branch). Two distinct bodies of work: **1. Dashboard UI TODOs (pre-existing branch work):** - Extracted status panel into its own `status.js` module (124 lines, was inline in `app.js`). - Extracted polling/panel CSS into `panels.css` (was inline in `styles.css`). - New `dashboard-config.js` centralising polling constants (`STATS_POLL_MS`, `HEALTH_POLL_MS`, `SERVER_HEALTH_POLL_MS`). - Hardened `auth.rs` SessionMetadata RBAC branch for the new metadata tool wiring. **2. Review-applied fixes (5 commits from review team `dashboard-review-fixes`):** | Commit | What | |---|---| | `b569291` | `get_session` response now additively emits both `id` and `session_id` (production/mock parity, dashboard prefers `session_id`). | | `4ce4c7d` | SessionMetadata RBAC: deny on `Absent`+no-bound; deny on `Absent`+bound-but-no-role with `session_id` audit; malformed UUID returns JSON-RPC `-32602` `invalid_params` instead of 403 (was info-leaky). 4 canonical reasons extracted as `pub(super) const`. | | `c950102` | E2E: shared `mcpCallLog` race fixed via snapshot-and-compare; tightened permission-denied assertion to full canonical message + session header; armed `waitForRequest` before improve auth-refresh assertion. Mock `get_session` mirrors prod's additive response. | | `67435d0` | Frontend config expanded (3 polling constants + `readPollingConfig()` helper that copies into a local frozen object rather than freezing the caller-owned `window.__COGNIX_TEST_POLLING__`); `status.js` gets per-fn `AbortController` to cancel stale in-flight polls; first-render gate fixed so one side recovering re-renders; `graph.js` accepts `{ shouldRender }` to short-circuit before DOM mutation. New asset tests pin MIME/bytes and constants. | | `2d14d72` | Preserves bootstrap RPC id ordering after the AbortController refactor (the new signal wrapper added a microtask hop that let `list_sessions` steal id=1). Fix: only pass `signal` when there's a prior controller to abort. | 22 of 29 review findings applied; 10 deferred (cosmetic/structural — listed in branch fix report). ## Test Plan - [x] `cargo fmt --all -- --check` — clean - [x] `cargo clippy --workspace --features ui,sse --all-targets -- -D warnings -D clippy::unwrap_used -D clippy::panic -D clippy::dbg_macro -D clippy::print_stdout -D clippy::print_stderr -D clippy::wildcard_imports` — clean - [x] `cargo test -p cognix-mcp --lib` — 282 passed (baseline 278; +4 new RBAC tests) - [x] `cargo test -p cognix-server --lib` — 327 passed (baseline 327) - [x] `cargo test -p cognix-server --features ui,sse --test ui_tool_schema_shapes` — 6 passed - [x] `cargo test -p cognix-server --lib --features ui,sse` — 410 passed, 8 failed *(8 failures all pre-existing on `c8cc3ec` checkpoint; flagged for separate triage in `transport::ui::tests` / `transport::sse::tests` returning HTTP 500)* - [x] `pnpm playwright test e2e/dashboard/` — 39/39 passed - [x] `node --check` on every modified asset ## Self-Review Checklist - [x] No hardcoded secrets - [x] No `unwrap()` in library code - [x] No `println!` / `dbg!` / `print_stderr` - [x] Fail-open degradation preserved (RBAC denials use `permission_denied_response`; new `-32602` path is for malformed input only) - [x] Tests cover happy path AND error cases (Invalid UUID, Absent+no-bound, Absent+bound-ungranted, SessionScoped positive regression) - [x] No thought content in errors or logs (RBAC audit logs reference `session_id`/`agent_id` only) - [x] All SQL queries use prepared statements (no new SQL surface in this PR) - [x] Input limits enforced (UUID parsing rejects malformed input at the boundary) - [x] `cargo audit` and `cargo deny check` will run in CI ## Known follow-ups (not in this PR) - 8 pre-existing `--features ui,sse` test failures in `transport::ui::tests` / `transport::sse::tests` (HTTP 500 from router — likely shared init issue). Verified pre-existing on `c8cc3ec`. - Deferred structural refactors (full module-state factory for `app.js`; 124-line `initStatus()` split) — tracked in branch fix report.
- Deny Absent+no-bound with 'session context is required' (was: fell through
  to generic 'agent role is unavailable' second-pass check).
- Deny Absent+bound-no-role with 'agent is not granted access to the bound
  session for metadata tool' (was: same fall-through).
- Switch Invalid session_id to JSON-RPC -32602 invalid_params: a malformed
  UUID is a client protocol violation, not an authorization decision.
- Emit cognix.rbac.invalid_request audit event (was: cognix.rbac.deny) and
  include session_id field on bound-deny audit logs.
- Comment SessionScoped-only auto-injection gate.
- Extract canonical denial reasons into pub(super) consts; tighten the
  existing requested-metadata test to assert the full reason string.
- Add 4 new tests covering malformed UUID, SessionScoped injection
  regression, and both Absent-arm denial paths.
- mock-server: emit additive get_session payload (both id + session_id keyed
  to same UUID) matching backend's new additive contract. Keep legacy
  id-only toggle for backward-compat parsing test.
- dashboard.spec: tighten 'permission denied' assertion to canonical denial
  text + session-id header (the actual feature under test).
- dashboard.spec: snapshot mcpCallLog length before Graph-tab click so the
  get_thought_graph assertion proves the call was triggered BY the tab
  switch, not by earlier load steps.
- improve.spec: arm page.waitForRequest for analyze_reasoning BEFORE
  submitting the auth-refresh token, eliminating the race against the
  success-state assertions.
- dashboard-config.js: add HEALTH_POLL_MS/SERVER_HEALTH_POLL_MS plus
  readPollingConfig() helper so callers don't share-and-freeze the same
  window.__COGNIX_TEST_POLLING__ object across modules
- status.js: import the shared constants + readPollingConfig; add per-refresh
  AbortController so a slow in-flight fetch is cancelled when the next tick
  fires; fix the first-render gate (everSeen* booleans) so a side recovering
  after the other fails still produces an updated render; silently no-op
  AbortError in handleStatusError
- app.js: replace inline pollingConfig idiom with readPollingConfig(); pass
  shouldRender to renderGraph so a stale tab switch is dropped
- graph.js: accept and respect options.shouldRender as a caller-supplied
  freshness gate, combined with the existing per-mount token
- assets.rs: add test_new_dashboard_assets_serve_correct_mime_and_bytes and
  test_dashboard_config_pins_polling_constants; update the existing
  status-polling and PR5 stats-polling contract tests to match the new
  import / source shape (kept as strict contract assertions, not loosened)
- ui_tool_schema_shapes.rs: update the get_health / get_server_health
  mcpCall assertions to match the new shape with AbortSignal threaded
  through options (still strict literal contract)
fix: preserve bootstrap RPC id ordering after AbortController refactor
All checks were successful
CI / Detect Changes (pull_request) Successful in 20s
CI / Integration Tests (pull_request) Has been skipped
CI / Benchmarks (pull_request) Has been skipped
CI / Security Scan (pull_request) Successful in 22s
CI / Conventional Validation (pull_request) Successful in 55s
CI / Format (pull_request) Successful in 31s
CI / Clean Build Sample 1 (pull_request) Has been skipped
CI / Clean Build Sample 3 (pull_request) Has been skipped
CI / Clean Build Sample 2 (pull_request) Has been skipped
CI / Clean Build Summary (pull_request) Has been skipped
CI / Dashboard Browser (pull_request) Successful in 1m54s
CI / Documentation (pull_request) Successful in 2m35s
CI / Clippy (pull_request) Successful in 2m57s
CI / Deny (pull_request) Successful in 5m55s
CI / Audit (CVEs) (pull_request) Successful in 6m31s
CI / Test (pull_request) Successful in 7m49s
CI / Dashboard UI Build (pull_request) Successful in 7m59s
CI / Coverage (80% gate) (pull_request) Successful in 8m56s
CI / RSS gate (P-15) (pull_request) Successful in 6m27s
CI / Build (release) (pull_request) Successful in 7m51s
CI / PR Size Check (pull_request) Successful in 18s
CI / Check file lengths (pull_request) Successful in 33s
CI / CI Report (pull_request) Successful in 8s
CI / Check (linux-aarch64 compile-validation) (pull_request) Successful in 1m20s
CI / D-02 Clean Build Gate (pull_request) Successful in 3m24s
2d14d72dfb
Root cause: `withAbortSignal(P, signal)` in rpc.js (line 140) wraps the
SSE handshake promise in a new Promise when a signal is passed, which
adds an extra microtask hop between P resolving and `mcpCall` resuming.
`sessions.refresh()` calls `mcpCall('list_sessions', {})` with no signal,
so its `await` continues directly on P (1 microtask). The two new
signal-wrapped status calls now lose the post-SSE microtask race and let
list_sessions steal RPC id=1.

Fix: pass `signal` to mcpCall only on subsequent ticks where there's an
actual in-flight request to cancel. The first bootstrap tick of each
refresh has no prior controller, so we omit the option — avoiding the
withAbortSignal wrapper and preserving the dashboard.spec.ts:29 contract
that get_health and get_server_health receive ids 1 and 2 in that order.

Subsequent ticks still pass signal and still abort the prior in-flight
fetch, so the brief's bounded-concurrency + silent-supersession semantics
are unchanged. The first call simply can't be cancelled — an edge case
that doesn't matter in practice since there's nothing to cancel yet.

Test contract in ui_tool_schema_shapes.rs is updated to pin both
the new call site (`mcpCall(...callOptions)`) AND the option-construction
shape (the conditional ternary). Strict literal contract maintained;
not loosened.
jesse merged commit 1ee36ae9b7 into main 2026-05-16 20:50:41 +02:00
jesse deleted branch fix/dashboard-ui-remaining-todos 2026-05-16 20:50:41 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jesse/cognix!81
No description provided.