Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Feat: hidden-fee-annotate rule for drip-pricing fees (#119)#181

Merged
twschiller merged 3 commits into
mainfrom
hidden-fee-annotate
Jun 5, 2026
Merged

Feat: hidden-fee-annotate rule for drip-pricing fees (#119)#181
twschiller merged 3 commits into
mainfrom
hidden-fee-annotate

Conversation

@twschiller

Copy link
Copy Markdown
Contributor

Summary

  • New rule hidden-fee-annotate (default off, Experimental) — annotates mandatory fees that surface only at checkout (resort / service / convenience / processing / destination / facility / handling / venue fee, delivery surcharge) so agents can verify the total before paying. Closes Rule proposal: hidden-fee-annotate — annotate drip-pricing fees added during checkout #119.
  • Layered false-positive control: whole-string regex on a small leaf-ish label, required order-summary ancestor (<table> / [role="region"] / cart-shaped class/id / schema.org Order microdata), required adjacent currency amount, single-item-cart skip, legally-required-line-item exclude list (tax, VAT, GST, shipping, tip, gratuity), and a (launch-empty) per-host denylist.
  • Row is annotated, not removed — many of these fees are legally required to be surfaced (FTC Unfair-or-Deceptive-Fees rule, effective 2025-05-12).
  • Test coverage: 70 example-based tests + 8 fast-check property tests (regex precision, phrase/exclude disjointness, currency regex, single-item-cart invariant, idempotency). Full suite: 1444 tests pass.

Rollout

Ships default-off; promote to default-on after per-rule activity counts (#174) show the FP rate is acceptable, mirroring schema-trust-sanitize's posture.

Test plan

  • bun run preflight (build-site-data, build-injection-patterns, build-rule-defaults, biome, eslint, tsc, knip, jest --coverage) — clean
  • pre-commit run --files docs/src/content/docs/rules.md — clean (mdformat-reflowed on first run)
  • Manual smoke test: enable the rule, load a hotel checkout with a known resort fee, confirm the chip appears on the resort-fee row only and the popup's per-rule count increments
  • Manual smoke test: confirm the rule does not annotate marketing-copy mentions of "service fee" on a non-checkout URL or on a /checkout page outside the order-summary container

🤖 Generated with Claude Code

Annotates mandatory fees (resort, service, convenience, processing, etc.)
in checkout order summaries with a visible chip so agents can verify the
total before completing a purchase. Ships default-off pending real-world
per-rule activity counts (#174) — same rollout posture as
schema-trust-sanitize. Row is annotated, not removed, since many fees
are legally required to be surfaced (FTC Unfair-or-Deceptive-Fees rule).

FP control is layered: whole-string regex on a small leaf-ish label,
required order-summary ancestor (table / [role=region] / cart-shaped
container / schema.org Order), required adjacent currency amount,
single-item-cart skip, legally-required-line-item exclude list, and a
(launch-empty) per-host denylist.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@vercel

vercel Bot commented Jun 5, 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 5, 2026 9:57pm

Request Review

Action is annotation-only — worst case from a misfire is an extra chip
on a row, not a destructive edit. Per-rule activity counts (#174) and
the per-host denylist remain the fast lever if live signal surfaces a
false-positive cluster. Experimental tag retained.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

@unblocked unblocked Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found.

About Unblocked

Unblocked has been set up to automatically review your team's pull requests to identify genuine bugs and issues.

📖 Documentation — Learn more in our docs.

💬 Ask questions — Mention @unblocked to request a review or summary, or ask follow-up questions.

👍 Give feedback — React to comments with 👍 or 👎 to help us improve.

⚙️ Customize — Adjust settings in your preferences.

// aria-labelledby can list multiple IDs; concatenate their visible text.
const document_ = element.ownerDocument;
const parts: string[] = [];
for (const id of labelledby.split(/\s+/)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If aria-labelledby contains leading/trailing whitespace (e.g. " id1 id2 "), split(/\s+/) produces empty strings in the array. CSS.escape("") returns "", so querySelector("#") is called, which throws a DOMException ("'#' is not a valid selector").

The existing readAccessibleName in trust-badge-annotate.ts (line 137) guards against this with if (id === "") { continue; }. The same guard is needed here.

Suggested change
for (const id of labelledby.split(/\s+/)) {
for (const id of labelledby.split(/\s+/)) {
if (id === "") {
continue;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed in 5af258b. Added the empty-id guard alongside the split(/\s+/), matching readAccessibleName in trust-badge-annotate.ts as suggested. Also added a regression test for the leading/trailing-whitespace case in hidden-fee-annotate.test.ts (it needs a small CSS.escape polyfill because jsdom in this project doesn't expose the CSS global — same latent test-only quirk applies to trust-badge but isn't exercised there).

— Claude Code, on behalf of @twschiller

Without the guard, an aria-labelledby attribute with leading/trailing
whitespace (e.g. "  heading-a heading-b  ") would split into the empty
string, which `CSS.escape` returns unchanged, and `querySelector("#")`
then throws a DOMException. Matches the same pattern in
`readAccessibleName` in trust-badge-annotate.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@twschiller twschiller merged commit f6eac66 into main Jun 5, 2026
7 checks passed
@twschiller twschiller deleted the hidden-fee-annotate branch June 5, 2026 21:59
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.

Rule proposal: hidden-fee-annotate — annotate drip-pricing fees added during checkout

1 participant