feat(peek): R2 — stable refs + diff-after-action + get_element_detail#104
Conversation
Builds on R1 (get_page_view). Three pieces that make the steady-state write
loop cost near-zero perception tokens:
- Stable refs: an element keeps the same ref across get_page_view snapshots,
keyed by a MAIN-world WeakMap<Element,ref> + counter (JS globals, never DOM
attrs -> rrweb still never records them). Forward map keeps R1's __peekRefs
name, so the dispatcher is untouched; SPA soft-navs keep refs alive.
- get_element_detail(ref): on-demand, lossless, masked single-element drill-in
(role, full name, all aria-*, state, value, href, position, nearby heading,
interactive descendants with refs). Level 1+, audited, reuses level-1-read.
Never returns raw outerHTML.
- observe:true on a mutating execute_action returns a masked details.viewDelta
(added/removed/changed) in one round-trip; navigation returns a
{navigated:true} "refs expired" marker.
Privacy: in-page + SW masking; .rr-mask/data-private/data-dd-privacy/
data-peek-mask regions masked to '•••' across names/values/text/aria/children
(and via context.heading); CSP-safe MAIN-world injection (no eval). Honest
free-text caveat kept. Independent security review: no critical/bypass; the one
HIGH (annotation gap) + mediums/lows fixed.
621 extension + 303 mcp unit tests; 6/6 real-Chromium e2e. Changeset minor for
both packages. Headroom concepts (CacheAligner/CCR/no-re-send) borrowed as
architecture; library not adopted (no-network privacy).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
|
Warning Review limit reached
More reviews will be available in 26 minutes and 18 seconds. Learn how PR review limits work. To continue reviewing without waiting, enable usage-based billing in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the 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 credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. 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, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds identity-stable element refs using a MAIN-world ChangesR2 Agent API: stable refs, element detail, observe delta, masking
Sequence Diagram(s)sequenceDiagram
rect rgba(100, 149, 237, 0.5)
Note over Agent,MAIN: get_element_detail flow
end
participant Agent
participant MCPServer as MCP Server
participant BGScript as Background SW
participant ActionHandler as action-handler
participant MAIN as MAIN World
Agent->>MCPServer: get_element_detail(sessionId, ref)
MCPServer->>BGScript: execute_action {type:'element_detail', ref}
BGScript->>ActionHandler: handleActionRequest(element_detail)
ActionHandler-->>BGScript: verdict:allow (Level 1+, level-1-read)
BGScript->>MAIN: buildElementDetail(ref)
MAIN-->>BGScript: ElementDetail (masked) or ElementDetailError
BGScript-->>MCPServer: masked detail payload
MCPServer-->>Agent: tool result text
rect rgba(144, 238, 144, 0.5)
Note over Agent,MAIN: execute_action + observe:true flow
end
Agent->>MCPServer: execute_action(action, observe:true)
MCPServer->>BGScript: dispatch action
BGScript->>MAIN: run action
MAIN-->>BGScript: action result
alt navigating verb
BGScript->>MAIN: maskUrl(currentUrl)
BGScript-->>MCPServer: details.viewDelta = {navigated:true}
else diffable verb
BGScript->>MAIN: diffPageViewStandalone()
MAIN-->>BGScript: PageViewDelta
BGScript->>BGScript: maskPageViewNode(each changed node)
BGScript-->>MCPServer: details.viewDelta = {added, removed, changed}
end
MCPServer-->>Agent: tool result with viewDelta
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…cold-install CI) R2's SW masking helpers (maskPageViewNode/maskElementDetail) were top-level EXPORTED functions in the background entrypoint that use @cubenest/rrweb-core's maskTextContent. `wxt prepare` (the extension's postinstall, run BEFORE `pnpm build`) keeps an entrypoint's top-level exports but elides its defineBackground callback — so that kept rrweb-core import forced Vite to resolve rrweb-core's gitignored, unbuilt dist/ and `pnpm install --frozen-lockfile` failed on a cold CI checkout (ci/ci-windows/e2e-peek/e2e-wdio). main passed only because it uses maskTextContent solely inside the (elided) callback. Fix (match main's pattern — rrweb-core reachable only through the elided callback): - move maskPageViewNode/maskElementDetail into src/permissions/mask-view.ts - extract the pure maskUrl into src/relay/mask-url.ts (no rrweb-core); mask.ts re-exports it; background imports maskUrl from there - background's remaining maskTextContent/maskUrl uses are all inside defineBackground (tree-shaken by wxt prepare) Verified with a true cold `pnpm install --frozen-lockfile` (rrweb-core dist wiped) — now succeeds. 621 ext + 303 mcp tests, 6/6 e2e, typecheck + biome clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/peek-extension/e2e/page-view.spec.ts`:
- Around line 343-350: The find predicate on line 344 that selects pwRef using
only the condition n.value === '•••' is too generic and may match other masked
inputs in the fixture, allowing the test to pass without actually exercising the
password-specific detail path. Add an additional condition to the find predicate
that uniquely identifies the password node, such as checking a specific property
like node type, id, name attribute, or another identifying characteristic that
distinguishes the password field from other masked inputs in the fixture.
In `@packages/peek-extension/src/permissions/snapshot.ts`:
- Around line 321-333: The noise filtering check that skips unnamed non-control
elements happens after masked elements are assigned the '•••' name, which
prevents them from being filtered out since the name is no longer empty. Reorder
the logic in the masked element handling block to perform the empty-name filter
check before assigning the redaction marker: first determine the real accessible
name using accName(el), check if it should be skipped via the empty-name and
non-control condition, and only after the element passes that filter should the
name be replaced with '•••' if masked.
In `@packages/peek-mcp/src/mcp/server.ts`:
- Around line 841-847: The documentation comment for the get_element_detail
function incorrectly describes the returned data as "direct interactive
children" when the actual implementation returns interactive descendants with a
cap. Update the comment block describing get_element_detail to accurately
reflect that the function returns interactive descendants (capped) rather than
direct children, ensuring the tool contract documentation matches the actual
payload shape being returned.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 8cb3adca-386a-451a-9cb7-96adffaba2f7
📒 Files selected for processing (21)
.changeset/peek-r2-diff-detail.mdapps/peek-docs/src/pages/privacy.astrodocs/peek/PRIVACY_POLICY.mdpackages/peek-extension/e2e/fixtures/page-view.htmlpackages/peek-extension/e2e/fixtures/strict-csp.htmlpackages/peek-extension/e2e/page-view.spec.tspackages/peek-extension/entrypoints/background.tspackages/peek-extension/src/__tests__/action-handler.test.tspackages/peek-extension/src/__tests__/snapshot.test.tspackages/peek-extension/src/permissions/action-handler.tspackages/peek-extension/src/permissions/action-protocol.tspackages/peek-extension/src/permissions/mask-view.tspackages/peek-extension/src/permissions/snapshot.tspackages/peek-extension/src/relay/mask-url.tspackages/peek-extension/src/relay/mask.tspackages/peek-mcp/README.mdpackages/peek-mcp/src/mcp/action-schema.tspackages/peek-mcp/src/mcp/server.tspackages/peek-mcp/test/action-schema.test.tspackages/peek-mcp/test/server.test.tspackages/peek-mcp/test/stdio-smoke.test.ts
- snapshot.ts (MAJOR): run the empty-name noise filter on the REAL name BEFORE masking, in both buildPageView and the diffPageViewStandalone walker. The HIGH privacy fix replaced empty names with '•••' first, so unnamed non-controls in a masked region stopped being filtered → snapshot/delta bloat + maxElements pressure. + regression test (masked empty role=group is dropped, not surfaced). - server.ts: get_element_detail description now says "interactive descendants (capped)" to match the implementation (was "direct interactive children"). - e2e page-view.spec.ts: pin the password drill-in to the node named "Password" (other masked inputs, e.g. the data-private SSN, also carry value '•••', so matching on '•••' alone could drill the wrong element) + assert it's found. 622 ext + 303 mcp tests, 6/6 e2e, typecheck + biome clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Supersedes #102 (auto-closed when its stacked base branch
feat/peek-live-page-viewwas deleted on the #101 merge). Same work, now re-stacked as a single clean R2-only commit on top ofmain.What
R2 of the MCP token-optimization work, building on R1 (#101, merged). Three pieces that make the steady-state write loop cost near-zero perception tokens:
refacrossget_page_viewsnapshots, keyed by a MAIN-worldWeakMap<Element,ref>+ counter (JS globals, never DOM attrs → rrweb still never records them). Forward map keeps R1's__peekRefsname, so the dispatcher is untouched; SPA soft-navs keep refs alive.get_element_detail(ref)(new tool) — on-demand, lossless, masked single-element drill-in (role, full name, allaria-*, state, value, href, position, nearby heading, interactive descendants with refs). Level 1+, audited, reuseslevel-1-read. Never returns rawouterHTML.observe: trueon a mutatingexecute_action— returns a maskeddetails.viewDelta(added/removed/changed) in one round-trip; navigation returns a{navigated:true}"refs expired" marker.Safety / privacy
observeruns only aftergate()allows AND the action succeeds — never a read-on-denial; a ref-targeted destructive action withobservestill hits the confirm banner (noviewDeltaon deny)..rr-mask/data-private/data-dd-privacy/data-peek-maskregions masked to•••across names/values/text/aria/children (andcontext.heading). Honest free-text caveat kept. CSP-safe MAIN-world injection (noeval).Testing
621 extension + 303 mcp unit tests; 6/6 real-Chromium e2e (
e2e/page-view.spec.ts); typecheck + biome + build clean. Changeset: minor for both packages.Headroom lineage
Borrows CacheAligner → stable refs, CCR →
get_element_detail, don't-re-send → diff-after-action — as architecture, realized locally with no model/network. Library not adopted (would break the no-network privacy claim). Spec:_context/docs/specs/2026-06-20-peek-r2-diff-stable-refs-element-detail-spec.md.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
get_element_detailaction to retrieve full masked details for specific element referencesobserve: trueoption on action execution to receive post-action changes in a single round-tripDocumentation
Tests