Resume the action stream across reconnects - #334
Merged
Conversation
ndisidore
marked this pull request as ready for review
August 25, 2026 19:50
Preview:
|
ndisidore
force-pushed
the
feat/action-reconnect-resume
branch
from
August 25, 2026 20:36
1002a93 to
9b4ad7a
Compare
A settled shared action store now parks its pending set and change-time watermark (max appliedAt ?? createdAt received) by workspace key when it closes. The next store linked to the same key seeds from that carryover and subscribes with startAfter, so the server replays only the gap as upserts instead of the store re-paging the whole pending set — the subscribe call resolving is the settled signal. Unsettled or errored sessions never seed a resume, and unlinked stubs keep cold-open behavior. useWorkspaceOpen links every minted Overseer stub to its workspace id. useActionHistory keeps its loaded window and cursor across a resumed swap (dropping in-flight old-stub pages), and ChatInterface's gap-refetch effect is now only the cold-open safety net, resolving its startAfter TODO.
Drop the pending-set carryover: a settled store now parks just its last change time by workspace key, and the next linked store opens exactly as a cold one — subscribe, then page pending — passing the watermark as startAfter. The gap still replays as upserts through the subscription (which is what useActionHistory and ChatInterface rely on), the fresh pages re-snapshot the pending set, and the page loop stays the single settled signal. Buys back the seeding/skip-page dual path for the cost of re-paging a set PR #298 already made cheap.
Review fixes for the reconnect-resume stack: A store now parks its watermark only after a cleanly settled session: pages drained AND the subscribe call resolved (the server delivers the gap replay before resolving, so 'ready' alone can predate undelivered replay records) AND no entry listener threw on a delivery. ChatInterface carries cards a refetch run failed to repair (error, or cancelled mid-loop) to the next reconnect and retries them even when the resume replay covers everything else — restoring the repair channel the resume skip removed. Also: actionChangeTime() in workshop-shared now owns the appliedAt ?? createdAt formula shared by the server's byLastChanged index key, the client watermark, and Activity's display; a status-only useActionStatus() spares ChatInterface/useActionHistory a re-render per pending-set change; useActionHistory dedupes its session resets; the ChatInterface action test uses the shared harness root (fixing a leaked rAF queue) — plus regression tests for the three behavior changes.
ndisidore
force-pushed
the
feat/action-reconnect-resume
branch
from
August 25, 2026 21:25
d542870 to
cbda68a
Compare
The consumer-side recovery shell (useActionStatus, resumeFallbackRequired, unrepairedCardsRef, entryListenerFailed) defended a resumed subscription failing while the stub stays healthy — reachable only through a server-side bug, since transport failures swap the stub. The store's invariant already guarantees eventual healing without it: a failed session never parks a watermark, so the next stub swap replays its entire gap from the last good one. Until then the store shows status 'error', so nothing degrades silently. entryListenerFailed additionally protected nothing real: the refetch path applies updates through the same applyActionLogUpdateToCachedMessages the listener uses, so a record that throws in one path throws in both. Kept the subscription-resolved watermark gate — a page-only watermark is poison (a pending record's createdAt can exceed a missed resolution's appliedAt, hiding it from every future replay) — and documented on actionLogResumed why consumers may trust it without a failure path.
maxwellpeterson
self-requested a review
August 25, 2026 21:51
maxwellpeterson
approved these changes
Aug 25, 2026
Maximo-Guk
approved these changes
Aug 25, 2026
darjss
pushed a commit
to darjss/cloudflare-os-erxes
that referenced
this pull request
Aug 26, 2026
* feat(frontend): resume action subscriptions with startAfter on reconnect A settled shared action store now parks its pending set and change-time watermark (max appliedAt ?? createdAt received) by workspace key when it closes. The next store linked to the same key seeds from that carryover and subscribes with startAfter, so the server replays only the gap as upserts instead of the store re-paging the whole pending set — the subscribe call resolving is the settled signal. Unsettled or errored sessions never seed a resume, and unlinked stubs keep cold-open behavior. useWorkspaceOpen links every minted Overseer stub to its workspace id. useActionHistory keeps its loaded window and cursor across a resumed swap (dropping in-flight old-stub pages), and ChatInterface's gap-refetch effect is now only the cold-open safety net, resolving its startAfter TODO. * refactor(frontend): carry only a resume watermark across reconnects Drop the pending-set carryover: a settled store now parks just its last change time by workspace key, and the next linked store opens exactly as a cold one — subscribe, then page pending — passing the watermark as startAfter. The gap still replays as upserts through the subscription (which is what useActionHistory and ChatInterface rely on), the fresh pages re-snapshot the pending set, and the page loop stays the single settled signal. Buys back the seeding/skip-page dual path for the cost of re-paging a set PR cloudflare#298 already made cheap. * fix(frontend): recover from failed action log resume * fix(frontend): harden the resume watermark and repair channel Review fixes for the reconnect-resume stack: A store now parks its watermark only after a cleanly settled session: pages drained AND the subscribe call resolved (the server delivers the gap replay before resolving, so 'ready' alone can predate undelivered replay records) AND no entry listener threw on a delivery. ChatInterface carries cards a refetch run failed to repair (error, or cancelled mid-loop) to the next reconnect and retries them even when the resume replay covers everything else — restoring the repair channel the resume skip removed. Also: actionChangeTime() in workshop-shared now owns the appliedAt ?? createdAt formula shared by the server's byLastChanged index key, the client watermark, and Activity's display; a status-only useActionStatus() spares ChatInterface/useActionHistory a re-render per pending-set change; useActionHistory dedupes its session resets; the ChatInterface action test uses the shared harness root (fixing a leaked rAF queue) — plus regression tests for the three behavior changes. * refactor(frontend): drop the resume failure fallbacks The consumer-side recovery shell (useActionStatus, resumeFallbackRequired, unrepairedCardsRef, entryListenerFailed) defended a resumed subscription failing while the stub stays healthy — reachable only through a server-side bug, since transport failures swap the stub. The store's invariant already guarantees eventual healing without it: a failed session never parks a watermark, so the next stub swap replays its entire gap from the last good one. Until then the store shows status 'error', so nothing degrades silently. entryListenerFailed additionally protected nothing real: the refetch path applies updates through the same applyActionLogUpdateToCachedMessages the listener uses, so a record that throws in one path throws in both. Kept the subscription-resolved watermark gate — a page-only watermark is poison (a pending record's createdAt can exceed a missed resolution's appliedAt, hiding it from every future replay) — and documented on actionLogResumed why consumers may trust it without a failure path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#298 covered the card half with a reconnect refetch sweep and left a TODO for the mechanism it had just added server-side:
subscribeToActionstakes astartAfterand replays every record changed since, off the last-change index.This branch wires the client to it:
useWorkspaceOpenlinks every stub it mints to its workspace id. When a linked store closes settled, it parks its last change time (maxappliedAt ?? createdAtreceived, the server's index key) under that id.startAfter, so the gap replays through the subscription as upserts and live entries patch per-record consumers instead of each refetching. The watermark is the only state carried across stubs; the pending set re-pages fresh, which is small and indexed since Bound every action-log read path #298.Hook tests cover the watermark handoff (settled, unsettled, errored, release-and-reacquire) and the history window surviving the swap. No server changes.