Skip to content

Treat untabbable free-text controls under an in-form aria-hidden ancestor as traps and drop their wrapper copy - #3554

Merged
chubes4 merged 1 commit into
trunkfrom
fix/issue-3550-blocks-engine
Oct 27, 2026
Merged

chubes4 merged 1 commit into
trunkfrom
fix/issue-3550-blocks-engine

Conversation

@chubes4

@chubes4 chubes4 commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Summary

An untabbable free-text field was only treated as a trap when aria-hidden="true" was on the field itself. When the attribute was on a wrapper inside the form (a common honeypot pattern), the trap's label, input and wrapper came out of readable form blocks as a core/html block inside an empty group. Now the field counts as a trap when any ancestor inside its form is aria-hidden, and the trap claims its label and every trap-only container up to that ancestor, so none of its markup reaches readable blocks or the form layout graph.

What changed

  • isUnreachableTextTrap now also fires when an ancestor of the control is aria-hidden="true". The walk stops at the control's native form, so aria-hidden outside the form does not count. Only free-text entry qualifies, as before; checkbox, radio and select carriers behave as they did.
  • honeypotCopyElements now claims the trap's label and every container from the trap up to and including its outermost in-form aria-hidden ancestor. It still never claims a form, a fieldset, or a container that holds a real control.
  • Other trap kinds still claim only their immediate container, as before.
  • tests/unit/invisible-unreachable-text-trap.php adds the neutral name/email/message fixture with the aria-hidden off-screen wrapper. Through ArtifactCompiler it checks that the readable blocks have no core/html, no "Leave empty" text and no leftover wrapper group, and that the layout graph has no trap markup. It also checks the choice-carrier case and the form boundary.
  • Additive. A free-text field with a negative tabindex under an aria-hidden wrapper inside its form is no longer extracted as a field, and its wrapper copy no longer appears in readable output.

Verification

  • passed on 403ea54. Base trunk@4f62f31 unchanged.
set -e
cd php-transformer
composer install --no-progress --no-interaction
php tests/unit/form-honeypot-field.php
php tests/unit/invisible-unreachable-text-trap.php
php tests/unit/overlay-form-authorship.php
composer test:unit
php tests/parity/run.php
composer lint:php
  • CI: pending
Cook run
  • Gate 1: tree 972fd030f4c4eb4c8fda47ac071bc82fe12ffaf2, fingerprint 82c4a467637e1e3c96caff1483f7989581b6ac3087258b1531ba5bffdfd2caee

AI assistance

Tool: Homeboy (pi) · Model: claude-opus-5-5 (tier roadie/cook-heavy) · Used for: The AI read the classifier and its callers, reproduced the leaked core/html block through the compile path, made the fix, added the regression test (confirmed failing on trunk), and ran the gates.

Closes #3550

@chubes4
chubes4 merged commit bee481f into trunk Oct 27, 2026
15 checks passed
@chubes4
chubes4 deleted the fix/issue-3550-blocks-engine branch October 27, 2026 00:38
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.

Untabbable free-text trap under an aria-hidden wrapper is not a honeypot, so its label leaks into readable form blocks as core/html

1 participant