Skip to content

refactor(events): derive event groups on read in the grouped-event-buffer - #3790

Merged
tegan-temporal merged 39 commits into
mainfrom
refactor-buffer-prune-dead-api
Aug 17, 2026
Merged

tegan-temporal merged 39 commits into
mainfrom
refactor-buffer-prune-dead-api

Conversation

@tegan-temporal

@tegan-temporal tegan-temporal commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Rewrites the grouped-event-buffer so event groups are derived on read and memoized, instead of built at ingest and mutated in place, and gives the module a reactive read API so views stop hand-rolling their own subscribe / throttle / re-read effects.

Correctness

Group identity becomes a pure function of content version: an unchanged group reads back as the same object, a changed one always as a new object. The timeline row pool keys on group identity, so extending a rendered group in place is a recurring source of stale rows — this makes the invariant structural instead of a convention every call site has to remember.

The buffer holds pending activity/nexus metadata and applies it when a group is built, so a terminal event and a stale pending entry resolve the same way in either arrival order, and a head event that arrives after the refresh listing it still picks its own up.

Simplification

  • One ingestion path. ingestHistoryEvent replaces processEvent + appendLiveEvent, which each kept their own group store, pending-follower map, and dedup set, plus a reconcile step between them.
  • Deletes unreachable API. The Pixi track-layout machinery, the promise-per-row streaming API, and the group-registration listeners had no callers — 15 of 28 exports were reachable only from tests.
  • Six manual cache-invalidation sites collapse to one revision counter, and getEventArray / getWorkflowTaskFailedEvent become single passes with no sort or dual-store merge.
  • Module state drops from 22 variables to 11, excluding read caches. The buffer stays Svelte-free and unit-testable; the reactive wrapper is a separate .svelte.ts.

Views read a value, and only materialize what they render

const lazyGroups = $derived(eventBuffer.lazyGroupsWithoutWorkflowTasks);

That replaces ~20 lines of $state.raw + void $bufferVersion + rAF + re-read in each of the two workflow layouts, and producers no longer announce their own writes.

Nothing upstream of a rendered row needs a full group — the type filter reads category, the sorts read isPending and initialEvent.id, the status filter reads classification, and segment layout reads the head and last event times. GroupRecord carries those, so getLazyGroups() returns the records themselves and views call materializeGroup per rendered row: ~54 pooled rows on the timeline, one page of 100 in the compact view.

The feed view still takes full groups. Its gutter graph does cross-group layout needing eventList, level, and reverse event→group lookups (history-graph, positioning) — a separate piece of work.

Performance

Measured in Chromium against a production build, running the same scripted interaction on both branches: load a mocked 50k-event history on the timeline, wait for the fetch, then 20 fixed scrolls. Three runs each, medians, run-to-run spread under 2%.

main this branch
first row painted 1103ms 914ms
fetch complete 1580ms 1392ms
total blocking time 324ms 156ms
longest task 355ms 186ms
busy CPU 998ms 830ms

The blocking figures are the ones a user feels — on main that single 355ms task is a visible freeze during load.

Micro-benchmarks on a 200k-event / 80k-group history show where it comes from:

before after
ingest 1592ms 738ms
getEventArray 10.4ms 1.5ms
open a view (cold) 548ms 2.9ms

That last row is the point of deriving on read: opening a view used to build every group in the history, and now builds one screen. The 738ms that remains in ingest is 88% toEvent conversion, which this PR doesn't touch — deferring that is the next available win.

API surface

Three changes worth a look, all in published modules:

  • EventSummaryTable's props are a union discriminated on compact, so items narrows without a cast and a caller can't pair the wrong list with the wrong mode. Callers whose mode is dynamic spread one object.
  • hoveredEventId was a bindable prop on EventSummaryTable but is only ever set by its own rows, so it became local state. No consumer in this repo or cloud-ui referenced it.
  • enrichGroups becomes setPendingMetadata. Pending state is now applied during the initial load rather than only after it completes.

cloud-ui imports three of the files this touches — the two view layouts and workflow-run-layout — and none of their prop contracts changed.

Risk

