Skip to content

feat(agent,dom): recorder follow-ups — auto screenshots, nav-abort, durable pending buffer (#148) - #194

Merged
sebyx07 merged 1 commit into
mainfrom
feat/148-recorder-followups
Aug 15, 2026
Merged

sebyx07 merged 1 commit into
mainfrom
feat/148-recorder-followups

Conversation

@sebyx07

@sebyx07 sebyx07 commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Implements all three residuals from #9, with the behavioral decisions the issue left open taken as follows.

1. Per-edit before/after screenshot auto-capture — on the recordEdit drain, not per mutation

Decision: the cheap cadence. One before/after pair per recorded Edit (2 capture-lock rides, ~200ms settle each) instead of 2 rides per mutation — per-mutation fidelity isn't worth 400ms added to every setStyle.

Mechanics (src/agent/edit-shots.ts, wired in background.ts + src/agent/tools/session.ts):

  • Recorder events reach the SW only after a mutation ran, so a truthful "before" is captured where one still exists: the SW's content dispatch arms a per-tab BEFORE viewport shot ahead of the first unrecorded mutation (slot free AND pending buffer empty — the shot provably precedes everything the next drain folds). recordEdit's drain consumes the slot and captures the AFTER. Interleaved multi-element work degrades honestly to an after-only pair rather than a fabricated before.
  • Both rides go through the locking screenshot dispatch, exactly like the nav drivers — no new lock semantics, deadlock invariant untouched.
  • Best-effort: a failed/oversized capture never fails the edit (pinned by unit test). Size-guarded: per-image ceiling (700K chars) + whole-changeset budget (2.5M chars) — the record mirrors to chrome.storage.session twice, so the budget bounds the worst case at ~5MB of the ~10MB quota; an oversized capture is skipped, never stored.
  • A model-supplied screenshots pair wins; auto-capture stands down. The turn-end auto-finalize does not capture (nothing new to illustrate at that point; the slot dies with the buffer).

Schema (Edit.screenshots) and Diff-tab rendering pre-existed — this only produces the data.

2. Mid-turn navigation — ABORT the turn

Decision: abort, not rebase ("abort is safer; rebase preserves work" — work the new document cannot carry anyway).

  • webNavigation.onCommitted (cross-document commits only — hash/pushState never fire it) now aborts a turn RUNNING on the committed tab with the same clears session-stop uses, a turn-stamped error to the panel ("The page navigated away mid-turn, so the turn was stopped"), a debug-log entry, and a session-state push. User reload included: it's a cross-document commit and the live edits died with it.
  • The agent's own navigate/back/reload never aborts: runNav now marks its tab in agentNavTabs inside its capture-lock ride (commit lands before the load-complete wait settles, so the window covers it). Decision table is the pure src/agent/nav-abort.ts, unit-pinned.
  • The residual the issue names (agent nav mid-turn → wiped mirrors re-persisted by the in-flight store): the turn's persistChangeset now refuses to write a record whose URL no longer matches the tab's committed URL (same comparison chain as the turn-start guard: persisted stamp → in-memory stamp → turn tab.url). The turn stays alive, the wipe stands, and the next turn re-seeds clean — pinned end-to-end in changeset-record.test.ts.

3. Persist the pending-mutations buffer

The SW's recorder buffer now mirrors to chrome.storage.session (pendingMutations:<tabId>, beside changeset:<tabId>) via src/changeset/pending-persist.ts and re-seeds on SW wake, so an eviction mid-turn no longer drops recordEdit back to model-supplied values (pre-#9 behavior).

  • Cheap writes: appends are debounced per tab (300ms trailing, latest snapshot only — the buffer churns); shrinking ops (drain/remove/clear) write through, because losing one to eviction would resurrect events a recordEdit already folded (double-recorded deltas) or a revert already retracted (a phantom).
  • Zod-validated on load, cap-bounded (200/tab, unchanged), the cap-drop counter survives too. Every listener touch rides a pendingReady gate so pre-hydration events can neither be clobbered by the seed nor resurrect a cleared buffer; live events beat the mirror.

Tests

DZ_TEST_WORKERS=2 bun run verify fully clean — 196 files, 2696 tests (was 2634), lint + typecheck green. New: edit-shots (12), nav-abort (5), pending-persist (7), pending-restore integration (5, the eviction round-trip through real createSessionTools), changeset-record #148 item 2 block (2), plus onChange/seed coverage in pending-mutations and the capture contract in session-tools.

Closes #148

🤖 Generated with Claude Code


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

…ort, durable pending buffer (#148)

Per-edit screenshot auto-capture (src/agent/edit-shots.ts): the BEFORE viewport
shot arms ahead of the first unrecorded mutation (the SW dispatch is the one
point that still precedes the page change), recordEdit's drain consumes it and
captures the AFTER — one pair per Edit, two capture-lock rides, never per
mutation. Best-effort by contract (a failed capture never fails the edit) and
size-guarded (per-image ceiling + whole-changeset budget) so the record can't
blow the chrome.storage.session quota it lives in.

Mid-turn navigation aborts the turn (src/agent/nav-abort.ts + onCommitted): a
cross-document commit of the running turn's tab stops it with session-stop's
clears and a turn-stamped error — except the agent's own nav (runNav now marks
its tab for the commit window), which keeps the turn alive while the turn's
persistChangeset refuses to write a record whose URL no longer matches the
committed URL (the in-flight re-persist #148 names). Same-document navigations
never commit and never abort.

The pending-mutations buffer survives SW eviction (pending-persist.ts):
mirrored per tab beside the changeset record — appends debounced, shrinking
ops written through so a drained/reverted event can't resurrect — and
re-seeded on wake, so a resumed turn's recordEdit folds real ground truth
instead of falling back to model-supplied values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 19 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 32c554a5-b828-46d8-8022-288a0765cdaf

📥 Commits

Reviewing files that changed from the base of the PR and between 05d08ff and c6e2041.

📒 Files selected for processing (14)
  • src/agent/edit-shots.ts
  • src/agent/nav-abort.ts
  • src/agent/tools/session.ts
  • src/changeset/pending-mutations.ts
  • src/changeset/pending-persist.ts
  • src/changeset/store.ts
  • src/entrypoints/background.ts
  • test/integration/changeset-record.test.ts
  • test/integration/pending-restore.test.ts
  • test/unit/edit-shots.test.ts
  • test/unit/nav-abort.test.ts
  • test/unit/pending-mutations.test.ts
  • test/unit/pending-persist.test.ts
  • test/unit/session-tools.test.ts

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Too big: expected string to have <=250 characters at "tone_instructions"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Comment @coderabbitai help to get the list of available commands.

@sebyx07
sebyx07 merged commit af042ae into main Aug 15, 2026
7 checks passed
@sebyx07
sebyx07 deleted the feat/148-recorder-followups branch August 15, 2026 13:05
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.

recorder follow-ups: per-edit screenshot auto-capture + mid-turn navigation rebase

1 participant