Summary
Selector-mode (dom-) element IDs from charlotte_find's CSS selector mode (#191) are stored in a separate durable registration map (ElementIdGenerator.domQueryIds) and are never present in representation.interactive (which is AX-tree-only). Three code paths validate or look up element IDs solely against representation.interactive, so they silently degrade when handed a dom- ID.
None of these block the v0.7.0 release — they are edge cases on the new selector-mode feature, and the primary AX-tree paths are correct. Filing as a fast-follow. Surfaced during review of #219.
Symptoms
1. charlotte_toggle skips its toggleable-control guard for dom- IDs — src/tools/interaction.ts:349-352
The guard is if (targetElement && !TOGGLEABLE_TYPES.has(targetElement.type)). For a dom- ID, targetElement (looked up in preToggleRepresentation.interactive) is undefined, so the guard is skipped and toggle fires an unvalidated left-click on whatever the selector resolved to — the exact misrouted-click case #204 set out to prevent.
2. near / within throw a misleading error for a dom- spatial anchor — src/tools/observation.ts:355,387
The reference element is resolved via representation.interactive.find(el => el.id === near). A dom- reference is never found, so the bounds check throws "Reference element '<id>' has no bounds; cannot apply spatial filter." The real reason is that the element isn't in the interactive list, not that it lacks bounds.
3. matchIndex skew between registration and re-resolution — src/tools/observation.ts:121 vs src/tools/tool-helpers.ts:252
findBySelector increments matchIndex only after a successful DOM.describeNode (nodes that throw hit catch { continue } without incrementing). But reResolveDomQueryId uses the stored matchIndex as a direct index into the raw nodeIds[] returned by DOM.querySelectorAll (which includes every match). If any earlier match fails describeNode, re-resolution indexes the wrong node — resolving a cached dom- ID onto a different element. Low probability (requires a querySelectorAll match that fails describeNode), but it is the class of silent ID-migration bug the salted-hash change (#190) set out to eliminate.
Suggested fix
A single change closes all three: make dom- registrations resolvable for bounds/type validation — e.g. store bounds + a coarse type on the DomQueryRegistration and have these paths consult the ElementIdGenerator registration when the ID is absent from representation.interactive. For symptom 3, track the raw loop position separately from matchIndex (or filter nodeIds at re-resolution using the same describe-fallibility).
Verification
Each symptom confirmed by reading the cited file:line on release/0.7.0 @ 4ceffd2.
// ticktockbent
Summary
Selector-mode (
dom-) element IDs fromcharlotte_find's CSS selector mode (#191) are stored in a separate durable registration map (ElementIdGenerator.domQueryIds) and are never present inrepresentation.interactive(which is AX-tree-only). Three code paths validate or look up element IDs solely againstrepresentation.interactive, so they silently degrade when handed adom-ID.None of these block the v0.7.0 release — they are edge cases on the new selector-mode feature, and the primary AX-tree paths are correct. Filing as a fast-follow. Surfaced during review of #219.
Symptoms
1.
charlotte_toggleskips its toggleable-control guard fordom-IDs —src/tools/interaction.ts:349-352The guard is
if (targetElement && !TOGGLEABLE_TYPES.has(targetElement.type)). For adom-ID,targetElement(looked up inpreToggleRepresentation.interactive) isundefined, so the guard is skipped andtogglefires an unvalidated left-click on whatever the selector resolved to — the exact misrouted-click case #204 set out to prevent.2.
near/withinthrow a misleading error for adom-spatial anchor —src/tools/observation.ts:355,387The reference element is resolved via
representation.interactive.find(el => el.id === near). Adom-reference is never found, so the bounds check throws"Reference element '<id>' has no bounds; cannot apply spatial filter."The real reason is that the element isn't in the interactive list, not that it lacks bounds.3.
matchIndexskew between registration and re-resolution —src/tools/observation.ts:121vssrc/tools/tool-helpers.ts:252findBySelectorincrementsmatchIndexonly after a successfulDOM.describeNode(nodes that throw hitcatch { continue }without incrementing). ButreResolveDomQueryIduses the storedmatchIndexas a direct index into the rawnodeIds[]returned byDOM.querySelectorAll(which includes every match). If any earlier match failsdescribeNode, re-resolution indexes the wrong node — resolving a cacheddom-ID onto a different element. Low probability (requires aquerySelectorAllmatch that failsdescribeNode), but it is the class of silent ID-migration bug the salted-hash change (#190) set out to eliminate.Suggested fix
A single change closes all three: make
dom-registrations resolvable for bounds/type validation — e.g. store bounds + a coarse type on theDomQueryRegistrationand have these paths consult theElementIdGeneratorregistration when the ID is absent fromrepresentation.interactive. For symptom 3, track the raw loop position separately frommatchIndex(or filternodeIdsat re-resolution using the same describe-fallibility).Verification
Each symptom confirmed by reading the cited file:line on
release/0.7.0@4ceffd2.// ticktockbent