Skip to content

fix(dashboard): detail-sheet crash on null timeline; stable SSE consumption - #53

Merged
junhoyeo merged 3 commits into
mainfrom
agent/fix-dashboard-detail-sheet
Jul 17, 2026
Merged

junhoyeo merged 3 commits into
mainfrom
agent/fix-dashboard-detail-sheet

Conversation

@junhoyeo

Copy link
Copy Markdown
Owner

Wave 2/5 — dashboard (Ziikoo) fixes.

HIGH — detail sheet crashed with a TypeError whenever the timeline API returned "nodes": null (any issue with no workflow history). HIGH — the detail/timeline fetch effect keyed on object identity of data, refetching + flashing the loading state on every SSE snapshot; now keyed on stable identifiers.

Also: connection errors are visible before the first snapshot (no more eternal loading card when the backend is down), '最近完成' sorts by recency instead of object-key order, queue events carry monotonic sequence numbers so QueuePanel keeps consuming past the 1000-entry ring-buffer cap, board issues re-fetch after stream reconnect, stop-button state resets, reconnect backoff only resets after the stream proves stable, Issue.labels/tracker_meta nullability matches Go's JSON.

Adversarial review: approved, 0 blocking. Typecheck + production build green after rebase.

junhoyeo added 3 commits July 17, 2026 13:08
…ching on every snapshot

Two failure modes in IssueDetailSheet:

1. The Go timeline handler returns newSnapshot() for issues with no
   workflow history, whose nil Nodes slice marshals as "nodes": null.
   The empty-state check dereferenced timeline.nodes.length without a
   guard, so opening the sheet for any Todo/Backlog issue threw a
   render-time TypeError and white-screened the app (no error
   boundary). The fetch handler now normalizes all four snapshot
   collections to empty arrays.

2. The detail/timeline fetch effect keyed on [data, issueID]. data is
   rebuilt with a fresh object identity on every accepted SSE snapshot
   (5s refresh poll, stream frames, orchestrator events), so an open
   sheet aborted its in-flight requests and blanked back to the loading
   states at least every 5 seconds. The effect now keys on the stable
   issueID only, which is "" whenever data is null.

Also resets stopping/stopError when the sheet targets another issue so
a failed stop on one agent no longer renders its error under the next
agent's stop button.

Constraint: backend payload shape unchanged; nulls must be tolerated client-side
Rejected: optional chaining at the single crash site | leaves runs/sync_states null for other consumers
Rejected: React error boundary | containment, not a fix; out of scope for this finding
Confidence: high
Scope-risk: narrow
Not-tested: server actually emitting "nodes": null end-to-end (covered via fetch mock only)
…ct, gate backoff reset on stream data

Three data-layer defects in the SSE hook and its queue consumer:

1. useSSE caps queueEvents at 1000 by splicing the front, which pins
   the array length while shifting contents. QueuePanel consumed new
   events via an absolute index cursor (events.slice(processedCount)),
   so after the 1000th event the cursor stuck at the cap, every later
   event was silently dropped, and the TTL sweep left the panel
   permanently at "No blocked issues". Queue events now carry a
   monotonic seq and QueuePanel tracks the last seen seq, which
   survives front-trims.

2. Board issues were fetched once on mount only; board events
   published while disconnected are lost because the hub has no replay
   buffer, so the board stayed stale until a page reload. The board
   snapshot is now refetched on every successful (re)connect, which
   also covers the initial connect (the mount-only effect is removed).

3. reconnectAttemptRef was reset to 0 on HTTP 200 before any stream
   byte was read, so a server that accepts the request and immediately
   drops the body was retried every 500ms forever. The counter now
   resets only once a parsed message arrives.

Also aligns Issue.labels/tracker_meta types with the wire format (Go
marshals nil as null and the runtime validator admits it) so new code
cannot trust a non-null array/object the payload does not guarantee.

The "records queue channel events" reducer test pinned the old
seq-less payload shape and was updated to assert the new monotonic
seq contract instead.

Constraint: reducer must stay usable under StrictMode double-invocation, so seq gaps are acceptable; only monotonicity matters
Rejected: keying queue rows by issue_id in the reducer | changes the reducer's event-log contract and QueuePanel TTL semantics more than needed
Rejected: keeping the mount-only board fetch alongside the reconnect fetch | double-fetches on startup; board data is invisible pre-state anyway
Confidence: high
Scope-risk: moderate
Directive: consumers of queueEvents must track position by seq, never by array index
Not-tested: reconnect backoff growth timing across repeated 200-then-drop cycles (only the reset placement is covered)
…nt-done by updated_at

Backend down at page load left the dashboard on an eternal loading
card: the error from useSSE was only rendered on the post-state
branch, and the loading label reused a section heading. The pre-state
card now shows the connection error plus a reconnecting hint, with a
dedicated loading message key.

The recent-done queue sliced the first 50 done issues in wire map-key
order (lexicographic issue IDs), so newly completed issues could be
absent while months-old ones showed. Done issues are now sorted by
updated_at descending before truncation.

Constraint: badge count still reflects the truncated 50 rows; surfacing the full done count needs a QueueDef shape change
Rejected: threading a separate total count into the sidebar badge | widens the QueueDef/AppSidebar contract beyond the ordering defect
Confidence: high
Scope-risk: narrow
Not-tested: pre-state error card (App has no test harness; verified via typecheck/build and by the useSSE error-dispatch tests)
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
contrabass-landing Ready Ready Preview, Comment Jul 17, 2026 4:11am

Request Review

@junhoyeo
junhoyeo merged commit 5fc728b into main Jul 17, 2026
10 checks passed

This branch was successfully deployed

1 active deployment
Preview — 40b68484 Deployed Jul 17, 2026 by vercel[bot]
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