Skip to content

fix(desktop): anchor active-turn badge to skew-corrected agent start#1068

Merged
wpfleger96 merged 2 commits into
mainfrom
duncan/active-turn-timer-anchor
Jun 16, 2026
Merged

fix(desktop): anchor active-turn badge to skew-corrected agent start#1068
wpfleger96 merged 2 commits into
mainfrom
duncan/active-turn-timer-anchor

Conversation

@wpfleger96

Copy link
Copy Markdown
Collaborator

What

The active-turn badge elapsed counter anchored to Date.now() at the moment the desktop processed the turn event. Two consequences:

  • Turns processed in the same JS tick (observer buffer replay on first subscribe, or several turns in one syncAll()) all received the same Date.now() anchor, so their timers ticked in perfect lockstep — the user-reported "two timers at exactly the same number, in sync" symptom.
  • A turn that actually started minutes earlier rendered 0s the instant the desktop first observed it.

Why this approach

Switching the anchor to the raw agent-host startedAt was off the table — it reintroduces agent-host/desktop clock skew into the elapsed display, which the original Date.now() anchor existed to prevent.

Instead the store maintains a per-agent clock offset = running minimum of Date.now() - Date.parse(event.timestamp), sampled on every fresh observer event. The badge anchor is derived at read time in getActiveTurnsForAgent as startedAt + offset(agent):

  • Distinct agent-host start times produce distinct anchors → no lockstep.
  • A long-running turn shows its true elapsed immediately.
  • Skew is corrected without trusting the raw agent clock; the running-minimum estimator converges on true skew minus minimum network delay, so the badge never inflates elapsed (it under-reports by at most the smallest observed network delay).
  • Read-time derivation means a later, tighter offset retroactively corrects every live badge — freezing the anchor at insert would cancel that correction.

The ActiveTurnSummary.observedAt field is renamed to anchorAt to match the new semantics; the three consumers (UserProfilePanelSections, UserProfilePopover, ManagedAgentRow) get the identical mechanical rename and nothing else.

Scope

5 files, +263/-73. Logic + tests in activeAgentTurnsStore.{ts,test.mjs}; pure field rename in the three consumers.

npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 2 commits June 15, 2026 23:46
The badge elapsed counter anchored to Date.now() at event-processing
time. Turns processed in one JS tick (buffer replay, batched syncAll)
shared an anchor and ticked in lockstep, and a turn that began minutes
ago rendered 0s on first observe.

Maintain a per-agent clock offset — the running minimum of
Date.now() - Date.parse(event.timestamp) — and derive each badge anchor
at read time as startedAt + offset. Read-time derivation lets a later,
tighter offset retroactively correct every live turn; the running
minimum converges on true skew minus minimum network delay and is immune
to per-event jitter, preserving the original clock-skew protection
without trusting the raw agent-host clock. Distinct agent starts now
yield distinct anchors (no lockstep); a long-running turn anchors into
the past. Renames the ActiveTurnSummary.observedAt field to anchorAt to
match the new read-time semantics, updating all three consumers.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Two review-driven follow-ups on the skew-corrected anchor fix.

The design derives each badge anchor at read time so a later, tighter
clock offset retroactively corrects every live turn — the load-bearing
reason the anchor is not frozen at insert. No committed test asserted
it: a regression that froze anchorAt at startTurn would have passed the
whole suite. Add a test that goes live under a loose offset, tightens it
via a delay-free liveness sample, and asserts the live turn's read-time
anchor shifts earlier by exactly the tightening delta.

