Skip to content

feat(amp-semantic): complete capture runtime and replay validation - #2082

Draft
jxiwang wants to merge 50 commits into
mainfrom
codex/semantic-review-integration
Draft

jxiwang wants to merge 50 commits into
mainfrom
codex/semantic-review-integration

Conversation

@jxiwang

@jxiwang jxiwang commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Complete the standalone semantic runtime and integrate interaction, lifecycle, and page-feedback capture into rrweb custom events (type: 5, amp-semantic/*). Include shared privacy-aware target/name/state helpers, sampling and caps, versioned schemas, and the local replay inspector and browser tests. Capture remains opt-in.

The stream includes user agent and visibility context, tab/document identity, visibility boundaries, lifecycle signals, approved non-character keys/shortcuts, compact resource failures, interaction states, and checkbox/radio state. Resource failures use bounded 500 ms groups. Temporary SDK/extension logs are removed; existing errors and local inspector diagnostics remain. Generated extension vendor files are excluded.

Shared click path and performance

Route semantic primary clicks through replay's existing heatmap ClickHandler and rrweb mouse-interaction hook. The same path works with heatmaps disabled. Preserve native modifiers, keyboard activation, trust, and composed-path targeting without serializing the native event. Reuse selectors only for the same element within one click; actionable ancestors retain separate IDs/selectors. Isolate failures between consumers and keep disabled replay's empty-hook configuration unchanged.

Pin the recorder, recorder types, and console plugin to the published 2.1.5 release. It includes the native-event hook extension and record.supportsNativeMouseInteraction from rrweb #142. Remove the pnpm patch and its lockfile references so downstream unbundled installations receive the same shared-click capability. Older recorders fall back to a native listener. Keep an auxiliary-click listener because rrweb does not forward auxclick.

Notice/dialog capture consumes accepted rrweb full snapshots and mutation batches through a structural host signal and mirror node resolver. Remove its separate MutationObserver, startup dialog selector query, and live-DOM discovery walks. Initial snapshots seed dialog state silently; relevant additions, text changes, attributes, removals, and moves drive the existing semantic payloads. Queue synchronous startup input until the capture runtime starts and ignore custom events to prevent feedback loops. Standalone hosts must provide the DOM signal and node resolver; there is no observer fallback.

Inspect serialized changes incrementally, resolving only relevant live elements and changed parents. Deduplicate repeated parent lookups in flat rrweb additions with a bounded set. Keep privacy-aware text/name/state helpers and clear detached dialog names while retaining recorded IDs. Track closed/hidden dialogs too so ancestor changes and moves preserve open/close detection. Do not change the wire schema.

Delivery gets the first 2 ms of each frame and alternates families; serialized processing and delivery share a cooperative 4 ms budget, capped at 1,000 work items and 50 payload candidates per frame. Retain at most 128 replay batches, 256 notice candidates/timers, 256 dialog candidates, and 256 tracked dialogs. Queued batch references retain their source data until consumed. Overflow reports a payload-free diagnostic once per limit per setup. Timers enqueue rather than construct payloads, and processing unsubscribes when both families exhaust their caps.

State/timestamps remain processing-time observations. rrweb batching and deferred enrichment may coalesce transient changes or omit nodes removed before discovery; blocked or otherwise unrecorded changes are not reconstructed. CSS-only and full-page visibility remain outside scope. Limits are cooperative, not hard per-operation deadlines.

Preflight capacity before expensive payload construction, and cache only a heading DOM candidate for 250 ms with fresh privacy/visibility/text checks. Fix autocapture's capture-phase change-listener cleanup. No cross-SDK observer broker is introduced.

amp-semantic/click-outcome is removed from the current package scope, including its recorder, schema, types, cap, fixtures, outcome mutation observer, and per-click timers. Remove the autocapture → amp-semantic dependency introduced solely to share this logic; existing analytics dead-click behavior is retained. The local playground and v0 spec reflect the removal.

amp-semantic/page is also removed from the current scope: no page recorder, title observer, immediate/2-second/5-second page scans, schema, cap, or family toggle. Interaction URL/heading fields remain. The replay adapter forwards navigation to invalidate cached interaction headings, without emitting page events. Browser tests verify post-navigation click context and stable document identity through the remaining events.

Navigation from the existing URL plugin

Add amp-semantic/navigation with only a templated url plus version/tab/document identity. Reuse accepted amplitude/url-tracking@1 rrweb events for the initial URL, subsequent history/hash or configured polling observations, and recording resume. Remove the semantic adapter's separate URL subscription. The same observations invalidate cached interaction headings even when the navigation family is disabled.

Apply semantic URL normalization after the plugin's existing UGC filtering: omit origins and query values and template ID-shaped path segments. Literal path text remains subject to configured UGC rules. Do not emit title or navigation reason or restore page scans. Preserve distinct observations that share a template. Apply sampling, family switches, teardown, and a 200-events/session cap. Update the wire schema, v0 spec, fixtures, and local inspector's event guide.

Field values and accessibility metadata

Field events now include optional native input/textarea value when the current replay input policy permits it, with no length limit or whitespace normalization. Masked values are omitted; password, revealed-password (data-rr-is-password), hidden and blocked fields remain excluded. Files, checkbox/radio submitted values, selects and contenteditable contents are outside this value addition. Selection labels and checked state retain their existing contracts. No new observers or per-keystroke events are added. Long values increase serialization, deduplication memory and payload size proportionally; the event-count cap does not bound bytes.

Fields and key targets now include shared event-time states; fields, key targets and dialogs also include name_source. Native disabled state respects fieldset inheritance and the first-legend exception. Invalid state includes native constraint invalidity for participating controls plus ARIA grammar/spelling/unknown nonempty tokens. Native validity reads do not call checkValidity() or dispatch validation events. Field validity uses the same ARIA interpretation. Privacy-suppressed names and detached dialog names report masked; naturally unnamed targets remain unnamed. The new fields are optional in the v1 wire schema for legacy recordings. No additional observers, listeners, scans or timers are introduced.

The v0 spec retains relationship IDs, accessibility exposure, live-region enrichment and richer name/custom-widget handling as follow-up work. Lifecycle/context/resource events do not receive a blanket ARIA attribute bundle.

Review scope

This is the complete integration PR against main, including the semantic runtime, capture families, replay adapter, schema, privacy handling, and local validation fixture. The final review tree matches codex/amp-semantic and includes main's rrweb 2.1.5 release (491914d69).

The earlier interactions #2079, lifecycle #2080, and page feedback #2081 drafts remain open as workstream references. They are not independently buildable/releasable and are no longer this PR's merge prerequisites.

Lockfiles and bundle budgets

Repair missing semantic workspace importer/dependency entries across the review branches, and reconcile their shared lockfile ancestry to avoid duplicate package/snapshot keys in PR merge commits. Preserve main's dependency graph; this PR's lockfile diff against main is 19 added lines. Frozen-lockfile checks pass on all four review branches, and a full frozen install passes on the final integration tree.

Retarget the complete PR to main so the bundle-size comparison uses a buildable baseline. Explicitly account for the added semantic capture runtime: replay's gzip budget changes from 150 KB to 160 KB and unified's from 235 KB to 245 KB. Measured on the final main-based tree: 154,825 bytes and 242,245 bytes respectively. The full root build passes for all 31 projects; docs, lint, dependency checks, and all four local size checks pass. This is a feature budget adjustment, not a download-size optimization; the capture functionality and compilation targets are unchanged. Analytics-browser and element-selector limits are unchanged.

GitHub Actions on 6d9e3428 confirms Check Size Limits, Build Docs, and Check Unused Dependencies pass. The Node 22/24/26 jobs passed dependency installation and compilation and reached their test steps; their overall results are not yet confirmed.

Semgrep is outside this CI repair's scope. Semantic/replay coverage gates are now satisfied; broader local test limitations are documented below.

Related: SRAI-55, SRAI-56, SRAI-64, SRAI-81, SRAI-82.

Validation and draft blockers

Node 24.22.1 / pnpm 11.28.5, complete integration tree with published rrweb 2.1.5:

  • Frozen-lockfile install, full root build (31 projects), replay dependency build (pnpm build:semantic), semantic typecheck, root docs:check, root lint (30 projects), and lint:deps pass.
  • All 211 semantic, 1,133 replay, and 65 replay-plugin tests pass with the existing 100% statements, branches, functions, and lines thresholds enforced. No coverage exclusions or threshold changes were added. Semantic typecheck, root build/lint/docs/dependency checks, and all four size limits pass.
  • All 31 Chromium scenarios pass against a fresh local server, including equal primary-listener counts with semantics on/off, heatmaps on/off, native metadata, auxiliary clicks, unchanged raw rrweb keys, matching rrweb node IDs, privacy, transport, worker, stop/resume, disabled capture, and notice/dialog delivery while large subtrees keep changing, connected dialogs moved beneath hidden ancestors, and initial/SPA/back navigation, polling, worker mode, stop/resume and navigation-family disabling.
  • The fresh root pnpm test --concurrency 4 -- --runInBand run passed the semantic, replay, plugin, and analytics-core suites. Analytics-node initially failed because the sandbox denied its HTTP test servers permission to bind ports (listen EPERM); rerunning outside the sandbox passes all 39 tests at 100% coverage. The root command was stopped after its Segment bundle rebuild stalled for over nine minutes. Analytics-core and React Native also reported open handles after their assertions passed. This is not a clean green full-root test result.
  • Standalone browser smoke validates 309 events with masking and teardown checks. The broad replay tsc --noEmit -p tsconfig.json check still encounters an unrelated implicit-this error in e2e/idb.spec.ts:669; package production builds pass.
  • test:examples still fails because jest.setup.examples.js is absent. Node 22/26 remain unverified.

New deterministic regressions verify that 5,000 ordinary serialized nodes require only the relevant notice/parent lookups and that large batches of irrelevant attributes remain frame-bounded. They are operation-count checks, not a production CPU benchmark.

Local fixture measurements from the earlier performance change showed interaction dispatch p95 about 0.6 ms before versus 0.1 ms after on a 20,000-element page. These are fixture measurements, not production guarantees. Exact ±1 ms rrweb timestamp alignment, the production byte budget, accepted analytics wiring, iframe lifecycle coverage, and full accessibility/visibility reconstruction remain outside the validated scope. Actionable-ancestor IDs may differ from raw rrweb target IDs; consumers must scope IDs to their replay/document.

  • Conventional commit title; capture is opt-in.
  • Satisfy the unchanged 100% coverage gates in semantic, replay, and replay-plugin packages.
  • Resolve full-root/example-test limitations and verify the latest CI run before marking ready.

Deterministic analytics-core source-error tests

The existing asyncMap source-error test relied on a 10 ms wall-clock gap for a promise mapping to finish before the source errored. Control timers and promise flushing explicitly so this test verifies the intended ordering independent of runner load. Add the opposite ordering as a regression: a terminal source error suppresses a mapping that resolves later. Restore real timers in finally blocks; production observable behavior is unchanged.

Validation: focused observable suite passes (13 tests) and exits normally; all 918 analytics-core tests across 59 suites pass, with 100% statements/branches/functions/lines. ESLint and formatting pass. The full local runner reported open handles after finishing its tests and was stopped; this does not establish a clean full-suite process exit. Semantic/replay coverage blockers are resolved by the coverage completion below.

Coverage completion

Add behavior tests for privacy and naming fallbacks, malformed resource errors, shortcut modifiers, deferred form validity, recorder teardown and capacity, bounded notice/dialog queues, native heatmap/semantic clicks, host callbacks, terminal recording events, tab identity, and URL polling. Exercise analytics autocapture boolean configuration as well as object configuration.

Remove the unused base cleanup callback and redundant null checks after initialization. Keep dialog queues bounded by the tracking registry and remove detached, already-closed dialogs from both registries before admitting replacements; test a full 256-dialog replacement burst. Keep caps, wire payloads, masking, and capture enablement unchanged.

Validation: all 1,409 tests across the three packages pass with 100% coverage in all four dimensions. The full build and repository lint, docs, dependency, semantic typecheck, and bundle-size checks pass. Latest measured gzip sizes are approximately 154.75 KB for replay and 242.14 KB for unified, within the existing feature budgets. Generated extension vendor files remain excluded.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Session Replay Browser E2E Results

passed  157 passed

Details

stats  157 tests across 18 suites
duration  4 minutes, 24 seconds
commit  885a008

const dialog = document.getElementById('pressure-dialog')!;
for (let i = 0; i < 12; i++) {
const tree = document.createElement('section');
tree.innerHTML = '<span></span>'.repeat(1000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High severity blocking issue identified in your code:
User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities

Why this might be safe to ignore:

This match is in a browser test and assigns a fixed string expression generated by repeating a literal, with no user-controlled input or dataflow to the sink. It is not reasonably exploitable as XSS, and the low-confidence rule matched because its exclusions do not recognize this safe expression.

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by insecure-document-method.

You can view more details about this finding in the Semgrep AppSec Platform.

@jxiwang
jxiwang changed the base branch from codex/semantic-review-page-feedback to main October 23, 2026 04:07
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

size-limit report 📦

Path Size
packages/analytics-browser/lib/scripts/amplitude-min.js.gz 67.76 KB (+0.05% 🔺)
packages/session-replay-browser/lib/scripts/session-replay-browser-min.js.gz 151.07 KB (+9.3% 🔺)
packages/unified/lib/scripts/amplitude-min.umd.js.gz 236.49 KB (+5.71% 🔺)
@amplitude/element-selector (gzipped esm) 3.48 KB (0%)

jxiwang and others added 6 commits October 22, 2026 21:58
 - @amplitude/amp-semantic@0.2.0-codexsemantic-review-integration.0
 - @amplitude/analytics-browser@2.48.2-codexsemantic-review-integration.0
 - @amplitude/analytics-core@2.62.0-codexsemantic-review-integration.0
 - @amplitude/analytics-node@1.5.82-codexsemantic-review-integration.0
 - @amplitude/analytics-react-native@1.12.5-codexsemantic-review-integration.0
 - @amplitude/plugin-autocapture-browser@1.31.0-codexsemantic-review-integration.0
 - @amplitude/plugin-event-property-attribution-browser@0.2.25-codexsemantic-review-integration.0
 - @amplitude/plugin-experiment-browser@1.0.0-codexsemantic-review-integration.0
 - @amplitude/plugin-experiment-react-native@1.0.0-codexsemantic-review-integration.0
 - @amplitude/plugin-network-capture-browser@1.10.25-codexsemantic-review-integration.0
 - @amplitude/plugin-page-url-enrichment-browser@0.7.35-codexsemantic-review-integration.0
 - @amplitude/plugin-page-view-tracking-browser@2.12.0-codexsemantic-review-integration.0
 - @amplitude/plugin-session-replay-browser@1.37.0-codexsemantic-review-integration.0
 - @amplitude/plugin-session-replay-react-native@0.5.12-codexsemantic-review-integration.0
 - @amplitude/plugin-web-attribution-browser@2.2.35-codexsemantic-review-integration.0
 - @amplitude/plugin-web-vitals-browser@1.2.8-codexsemantic-review-integration.0
 - @amplitude/segment-session-replay-plugin@0.0.54-codexsemantic-review-integration.0
 - @amplitude/session-replay-browser@1.52.0-codexsemantic-review-integration.0
 - @amplitude/session-replay-dom-privacy@0.3.0-codexsemantic-review-integration.0
 - @amplitude/unified@1.1.44-codexsemantic-review-integration.0
 - @amplitude/unified-react-native@1.0.0-codexsemantic-review-integration.0
@jxiwang

jxiwang commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

The integration draft is superseded for merging by the clean stack: #2120, #2126, #2127 (merged), then #2128 → #2150 → #2151 → #2152 → #2153 → #2154 → #2155 → #2156. Each event family now has an isolated PR with passing focused package, SDK, and browser tests. The old branch remains available as a reference. page and click-outcome are deferred; amp-event still needs a post-acceptance analytics host signal, so its old recorder would be dormant.

This branch has not been deployed

No deployments
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.

2 participants