fix(chrome): name a web field by its hint, not its CSS class

visibleLabel reads hintText first for an editable element. The dump never
emitted it, so an empty web input fell through text, description and
descendant text to its class name, and the model was shown an identifier no
user can read on exactly the fields a labelling experiment varies.

Same ladder as fieldHint in web-runtime.ts, so one field is named one way on
both hosts.

Claude-Session: https://claude.ai/code/session_01A5KmftdEJ49A9z5mF5ESrX
This commit is contained in:
pj committed 2026-08-15 13:45:30 +05:30
1 parent ebed84afc3
commit 9b9c7cf6e2
4 files changed
+103 -2

No files matched your search

+19
View File
@@ -408,6 +408,23 @@ func (d *Driver) Hierarchy(ctx context.Context) (string, error) {
if (tag === 'input') return !NON_TEXT_INPUT_TYPES.includes((el.type || '').toLowerCase());
return false;
}
// An editable field's own text is the transient typed value; its hint names
// its purpose, which is the rung visibleLabel (internal/verifier/llm.go) reads
// first for such an element. Without it a web field reached the model named by
// its CSS class, an identifier no user can read. Same ladder as fieldHint in
// pkg/spec/src/web-runtime.ts, so one field is named one way on both hosts.
function fieldHint(el) {
if (!isEditableElement(el)) return '';
const ariaLabel = el.getAttribute('aria-label');
if (ariaLabel) return ariaLabel;
for (const label of el.labels || []) {
const text = (label.textContent || '').trim();
if (text) return text;
}
const placeholder = el.getAttribute('placeholder');
if (placeholder) return placeholder;
return el.getAttribute('name') || '';
}
// Shadow roots are part of the page a user sees, so they are part of the page
// we enumerate. Compose for Web mounts its canvas AND its accessibility tree
// inside a shadow root on the mount element, so a light-DOM-only walk reports
@@ -446,6 +463,8 @@ func (d *Driver) Hierarchy(ctx context.Context) (string, error) {
if (el.className && typeof el.className === 'string' && el.className.trim()) {
attrs['class'] = el.className.trim();
}
const hint = fieldHint(el);
if (hint) attrs['hintText'] = hint;
// The goja host reads scrollable off this attribute (internal/verifier
// worker.go targets). Without it every web element looks unscrollable there,
// so the goja-side enumeration offers no scroll while the V8 picker, which