Skip to content

feat: Phase 1b wiring — step timeline, run reconstruction, SSE recovery - #103

Merged
nutt-adam merged 3 commits into
mainfrom
feat/phase-1b-dashboard-wiring
Mar 21, 2026
Merged

feat: Phase 1b wiring — step timeline, run reconstruction, SSE recovery#103
nutt-adam merged 3 commits into
mainfrom
feat/phase-1b-dashboard-wiring

Conversation

@nutt-adam

@nutt-adam nutt-adam commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Step timeline in detail drawer: click a run dot → see per-step progress with status badges (green/red/pulsing), durations, agent names, and failure messages
  • Historical run reconstruction: on page load, fetches /v1/events and replays workflow events to rebuild appState.runs — dashboard shows runs in progress even after a page refresh
  • SSE reconnect recovery: on reconnect after disconnect, refetches health + events to recover missed state
  • Run-stage HUD: shows current stage and step progress (e.g. "1 run → implementer 10/23") instead of just a count
  • formatDuration: human-readable step durations (ms/s/m)
  • 20+ new JS tests: step timeline tracking, formatDuration, terminal-state filtering

Test plan

  • cargo test — 327 tests pass
  • cargo clippy + cargo fmt clean
  • Open dashboard/test.html in browser — all JS tests pass
  • Visual: start tt serve, trigger tt run sdlc-auto, click run dot → step timeline visible
  • Visual: refresh page mid-run → runs reconstructed from /v1/events
  • Visual: HUD shows "1 run → planner 9/23" during active run

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Click run dots to open a run-detail drawer showing per-step timeline, human-readable step durations, and metadata; selecting a run clears other selections, closing the drawer clears selections.
    • Pipeline HUD includes current stage/step progress for active runs.
    • Automatically reconstructs workflow history on boot and after reconnects.
  • Style

    • Added step-timeline styles and run-dot selection highlight.

Complete the remaining Phase 1b dashboard items:
- Step timeline in detail drawer: click a run dot to see per-step
  progress with status badges, durations, and failure messages
- Historical run reconstruction: on page load, fetch /v1/events and
  replay workflow events to rebuild appState.runs
- SSE reconnect recovery: on reconnect, refetch health + events to
  recover missed state changes