A LazyGroup and its materialized EventGroup must agree field for field, or a view filters on one and renders the other. Rather than test two implementations against each other, isPending and category now share one function, so those cannot diverge; the rules themselves have direct tests. The remaining fields are compared at every prefix of every group type, over a field list whose satisfies Record<keyof LazyGroup, boolean> makes TypeScript reject a new LazyGroup field until it is classified.

The buffer still carried three subsystems with no callers: the Pixi
track-layout machinery (assignTrackIndices, mergeHeads, getGroupMeta and
the trackIndex/pixiType/pixiStatus/startMs/endMs fields it maintained),
the promise-per-row streaming API (getRows, getLatestGroup and their
resolver maps), and the group-registration listeners (onLatestGroup,
onLiveGroup). Also removes five unused count accessors, getLatestEvent,
and resetLive, which duplicated what reset already clears.

The app imports 7 functions from this module; 15 of the 28 exports were
reachable only from the test file. Tests whose subject was the deleted
API are gone; the rest now read groups via getGroupArray.

No behavior change.
Replaces the buffer's two ingestion pipelines and mutable group store with
a single flat event array plus per-group records that materialize
EventGroup objects on read.

Before, `processEvent` (bidirectional fetch) and `appendLiveEvent` (live
poll) each kept their own group store, pending-follower map, and dedup
set, and a reconcile step merged them; three read paths then had to
de-duplicate the two stores. "Add an event to a group" was written six
times, and each copy had to remember to publish a fresh group reference
so reference-tracking Svelte views re-derive — the timeline row pool keys
on group identity, and two separate copies had already been found
mutating a rendered group in place, leaving completed activities
rendering as in progress.

Now `ingest` handles both producers: it writes the converted event into
events[eventId - 1] and appends the slot to the GroupRecord for its head
event. Groups are built by `materialize` and memoized on the record's
version, so identity is a pure function of content — unchanged groups
read back as the same object, changed groups always as a new one, and
there is no published group to forget to replace. Each materialized group
owns its eventList, so a superseded reference stays a consistent snapshot
rather than a half-updated one.

Consequences:
- pendingFollowers/livePendingFollowers, liveGroups, liveSeenIds,
  absorbLiveGroupIntoPool, the write-once guards and the eventSlots
  release dance are all gone: an out-of-order follower just creates its
  record early, and one `events[slot]` check covers every dedup case.
- Pending activity/nexus metadata is applied at build time rather than
  written into the group, so a terminal event and a stale pending entry
  resolve the same way in either arrival order. clearResolvedPendingState
  and enrichGroup's clone dance go away.
- Six manual cache-invalidation call sites collapse to one revision
  counter; getEventArray and getWorkflowTaskFailedEvent become single
  passes with no sort, dedup, or dual-store scan.
- cloneEventGroup is no longer needed.

Module state drops from ~20 variables to 6. Measured on a synthetic
200k-event / 80k-group history: ingest 1592ms -> 703ms, getEventArray
10.4ms -> 1.7ms, per-page group rebuild 1.2ms -> 3.5ms (work moves from
ingest to read), net ~23% less total main-thread work.
Both workflow layouts hand-assembled the same subscribe / throttle /
re-read effect to observe the buffer — 20 duplicated lines each — and the
producer had to remember to announce its own writes via three separate
bufferVersion.update() calls. A missed re-read or a missed announcement
silently leaves the view showing stale groups.

The buffer now reports its own writes through onChange, and a new
grouped-event-buffer.svelte.ts wraps that in the reactive read surface
consumers actually want:

  const groups = $derived(eventBuffer.groupsWithoutWorkflowTasks);
  const events = $derived(eventBuffer.events);

Updates coalesce to one per animation frame, as the layouts' own rAF did;
reset propagates immediately so navigating between runs cannot leave the
previous run's groups on screen for a frame.

enrichGroups moves to the writer. Both layouts were calling it on every
buffer change once the fetch completed, racing the run layout's own call;
workflow-run-layout already owns $workflowRun, so it now pushes pending
metadata when the run refreshes and readers never touch it.

The buffer stays a plain, Svelte-free data structure, unit-testable
without a component runtime. bufferVersion survives as a store mirror for
the pre-runes derived stores in $lib/stores/events. live-poll's
onNewEvents hook is now optional — the buffer notifies on its own.
@tegan-temporal
tegan-temporal requested a review from a team as a code owner August 5, 2026 00:06
@vercel

