Skip to content

fix(timeline): recolor a group when a later event extends it - #3787

Merged
tegan-temporal merged 3 commits into
mainfrom
refactor-buffer-copy-on-write
Aug 6, 2026
Merged

fix(timeline): recolor a group when a later event extends it#3787
tegan-temporal merged 3 commits into
mainfrom
refactor-buffer-copy-on-write

Conversation

@tegan-temporal

@tegan-temporal tegan-temporal commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

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. The eventCount prop still changes, so the new dot gets drawn — but lineColor and accessibleName derive from group alone 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:

  • the live poll extending a group loaded by the fetch
  • the live poll extending a group the poll itself created
  • the fetch attaching a follower whose head arrived in an earlier page (histories over 1000 events)

processEvent re-syncs its local reference after the pending-follower flush, which can now swap meta.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

@tegan-temporal
tegan-temporal requested a review from a team as a code owner August 4, 2026 21:28
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview Aug 5, 2026 11:22pm

Request Review

@tegan-temporal tegan-temporal changed the title refactor(events): centralize group copy-on-write via cloneGroupWith fix(timeline): recolor group bar when a completion arrives live Aug 4, 2026
@tegan-temporal
tegan-temporal changed the base branch from fix-timeline-bar-color-stale to main August 4, 2026 21:37
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.
@tegan-temporal
tegan-temporal force-pushed the refactor-buffer-copy-on-write branch from 66c086a to 203eaa8 Compare August 5, 2026 15:53
@tegan-temporal tegan-temporal changed the title fix(timeline): recolor group bar when a completion arrives live fix(timeline): recolor a group's bar when its completion arrives live Aug 5, 2026
@tegan-temporal tegan-temporal changed the title fix(timeline): recolor a group's bar when its completion arrives live fix(timeline): recolor a group when a later event extends it Aug 5, 2026
@tegan-temporal tegan-temporal self-assigned this Aug 5, 2026
* the caller has stored the new one.
*/
function withAddedEvent(group: EventGroup, event: WorkflowEvent): EventGroup {
const next = cloneEventGroup(group);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We were previous just mutating (with addEventToGroup), now we're doing a clone. Could this cause any performance issues with large event histories?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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 │
└────────────────┴──────────┴────────┴──────┴──────┘

Comment thread src/lib/services/grouped-event-buffer.ts
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.
@tegan-temporal
tegan-temporal merged commit 0c4b698 into main Aug 6, 2026
21 checks passed
@tegan-temporal
tegan-temporal deleted the refactor-buffer-copy-on-write branch August 6, 2026 18:59
tegan-temporal added a commit that referenced this pull request Aug 6, 2026
#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.
ardiewen added a commit that referenced this pull request Aug 11, 2026
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>
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.

2 participants