Skip to main content
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 *.yaml and *.yml file 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 a components: array — at file root, within a view, or under children: — may be a reference object instead of an inline definition:
A reference is expanded inline (deep copy) to the named component’s full definition before matching. The name is resolved against a registry built from the root-level 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 children are 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 a button.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). children scoping 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 live document.querySelector, which does not cross shadow roots: a tool re-implementing matching against the live DOM MUST traverse shadow roots (walk each element’s shadowRoot) to agree with the corpus.

Component properties

A component may declare properties: 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

The dependencies 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 in dependencies 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:
  1. An entry’s resolved dependencies[] set contains its own source. Diagnostic code: dependencies.self-redundant.
  2. An entry’s resolved dependencies[] set contains a path that is the source of any other entry in the same .sightmap/. Diagnostic code: dependencies.overlaps-entry.
  3. A glob in dependencies[] resolves to zero files. Diagnostic code: unknown-source (existing vocabulary, narrowed to apply to dependencies[] 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 what requests: 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 as message-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’s pattern (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.
Design points:
  • 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/**/b matches /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
For requests, Express-style :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 whose route 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: or requests: 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 optional stability 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 optional tags: 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 optional reason.
  • snapshots (file-level) — named page states to capture (name, notes, url), used to enumerate capture/probe targets.
These are reserved rather than standardized: their shape may change, and other tooling need not implement them. Do not rely on them for cross-SDK matching behavior.

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 RequestProperty with no source, or a source outside the four-value enum (req.body/rsp.body/req.headers/rsp.headers)
  • MUST reject a RequestProperty that declares neither field nor pattern
  • MUST reject a RequestProperty whose source is a headers source but omits field
  • MUST reject a RequestProperty whose pattern is not a valid RE2 regular expression (see Regular expressions)
  • MUST reject a messages: entry whose message is not a valid RE2 regular expression (see Regular expressions)
  • MUST reject a MessageProperty with a source other than stack, or one that omits field, or whose pattern is not a valid RE2 regular expression
  • SHOULD surface memory entries to the agent when the parent definition is active
  • MAY ignore fields it doesn’t use (e.g. a consumer that never surfaces description at runtime)
  • MAY implement additional, non-standard behavior as long as it doesn’t change the meaning of conforming inputs
An SDK that also evaluates live activity (observed network requests, console records, DOM state) additionally:
  • MUST resolve properties: only from live traffic, and MUST NOT error when a properties:-declaring request is used in a static context — omit the value instead
  • MUST omit an unresolved property value silently, without a diagnostic
  • MUST apply pattern to the value field resolved (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 on level and by regex on message, 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 MessageProperty only from a live record’s stack, omitting the value silently when the record has no stack or the addressed frame/attribute doesn’t resolve
Not yet implemented in the reference SDK. The Go SDK under 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.fields against real responses (today fields is documentary, not enforced)
  • Macros — learned trajectories that replay and heal when the site changes (not yet in the spec)
See ../seps/README.md to propose.