vercel Bot commented Aug 5, 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 14, 2026 6:33pm

Request Review

Three fixes from review of the buffer refactor.

Centralizing enrichGroups in the writer dropped its dependency on the
buffer: both layouts previously re-ran it on every bufferVersion bump. A
run refresh can list a pendingActivity whose ActivityTaskScheduled the
live poll ingests moments later, and enrichGroups skips records whose head
event has not arrived, so that group rendered non-pending until the next
refresh — up to 10s. The effect now tracks eventBuffer.version too; the
function is idempotent and no-ops when nothing changed, so it settles.

Coalescing moves from requestAnimationFrame to a microtask. rAF never
fires in a hidden tab, and unlike before the refactor the bufferVersion
store bump was behind that gate too, so a run opened in a background tab
would leave $fullEventHistory and the SDK badge empty until focused.
Fetch pages and poll batches are ingested in synchronous loops, so a
microtask collapses each batch into one update just as rAF did.

The live-completion spec held its response for a fixed 1000ms before
asserting the group had not yet completed. The live poll starts before the
initial fetch, so that raced hydration under load. It now holds until the
test has observed the in-progress row and releases explicitly — also 0.8s
faster per run. Verified it still fails when group updates mutate a
published group in place.

Also drops the unfiltered `groups` accessor, which had no consumer, and
corrects the heap smoke test's title to match its threshold.
$fullEventHistory = ... compiles to store_set with no store_get, and this
component only ever writes the store, so there was no dependency to
untrack. The sibling effects keep theirs — those call helpers that read
several stores.
The timeline renders a fixed pool of ~54 rows, but every consumer took a
fully materialized EventGroup[], so opening a view built every group in
the history — 561ms for 80k groups.

Nothing upstream of a rendered row needs a group. The type filter reads
`category`; sort-timeline-groups reads `isPending` and `initialEvent.id`;
get-failed-or-pending reads `classification` and `initialEvent`;
build-time-segments reads the head and last event times. All of those are
derivable from the buffer's GroupRecord, so GroupRecord now exposes them
as getters and `getGroupSummaries()` returns the records themselves —
ordered, cached, and allocation-free beyond the array. Views materialize
per rendered row via `materializeGroup`.

Cold read at 200k events / 80k groups: 8.3ms for summaries plus 0.4ms for
54 rows, against 561ms to materialize everything.

Supporting changes:
- `isGroupHeadEvent` becomes the single source of truth for the group
  dispatch, so the buffer can ask whether a record forms a group without
  building one. createEventGroup's per-type branches only ever differed in
  their type parameter, so they collapse into it.
- The four pure utilities take a structural type instead of EventGroup, so
  they serve summaries and groups alike and the compact view is unaffected.
- EventGroup gains an `eventCount` getter, which makes it satisfy
  GroupSummary — graph-widget builds its own groups via groupEvents and
  passes them to the same component. `materializeGroup` is idempotent so
  that path needs no branching.
- The row pool's reuse check now compares the summary's version as well as
  its identity. A summary's identity is stable for the whole run, so
  without that a group which gained an event would keep its slot object
  and the row would never re-read it — caught by the live-completion
  integration test.

Group summaries and materialized groups must agree field for field, or a
view filters on one and renders the other; tests assert that across every
group type, complete and partially loaded, and both mutations I tried are
caught.
The compact history view paginates 100 rows but took a fully materialized
EventGroup[], so opening it built every group in the history.

It now reads group summaries. Grouping, the category filter, pending-order
and the status filter all run on summaries, and the table materializes
each row as it renders — the rows snippet already receives only the
paginated page.

Supporting changes:
- GroupSummary carries pendingActivity/pendingNexusOperation so the
  pending-row lookups match without materializing, and `isGroupSummary`
  lets a view tell a summary from a group or a plain event.
- The compact view filters by status with getFailedOrPendingGroups rather
  than getFailedOrPendingEvents; the latter routes through isEventGroup,
  whose eventList check a summary cannot satisfy. The predicates are
  otherwise identical.
- workflow-history-layout reads the materialized array only for the feed
  view, so the compact view never touches it.

