Conversation
P5.4 (docs/conductor-frontends-design.md §4). The rail gains a second lens: what was dispatched when, and what came back. The lanes answer "what needs me now" by re-sorting on urgency, which is exactly wrong for retrospection — a list that reorders itself cannot be read as history. §4 asks for both, so the two now sit behind a switch rather than one pretending to do the other's job. Built from the two streams the board already carries, tasks and events, with no new wire data. Four rules, each from a case that would otherwise read wrong: **A dispatch and its outcome routinely share a millisecond** on a fast task. Newest-first then has to render the dispatch SECOND, or the row order implies the result preceded the request. **An unknown event type is kept, not dropped.** A newer daemon naming something differently must not silently erase rows from the one view whose purpose is history. **Events outlive their tasks.** The board is capped, so a long run ages tasks out while their events remain — and "the digest for a task I can no longer see" is the most useful thing left. Such a row keeps its task id so it still correlates by eye. **Days split on LOCAL midnight.** A UTC split files an evening dispatch under tomorrow for anyone east of the meridian. The target resolves to a session NAME where one exists, degrading to a short id prefix otherwise. That came from rendering the real board rather than fixtures: every dispatch row showed a full UUID, which is noise in a 288px rail and tells the reader nothing — and a finished task has no session at all, since the dispatcher tears its worker down after the digest. Verified against a captured board of 5 tasks and 5 events: day separators land correctly, each outcome sits directly above its dispatch, and the two tasks blocked by the old daemon-collision bug show their real task_blocked digests. 13 timeline tests; 279 web tests; typecheck, lint and build clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🔮 Oracle Review
🎯 Start Here
web/src/components/FleetRail.tsx (~21 min) — Security changes in FleetRail.tsx
📋 PR Summary
What this PR does: Adds a dispatch timeline lens to the fleet rail that displays historical dispatch data with outcomes, separate from the existing urgency-based lanes view.
Key changes:
- Implemented timeline view using existing tasks and events streams (no new wire data)
- Added logic to handle dispatch/outcome ordering on same millisecond timestamps
- Implemented local midnight day splitting instead of UTC
- Added session name resolution with UUID prefix fallback for readability
Areas affected: Fleet rail component, Timeline display logic
Testing notes: Verified against real board data with 5 tasks and 5 events; includes 13 timeline tests as part of 279 total web tests
🔍 Code Review
This is a thoughtful implementation that solves a real UX problem—separating urgency-based workflows from historical retrospection. The four design rules show deep consideration of edge cases, and leveraging existing data streams avoids unnecessary backend changes. Strong verification against real data and comprehensive test coverage gives confidence in the quality.
What's good:
- ✨ Excellent architectural decision to use two separate lenses rather than overcomplicating a single view
- ✨ Smart approach of building from existing wire data (tasks + events) without new API endpoints
- ✨ Thorough handling of edge cases: same-millisecond ordering, unknown event types, outliving tasks, and local timezone splitting
Generated by Oracle - Highflame's AI Code Reviewer
| > | ||
| <For each={lanes()}>{(lane) => <Lane group={lane} />}</For> | ||
| </Show> | ||
| </Show> |
There was a problem hiding this comment.
Verify date parsing for day headers
Using new Date(day.day) relies on the format returned by groupByDay. If day.day is a date string (e.g., '2026-09-06'), browsers typically parse it as UTC, which may shift the header by one day depending on the user's local timezone. Ensure groupByDay returns an ISO timestamp or that parsing logic explicitly handles the local time context intended by the 'LOCAL midnight' requirement.
Suggested fix:
| </Show> | |
| If `day.day` is a string, consider using a local-parsing library or constructing the date explicitly from parts to avoid UTC shifts. If it is a timestamp, the current implementation is correct. |
Related: lib/fleet-timeline.ts
P5.4 (conductor-frontends-design.md §4). The rail gains a second lens: what was dispatched when, and what came back.
Why a second lens rather than a richer list
The lanes answer "what needs me now" by re-sorting on urgency — which is exactly wrong for retrospection. A list that reorders itself cannot be read as history. §4 asks for both, so they now sit behind a switch instead of one pretending to do the other's job.
Built from the two streams the board already carries (tasks + events). No new wire data.
Four rules, each from a case that would otherwise read wrong
A dispatch and its outcome routinely share a millisecond on a fast task. Newest-first then has to render the dispatch second, or the row order implies the result preceded the request.
An unknown event type is kept, not dropped. A newer daemon naming something differently must not silently erase rows from the one view whose entire purpose is history.
Events outlive their tasks. The board is capped, so a long run ages tasks out while their events remain — and "the digest for a task I can no longer see" is the most useful thing left. Such a row keeps its task id so it still correlates by eye.
Days split on LOCAL midnight. A UTC split files an evening dispatch under tomorrow for anyone east of the meridian.
A fix that only showed up on real data
The target now resolves to a session name where one exists, degrading to a short id prefix otherwise.
That came from rendering the actual captured board rather than fixtures — every dispatch row showed a full UUID like
7bdbd557-656a-4b0c-bab1-6a7894ab3efe, which is noise in a 288px rail and tells the reader nothing. And a finished task has no session at all, since the dispatcher tears its worker down after the digest, so the fallback matters as much as the happy path.Verified against a real board
5 tasks, 5 events:
Day separators land correctly, each outcome sits directly above its dispatch, and the two tasks blocked by the old daemon-collision bug (#319) show their real
task_blockeddigests.Verification: 13 timeline tests, 279 web tests total, typecheck / lint / build clean.
What's still blocked in P5.4
The map/tree lens remains undrawable —
createdByis always the conductor (never the dispatching agent), workers get no fleet tools so sub-workers can't exist, and finished workers are torn down before they could be leaves. It needs daemon changes first; details in my earlier analysis.Routing rationale and cross-vendor review edges likewise have no wire representation yet.
🤖 Generated with Claude Code