refactor(webviews): migrate clock consumers to page clock

Merged
TE
6d ago
+48-81

Summary

Request: “B (clock consumers) and C (changes-status) as their own PRs from current base.”

Migrates the existing WorkingFor, SubagentGroup, and relative-time clock consumers onto PR A's shared visible page clock. Hidden pages now release these consumers' repeating work through the same page-owned lifecycle; showing a page starts a fresh visible deadline.

Prior art read: #22485 (SubagentGroup session clock offset), #23995 (shared page animation infrastructure), #24017/#25201 (open SubagentGroup settings work; this PR changes no pane behavior). PR A #28254 is merged. Native #24806 remains separate.

No visual change: timer scheduling only; rendering is byte-identical (same text, same cadence while visible).

Validation

  • Final-head cmux-ci test at bffe8fe288db556db9410da390e0764cd38f074b: 36 tests passed, 0 failed, followed by webviews typecheck with 0 errors in 3,319 files, in one run 41138682314. Command: cd webviews && bun test test/subagent-tray.test.tsx test/page-clock-consumers.test.ts && bun run typecheck. The tray regression records the shared 1000 ms timeout and asserts one visible, none hidden, one on show, and none after unmount.

  • Focused cmux-ci test passed all 4 tests in page-clock.test.ts, page-clock-consumers.test.ts, and subagent-peer-clock.test.ts, plus lint (0 warnings/errors), at 17251344a917f038adfa7d0560b20b6f2a979165: run 41134686458.

  • The consumer test asserts these modules use the shared page clock and own no private repeating timer or visibility listener.

  • Every shared timer stops when the page hides or its last subscriber unsubscribes; cleanup removes its timeout and visibility listener.

Performance

metricbeforeafterdeltaharness
Private repeating-timer registration sites in migrated consumers30-3Same Bun source harness at base/head; cmux-ci run 41132299009
Hidden timer allocations (source-derived lifecycle, not runtime benchmark)20-2Same base/head source inspection with the active subscriptions described below
Visible elapsed-label cadence1,000 ms1,000 msunchangedSame source comparison: WorkingFor/SubagentGroup at base versus subscribePageSecond at head
Visible relative-time cadenceNext minute boundary + 5 msNext minute boundary + 5 msunchangedSame source comparison: clock.ts at base versus aligned subscribePageMinute at head

The metric counts the three consumer-local setTimeout/setInterval registration sites present at the base and none after migration. It does not claim CPU or wakeup savings beyond this timer ownership change. The shared page clock's one visible timer per cadence and its stop condition are covered by PR A's browser harness.

Hidden behavior from source inspection: with one active WorkingFor, one minute-clock subscriber and one live tray, base retains two timers while hidden (WorkingFor and the minute clock; the tray already pauses), while head retains zero. This is a lifecycle analysis, not a before/after runtime measurement. The combined tray/page-clock-consumers run linked above validates the timeout lifecycle regression.

Cleanup

  • useMinuteClock, useTicking, and useNow unsubscribe on component cleanup.
  • The shared second/minute page clocks clear their timeout, remove visibilitychange, and unregister when their final subscriber leaves.
  • Hidden pages own no repeating timer; showing a page schedules a fresh deadline.

Visual evidence

I viewed both after images and compared them with the matching before images. These are 2× crops of synthetic gallery fixtures in light and dark, captured at production-equivalent head 17251344a917f038adfa7d0560b20b6f2a979165 (the final commit changes only the tray test) in gallery run 41134637989. The timer migration changes no visible layout or styling; running-spinner phase can differ between captures.

ThemeBeforeAfter
LightBefore lightAfter light
DarkBefore darkAfter dark

CI warning: SigningHooksInStepWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check fails with the same missing upstream hook hash on base 901c7698. It is a release-notary guard and does not hide web test or compile signal.

Agent session: agent session


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Migrates WorkingFor, SubagentGroup, and the relative-time clock consumers onto the shared page clock so hidden pages no longer run private repeating timers.

  • Consumers now use subscribePageMinute/usePageNow instead of their own setInterval ticks and visibilitychange listeners.
  • Hidden pages stop all timer work; showing a page schedules a fresh visible deadline, and unmounting unsubscribes from the shared clock.
  • Adds page-clock-consumers.test.ts asserting the migrated modules use the shared clock and declare no private repeating timer or visibility listener.