The feed view keeps full groups. Its gutter graph does cross-group layout
that needs eventList, level, and reverse event->group lookups
(history-graph, positioning), which is a separate piece of work.

Adds an integration test for the compact view, which previously only
asserted its tab button was visible. It renders rows, checks the
group-derived cells, and switches back to feed; verified to fail when the
rows are handed summaries without materializing.
…ey hold

TimelineGraph's `groups` prop, and the locals feeding it, hold
GroupSummary rather than EventGroup. Renames them so the type is legible
at the call site: the prop becomes `groupSummaries`, `filteredGroups`
becomes `filteredSummaries`, a row-pool slot holds a `summary`, and
Timeline's `getEventGroups`/`eventGroups` become `getGroupSummaries`/
`groupSummaries`.

EventSummaryTable's `groups` prop keeps its name — it genuinely takes
either, summaries from the compact view and full groups from the feed
view — as does history-graph, which only ever takes groups.
"Summary" reads as a condensed rendering of something — prose or a digest
— but this type holds no contents at all, just the attributes a view needs
to decide whether and where to draw a group. It is also not derived from
an EventGroup: both come from the same record, and this is the one that
lives for the whole run while the EventGroup is rebuilt on demand.

LazyGroup names the deferral instead, and pairs with the verb that already
exists: materializeGroup(lazy) forces it into an EventGroup. It also
avoids implying the type is an EventGroup with missing fields, which would
invite a cast rather than a call.

EventGroup keeps its name — 142 references across 33 files of published
API, and it is the domain's noun.
…sted

Agreement between a LazyGroup and its materialized EventGroup was enforced
only by tests comparing two independent implementations of the same rules.

Three changes, so the tests police less:

- isPending and category had their rules written twice, once over
  eventList and once over the record's slots. Both now call one shared
  function, so those fields cannot diverge. The rules themselves gain
  direct tests, since the agreement check can no longer catch a wrong rule
  — both sides would move together.
- The agreement test now derives its field list from
  SHARED_WITH_EVENT_GROUP, whose `satisfies Record<keyof LazyGroup,
  boolean>` makes TypeScript reject a new LazyGroup field until it is
  classified. That table lives in the module rather than the test because
  src/lib/services/**/*.test.ts is excluded from `pnpm check`, which made
  the first version of this guard inert.
- Agreement is checked at every prefix of each group's events rather than
  only complete and head-only, covering the partially loaded states the
  fetch actually produces.
Cleanup pass over the branch, no behavior change.

Reuse:
- One isFailedGroup rule, wrapped by isFailedEventGroup for the mixed-array
  filter, instead of the same three clauses written twice in one file.
- materializeGroup and isLazyGroup use isEventGroup, the existing type
  guard for exactly that distinction; the `as unknown as` cast goes away.
- getWorkflowTaskFailedEvent uses isFailedTaskEvent / isTimedOutTaskEvent
  rather than re-spelling their eventType comparisons.
- isGroupHeadEvent covers WorkflowTask heads, so the buffer no longer bolts
  that case on at its one call site.

Efficiency — id, category and head-eligibility are pure functions of a head
event that never changes, but were getters on the hottest paths there are:
the category filter reads `category` twice per group per pass and the row
pool reads `id` for every group, both re-running on every batch. They are
now assigned once, when the head event lands.

- orderGroupsByPending and sortGroupsDuringLoading are stable partitions
  rather than toSorted. The key is a boolean over an already-ordered array,
  so this reads isPending once per group instead of ~n log n times.
- getWorkflowTaskFailedEvent gets a revision cache like the other reads; it
  was rescanning every event on every version bump.
- enrichGroups returns immediately when nothing is pending and nothing was
  previously enriched, instead of walking every record. It is re-driven on
  each batch so late-arriving heads pick up metadata, which made that walk
  the common case.

Simplification:
- createGroupFor's generic and the StartingEvents map were vestigial once
  the dispatch collapsed; dropping them removes nine type imports.
- SHARED_WITH_EVENT_GROUP moves to test-helpers, which `pnpm check` still
  covers, so a test fixture is no longer exported from the module.
Audit of every comment the branch touches.

Stale — mostly comments my own later commits invalidated:
- GroupRecord's doc still said "every getter is a lookup into events" and
  "each getter must agree" after id and category became stored fields.
