Skip to content

Selectors & match strategy

A sightmap names parts of the UI. To do that, it has to point at them — somehow — in a way that survives across frameworks, refactors, and rendering modes. Sightmap uses CSS selectors. The choice is not arbitrary; the alternatives are worse.

CSS selectors are the one selection language every web framework and every browser-driving tool already understands. React, Vue, Svelte, Angular, server-rendered HTML, web components — all produce DOM, and DOM is queryable with CSS. Playwright, Puppeteer, every accessibility tree, every devtools console: CSS works.

The alternatives each fail one constraint:

  • Framework-specific component handles (<DatePicker> in React DevTools, Vue component instance IDs) require the curator and the consumer to agree on the framework.
  • XPath is universal but verbose and brittle.
  • Custom DOM mutations (injecting unique IDs at runtime) change the page the agent is observing — a non-starter for tools like Subtext that enrich live sessions.

CSS selectors are universal, non-mutating, and already in every agent’s toolkit. They are also imperfect — which is why the spec adds two things on top.

A selector field accepts either a single CSS string or an array of strings tried in order. First match wins.

- name: DepartureDatePicker
selector:
- '[data-component="DepartureDatePicker"]'
- '[data-picker="departure"]'
- '.flight-search .date-picker:first-child'

This is how sightmaps survive refactors. The first selector is the long-term ideal — a data-component attribute the team has agreed to maintain. The second is the legacy attribute the picker shipped with. The third is the structural fallback for the day someone removes both. The map keeps working through all three states; the curator drops the stale entries when they’re confident nothing relies on them.

For nested components, child selectors are evaluated within the parent’s matched subtree, not against the document root. Two unrelated card components can both contain a button.primary without colliding.

The recommended marker is a data-component="ComponentName" attribute on the root element of each component you want to name. It is:

  • Stable across refactors — the attribute moves with the component file.
  • Invisible to users and to most CSS — no styling implications.
  • Trivially queryable[data-component="DatePicker"].
  • Consistent with framework norms — React, Vue, and Svelte all pass data-* props through to the DOM.

@sightmap/react’s runtime fiber walker recognizes a related attribute, data-sightmap, when ranking selector candidates during agent bootstrap — it scores highest in the walker’s stability ranking, so a marker always wins. Either attribute works as the stable handle; the convention exists so a new project has a default to reach for.

Selectors: first-match-wins. Routes: most-specific-wins.

Section titled “Selectors: first-match-wins. Routes: most-specific-wins.”

The two halves of the spec resolve differently — by design, because the costs of getting them wrong are different.

Selector arrays within a component use first-match-wins. The first pattern that resolves to one or more elements is the winner. This is what makes the legacy-then-fallback pattern in the section above work: list the long-term ideal first, the legacy attribute second, the structural fallback last.

Routes in views[].route and requests[].route use most-specific-match-wins, with declaration order as a tiebreak. A URL is scored against every matching pattern (literal segments outweigh :param, which outweighs *, which outweighs **), and the highest-scoring pattern wins. This was tightened in v0.7 — earlier versions used first-match-wins for routes too, but the lint rule (route-shadowing) already assumed specificity semantics, and the silent disagreement caused surprises.

In practice: order doesn’t matter for routes as much as it used to (the matcher will pick /install over * regardless of declaration order), but keeping the more specific routes near the top is still good hygiene. See Route matching in the schema for the glob syntax and the exact scoring weights.