fix(dashboard): detail-sheet crash on null timeline; stable SSE consumption - #53
Merged
Merged
Conversation
…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)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
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.
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 ofdata, 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.