- Six comments still said "summary"/"summaries" after the LazyGroup rename.
- The live-completion spec's header still described copy-on-write handing
  the row a fresh reference; groups are now rebuilt on read and memoized
  per content version.
- The boolean-return regression note cited bufferVersion.update(), which
  the reactive facade removed.
- setFailedEvent's doc told callers to wire it from onFirstDescPage. No
  caller does, and none has since before this branch — it now says so.
- The test file's section numbering jumped 13 to 17 after the prune.

Trimmed, where the comment was longer than the code it explained: the
facade's header, both partition helpers, the three structural-type notes,
the stored-field rationale, and the enrich effect.

Kept: the module header, the version field, the row pool's version
comparison, and the microtask-not-rAF rationale — each records a decision
whose reversal caused a bug.
Six of the casts the branch added were standing in for facts the compiler
could have known.

- materializeGroup discriminated with `isEventGroup` then cast to
  GroupRecord. `lazy instanceof GroupRecord` narrows for real, so the
  cast goes; isLazyGroup becomes the same one-line check.
- initialEvent and lastEvent were getters casting away the nullability of
  the events array. Both are now assigned as members arrive — non-null
  from the moment the head lands, which is also when the record starts
  being handed out. That also drops a lookup and a scan per read.
- EventSummaryTable's `groups` prop was `EventGroups | LazyGroup[]`,
  forcing `groups as EventGroups` at both use sites. The compact view
  never reads it — its items are groups themselves — so the layout passes
  none and the prop narrows back to EventGroups. The empty-Map branch and
  the materializing findGroup wrapper go with it.

Remaining casts are load-bearing: `event as CommonHistoryEvent` bridges
two event models and predates this branch, `materializeEventGroup(...) as
EventGroup` drops a null that only unexposed records can produce, and the
items union needs the component split to disappear.
The helper wrapped one function call and branched on `compact`, but its two
callers weren't symmetric: paginatedHistory only runs under showGraph,
which is false when compact, so it was routed through a compact check that
can never be true — and had to widen its parameter to the union to get
there.

The branch now appears once, in a $derived, which also computes the filter
once per change instead of once per call — it was running twice per render.
paginatedHistory slices that and stops taking a parameter, so it no longer
shadows the `items` prop.
The doc stated the mechanism without its premise, so the structure reads as
arbitrary. Records that Temporal event IDs are a gapless sequence from 1,
that the same arithmetic is what resolves a follower to its head in one
array read, and that sparse IDs would cost memory rather than correctness.
"Records, Not Parking" only parsed if you already knew what parking was —
and this branch deleted it, so most readers never will. The heading now
states the property: arrival order doesn't matter.

The historical subsection explains the old design forwards, from the
problem it solved, rather than assuming the reader recognises the
machinery by name.
latestEventRef = raw;
}
grow(slot);
if (events[slot]) return false;

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.

Is there a potential issue r.e. total billable events if we return before calling toWorkflowEvent here?

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.

if an event is in events[slot] it means it was ingested already which accounts for the total billable events.

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.

Got it. Might be worth adding in test case for this so we know to keep it before toWorkflowEvent.

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.

added

Comment thread src/lib/components/event/event-summary-table.svelte Outdated
Comment thread src/lib/services/grouped-event-buffer.test.ts Outdated
Comment thread src/lib/utilities/sort-timeline-groups.ts
Comment thread src/lib/services/grouped-event-buffer.ts
Two bugs from review, both the same shape: something cached against a lazy
group's identity, which is stable as the group gains events.

A record kept the run's pending entry after the completion landed, because
only a head arrival or a setPendingMetadata call refreshed it. The lazy
isPending then said true while the materialized group said false — and
sorting and filtering read the lazy one, so a finished activity was hoisted
to the top as pending. A closed run stops refreshing, so nothing corrected
it. The record now tracks whether a terminal event has arrived and discounts
the run's entry itself, which also makes it the single source of truth:
materialize copies rather than re-deciding.

The compact table keyed its rows on group id alone, so a group that gained
an event reused the keyed block, the {@const} never re-ran, and the row kept
rendering its stale materialized group until the view remounted. The key now
includes the version.