Also scope the clockOffsetByAgent comment: the running minimum is only
conservative (never over-reports) while true skew is constant or
shrinking. Under growing skew it goes stale-too-small and elapsed can
inflate — bounded and sub-second, but the prior comment overstated the
guarantee.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96 wpfleger96 merged commit 2d26db6 into main Jun 16, 2026
22 checks passed
@wpfleger96 wpfleger96 deleted the duncan/active-turn-timer-anchor branch June 16, 2026 15:18
wpfleger96 pushed a commit that referenced this pull request Jun 16, 2026
* origin/main: (50 commits)
  chore(release): release version 0.3.24 (#1074)
  feat(desktop): refine thread-unread badge to two-token form (#1069)
  fix(buzz): prevent reconnect storms from reaped ephemeral channels (#1071)
  fix(buzz-acp): trim oversized observer frames to fit instead of dropping (#1072)
  perf(ci): speed up PR CI wall clock and local dev builds (#1028)
  chore(deps): update react monorepo (#1048)
  Polish desktop visual details (#1067)
  ci: use running postgres for pgschema desired-state planning (#1070)
  fix(desktop): anchor active-turn badge to skew-corrected agent start (#1068)
  feat(desktop): add configurable transport reconnect hook (#1059)
  Add automatic database migrations (#988)
  Add composer spoiler formatting (#1055)
  feat(desktop): in-channel and in-thread unread indicators (#1008)
  perf(timeline): gate heavy message render behind useDeferredValue (#1022)
  Add animated profile avatars (#1031)
  Polish direct message and members modals (#1054)
  Polish huddles UI (#1041)
  Fix video review comments in threads (#1056)
  Polish message reaction tray (#1002)
  Refine app loading skeletons (#1001)
  ...

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

# Conflicts:
#	desktop/src-tauri/Cargo.lock
wpfleger96 added a commit that referenced this pull request Jun 16, 2026
…fleger/persona-instantiation

* origin/wpfleger/persona-events: (50 commits)
  chore(release): release version 0.3.24 (#1074)
  feat(desktop): refine thread-unread badge to two-token form (#1069)
  fix(buzz): prevent reconnect storms from reaped ephemeral channels (#1071)
  fix(buzz-acp): trim oversized observer frames to fit instead of dropping (#1072)
  perf(ci): speed up PR CI wall clock and local dev builds (#1028)
  chore(deps): update react monorepo (#1048)
  Polish desktop visual details (#1067)
  ci: use running postgres for pgschema desired-state planning (#1070)
  fix(desktop): anchor active-turn badge to skew-corrected agent start (#1068)
  feat(desktop): add configurable transport reconnect hook (#1059)
  Add automatic database migrations (#988)
  Add composer spoiler formatting (#1055)
  feat(desktop): in-channel and in-thread unread indicators (#1008)
  perf(timeline): gate heavy message render behind useDeferredValue (#1022)
  Add animated profile avatars (#1031)
  Polish direct message and members modals (#1054)
  Polish huddles UI (#1041)
  Fix video review comments in threads (#1056)
  Polish message reaction tray (#1002)
  Refine app loading skeletons (#1001)
  ...

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
tellaho added a commit that referenced this pull request Jun 17, 2026
* origin/main: (26 commits)
  fix(desktop): restore timeline zoom via rem tokens + chat-as-base type scale (#1052)
  fix(release): format changelog as linked markdown bullets (#1075)
  chore(release): release version 0.3.24 (#1074)
  feat(desktop): refine thread-unread badge to two-token form (#1069)
  fix(buzz): prevent reconnect storms from reaped ephemeral channels (#1071)
  fix(buzz-acp): trim oversized observer frames to fit instead of dropping (#1072)
  perf(ci): speed up PR CI wall clock and local dev builds (#1028)
  chore(deps): update react monorepo (#1048)
  Polish desktop visual details (#1067)
  ci: use running postgres for pgschema desired-state planning (#1070)
  fix(desktop): anchor active-turn badge to skew-corrected agent start (#1068)
  feat(desktop): add configurable transport reconnect hook (#1059)
  Add automatic database migrations (#988)
  Add composer spoiler formatting (#1055)
  feat(desktop): in-channel and in-thread unread indicators (#1008)
  perf(timeline): gate heavy message render behind useDeferredValue (#1022)
  Add animated profile avatars (#1031)
  Polish direct message and members modals (#1054)
  Polish huddles UI (#1041)
  Fix video review comments in threads (#1056)
  ...

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>

# Conflicts:
#	desktop/src/features/messages/lib/useRichTextEditor.ts
#	desktop/src/features/messages/ui/MessageComposer.tsx
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