Status: spec stream 1, project semver 0.1.0 (pre-1.0). In-place tightening allowed until the project hits 1.0.0. See the versioning policy.
A sightmap is a directory of YAML files at the root of a project, under .sightmap/. It describes the app’s views, components, and requests, with optional memory entries that carry notes agents can use at runtime.
This document is the human-readable reference. The machine-readable contract is sightmap.schema.json.
File discovery
- Every
*.yamland*.ymlfile under.sightmap/is discovered recursively. - All files are loaded and merged at load time. The directory layout is a convenience for authors; it has no semantic meaning.
- Every file must begin with
version: 1. - Merging is shallow-append per top-level collection (
views,components,requests). Two files may define the same view; the runtime behavior in that case is implementation-defined and SDKs SHOULD emit a warning.
File root
View
A named screen in the app, identified by a URL route.Component
A named DOM subtree, identified by one or more CSS selectors.Component references
Any entry in acomponents: array — at file root, within a view, or under children: — may be a reference object instead of an inline definition:
components: arrays of all loaded files. Nested children of an inlined definition are themselves re-expanded if they contain further $ref entries.
Lookup scope. Only components defined at the root of some file’s
components: array are addressable. Components nested under children:, or defined inside a view’s components:, are not in the registry. First-seen wins on duplicate names (sorted by source-file path); SDKs SHOULD emit a merge-collision-component warning.
Conformance. SDKs MUST expand $ref entries before matching, MUST emit ref-unresolved (error) for an unknown name, MUST emit ref-circular (error) for a self-referential chain, and MUST NOT produce two matches for the same view when a view-scoped $ref and a file-root global share a name (the view-scoped expansion subsumes the global for that view).
See SEP-0002 for the full proposal and rationale.
Selector semantics
- A single string is a standard CSS selector.
- A list of strings is tried in order; the first selector that matches wins.
- Selectors in
childrenare evaluated within their parent’s matched subtree, not globally. This scoping is how Sightmap avoids naming collisions between, say, two different card components that both contain abutton.primary. - Selectors are not required to be unique at their level. If a selector matches multiple elements, all matches are named.
- Shadow DOM. Selectors are matched against the captured component tree, which is a flattened representation: each shadow root’s content is inlined as ordinary children of its host. Selectors therefore match across shadow boundaries — shadow-DOM content is addressed exactly like light-DOM content, and there is no piercing syntax (standard CSS has none:
>>>//deep/were removed, and::part()/::slotted()reach only explicitly-exposed nodes).childrenscoping and combinators operate over the flattened tree, where a shadow host → shadow child is an ordinary parent→child edge. This is a deliberate divergence from a livedocument.querySelector, which does not cross shadow roots: a tool re-implementing matching against the live DOM MUST traverse shadow roots (walk each element’sshadowRoot) to agree with the corpus.
Component properties
A component may declareproperties: Property[] — named values surfaced alongside the component name in enriched snapshots, e.g. [DateFilterButton label="This Weekend"]. Values are resolved over the component tree, from the matched component’s own node and the extracted properties of components nested beneath it — never from arbitrary DOM. This makes resolution work offline, against a serialized tree, on any UI platform. See SEP-0010, which supersedes the extraction model of SEP-0003.
Extract grammar. The
extract value is exactly one of the following forms; any other value is invalid and MUST be rejected by validation.
PATH is a dotted sequence of component names naming a descendant, each segment resolved first-match (in document order) within the previous segment’s matched subtree (Price, Row.Price). In a PATH.prop value reference the final segment is a property name; in exists:PATH the whole path names components. References descend only — a property may address a component nested beneath the one declaring it, never a parent, sibling, or cousin — so resolution is a bottom-up pass over a DAG. To surface a value from a sub-element, promote that sub-element to a declared child component and reference it.
The observed attribute set read by attr=NAME is implementation-defined: which attributes a node carries depends on the consumer (a web SDK may carry a fixed allowlist plus aria-*/data-*; other platforms carry synthetic attributes). An attribute the consumer did not carry is indistinguishable from one that was absent.
Value omission is silent — a property whose text is empty, whose attribute is not carried, or whose PATH matches nothing is simply dropped from the annotation; consumers MUST NOT treat omission as an error.
Dependencies
Thedependencies field on a view or component declares supplementary files whose changes SHOULD trigger re-curation of the entry. It is purely curation-time metadata — runtime consumers (browser-driving agents, session-replay enrichers) read DOM/runtime state, not source files, and MUST NOT introduce page-load runtime cost on the basis of this field.
What belongs
- Hooks the view or component consumes (e.g.
useChecklist,useAuth) - Services / stores / shared utilities
- CSS / style files the entry loads
- Helper modules that don’t warrant their own entry
What does NOT belong
- Tests (
*.test.ts,*.spec.tsx) - Type-only imports
- Framework code (React, Vue, etc.)
- Files that have their own component or request entry — use the existing entry-level binding, don’t restate
Glob semantics
Strings independencies are interpreted as minimatch globs, project-root-anchored (the directory containing .sightmap/). A ! prefix negates. When multiple positive globs match the same file, the first positive glob in declaration order wins for provenance reporting.
Normative rules
A conforming SDK MUST surface a diagnostic when:- An entry’s resolved
dependencies[]set contains its ownsource. Diagnostic code:dependencies.self-redundant. - An entry’s resolved
dependencies[]set contains a path that is thesourceof any other entry in the same.sightmap/. Diagnostic code:dependencies.overlaps-entry. - A glob in
dependencies[]resolves to zero files. Diagnostic code:unknown-source(existing vocabulary, narrowed to apply todependencies[]globs).
Request
A named API endpoint.Request properties
properties: declares named values to pull out of a live request/response pair, so a consumer can reason about what an endpoint’s traffic actually said. An HTTP status of 200 does not distinguish an approved payment from a declined one when the outcome lives in the response body.
At least one of
field/pattern is required — the two compose: field selects a value, pattern optionally extracts a substring from it. source is always required, and when it names a headers source field is required too (a bare regex across a raw header block is the addressing foot-gun this shape removes). pattern is an RE2 regular expression; the reference CLI rejects an invalid one (request-property-pattern-invalid).
Value omission is silent — a property that doesn’t resolve (a missing key, an out-of-range index, no pattern match) is simply absent; consumers MUST NOT treat omission as an error. Omission is the normal case, not an edge case: whether a body or header is even available to read depends on the capture layer’s own payload and privacy settings.
Extraction requires live traffic. A tool operating on static corpus definitions alone MUST treat properties: as declared-but-unavailable, not an error.
status, method, and duration are reserved identity names, addressing the request’s own already-structured HTTP identity. They sit outside source entirely — a consumer may reference them wherever a property name is expected with no properties: declaration at all. Declaring a property under one of those names is legal and shadows the identity: the name then resolves to the extracted value, and the HTTP identity becomes unreachable. The reference CLI warns (request-property-shadows-reserved). Prefer a distinct name such as outcome unless shadowing is what you want.
properties: and request:/response: (Payload) answer different questions: Payload.fields[] documents expected shape for a reader and is not enforced; properties: names a value to extract from live traffic. The two lists are independent. See SEP-0005.
Payload
Field
Message
A named console-output or runtime-exception pattern. This gives console activity whatrequests: gives network activity: a named entity the rest of the corpus can point at, so “a cart version mismatch broke checkout” is stated once rather than re-matched by every consumer.
A record matches when every declared constraint holds. Declaring neither
level nor message matches every record, which is legal but rarely useful. message is an RE2 regular expression.
Message properties
properties: declares named values to pull out of an uncaught exception’s stack trace, so a corpus can classify an exception by where it came from and extract the failing location — the message-side analogue of a request’s properties:. It reuses that mechanism’s source / field / pattern shape.
Extraction requires live traffic and value omission is silent, exactly as for request properties: a property that doesn’t resolve (a plain console record with no stack, a frame index out of range, an unknown attribute, no pattern match) is simply absent, never an error. See SEP-0006.
Message levels
The reference capture emits these levels:
An uncaught exception arrives as
exception, not as error. So level: ERROR does not match one, and a corpus that wants exceptions must say level: EXCEPTION. Console output and exceptions still share one entity rather than needing a kind: discriminator, because the origin is carried as a level value.
The vocabulary is open: level is free text, not an enum, so a corpus may name a level this capture never emits. That is deliberate, since another consumer’s capture may have levels of its own. The tradeoff is that a typo (level: WARNING, which this capture normalizes to warn) matches nothing rather than being rejected.
Ambiguous matches
Two entries that can match the same record are reported asmessage-conflict (a warning) when the overlap is statically decidable: the same level, or one omitting it, with an identical or absent message. Deciding whether two different regexes can both match some record is not decidable in general.
A consumer evaluating live records MUST surface an ambiguity when a record matches more than one entry, rather than silently resolving to a first match. See SEP-0006.
Regular expressions
Every author-written regular expression in a sightmap — a request property’spattern (Request properties), a message’s message (Message), and a message property’s pattern (Message properties) — uses RE2 syntax: the dialect of Go’s regexp, Rust’s regex, and the re2 npm package for JavaScript. RE2 is pinned deliberately. It matches in guaranteed linear time (no catastrophic backtracking), and because a pattern is validated at authoring time by one SDK and evaluated against live activity by another, one predictable dialect keeps the two from disagreeing about the same expression. The tradeoff is expressivity: RE2 has no backreferences and no lookahead/lookbehind. Character classes, alternation, quantifiers, anchors, and capture groups all work — essentially every pattern in practice.
A conforming SDK MUST reject a regular expression that is not valid RE2; the reference CLI reports request-property-pattern-invalid for a pattern and message-regex-invalid for a message.
Memory
Memory entries are short freeform notes attached to any definition — file, view, component, or request. They exist so that agents can carry forward context that isn’t recoverable from the source code: quirks, invariants, workarounds, “you have to click this twice” lore.- Each entry is a single human-readable sentence or short bullet.
- Entries on a component apply whenever that component is matched on the current view.
- Entries on a view apply whenever the current URL matches that view’s route.
- File-level entries apply whenever any definition from that file is active.
- Entries on a request apply in the network-trace detail view.
- Conforming SDKs SHOULD surface applicable memory entries in a
[Guide]section at the top of enriched output.
Route matching
Routes use glob patterns against the URL pathname.*matches exactly one path segment:/users/*matches/users/42, not/users/42/edit**is a globstar: as a whole path segment it matches zero or more segments, so/admin/**matches/admin,/admin/users, and/admin/users/42/edit, and/a/**/bmatches/a/b,/a/x/b, …- A
**glued into a segment (e.g./foo**) is treated as a regular*— an in-segment wildcard that does not cross a/. Write**as its own segment when you mean “any depth”. - Literal segments match themselves
- Matching is case-sensitive
- Query string and fragment are ignored
- Trailing slashes are normalized away before matching
:param segments are normalized to *. These are equivalent:
View matching: most specific wins
When multiple views could match a URL, the most specific wins. Specificity is the sum of per-segment scores:
The root route
/ scores 1 — more specific than any wildcard-only pattern. When two patterns score equal, the first declared view wins.
For example, given /users/* (score 4) and /users/admin (score 6), the URL /users/admin matches the literal — /users/* only wins for paths like /users/42.
Request matching: all matches apply
Requests are matched independently. Every request whoseroute matches the URL — and whose optional method matches the request method — is applied. There is no “winning” request; all matches contribute to the enriched output.
Global vs view-scoped
Components and requests can be declared at the file root or nested inside a view.- Global (
components:orrequests:at file root): matched against every view. - View-scoped (nested inside a
view): matched only when that view is active. - They are additive. A view that defines its own components receives both the global components and its own.
Stability
Both views and components may carry an optionalstability marker recording how much the author trusts the definition. It is advisory metadata — it does not change matching — and conforming tools SHOULD surface it (e.g. in enriched output or lint) so agents know which parts of the map are provisional.
Omit the field for an active view or a stable component.
Tags
Views, components, and requests may all carry an optionaltags: string[] — open-vocabulary
classification labels (e.g. defect) distinct from name. Where name (or a view/request’s
identity) answers “what is this,” tags answers “does this belong to some cross-cutting
classification I care about.” See SEP-0004 for the full
proposal and rationale.
Each entity type already has a rule for resolving identity when more than one definition
could apply to the same match. Tags deliberately do not follow that rule — a broader,
tagged definition must not be shadowed by a narrower, untagged one that wins identity. Tag
resolution is instead a union across every applicable definition:
A component example: a
CheckoutForm tagged defect with an untagged SubmitButton child
— a click on the button resolves name: SubmitButton (nearest-enclosing, unchanged) and
tags: [defect] (inherited from the tagged ancestor).
A view example: a broad /checkout/** view tagged defect, and a more specific
/checkout/payment view with no tags of its own. The URL /checkout/payment resolves the
view identity CheckoutPayment (most-specific wins, unchanged) but still carries
tags: [defect] from the broader, tagged view — exactly the same shadowing concern
component tags solve, applied to route specificity instead of DOM depth.
In every case the resolved tag set MUST be deduplicated, and SHOULD be emitted in a stable
(lexicographically sorted) order wherever it is serialized. A definition that declares no
tags contributes nothing; this is not an error, and tags: [] is equivalent to omitting
the field entirely.
Reserved tooling fields
Some fields are consumed by tooling built on Sightmap (the reference CLI’s capture and probe workflows) but are not part of this spec’s matching or merge semantics. They are permitted by the schema so corpora that use them validate, but conforming SDKs MAY ignore them:access(on a view) — reachability of the view for a tool’s reference account:status(open|blocked|needs-data) and an optionalreason.snapshots(file-level) — named page states to capture (name,notes,url), used to enumerate capture/probe targets.
Conformance
A conforming SDK:- MUST accept any file that validates against
sightmap.schema.json - MUST reject any file that does not
- MUST implement route matching as specified
- MUST implement global vs view-scoped precedence as specified
- MUST implement tag resolution as a union across every applicable definition, as specified in Tags — never narrowed by identity-resolution rules (nearest-wins, most-specific-wins)
- MUST reject a
RequestPropertywith nosource, or asourceoutside the four-value enum (req.body/rsp.body/req.headers/rsp.headers) - MUST reject a
RequestPropertythat declares neitherfieldnorpattern - MUST reject a
RequestPropertywhosesourceis a headers source but omitsfield - MUST reject a
RequestPropertywhosepatternis not a valid RE2 regular expression (see Regular expressions) - MUST reject a
messages:entry whosemessageis not a valid RE2 regular expression (see Regular expressions) - MUST reject a
MessagePropertywith asourceother thanstack, or one that omitsfield, or whosepatternis not a valid RE2 regular expression - SHOULD surface
memoryentries to the agent when the parent definition is active - MAY ignore fields it doesn’t use (e.g. a consumer that never surfaces
descriptionat runtime) - MAY implement additional, non-standard behavior as long as it doesn’t change the meaning of conforming inputs
- MUST resolve
properties:only from live traffic, and MUST NOT error when aproperties:-declaring request is used in a static context — omit the value instead - MUST omit an unresolved property value silently, without a diagnostic
- MUST apply
patternto the valuefieldresolved (not the whole source) when both are present, taking capture group 1 as the value when the pattern has one, else the entire match - MUST match a
messages:entry by case-insensitive equality onleveland by regex onmessage, treating either as match-any when omitted - MUST surface an ambiguity when a record matches more than one
messages:entry, rather than silently resolving to a first match - MUST resolve a
MessagePropertyonly from a live record’s stack, omitting the value silently when the record has no stack or the addressed frame/attribute doesn’t resolve
go/ parses and validates every field above, but does not evaluate live activity: it resolves no source/field/pattern and matches no messages: entry against a console record. The evaluation requirements in this section are normative for consumers that do evaluate, and are not yet exercised by the reference implementation or by the conformance fixtures.
Open questions
These are explicitly unresolved in v1 and candidates for SEPs:- Cross-sightmap (cross-project) component references — within-project is resolved by SEP-0002
- Parameterized memory — interpolating runtime values into memory entries
- Schema for validating the shape of
response.fieldsagainst real responses (todayfieldsis documentary, not enforced) - Macros — learned trajectories that replay and heal when the site changes (not yet in the spec)
../seps/README.md to propose.