Both are covered: unit tests for the lazy/materialized agreement across the
completion, and an integration test asserting a compact row picks up a live
completion. Both verified to fail without their fix.
The "the run can outlive the completion" rationale was written three times —
on the terminal flags, at the gate, and on the materialize-side copy. It
now lives at the gate, which is the only writer of the record's pending
fields, and the other two say what they do rather than why.

Also shortens the key comment, the regression note on the agreement test,
and the compact spec header.
Takes #3807's marker dedup fix into the rewritten buffer: the shared
processedWorkflowTaskIds set is now passed for every event rather than only
the ascending cursor, and toWorkflowEvent no longer takes a direction.

Conflicts were the rewrite against main's version of the same file, resolved
in favour of the rewrite with #3807's semantics reapplied by hand:

- grouped-event-buffer.ts — ours, plus the unconditional dedup set.
  ingestHistoryEvent's isAscending parameter is now unused; removing it
  touches ~180 call sites, so it is left for a follow-up commit.
- grouped-event-buffer.test.ts — ours, plus the four marker tests ported
  from processEvent to ingestHistoryEvent. Verified they still fail 2/4
  against the old gate.
- synthetic-events.ts — auto-merge produced two makeLocalActivityMarker
  declarations; folded into one whose second argument defaults to
  eventId - 1, preserving both call styles.
…oryEvent

The workflow-task dedup set was the flag's last consumer; #3807 made that set
unconditional, and the rewritten buffer has no track assignment to distinguish
cursors for. Every event is now ingested the same way regardless of which
producer delivered it.

190 call sites, all but two in tests. fetchBidirectional still reports the
direction on onRawPage — the layout just no longer forwards it.

The marker tests are reworded around arrival order rather than cursor, since
the distinction no longer exists at this boundary.
if (isLazyGroup(event)) {
// Version, because a lazy group keeps its identity as it gains events —
// key on id alone and the row below renders a stale materialized group.
return `group-${event.id}-${event.version}`;

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.

NIT: If the version changes (e.g. the group gains an event) does this mean it's going to remount and potentially lose the "expanded" state for the row?

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.

yes! I reverted that change and moved to use a materializeRowReactive function instead of changing the key.

latestEventRef = raw;
}
grow(slot);
if (events[slot]) return false;

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.

Got it. Might be worth adding in test case for this so we know to keep it before toWorkflowEvent.

Both cursors can deliver the same event, and conversion is what charges
billable actions and registers a marker's workflow task. The write-once
guard therefore has to reject a redelivery before toWorkflowEvent runs.

Spies on toEvent rather than asserting the total: the total sums the stored
events, one per slot, so a converted-then-discarded duplicate cannot change
it. Verified by moving the conversion above the guard — the totals stay
correct and only the call counts catch it.
Keying the row on the group's version fixed stale content by forcing a
remount, but EventSummaryRow holds `expanded` in component-local state — so
on a running workflow, expanding an activity and then receiving any event
for that group snapped the row shut.

Key on the group id again and make the row's materialization itself depend
on the buffer's version, so rows update in place instead of remounting.
Group records are plain objects, so nothing about them is reactive; reading
eventBuffer.version inside the {@const}'s own computation is what
subscribes it. A sibling {@const} reading the version does not work —
each compiles to its own derived with its own dependencies.

Both properties are now pinned by tests that fail independently: dropping
the version read leaves the row stale, and putting the version back in the
key drops the expansion.
@tegan-temporal
tegan-temporal merged commit af39e48 into main Aug 17, 2026
21 checks passed
@tegan-temporal
tegan-temporal deleted the refactor-buffer-prune-dead-api branch August 17, 2026 16:33
laurakwhit added a commit that referenced this pull request Sep 9, 2026
Auto-generated version bump from 2.53.3 to 2.54.0

Bump type: minor

