Skip to content

fix(dashboard): surface error states + safeParseJSON helper#34

Merged
kaicianflone merged 8 commits into
mainfrom
fix/dashboard-error-handling
May 6, 2026
Merged

fix(dashboard): surface error states + safeParseJSON helper#34
kaicianflone merged 8 commits into
mainfrom
fix/dashboard-error-handling

Conversation

@kaicianflone
Copy link
Copy Markdown
Collaborator

Summary

Net-positive incremental improvement to dashboard error handling. Replaces silent JSON.parse fallbacks and silent network failures with explicit error surfacing in components that render untrusted server data.

Changes:

  • apps/dashboard/src/lib/safeJson.ts (new) — generic safeParseJSON<T>(input, fallback, context?) helper. Wraps JSON.parse in try/catch, returns fallback on failure, warns in DEV mode with input length only (no PII leak).
  • apps/dashboard/src/lib/__tests__/safeJson.test.ts (new) — 15 unit tests covering valid/invalid/null/undefined input, DEV-only warning, no-leak guarantee.
  • Wired safeParseJSON into EventTimeline, BoardDetailPage, RunDetailPage, AgentsPanel.
  • Wired setApiError into WorkflowsDashboard user-action paths so failures surface visibly instead of console-only.
  • Two adversarial review fix-up commits addressing round 1 / round 2 regressions caught during iteration.

Test Coverage

  • pnpm --filter @consensus-tools/dashboard test — 36 tests pass (2 files, +15 new in safeJson).
  • pnpm --filter @consensus-tools/dashboard typecheck — clean.

Known limitations (addressed in follow-up)

This branch went through 3 rounds of adversarial review. Round 3 found 5 multi-source-confirmed issues with the ad-hoc shape-check approach (typeof === 'object' && !Array.isArray(), empty-string detection, last-write-wins maps). Rather than chase regressions in a 4th round, the structural fix is being implemented as a separate PR on fix/dashboard-zod-trust-boundary — runtime schema validation via zod at the trust boundary, replacing scattered ad-hoc checks.

This PR ships as net-positive over main (silent failures and missing error surfaces are the worst state). The zod plan supersedes the safeParseJSON helper but reuses the test patterns and setApiError wiring done here.

Pre-Landing Review

Branch was reviewed 3 times via /review adversarial pass on commit 1a561cf. Round 3 identified the 5 issues (B/D/E/F + implicit C/J) now scoped into ~/.gstack/projects/consensus-tools-toolkit/ceo-plans/dashboard-zod-trust-boundary.md.

Test plan

  • Dashboard test suite passes (36/36)
  • TypeScript typecheck passes
  • Visual smoke check after merge: load board detail, run detail, agents panel — confirm error banners appear on simulated 500 responses

🤖 Generated with Claude Code

kaicianflone and others added 8 commits May 4, 2026 22:00
Adds apps/dashboard/src/lib/safeJson.ts with a generic safeParseJSON<T>
helper that returns a typed fallback on empty/null/invalid JSON input and
emits console.warn (with context label and 80-char snippet) only in DEV mode.
Includes 10 vitest unit tests covering all specified acceptance criteria.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… pass

Cross-source review (Claude A + Claude B + Codex) surfaced 7 real issues in
the prior 6 commits that wired safeParseJSON + setApiError into the dashboard.
All fixed:

- Sticky apiError: refresh()/refreshList()/handleDelete()/executeWorkflow()
  now setApiError(null) at start so a transient blip doesn't pin the red
  banner forever (mirrors BoardDetailPage.load() pattern).
- Empty-string payload regression: BoardDetailPage.runDecisions and
  RunDetailPage.parsed now use {} as fallback (was null), preserving the
  old `JSON.parse(s || '{}')` behavior. Decision card and run map entries
  no longer silently vanish on empty payloads.
- (error as Error).message unsafe cast: WorkflowsDashboard delete/execute
  handlers now use `error instanceof Error ? error.message : String(error)`.
  No more "Failed to delete workflow: undefined" on non-Error throws.
- Token/PII leak via console.warn: safeJson dev-mode warning now logs
  structural metadata only (input length), never raw content. Sentry RUM
  / DevTools history can no longer capture API keys from malformed payloads.
- _setDevForTesting in production bundle: now a no-op constant in prod
  builds via import.meta.env.DEV ternary so the override can't be flipped
  by injected code at runtime.
- AgentsPanel.parseMetadata NPE risk: guard against valid JSON parsing to
  primitives (`"null"`, `"false"`, etc.) so downstream `meta.x` access
  doesn't crash if backend stores a non-object.
- Falsy-valid JSON pinning tests: add 4 tests for `"false"`, `"0"`, `"null"`,
  `'""'` so a future refactor can't silently break round-tripping.

Tests: 36 pass (was 31). Typecheck clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Round-2 review (Codex + structured pass) caught 4 real issues including
a regression introduced by the round-1 fix-up. All addressed:

- BoardDetailPage runDecisions overwrite bug: the round-1 {} fallback
  unconditionally wrote map[run_id]={}, which could overwrite a
  previously-valid decision with an empty fallback when a later
  FINAL_DECISION event for the same run was malformed. Now distinguishes
  empty input (legitimate, write {}) from parse failure (skip, never
  overwrite).
- RunDetailPage parsed memo: same distinction. Empty payload renders
  the degraded card; malformed payload is treated as null so the card
  doesn't render with NaN% / undefined garbage.
- WorkflowsDashboard refreshList race: removed setApiError(null) from
  refreshList because it's auto-fired from the mount useEffect alongside
  ensureDefaultBoard, where a concurrent error could be clobbered by
  the null reset. User-initiated handlers (handleDelete, executeWorkflow)
  still pre-clear, which is the intended pattern.
- AgentsPanel parseMetadata: tightened guard so the typeof-object branch
  also rejects arrays. Single guard now covers both branches.
- AgentsPanel modal handlers (5 sites): handleAddInternal, handleAddExternal,
  handleAddHuman, saveEdit, handleDeleteParticipant — all silent empty
  catches replaced with apiError plumbing matching the refresh() pattern.
- EventTimeline polling catch: matches sibling getRunIds pattern with
  console.warn.

Tests: 36 pass. Typecheck clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@kaicianflone kaicianflone merged commit e5e6a98 into main May 6, 2026
3 checks passed
@kaicianflone kaicianflone deleted the fix/dashboard-error-handling branch May 6, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant