Skip to content

Refactor: createScanRule factory for the hand-rolled watcher lifecycle (15 rules)#244

Merged
twschiller merged 1 commit into
mainfrom
refactor/create-scan-rule-factory
Jun 10, 2026
Merged

Refactor: createScanRule factory for the hand-rolled watcher lifecycle (15 rules)#244
twschiller merged 1 commit into
mainfrom
refactor/create-scan-rule-factory

Conversation

@twschiller

Copy link
Copy Markdown
Contributor

What

Adds a createScanRule lifecycle factory and migrates the 15 holdout rules that hand-rolled the identical subtree-watcher skeleton onto it.

Two factories already cover the clean rule shapes — createSelectorHideRule (7 rules) and defineInlineTextRedactRule (3 rules). The remaining DOM-mutating rules each hand-rolled the same four-part skeleton, ~23 LOC each:

  • a module-level const watcher = createSubtreeWatcher({...})
  • a scanAndX(root) scan function
  • apply = scan(root); watcher.start(root)
  • teardown = watcher.stop()

AGENTS.md documented the skeleton, but documenting boilerplate doesn't eliminate the bug surface — a new rule can still forget skipPlaceholderSubtrees (its own placeholders then re-trigger the watcher) or drop the teardown (the observer leaks).

The factory

extension/src/lib/scan-rule.ts:

createScanRule({ id, label, description, scan, skipPlaceholderSubtrees?, topFrameOnly? })

Owns the watcher lifecycle and returns a Rule with apply/teardown wired. Generic over the literal RuleId (mirroring the two existing factories) so the catalog's compile-time id-agreement check in rules/index.ts keeps narrowing.

Migrated (15 rules, −244 LOC in the rule files)

scarcity-redact, disguised-ad-flag, html-comment-strip, noscript-strip, svg-text-strip, unicode-invisibles-strip, link-spoof-annotate, schema-trust-sanitize, trust-badge-annotate, cart-addon-annotate, attribute-injection-sanitize, json-ld-sanitize, hidden-fee-annotate, hidden-affiliate-sanitize, hidden-text-strip.

Deliberately left bespoke

Rules whose lifecycle is more than scan-on-apply-and-rescan keep hand-wiring createSubtreeWatcher directly: head router (meta-injection-strip), route/focus subscription (form-prefill-annotate), deferred snapshot (countdown-timer-redact), onSubtrees that re-scans document.body (cross-origin-frame-redact, svg-sprite-strip), teardown that restores page state (confirmshame-sanitize). The factory's doc comment and the updated AGENTS.md section spell out when to reach past it.

Tests

New scan-rule.test.ts (7 tests) pins the factory's own contract: scans once on apply, re-scans surfaced subtrees, forwards skipPlaceholderSubtrees, stops observing on teardown. Existing per-rule suites are unchanged and green.

Verification

bun run check (biome + eslint), tsc ×2, knip, full jest (2059 tests, 110 suites), and the markdown pre-commit all pass.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-browser-shield-demo-site Ready Ready Preview, Comment Jun 10, 2026 3:17am

Request Review

…e (15 rules)

Two factories already cover the clean rule shapes — createSelectorHideRule
(7 rules) and defineInlineTextRedactRule (3 rules). The remaining DOM-mutating
rules each hand-rolled the identical four-part skeleton: a module-level
watcher, a scanAndX(root), an apply wiring the two, and a teardown calling
watcher.stop(). AGENTS.md documented the skeleton, but documenting boilerplate
doesn't stop a new rule from forgetting skipPlaceholderSubtrees or its teardown.

Add createScanRule({ id, label, description, scan, skipPlaceholderSubtrees? })
in extension/src/lib/scan-rule.ts, generic over the literal RuleId so the
catalog's compile-time id check keeps narrowing. Migrate the 15 holdouts whose
lifecycle is exactly scan-on-apply-and-rescan (−244 LOC in the rule files):
scarcity-redact, disguised-ad-flag, html-comment-strip, noscript-strip,
svg-text-strip, unicode-invisibles-strip, link-spoof-annotate,
schema-trust-sanitize, trust-badge-annotate, cart-addon-annotate,
attribute-injection-sanitize, json-ld-sanitize, hidden-fee-annotate,
hidden-affiliate-sanitize, hidden-text-strip.

Rules with more lifecycle than that keep hand-wiring createSubtreeWatcher
directly — a head router (meta-injection-strip), a route/focus subscription
(form-prefill-annotate), a deferred snapshot (countdown-timer-redact), an
onSubtrees that re-scans document.body (cross-origin-frame-redact,
svg-sprite-strip), or a teardown that restores page state
(confirmshame-sanitize). The factory comment and the updated AGENTS.md section
spell out when to reach past it.

Add scan-rule.test.ts pinning the factory's own contract (scan-once-on-apply,
re-scan surfaced subtrees, forward skipPlaceholderSubtrees, stop on teardown).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@twschiller twschiller changed the title Refactor: createScanRule factory for the hand-rolled watcher lifecycle (13 rules) Refactor: createScanRule factory for the hand-rolled watcher lifecycle (15 rules) Jun 10, 2026
@twschiller twschiller force-pushed the refactor/create-scan-rule-factory branch from de507d5 to c7e5966 Compare June 10, 2026 03:17
@twschiller twschiller merged commit c711cd6 into main Jun 10, 2026
7 checks passed
@twschiller twschiller deleted the refactor/create-scan-rule-factory branch June 10, 2026 03:23
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.

1 participant