- Run-stage HUD: shows current stage and step progress (e.g. "1 run
  → implementer 10/23") instead of just a count
- formatDuration helper for human-readable step durations
- 20+ new JS test assertions for step timeline, formatDuration, and
  terminal-state filtering in runsAtStage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@nutt-adam has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 27 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 15e82303-b139-4d8d-92f0-f68ea2e501b1

📥 Commits

Reviewing files that changed from the base of the PR and between 12260d0 and 0b0dca9.

📒 Files selected for processing (2)
  • dashboard/app.js
  • dashboard/style.css
📝 Walkthrough

Walkthrough

Added run selection UI and a run-detail drawer, per-step timelines on runs, duration formatting, and historical run reconstruction by replaying /v1/events. Workflow event handling now populates run.steps[] and sets finishedAt; SSE reconnect triggers reconstructor and health refetch.

Changes

Cohort / File(s) Summary
Run Selection & Runtime Logic
dashboard/app.js
Added appState.selectedRun, selectRun(), reconstructRuns(), formatDuration(); enhanced processWorkflowEvent() to initialize run.steps, append/update step entries on workflow.step.* events, set finishedAt on terminal events, update runs HUD to include stage/step progress, and clear selections when drawer closed. SSE reconnect now triggers health refetch and run reconstruction.
Step Timeline UI Styling
dashboard/style.css
Added CSS for step timeline and selection: .step-row, .step-badge (+.step-ok/.step-fail/.step-running with pulse), .step-desc, .step-dur (+.running-text), .step-msg, and .run-dot-selected.
Tests & Test Helpers
dashboard/test.html
Added formatDuration(ms) tests; extended processWorkflowEvent() tests to cover steps lifecycle and finishedAt; updated runsAtStage() logic and tests to exclude terminal runs.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser
    participant App as App State
    participant EventAPI as /v1/events API
    participant SSE as SSE Stream

    rect rgba(100, 150, 200, 0.5)
    Note over Browser,App: Boot / Reconstruction
    Browser->>App: Initialize appState
    App->>EventAPI: GET /v1/events
    EventAPI-->>App: event history
    App->>App: reconstructRuns() (replay events -> build runs & steps)
    end

    rect rgba(150, 100, 200, 0.5)
    Note over SSE,App: Live Event Processing
    SSE->>App: workflow.started
    App->>App: init run.steps = []
    SSE->>App: workflow.step.started
    App->>App: append step (type, agent, stage, startedAt)
    SSE->>App: workflow.step.completed/failed
    App->>App: update step (status, durationMs, message)
    SSE->>App: workflow.completed/failed
    App->>App: set run.finishedAt
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Browser,App: Run Selection & Drawer
    Browser->>App: click run dot
    App->>App: selectRun(id) (clear selectedAgent)
    App->>Browser: render drawer with run.steps (badges, durations, messages)
    Browser->>App: close drawer
    App->>App: clear selections
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through events, one by one,
Dotted runs now tell their tale;
Steps lined up beneath the sun,
Durations small, messages pale.
Drawer opens—rabbit grins, and hops to scale.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is missing required versioning information (Cargo.toml update, CHANGELOG.md, SemVer choice, and version number) specified in the repository template. Add Versioning section with checkboxes for Cargo.toml/CHANGELOG updates, select appropriate SemVer level (likely MINOR for new features), specify version number, and confirm CI validation steps.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: step timeline feature, historical run reconstruction capability, and SSE recovery mechanism are all clearly referenced.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/phase-1b-dashboard-wiring

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

When /v1/events returns only recent events, workflow.started events
for old runs get reconstructed without their matching terminal events,
creating zombie "running" runs. Prune runs older than 30 minutes that
are still marked running after reconstruction completes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
dashboard/test.html (1)

409-448: Add a zero-duration step assertion.

formatDuration(0) is covered above, but the step timeline suite only exercises non-zero duration_ms. That won't catch the duration_ms || null edge case in the production processWorkflowEvent path, so a legitimate 0ms step can still slip through.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dashboard/test.html` around lines 409 - 448, Add a test case in the "step
timeline tracking" suite that sends a workflow.step.completed (or .failed) event
with data.duration_ms set to 0 via processWorkflowEvent and then asserts that
testRuns["st-1"].steps[<index>].durationMs is 0 (not null); reference
processWorkflowEvent and testRuns so the test exercises the production path that
currently uses "duration_ms || null" and ensures zero-duration steps are
recorded as 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dashboard/app.js`:
- Around line 89-93: The duration assignment drops legitimate zero values
because it uses the || operator; change the assignment in the block that updates
run.steps[cidx].durationMs from "(evt.data && evt.data.duration_ms) || null" to
use the nullish coalescing operator "(evt.data && evt.data.duration_ms) ?? null"
(so 0 is preserved), and make the identical fix in the other analogous block
(the second occurrence around lines 98-102) that sets run.steps[...].durationMs.
- Around line 545-549: reconnect flow currently replays full history directly
into appState.runs and appState.events while EventSource.onopen is already
delivering live events, allowing older replayed events to overwrite newer live
ones and making reconnect cost proportional to full history; change
reconstructRuns() to (a) use the server-supported cursor (the same cursor used
by /v1/events) to fetch only the delta since the last seen cursor OR (b) replay
history into temporary structures (e.g., tempRuns, tempEvents) and atomically
replace appState.runs and appState.events only after replay finishes;
additionally ensure EventSource.onopen either supplies a starting
cursor/lastEventId to avoid duplicate older events or pauses/buffers incoming
live events until the swap completes so live events cannot be overwritten.
- Around line 276-283: Replace the non-semantic span used for run dots by
creating a button instead of a span via el("button", "run-dot") where the
variable dot is created, set dot.type = "button", move the existing dot.title
content into an accessible label by adding dot.setAttribute("aria-label",
dot.title) (keep dot.title if desired), and keep the existing click handler that
calls selectRun(stageRuns[r].id) so keyboard activation works; also update the
styling rules that target the "run-dot" class to reset default button styles and
add :focus-visible (or similar) focus styling so the new buttons look the same
as before but are keyboard-visible and accessible.

In `@dashboard/style.css`:
- Around line 355-360: Replace the deprecated word-break usage in the .step-msg
rule: remove the "word-break: break-word" declaration and add "overflow-wrap:
anywhere" to achieve equivalent long-word wrapping; update the .step-msg CSS
block (target the .step-msg selector) to use overflow-wrap: anywhere instead of
word-break: break-word so modern browsers follow the current CSS Text spec.

---

Nitpick comments:
In `@dashboard/test.html`:
- Around line 409-448: Add a test case in the "step timeline tracking" suite
that sends a workflow.step.completed (or .failed) event with data.duration_ms
set to 0 via processWorkflowEvent and then asserts that
testRuns["st-1"].steps[<index>].durationMs is 0 (not null); reference
processWorkflowEvent and testRuns so the test exercises the production path that
currently uses "duration_ms || null" and ensures zero-duration steps are
recorded as 0.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f2e2c46b-9a08-402a-941f-92a2b1e772e1

📥 Commits

Reviewing files that changed from the base of the PR and between 9e16afc and 1894d76.

📒 Files selected for processing (3)
  • dashboard/app.js
  • dashboard/style.css
  • dashboard/test.html

Comment thread dashboard/app.js
Comment thread dashboard/app.js Outdated
Comment thread dashboard/app.js Outdated
Comment thread dashboard/style.css Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
dashboard/app.js (1)

676-679: Add defensive check for null response.

If the fetch succeeds but returns an unexpected response structure (e.g., empty body), json.data access could throw.

Suggested fix
   return fetch("/v1/events").then(function(res) {
     return res.json();
   }).then(function(json) {
-    var events = json.data || [];
+    var events = (json && json.data) || [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dashboard/app.js` around lines 676 - 679, The code that processes the fetch
response (the promise chain starting with fetch("/v1/events") and the assignment
to var events = json.data || [];) should defensively handle a null/undefined
JSON body; change the assignment to verify json is truthy and json.data is
present (e.g., if (!json || !json.data) events = []; else events = json.data) or
use a safe expression like var events = (json && json.data) || []; ensure this
guard sits immediately after res.json() resolves so accessing json.data cannot
throw.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@dashboard/app.js`:
- Around line 676-679: The code that processes the fetch response (the promise
chain starting with fetch("/v1/events") and the assignment to var events =
json.data || [];) should defensively handle a null/undefined JSON body; change
the assignment to verify json is truthy and json.data is present (e.g., if
(!json || !json.data) events = []; else events = json.data) or use a safe
expression like var events = (json && json.data) || []; ensure this guard sits
immediately after res.json() resolves so accessing json.data cannot throw.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 328b088b-ba14-4f4b-89ca-2d15b3bd9fde

📥 Commits

Reviewing files that changed from the base of the PR and between 1894d76 and 12260d0.

📒 Files selected for processing (1)
  • dashboard/app.js

- Preserve zero-length step durations (use != null instead of ||)
- Use button element for run-dot (keyboard accessibility)
- Skip full event replay on SSE reconnect (health snapshot only)
- Replace deprecated word-break: break-word with overflow-wrap: anywhere

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nutt-adam
nutt-adam merged commit 75bbff2 into main Mar 21, 2026
11 checks passed
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