fix(timeline): recolor a group when a later event extends it - #3787
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
A running activity's timeline bar and label stayed "in progress" after it completed, until reload. The timeline row pool reuses a row while its group reference is unchanged (timeline-graph.svelte), and the paths that add an event to an already-rendered group extended it in place. The row therefore kept its old classification-derived color and accessible name. Those paths now copy the group and publish the new reference: the live poll extending a fetched group (the reported case), the live poll extending a live group, and the bidirectional fetch attaching a follower whose head arrived in an earlier page — the same symptom on histories over 1000 events. processEvent re-syncs its local reference after the pending-follower flush, which can now swap meta.group. Covered by unit tests for each of the three paths, plus an integration test asserting the accessible name flips to Completed with no reload.
66c086a to
203eaa8
Compare
| * the caller has stored the new one. | ||
| */ | ||
| function withAddedEvent(group: EventGroup, event: WorkflowEvent): EventGroup { | ||
| const next = cloneEventGroup(group); |
There was a problem hiding this comment.
We were previous just mutating (with addEventToGroup), now we're doing a clone. Could this cause any performance issues with large event histories?
There was a problem hiding this comment.
the mutation is why things were stale. by doing this shallow clone we ensure svelte reactivity is triggered due to the reference change. For what it's worth I have #3790 which is a much larger change that rethinks the whole event buffer entirely. I just wanted to get this fix in since it's a small immediate fix and the larger refactor is not rushed.
There was a problem hiding this comment.
also for what it's worth the clone is pretty cheap, it's adding only ~16ms in a synthetic benchmark with 200k events:
┌────────────────┬──────────┬────────┬──────┬──────┐
│ │ median │ mean │ min │ max │
├────────────────┼──────────┼────────┼──────┼──────┤
│ main (mutate) │ 1573ms │ 1611ms │ 1550 │ 1813 │
├────────────────┼──────────┼────────┼──────┼──────┤
│ branch (clone) │ 1588.5ms │ 1618ms │ 1564 │ 1793 │
└────────────────┴──────────┴────────┴──────┴──────┘
Addresses review feedback asking whether the re-sync deserved its own test. It does: when the fetch parks a follower and the live poll parks another, the head's registration flushes the first (replacing meta.group with the copy) and then the second, which must land on that copy. Without the re-sync it lands on the pre-copy reference. eventList is shared so the event still appears, but addEventToGroup's flags are not — a timed-out activity ends up with isFailureOrTimedOut false and renders as if it had not failed. The test asserts that flag and fails when the re-sync is removed.
The test names described their setup — "publishes a fresh reference when a live completion extends a fetched group" — which says what the assertion does, not why anyone should care. Each now names the symptom it prevents, with a line above saying what goes wrong without it. The failure-state one gets the most, since its setup is the least obvious: it needs both producers to park a follower, and its eventList assertion passes either way because the copy shares that array. The flag is the guard.
#3787 landed the copy-on-write fix, which this branch supersedes: groups are rebuilt on read and memoized per content version, so there is no in-place mutation left to guard. Conflicts in grouped-event-buffer.ts and its tests resolved to this branch — the code #3787 changed no longer exists here. The integration spec differed only in a header comment describing the old mechanism. Ports #3787's failure-state test. It guarded a stale local reference during the park-and-flush, which is gone, but the behaviour it asserts — followers landing before their head still produce the right failure state — still holds and was not otherwise covered.
Auto-generated version bump from 2.53.1 to 2.53.2 Bump type: patch Changes included: - [`54a6f782`](54a6f78) refactor: split worker deployment API types (#3795) - [`0c4b6987`](0c4b698) fix(timeline): recolor a group when a later event extends it (#3787) - [`7a1b1288`](7a1b128) Remove task queue types from compute configs (#3801) - [`cf68d9e8`](cf68d9e) Resync filter bar pills on browser back/forward navigation (#3796) - [`000bddb7`](000bddb) Only pass start delay on activity update if value changes (#3798) - [`04a1a50a`](04a1a50) fix(schedules): resolve recent run statuses from visibility (#3799) - [`5e8bbebb`](5e8bbeb) fix(events): dedupe marker billable actions across both fetch cursors (#3807) - [`1ed83780`](1ed8378) fix(auth): derive auth cookie Secure attribute from the OIDC callback URL (#3806) Co-authored-by: ardiewen <39966887+ardiewen@users.noreply.github.com>
A group's timeline bar and label kept their in-progress color and text after the group completed, until reload.
The row pool reuses a row while its group reference is unchanged (
timeline-graph.svelte:427), and the buffer extended already-rendered groups in place. TheeventCountprop still changes, so the new dot gets drawn — butlineColorandaccessibleNamederive fromgroupalone and never re-derive. When testing by hand, the dot appearing is not evidence the bug is gone.Three paths now copy the group and publish the new reference:
processEventre-syncs its local reference after the pending-follower flush, which can now swapmeta.group. Unit tests cover each path, plus an integration test asserting the label flips without a reload.The copy is shallow and shares
eventList, so a superseded reference must be treated as dead; #3790 removes that sharp edge by deriving groups on read.CleanShot.2026-08-05.at.09.36.01.mp4