Changes included:
- [`7a5684a5`](7a5684a) Check if api has been upgraded for minimum minor bump (#3823)
- [`af39e483`](af39e48) refactor(events): derive event groups on read in the grouped-event-buffer (#3790)
- [`3ce42ebb`](3ce42eb) Add the developer Workflow Catalog (#3785)
- [`a1234e36`](a1234e3) chore: split Catalog into a focused Temporal UI plugin (#3835)
- [`060eacf7`](060eacf) Get total slots based on available plus used (#3833)
- [`283d3cfc`](283d3cf) fix(timezone): use regular font weight for the timezone switcher (#3834)
- [`88d61845`](88d6184) Manual on-call dependency review with constrained Claude remediation (#3816)
- [`f87d239e`](f87d239) ci: validate the weekly review candidate the way the PR checks do (#3841)
- [`dd80eca0`](dd80eca) feat(ci): apply the dependency remediation without a model (#3845)
- [`403e5b97`](403e5b9) refactor(icons)!: replace holocene's icon registry with Io icon components (#3822)
- [`d6823cc4`](d6823cc) fix(ci): correct the provenance claim in the remediation PR body (#3847)
- [`f1b05e99`](f1b05e9) feat(ci): post the weekly review only when it needs a person (#3848)
- [`b993c04b`](b993c04) ci: run the on-call review test only for an on-call change (#3850)
- [`4307d20f`](4307d20) fix(copyable): pin the copy icon to 16px (#3855)
- [`62e3014f`](62e3014) Update SAA heartbeat info (#3856)
- [`3a797592`](3a79759)  Headers spacing alignment and Small Standalone Activity UI fixes  (#3854)
- [`41687b1f`](41687b1) Add total Worker count (#3641)
- [`68a3bc2f`](68a3bc2) fix(catalog): keep isolated test fixtures out of the repo root (#3859)
- [`9a56d3d9`](9a56d3d) fix: don't render an empty selected-count badge in combobox (#3853)
- [`4b97f35b`](4b97f35) Make only keyboard focus pin Tooltip open (#3861)
- [`2fd63c0e`](2fd63c0) Add Rust SDK to worker heartbeats SDK warning (#3863)
- [`4b43f7f9`](4b43f7f) Move Saved Views above Filtering (#3858)
- [`cdfd92c9`](cdfd92c) Add option to maximize paginated tables (#3864)
- [`ea4d3f14`](ea4d3f1) Fix ConfigurableTableHeadersDrawer for SAA nexus operations (#3867)
- [`10f50c8b`](10f50c8) Fix duplicate schedule action keys (#3868)
- [`9feeb756`](9feeb75) feat(DT-3944): SignalWithStart system Nexus obfuscation — decode infrastructure + UI (#3356)
- [`5af59f03`](5af59f0) feat(workers): let users set the Cloud Run scale-down stabilization window (#3860)
- [`bb7d6fbd`](bb7d6fb) Support secondary text on Combobox options (#3866)
- [`e0925297`](e092529) Fix paginated table height and mobile scrolling (#3871)
- [`6dbd9b96`](6dbd9b9) fix: categorize local activity markers as local-activity (#3872)
- [`353fbbc8`](353fbbc)  Wrap long filter chips instead of overflowing the filter bar (#3877)
- [`f4c71240`](f4c7124) Fix workflow list timestamp formatting (#3880)
- [`fa9c51e9`](fa9c51e) Open saved views in a new tab on modifier-click (#3881)
- [`376af414`](376af41) feat(workers): warn about Cloud Run deployment latency when creating a version (#3879)
- [`1bde3187`](1bde318) Fix extra horizontal scroll from filter bar dropdowns (#3885)
- [`dd1dcd9b`](dd1dcd9) Add Next Retry Delay under Retry State (#3886)
- [`66c57a40`](66c57a4)  Make system saved views additive on custom saved views (#3876)
- [`ffa1f2fb`](ffa1f2f) Audit fields for defaults and order (#3887)
- [`9ab9c40b`](9ab9c40) fix(server): update labstack/echo/v4 to 4.15.4 (FE-493) (#3874)
- [`e4449991`](e444999) Make Build ID filterable and show worker status counts (#3889)
- [`0a5e01f5`](0a5e01f) SAA fields audit part 2 (#3890)
- [`0d604b5a`](0d604b5) Fix payload input focus loss (FE-643) (#3891)
- [`06712381`](0671238) Keep portal menus anchored when the layout shifts (#3893)

Co-authored-by: laurakwhit <15069288+laurakwhit@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — c29be2ee Deployed Aug 14, 2026 by vercel[bot]
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