> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sightmap.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> New features, improvements, and fixes in the sightmap CLI, library, and spec.

<Update label="sightmap 0.28.0" description="Released August 29, 2026" tags={["cli"]}>
  Component properties now resolve offline over the component tree (SEP-0010). `Matcher.Match` populates each `ComponentMatch.Properties` from the matched tree with no live DOM, so a downstream consumer holding a serialized component tree gets property values directly. The `extract` grammar is the four tree-closed forms — `text`, `attr=NAME`, `PATH.prop`, `exists:PATH` — and `transform` is removed from component properties. `sightmap validate` now checks component `properties[]`: duplicate names within a component and unrecognized/removed extract modes are errors.

  Retire the live-DOM component-property extraction pass and remove `transform` from request and message properties (SEP-0010). Component property values now come solely from the matcher's offline, tree-closed resolution — the CDP/JS extraction (`observe.ExtractProperties` and its `properties.js`) is gone, and `snapshot`/`bounds`/`query` read the values the matcher already resolved. Request and message `properties[]` keep `source`/`field`/`pattern`; fold any post-processing into `pattern` (RE2 + capture groups). `ApplyTransform` is removed.

  Port the DevTools extension's component-property extraction to the SEP-0010 tree-closed model. The embedded overlay (`resolver.js` and its inlined `content.js` copy) now resolves the four extract forms — `text`, `attr=NAME`, `PATH.prop`, `exists:PATH` — over the matched component tree, replacing the removed DOM-shaped modes (`inner_text`/`text_only`/`inner_html`/raw CSS sub-selector) and transforms. Descendant paths resolve a child component's own extracted property (`text` is the element's DOM text content, the extension's implementation-defined accessible text).

  Update the `sightmap-authoring` skill for SEP-0010: component `properties[]` now document the four tree-closed extract forms (`text`, `attr=NAME`, `PATH.prop`, `exists:PATH`) and the promote-a-sub-element-to-a-child-component pattern. Removed the DOM-shaped modes (`inner_text`/`text_only`/`inner_html`/raw CSS sub-selectors) and all `transform` guidance (including the request-properties `transform` reference).
</Update>

<Update label="sightmap 0.27.0" description="Released August 26, 2026" tags={["cli"]}>
  `sightmap browser start --detach` runs the daemon in the background (in its own session) and returns once it is serving, so scripts and agents no longer hang on the foreground daemon. Unlike `nohup start &`, the detached daemon survives the launching shell (it `setsid`s into its own session). `browser start` also now prints that it is a foreground daemon holding the shell, and `browser status` probes the sightmap HTTP server in addition to Chrome's CDP — reporting `⚠ degraded` when CDP is up but the server has been reaped, instead of a misleading `running`.

  `browser bounds` now accepts the full component-query grammar — property predicates and descendant chains — the same engine as `click`/`fill`/`hover`/`wait-for`. Previously it matched component names only, so `bounds 'Card[title="X"]'` or `bounds 'Row Star'` returned "matched no component". Multi-match is preserved (a query returns every matching component's box), `--substring` still does name-only fuzzy matching, and `--all` is unchanged. Also documented the existing `wait-for --component` and `--view` flags in `browser --help`.

  Authoring + browser skills: document the browser daemon lifecycle. `browser start` is a long-running foreground daemon that holds the shell; scripts and agents should use `browser start --detach` (which returns once serving and survives the shell) rather than `nohup start &`. Also note headless auto-detection and the `--no-sandbox` hint on sandboxed hosts, and that `browser status` can report a `⚠ degraded` daemon.

  `browser start` now comes up out of the box on headless and sandboxed Linux hosts. With no display (`$DISPLAY`/`$WAYLAND_DISPLAY` unset) it defaults to headless instead of dying with "Missing X server or \$DISPLAY", and when Chrome's launch fails because the host restricts its sandbox (unprivileged user namespaces clamped by AppArmor or a container) the error points straight at the fix (`--chrome-flag=--no-sandbox`) rather than leaving you to decode Chrome's stderr.
</Update>

<Update label="sightmap 0.26.3" description="Released August 25, 2026" tags={["cli"]}>
  Authoring skill: document the component model. Add "Component hierarchy (`children:`)" and "Cross-view references (`$ref`)" sections covering nested components, parent-scoped selectors, the depth budget, and `$ref` attestation. The browser skill now spells out that component queries use a descendant combinator (no `>` child combinator) and that a multi-match is an ambiguity error, not a missing component.

  Authoring skill: fix the "Further reading" pointers. They referenced `go/README.md` (not shipped with the installed skill) and `docs/reference.md` (which does not exist). The skill is self-contained — coverage model, outer loop, tool surface, lint rules, quality checklist, and the component model are all in it — and now points to the published docs for the normative spec instead of dead repo-relative paths.

  Authoring skill: fill three small content gaps. Document the `memory:` shape (a list of plain strings, attachable at the file root or on a view/component), add a route-matching note (`*` matches one path segment, `**` matches zero or more, and the URL is matched decoded so `%2F` counts as a separator), and reconcile the class-selector guidance (stable hand-authored class names are fine — the volatility risk is generated/hashed classes).
</Update>

<Update label="sightmap 0.26.2" description="Released August 24, 2026" tags={["cli"]}>
  `sightmap multi-coverage` no longer manufactures phantom "global candidate" promotions from stale capture directories. It grouped columns purely by directory name under `snapshots/`, so a leftover or renamed dir (e.g. an old `snapshots/views/` kept alongside the current `snapshots/home/` for the same page) became a second column and made that page's own components look like they "appear in 2+ views" — advising the author to wrongly globalize view-scoped components.

  A capture dir is now treated as a real view only when it matches a view in the *current* corpus (by `SnapBasename`). Non-current dirs are still shown in the matrix (marked `*`) for context but are excluded from the cross-view global-candidate analysis, and a warning names them so the author can delete the stale dir or re-capture under the current name. The `sightmap-authoring` skill documents the new behavior.

  Two authoring-clarity fixes for the offline inventory and the capture gate.

  `sightmap stats` now attributes corpus-root **global** components and requests to their own `(global) · (all views)` row instead of showing `0` against every view. A corpus whose coverage lives entirely in globals (e.g. a single-view app mapped with global components) previously rendered a per-view table that read as empty, with the confusing footer "per-view rows sum to 0". The globals are shown once, on a leading row, and the footer explains them — globals apply to every view, so folding them into one view's row would misattribute coverage. The `--json` contract is unchanged.

  `sightmap capture`'s novelty-gate message no longer reads like it is refusing a first baseline. The first capture of a view always writes (an empty set can't be redundant); reaching the gate means a baseline already exists and the new capture adds nothing, so the message now says so plainly ("`<view>` already has N capture(s); this one adds no new component or interactive slot — not saved"). The `sightmap-authoring` skill is updated to match and to reinforce the first-capture-always-writes guarantee.
</Update>

<Update label="sightmap 0.26.1" description="Released August 24, 2026" tags={["cli"]}>
  `browser install` now fetches the correct architecture on **linux-arm64**. It previously mapped every Linux host to the `linux64` (x86-64) Chrome for Testing build regardless of CPU, so arm64 machines silently got an x64 Chrome under `~/.sightmap/browsers/chrome-<ver>/chrome-linux64/`. The platform is now resolved per-arch (`linux-arm64` on arm64, `linux64` on x64). Because Google does not currently ship an arm64 build to the **Stable** channel, resolution falls back through Beta → Dev → Canary for platforms Stable does not carry, printing a one-line warning when it uses a non-Stable channel. Every mainstream platform continues to install from Stable unchanged.
</Update>

<Update label="sightmap 0.26.0" description="Released August 21, 2026" tags={["cli"]}>
  Authoring efficacy on hook-poor DOMs (Salesforce Lightning, Angular, framework-generated markup):

  * **Generalized selector-candidate generation.** `gap` and `suggest` no longer dead-end when a DOM has no `data-testid`/`data-component`. Candidate generation now ranks custom-element tags, design-system classes, ids, other stable `data-*`, and `name`/`href`/`aria` hooks (data-attributes remain the top-ranked input, not an override), dropping only clearly machine-generated tokens. `gap` also emits a container hook on the hook-poor path.
  * **New `explain` command.** Node-first inspection: pick nodes by selector, `--id`, or `--grep` (role/name) — live or offline via `--snap` — and dump each node's facts, ranked selector candidates, coverage tier + owning component, and ancestor hooks. Shadow-transparent (matches the offline matcher), so authors no longer hand-read `*.snap.tree.json`.
  * **Honest coverage.** `snapshot --coverage` and `gap` now warn when a clean "0 orphaned" pass is carried entirely by global (chrome) components — i.e. the current view modeled nothing and the pass reflects a global backstop rather than a real view.
  * **Authoring skill:** a second mandatory property rule requires a per-instance discriminator on any component whose selector matches more than one instance, so repeated cards/rows/tabs stay individually addressable; plus `explain` documentation.

  Fix the browser overlay extension rendering **0 components** on pages whose corpus the sightmap server serves correctly. The content script proxied its `/sightmap` fetch through the background service worker, and on a cold/just-woken MV3 worker that round-trip intermittently delivered an empty corpus at page load; the empty result then stuck for the life of the page because the per-instance `version` never changes (so `pollVersion` never refetched) and an open side panel keeps the worker alive. `sightmap snapshot` was unaffected (it reads the corpus in-process over CDP). The content script now fetches the local server **directly** on http-origin pages — `host_permissions` already grants `http://localhost:*` and the server sends `Access-Control-Allow-Origin: *` — using the background proxy only as an https mixed-content fallback, and it treats an empty corpus as "not loaded yet" and keeps retrying instead of caching it.

  Fix `snapshot`/`capture` rendering an empty component tree when the page root (or any ancestor) is reported invisible. `render.Filter` dropped an entire subtree at the first `IsVisible=false` node, but a zero-size or `visibility:hidden` ancestor — notably the document `<html>` root, which reports height 0 / ignored — can have fully visible descendants, so the whole annotated tree vanished even though coverage still counted the visible nodes. Invisible nodes are now made transparent (the node itself is omitted, visible descendants survive); a genuinely hidden `display:none` subtree still renders nothing, keeping render in lockstep with coverage.

  `sightmap stats` now reports a **Messages** count. The offline inventory listed views, components, requests, properties, and memory but silently omitted the SEP-0006 `messages:` entity, so a corpus's console/exception matchers were invisible in the totals (and in `--json`). Adds `messages` to the totals table and the `--json` contract.
</Update>

<Update label="sightmap 0.25.0" description="Released August 18, 2026" tags={["cli"]}>
  `browser start --attach host:port` attaches the daemon (devtools server +
  console/network collector) to an already-running Chrome's CDP endpoint instead
  of launching and owning its own. It is a deliberately degraded mode: no owned
  profile or extension guarantees, capture is complete only from attach onward
  (pre-attach network/console history can't be recovered — `Network.enable`
  replays nothing), and `browser stop` detaches rather than killing the
  caller-owned browser. The collector and devtools query surface are unchanged —
  the collector only ever needed a CDP address — so live console/exception and
  request matching work identically once attached.

  Also fixes a latent shutdown-order bug in the collector surfaced by attach mode:
  `Collector.Stop` cancelled the per-tab drain contexts *after* `wg.Wait()`, which
  deadlocked whenever the CDP connections were still healthy at stop time (the norm
  when a consumer stops the collector without tearing down the browser). Stop now
  cancels the drains first and refuses new tab attachments during teardown.

  And fixes extension-server-port injection under attach: `findExtensionSWWS` took
  the *first* extension service worker it found, so in a real browser with several
  extensions installed (the attach case) it injected the port hint into the wrong
  one and the sightmap overlay never learned the server port — falling back to
  probing 7891–7900 and latching onto whatever sightmap server answered first
  (often a different session). It now identifies the sightmap extension by its
  manifest name before injecting, so the correct overlay gets the hint. An owned
  launch never hit this: its isolated profile contains only the sightmap extension.

  The `browser start` devtools now surface extracted property values, not just
  matched-def names. The collector eagerly retains request/response bodies for
  XHR/Fetch traffic (request bodies inline from `requestWillBeSent`; response
  bodies fetched on `Network.loadingFinished`, off the event loop), so a buffered
  `Request` record is complete. `sightmap network list|get` annotate via
  `RequestsForRecord` — resolving each matched request's `properties[]` against the
  live headers/body — and `sightmap console list|get` surface a matched message's
  stack `properties[]`. Extracted values render as a trailing `{name=value, …}`
  token on list lines and a `Properties:` block in `get` detail, e.g.
  `GET /api/checkout/pay → 200 OK (Fetch) {outcome=declined}` — the "200 OK but the
  body says declined" case, now visible.

  Make selectors behave consistently across shadow DOM. The captured component tree already flattens shadow roots (so offline matching, `sel-probe`'s offline count, and coverage pierce shadow), but every live-DOM operation re-found nodes with `document.querySelector`, which cannot cross shadow boundaries — so property extraction, interaction (click/fill/hover/value/scroll/wait-for), `bounds`, `sel-probe`'s live count, and `suggest`/`discover` were silently shadow-blind and disagreed with the corpus. A shared shadow-piercing resolver (`browser.DeepQueryJS`) now backs all of them, so live operations reach shadow-DOM content the same way matching does (property extraction on shadow-DOM leaves, interaction with shadow-DOM controls, and discovery of shadow-DOM links now work). `spec/v1/schema.md` formalizes the selector/tree model and its shadow-DOM semantics — a deliberate divergence from live `document.querySelector`, which does not cross shadow roots.

  Expand the authoring and browser skills to cover the runtime spec additions.

  * **sightmap-authoring**: new "Requests and messages" section documenting `requests:` (route/method), request `properties:` extraction (SEP-0005 `source`/`field`/`pattern`/`transform`), and `messages:` (SEP-0006 `level`/`message` + `source: stack` stack-addressing properties); a new "Runtime activity" step in the per-page loop plus a runtime line in the "Done when" checklist so the loop routes authors to these entities; and view-route-generality guidance (a catch-all route that matches every page is a smell — carve specific routes).
  * **sightmap-browser**: reframe the `console`/`network` tooling as the runtime view of the corpus — each record leads with a `[Match]`/`[--]` slot and trails extracted `{name=value}` properties — with the observe-only "reproduce the traffic" note and `--url`-substring / SPA `wait-for` caveats.
  * Fix stale command references: `sel-probe -- 'selector'` (the bare `sel-probe 'selector'` form fails) and `gap --include-hidden` (the documented `--visible` flag does not exist).

  The reference network/console collector now populates the faithful-record fields
  added for SEP-0005/0006 runtime matching. Observed `Request` records carry
  request/response headers (from the CDP `requestWillBeSent`/`responseReceived`
  events — cheap, no extra round-trip) and an observed `DurationMs`; observed
  exception `Message` records carry the `Stack` (from `exceptionThrown`
  `stackTrace.callFrames`, throwing frame first, with 0-based line/column
  preserved). Bodies remain fetched on demand. This is what lets
  `RequestsForRecord` extract body/header properties and `MessagesForRecord`
  resolve stack properties against traffic the reference tooling captured.

  Make `network get` reliable and header-aware. The devtools body endpoint now serves the response/request body eagerly retained on the record (captured at `loadingFinished`) instead of always re-fetching it from Chrome via `getResponseBody`, which failed once the browser had evicted the body ("response body no longer available"). And `network get` now renders the captured request/response headers (they were already on the record from the collector but never surfaced). This closes the HTTP response header/body residual split out of the network-collector gaps.
</Update>

<Update label="sightmap 0.24.0" description="Released August 13, 2026" tags={["cli"]}>
  `match.Matcher` gains a chain-matching entry point for consumers that classify a
  stream of individual observed elements rather than a full component tree. Each
  observed element carries only its own root→leaf ancestor chain, so there was no
  first-class API for it — a consumer had to reimplement the spec's component
  identity/tag resolution by hand (and route-blind). `MatchChain(chain, pageURL)`
  runs the shared NFA matcher over a single-branch spine built from the chain and
  returns depth-annotated `ChainMatch` values; `NamesForChain` applies the
  nearest-enclosing identity rule and `TagsForChain` the tag-union rule (deduped,
  sorted), so the spec's resolution lives once in the shared library. Matching is
  route-aware, so view-scoped components apply on the chain exactly as in a
  full-tree match. Purely additive packaging over already-exported pieces — no new
  matching semantics.
</Update>

<Update label="sightmap 0.23.0" description="Released August 13, 2026" tags={["cli"]}>
  `messages:` can now match and extract on exception **stack traces** (a SEP-0006
  follow-on). An observed `Message` gains an optional `Stack []Frame` (throwing
  frame first; `Frame{Function, File, Line, Column}`), empty for a plain console
  record. A `MessageDef` gains a stack-addressing `properties[]` mirroring
  SEP-0005's `source`/`field`/`pattern`/`transform`: the one source is `stack`,
  `field` names a frame and attribute (`top.file`, `top.function`, `1.line`, where
  `top` aliases frame `0`), an optional RE2 `pattern` refines the resolved value,
  and `transform` post-processes it. `Corpus.MessagesForRecord` now folds the
  extracted values into each `MessageMatch.Properties`, bringing it to parity with
  `ComponentMatch`/`RequestMatch`; unresolved values are omitted silently, so a
  plain console record (no stack) simply extracts nothing. The reference CLI
  validates the new declarations (`message-property-invalid-name` /
  `-source-invalid` / `-no-field` / `-pattern-invalid`).

  Observed `Request` records can now carry the payload a `RequestDef`'s
  `properties[]` addresses, and a new matcher resolves them — the runtime half of
  SEP-0005. `Request` gains optional (omitempty) `DurationMs`, `ReqHeaders` /
  `RspHeaders` (`[]Header`), and `ReqBody` / `RspBody` (`*Body`) fields, so a
  producer holding a full capture can populate them while a lazy producer leaves
  them nil and the wire stays lean. `Corpus.RequestsForRecord(rec) []RequestMatch`
  does route+method identity matching (via `RequestsForURL`) and then resolves each
  matched def's `properties[]` against the record — `source` → `field` (JSON
  dot-path with numeric array indexing for a `*.body`; case-insensitive header
  lookup for a `*.headers`) → optional RE2 `pattern` (capture group 1 else the whole
  match, or a scan of the raw source when `field` is absent) → optional
  `transform`. Unresolved values are omitted silently per the spec, so an
  incomplete record degrades to fewer properties rather than erroring.
</Update>

<Update label="sightmap 0.22.0" description="Released August 12, 2026" tags={["cli"]}>
  Add `sightmap export [dir]`: load a `.sightmap/` corpus and emit the canonical
  Corpus wire — the exact `json.Marshal(sightmap.Corpus)` shape a library consumer
  reads (`selectors[]` arrays, components nested under each view, plus `requests`
  and `messages`) — to stdout, a file (`-o FILE`), or an HTTP endpoint (`--url`,
  POSTed as `application/json` with no auth headers). The `.sightmap/` directory is
  auto-detected by walking up from `[dir]` or the cwd, and TLS verification is
  skipped for local hosts (`localhost`, `127.0.0.1`, `.test`) or under an explicit
  `--insecure`. A companion `sightmap push URL [FILE]` POSTs a corpus JSON (from a
  file or stdin) through the same transport.

  This replaces the hand-rolled Python collector (`collect_and_upload_sightmap.py`)
  that shipped a second, lossy serializer — it flattened views into compound
  selectors and dropped routes, requests, and messages. Routing the upload through
  the Go loader makes it the single source of truth and shares the exact
  `sightmap.Corpus` type with the server-side reader, so the two ends cannot drift.

  `sightmap serve` and the `browser start` daemon now serve the canonical Corpus
  wire — the same shape library consumers see (`selectors[]` arrays, components
  nested under each view) — inside a thin `{site, version, corpus}` envelope,
  replacing the bespoke pre-compiled shape. The bundled browser extension consumes
  it directly; cache-busting via `/sightmap/version` is unchanged.
</Update>

<Update label="sightmap 0.21.0" description="Released August 12, 2026" tags={["go"]}>
  Restructure the Go library for downstream consumers. All corpus vocabulary,
  observed runtime records, and match-result types now live in a single
  self-contained `sightmap` package, with the matching engines consolidated behind
  one `match.Matcher`. Types follow a consistent naming model — `…Def` for a spec,
  bare structs for observed records, `…Match` for results — and extracted values
  share a typed `PropertyValue`. This is a breaking change to the Go import surface
  for library consumers (there were none before this release).

  Add runtime matching for console/exception messages. `Corpus.MessagesForRecord`
  classifies an observed console record against `messages:` definitions
  (case-insensitive level equality + an RE2 `message` regex, precompiled at load),
  returning every match so an ambiguous record is surfaced rather than silently
  resolved. The `sightmap console` and `sightmap network` devtools listings now
  annotate each captured record with the corpus definitions that classify it —
  messages via `MessagesForRecord`, requests via route+method identity — leading
  each line with the match.
</Update>

<Update label="sightmap 0.20.0" description="Released August 10, 2026" tags={["cli"]}>
  Add a top-level `messages:` entity ([SEP-0006](https://github.com/sightmap/sightmap/blob/main/spec/seps/0006-message-entity.md)): named console-output and exception patterns, matched by `level` and a `message` regex. This gives console activity what `requests:` gives network activity, a named entity the rest of the corpus can reference by name.

  **An uncaught exception arrives as `level: exception`, not `level: error`.** The reference capture emits `log`, `debug`, `info`, `warn`, `error`, and `exception`, so `level: ERROR` matches `console.error` and does not match an exception. SEP-0006 still needs no `kind:` discriminator, because the origin is carried as a level value, but a corpus that wants exceptions has to name them.

  New validation:

  * `message-regex-invalid` (error) compiles `message` at validation time, matching how component selectors are already checked. The corpus no longer stores a pattern nobody has proven is a pattern. The dialect is pinned to **RE2** (Go `regexp` / the `re2` npm package for JS): a linear-time syntax with no backreferences or lookaround, so authoring-time validation and runtime matching agree across SDKs.
  * `merge-collision-message` (warning) reports a duplicated name. This one is load-bearing for SEP-0007: `ref:` resolution counts distinct entity kinds, so two messages sharing a name collapse to one kind and the ambiguity check never fires.
  * `message-conflict` (warning) reports two entries that can match the same record, where that overlap is statically decidable: same `level`, identical or absent `message`.
  * `message-level-unknown` (lint) catches a level outside the emitted vocabulary. The realistic trap is `WARNING`, CDP's own spelling, which the capture normalizes to `warn`.
  * `field-type-invalid` (error) now covers message fields, so `message: 404` and `level: 500` are rejected in Go as ajv already rejected them.

  This resolves SEP-0006's open question on ambiguous-match diagnostics, which the SEP delegated to its implementation.

  Matching is not implemented: the SDK parses and validates these declarations but does not evaluate them against console records, even though the capture layer already collects both console output and exceptions.

  Add `properties:` to request definitions ([SEP-0005](https://github.com/sightmap/sightmap/blob/main/spec/seps/0005-request-properties.md)): named values a consumer extracts from a live request/response pair, so a `200 OK` whose body says a payment was declined can be reasoned about.

  * `source` names the root (a closed enum: `req.body`/`rsp.body`/`req.headers`/`rsp.headers`).
  * `field` selects a value within `source`: an object-key dot-path for a body source, a header name for a headers source (required there).
  * `pattern` is an **RE2** regex (Go `regexp` / the `re2` npm package for JS; no backreferences or lookaround) that refines what `field` resolved, or scans the raw source text when `field` is absent. `field` and `pattern` compose (`anyOf`) instead of being mutually exclusive.
  * `transform` shares the component-property vocabulary (unchanged; cleanup tracked separately).

  New validation, closing a gap where the Go SDK accepted corpora the JSON Schema rejected:

  * `request-property-invalid-name`, `request-property-no-extractor`, `request-property-source-invalid`, `request-property-headers-require-field`, and `request-property-pattern-invalid` (errors) enforce in Go what only ajv enforced before.
  * `request-property-shadows-reserved` (warning) fires when a property is named `status`, `method`, or `duration`, which shadows the request's HTTP identity and makes it unreachable from a signal filter.
  * `field-type-invalid` (error) rejects an unquoted non-string scalar in a schema-string field. yaml.v3 decodes any scalar into a Go string by taking the raw lexeme, so `source: 200` used to load as `"200"` while ajv rejected it.

  Extraction itself is not implemented: the SDK parses and validates these declarations but resolves no `source`/`field`/`pattern` and applies no transform. `spec/v1/schema.md` marks the evaluation requirements as such, and the `018-request-properties` conformance fixture is now executed by the Go test suite.
</Update>

<Update label="sightmap 0.19.0" description="Released August 7, 2026" tags={["cli"]}>
  Add `sightmap atlas`: `find` searches the community atlas at sightmap.org/atlas, `list` browses it, and `add` installs one corpus into `.sightmap/`.

  `find` matches slug, name, domains, categories, and description, ranked so an exact domain match comes first, because an agent about to automate a site starts from a URL and has no way to guess a slug. Each hit prints its own `sightmap atlas add` command, and a query that matches nothing exits 0.

  `add` fetches one `.tar.gz` and never reads the index, so an index outage cannot stop an install. It refuses a non-empty target before touching the network; there is no `--force`. `--index` and `--source` point the verbs at a mirror or a private corpus store, under the same HTTPS-only transport policy and the same archive caps.

  New public API: `sightmap.Totals`, the five corpus-wide counts split out of `sightmap.Stats`, which now embeds it. Field access and the `sightmap stats --json` output are unchanged — the JSON is byte-identical — but a composite literal must now write `Stats{Totals: Totals{Views: 1}}`. The split exists because a published catalog entry carries these five numbers as its `stats` object and puts the per-view rows in a sibling field, so `atlas.Entry.Stats` is a `sightmap.Totals`: one definition of what the counts mean, and no `PerView` field that no valid catalog could ever fill. As a side effect `sightmap atlas find --json` now prints a count that is zero rather than omitting it, and reports `properties` and `memory`, which it previously discarded.

  Add corpus statistics, in the library and on the CLI.

  New public API: `sightmap.Stats`, `sightmap.ViewStats`, and `(*Corpus).Stats()`, so any consumer of a loaded corpus — the atlas index generator, Subtext — gets the counts without shelling out to the CLI. `Components` counts distinct component names corpus-wide (a global reused by three views is one component), while `Properties` and `Memory` are summed over distinct component *definitions*, so a `$ref`-expanded copy counts once but two views that each define a different component under the same local name both count. `Stats.IsEmpty` reports a corpus with nothing in it — memory alone is not nothing.

  New `sightmap stats` verb over that API: the totals plus a per-view component/request table, and `--json` for a stable machine-readable form (`views` / `components` / `requests` / `properties` / `memory` / `per_view`) suitable for CI consumers. `stats` refuses a corpus that `sightmap validate` rejects, since the loader drops the definitions it cannot resolve and the counts would silently under-report; in `--json` mode the failure is itself JSON (an `error` key plus `diagnostics`), so a consumer always has something to parse.
</Update>

<Update label="sightmap 0.18.0" description="Released August 5, 2026" tags={["go","cli"]}>
  **Breaking:** rename the rule-side corpus types to the `Def` convention — `match.SightmapComponent` → `match.ComponentDef` and `match.SightmapMatch` → `match.ComponentMatch`. A `ComponentDef` describes what to match; a `ComponentMatch` is the result of matching one. No behaviour change; callers update to the new names.

  Add `sightmap init`: scaffold a schema-correct `.sightmap/` corpus (a commented `components.yaml` and `views/example.yaml` carrying `version: 1` and the top-level `views:` wrapper) so the first files an author sees are already valid instead of written from memory. Existing files are never overwritten, and the scaffolded corpus passes `sightmap validate` as-is.

  **Breaking:** split the matching engine out of `Corpus`. `Corpus` is now pure, serializable data; build a `Matcher` with `NewMatcher(corpus)` to match a live component tree. `Corpus.MatchTree` and `Corpus.Components` move to `Matcher.MatchTree` / `Matcher.Components` (the per-URL compiled-query cache now lives on the `Matcher`). Read-only corpus queries (`ComponentsForURL`, `ViewForURL`, `RequestsForURL`, `GlobalComponentNames`) stay on `Corpus`.

  Model `requests:` in the corpus and make it serializable to a stable wire form:

  * Global and view-scoped API request definitions (`RequestDef` / `Payload` / `Field`) are now parsed into the corpus — previously `requests:` was known to the schema but silently dropped.
  * New `Corpus.RequestsForURL(url, method)` returns every request whose route glob (and optional method) matches the observed request — "all matches apply", per the spec. Reuses the existing route matcher (`:param`, trailing-slash, `**`).
  * `Corpus`, `View`, and `RequestDef` now carry JSON tags, so a corpus serializes to a lean wire form (`memory` / `globals` / `views` / `requests`); authoring-only `View` fields (`url` / `access` / `snapshots` / `sourceFile` / `stability`) are excluded. The flattened list is emitted in a stable pre-order (lexical file order, then declaration-order depth-first), locked by a test, so the wire form is reproducible.

  First-run papercuts fixed:

  * Subcommand `--help`/`-h` now exits 0 and no longer prints `flag: help requested` — a help request is a success, not an error (#117).
  * `browser status` on a session file that exists but doesn't parse (e.g. hand-written or corrupted, so no valid `port`) now reports it as an unrecognized format with the expected shape, instead of a misleading "server and CDP were assigned the same port (0)" collision hint. The collision hint is also gated on a real server port (#118).
  * `validate` no longer warns about unknown fields in tooling files it owns (`survey.yaml`), as it already skips `config.yaml` (#119).

  Make `browser start` work — and fail legibly — on Linux and in root containers (the standard agent/CI environment):

  * `FindChrome` now checks the sightmap-managed Chrome for Testing install (`~/.sightmap/browsers/`) on Linux and Windows, not just macOS, so the documented `browser install` → `browser start` flow works. The "no Chrome found" errors now point at `browser install`.
  * `browser start` adds `--no-sandbox` automatically when running as root (Chrome refuses to start otherwise), and accepts `--chrome-flag` (repeatable) and `--chrome-binary` to override the launch.
  * On a startup timeout, `browser start` now reports the resolved binary, the full argument list, and the tail of Chrome's stderr — instead of a bare "timed out waiting for CDP" that hid the real cause.

  Three first-run papercuts:

  * `validate` now errors on an unsupported `version:` value (e.g. `version: 2`) — the spec requires `version: 1`. This is the companion to the missing-`version:` warning.
  * `browser install --help` prints usage and exits 0 instead of ignoring its arguments and starting the 184 MB Chrome-for-Testing download (the subcommand now parses its flags).
  * `search` names the near-miss when a pattern matches a **view** or **request** name (search covers component fields only), instead of a bare "no matches".

  Route matching: `**` is now a proper globstar. As a whole path segment it matches zero or more segments, so `/admin/**` matches `/admin` itself (as the spec always stated) as well as `/admin/users` and deeper, and `/a/**/b` matches `/a/b`. A `**` glued into a segment (e.g. `/foo**`) is treated as a regular single-segment `*` that does not cross a `/`. This fixes the previous behaviour where `/admin/**` failed to match `/admin` and `/messages**` failed to match `/messages`.

  Remove the authoring skill's instruction to run `sightmap browser register --addr localhost:PORT` — that subcommand does not exist, so agents following it dead-ended. Attaching to an externally-launched browser remains a possible future feature (tracked upstream); until it lands the skill no longer documents it.

  `report` and `capture` errors now teach the `views:` file structure instead of naming a field in isolation:

  * `report` distinguishes "no views defined at all" from "views exist but none has a `url:`", and both print a minimal `views:` example (with `route:` and `url:` and their roles) — previously it always said "no views with URLs found / Add a url: field", which contradicted `capture`'s route-only advice.
  * `capture`'s "no view matches" error shows the same `views:` example and names `route:` explicitly.

  The `sightmap-authoring` skill's Phase 1a now shows a complete view-file example (the top-level `views:` list with `version:`/`route:`/`url:`), instead of only telling authors to "create a view file with `route:`" — which invited putting view fields at the file root (silently making it a globals file).

  Teach the corpus schema at the point authors get it wrong, instead of failing silently or generically:

  * A **view field at the file root** (e.g. a top-level `route:`/`name:`) now warns that view fields belong under a top-level `views:` list, with a short example, instead of a bare "unknown field".
  * A **view-shaped file** that sets `url:` and `components:` but no `views:` now warns that it defines no views and its components are treated as global (previously it validated clean and silently became a globals file).
  * A **missing `version:`** now warns (the spec requires `version: 1` in every corpus file).
  * `validate` now warns when the **whole corpus has global components but no views** — it can never match a view, so capture, report, and per-view coverage are unavailable.

  All are advisory warnings (exit 0); correct corpora stay warning-free.
</Update>

<Update label="sightmap 0.17.0" description="Released July 31, 2026" tags={["go"]}>
  Go library: components can now carry `source:` (relative path to the implementing file — already schema-recognized, previously dropped by the loader) and `tags:` (authored classification labels, e.g. `defect`). Neither is inherited by children, matching `memory`/`properties`/`stability`'s existing convention. `Tags` also flows through `ApplySightmap`'s match result alongside `Memory`. New `Corpus.AllComponents()` returns every component in the corpus (globals plus every view's), deduped by first-seen name — the flat, whole-corpus list a consumer building an upload payload or a lint/coverage report wants, replacing an equivalent hand-rolled loop in `cmd_lint.go`.
</Update>

<Update label="sightmap 0.16.1" description="Released July 30, 2026" tags={["go"]}>
  The release workflow now pushes a `go/vX.Y.Z` tag alongside the existing `vX.Y.Z` tag. This module lives in the repo's `go/` subdirectory rather than at the root, and Go's module versioning requires a nested module's tags to be prefixed with that subdirectory — so `go get github.com/sightmap/sightmap/go@vX.Y.Z` has never actually resolved to a tagged release, only to `@latest`'s branch-tip pseudo-version. The bare tag is unchanged and still drives everything else (goreleaser, npm publishing, the release-already-tagged check).
</Update>

<Update label="sightmap 0.16.0" description="Released July 30, 2026" tags={["go"]}>
  Go library: `Corpus.Memory` now carries file-level `memory` entries (the loader previously dropped them). Lower the module's `go` directive from 1.25.2 to 1.23, its actual dependency floor, so consumers aren't forced onto a newer toolchain than the code requires.
</Update>

<Update label="sightmap 0.15.10" description="Released July 30, 2026" tags={["cli"]}>
  Verify the changesets release automation end-to-end: no functional change.
</Update>

<Update label="sightmap 0.15.9" description="Released July 28, 2026" tags={["cli"]}>
  Offline selector matching now matches the live DOM for `id`, `class`, and SVG. Three gaps are closed so a selector `sel-probe` verifies live behaves the same way `snapshot`/`coverage`/`capture` see it offline:

  * **Attribute selectors on `id`** (`[id^="issue_"]`, `[id$=…]`, …) now match. `id` lives in a dedicated node field, so the matcher resolves `id`/`class` attribute selectors to those fields — not only to captured `attrs`.
  * **`placeholder`** is captured, so `input[placeholder="…"]` matches offline.
  * **SVG classes** are captured. On SVG elements `className` is an `SVGAnimatedString`, which broke the old extraction (it threw and dropped the whole selector); `probe.js` now uses `classList`, so `svg.lucide`, `[class*="lucide"]`, and `:has(svg.lucide-x)` match offline.

  The authoring skill and docs are corrected accordingly: attribute selectors on `class` and `id` are no longer described as "don't work offline" — they work, for HTML and SVG alike.

  Better handling of asynchronous SPA navigation, without baking implicit waits into `click`.

  * **`navigate` now reports client-side redirects.** It previously only saw server-side (HTTP) redirects, which are reflected by the load event; a client-side redirect that fires during hydration (an auth guard bouncing `/login → /`, or `/ → /workspace`) went unreported, so the caller was told it was somewhere it wasn't. `navigate` now waits briefly after load for a follow-up navigation and prints `(redirected to FINAL)` for those too.
  * **`wait-for` gains `--view` and `--component`.** These are the explicit, corpus-aware step boundaries to use after an action that should navigate (the act-then-wait split Playwright and Selenium use): `--view <Name>` waits until the current URL resolves to a named sightmap view, and `--component '<Query>'` waits until a component query — including property filters like `WorkItemRow[key="FALCON-7"]` — matches a node. Both auto-retry until they hold or time out loudly. `--url`, `--selector`, and `--load` remain the raw equivalents.

  `click` deliberately does **not** wait for or guess about resulting navigation — it acts, reports, and keeps its loud covered/off-screen refusals. Adds `browser.AwaitNavigation` (waits for `Page.frameNavigated` / `Page.navigatedWithinDocument`, settling chained redirects) behind `navigate`.
</Update>

<Update label="sightmap 0.15.8" description="Released July 27, 2026" tags={["cli"]}>
  Coverage and the annotated tree now agree on what counts as visible. `probe.js` set each node's `isVisible` from that element's *own* computed style, which misses ancestor-driven hiding: a control inside a closed `opacity:0` overlay (a dismissed dropdown or context menu) has computed `opacity:1` of its own, so it reported visible even though it is painted nowhere. Coverage counted these while the renderer dropped the hidden container's subtree, so on real apps a large share of a page's interactive nodes (all the closed menus) silently inflated the count and tanked the score.

  `probe.js` now computes `isVisible` with the browser's own `Element.checkVisibility`, which accounts for ancestors hidden by `display:none`, `visibility:hidden`, `opacity:0`, or `content-visibility` — keeping the layout/rendering judgment in the real browser instead of re-deriving CSS semantics. `snapshot`/`coverage` "(visible only)" now excludes descendants of hidden containers; `--include-hidden` still counts every interactive node. The renderer also drops invisible subtrees regardless of interactivity so it and coverage read one signal.
</Update>

<Update label="sightmap 0.15.7" description="Released July 27, 2026" tags={["cli"]}>
  `snapshot` now surfaces runtime match conflicts in a `[Conflicts]` section — the ambiguities that only exist against a live page, complementing the static corpus-conflict warnings `validate` emits. Two triggers:

  * **A single DOM node matched by more than one distinct component name.** Matching is first-match-wins, so only one applied and the others were silently dropped — which is also why a correct-looking component can report `0 matches`. The section names the node, the competing components, and which one won.
  * **Two or more views matching the current URL at equal specificity**, where declaration order alone decided the winner.

  Adds `match.FindConflicts` and `Corpus.TiedViews`; both are computed during `observe.Page`. (The view-tie half is provisional pending the route-specificity decision in the functional-decomposition proposal.)

  `validate` now surfaces two classes of invalid corpus the loader previously dropped silently (so they shipped with a green check):

  * **Missing required fields** — a component with no `selector` (or no `name`), and a view with no `name`, are now errors (`missing-selector` / `missing-name` / `missing-route`) instead of being quietly discarded during load.
  * **Unresolved `$ref`** — a `$ref` naming a component that no file defines is now a `ref-unresolved` error (matching the spec's MUST), instead of the reference being silently skipped.

  Both exit non-zero. The spec's diagnostic-code table documents the new error codes alongside the corpus-conflict warnings.

  `validate` now warns on unknown fields. The typed loader silently ignored any YAML key it didn't recognize, so a typo like `memroy:` — or a half-baked field — vanished without a trace. `validate` now walks the raw YAML and emits an `unknown-field` **warning** (not an error) for any key the spec doesn't define at its position, at any nesting depth. It warns rather than rejects, so authors can stash experimental fields (e.g. `macros:`) during development; recognized fields — including the reserved tooling fields `access` and `snapshots` — are never flagged, and `.sightmap/config.yaml` is excluded. This completes strict `validate` alongside the earlier required-field and `$ref` checks.

  Fix `report` and make the view `url:` field first-class. `url:` is now read **per view** (with a file-level `url:` as a default for views that omit their own), instead of only at the file level. Previously per-view `url:` was silently dropped, so `report` — which needs a representative URL per view — errored with `no views with URLs found` even when every view declared one. `url:` is also now part of the published schema (SEP-accepted alongside `properties`), so a corpus that declares it validates instead of failing `additionalProperties: false`.
</Update>

<Update label="sightmap 0.15.6" description="Released July 27, 2026" tags={["cli"]}>
  `validate` now distinguishes **errors** (which fail validation, exit 1) from **warnings** (advisory, exit 0), and warns on silent corpus conflicts that a fallback rule was resolving without telling you:

  * **`merge-collision-view`** — two or more views share a `name`.
  * **`route-conflict`** — two or more views share the same (normalized) `route`; only the first-declared applies to that URL (this is the "same-route hijack" that can silently drop a view's components).
  * **`merge-collision-component`** — two or more root-level global components share a `name` with different selectors.

  Findings now carry a stable `code` and `severity`. Scoped component name reuse (the same child under multiple parents) is correctly *not* flagged, since that is intentional. The spec's diagnostic-code table documents the new corpus codes.

  Make `snapshot` loud about corpus-match state instead of silently omitting it. When a corpus is loaded but no view's route matches the URL (e.g. an auth redirect to a login page), the output now shows a `[No view matched] <url>` notice instead of a headerless tree that looks identical to a normal snapshot — so an agent knows the page is off the map. And the `[Coverage]` summary is now printed whenever a corpus is applied, including for a matched view that has no components yet (every interactive node reads as an orphan); previously that case printed only the view header, leaving the documented `snapshot --coverage` bootstrap with nothing to iterate on.
</Update>

<Update label="sightmap 0.15.5" description="Released July 26, 2026" tags={["cli"]}>
  Make `browser click` and `fill` fail loudly instead of silently no-op'ing. `click` now scrolls the target to the center of the viewport and verifies its center is the top-most element there before dispatching — it errors when the target can't be positioned in the viewport (previously an off-screen target below the fold was clicked at a coordinate that hit nothing and still exited 0) or is covered by another element (an open overlay/modal). The success confirmation reports the real post-scroll coordinates. `fill` now reads the value back after typing and errors when a non-empty value was typed but the field is still empty — the signature of a React-controlled input where plain typing doesn't stick — telling you to retry with `--clear`.

  `sel-probe` now cross-checks the offline matcher. It queries the live DOM as before, but also runs the same selector through the offline matcher that `snapshot`/`coverage`/`capture` use, prints that count, and emits a `⚠ offline/live divergence` warning (with a hint) when the two disagree. This closes the false-confidence trap where a selector "verified" against the live DOM is silently dead in the corpus — e.g. attribute selectors on `id` (`[id^="…"]`) match live but never offline, because `id` is a dedicated node field rather than a matchable attribute.
</Update>

<Update label="sightmap 0.15.4" description="Released July 26, 2026" tags={["cli"]}>
  Make browser-use feedback loud: the interaction commands (`click`, `fill`, `hover`, `keypress`, `scroll`, `drag`, `wait-for`, `dialog`, `tabs resize`) now echo a short confirmation on success instead of exiting silently — e.g. `clicked CartNavButton @ (674,30)` — so an agent driving the browser can see what happened. Several raw CDP/Go errors are now rewritten into actionable messages: a `wait-for` timeout reads `timed out after 800ms waiting for selector "..."` (not `context deadline exceeded`), resolving a dialog when none is open says so plainly, and fetching an evicted network response body explains that bodies are only retained briefly. `snapshot` now prints a note when no `.sightmap` corpus is found at the target directory instead of silently rendering an un-annotated tree.
</Update>

<Update label="sightmap 0.15.3" description="Released July 26, 2026" tags={["cli"]}>
  Stop green-lighting broken corpora and blank pages:

  * `snapshot` now exits non-zero when the corpus is present but fails to parse (a missing corpus stays non-fatal — the tree still renders), and when the observed page has 0 interactive nodes (blank or still loading). It still renders whatever it observed first. `inspect` and `suggest` now warn on a bad corpus instead of silently ignoring it.
  * Coverage no longer marks a page with 0 interactive nodes as a pass. The coverage line renders a distinct `∅` mark (instead of `✓`), the `coverage` command counts empty captures as failures, and `capture` refuses to persist a blank/loading page as a view's baseline (override with `--force`).
</Update>

<Update label="sightmap 0.15.2" description="Released July 26, 2026" tags={["cli"]}>
  Make corpus loading robust against two malformed inputs that previously failed badly:

  * A circular `$ref` chain (`A → B → A`, or a component that references itself) sent every corpus-loading command into infinite recursion and hung the process. Loading now detects the cycle, stops expanding, and `validate` reports it as a `ref-circular` error instead of hanging.
  * `splitSelectors` only balanced parentheses, so a comma inside an attribute selector or quoted string (`[data-x="a,b"]`) was wrongly treated as a selector-list separator and split into two dead alternatives. Splitting is now aware of `[]` brackets and quoted strings (with backslash escapes), matching how CSS is actually written.

  Fix view route matching so it behaves as the spec documents. Trailing slashes are now normalized off both the route pattern and the URL path before matching, so a slash-free route like `/*/projects` matches the `/acme/projects/` paths that Django, Rails, and many React-Router apps emit — previously such URLs matched no view at all, silently dropping the `[View:]` header, view memory, and view-scoped components. Express-style `:param` segments in view routes now match a single path segment (and score between a literal and `*` for specificity). And `Corpus.ViewForURL` now returns the *most specific* matching view — using declaration order only to break equal-specificity ties — instead of the first match in corpus order, closing a divergence between the exported library and the spec.
</Update>

<Update label="sightmap 0.15.1" description="Released July 26, 2026" tags={["cli"]}>
  Isolate concurrent browser sessions so agents working in different projects no longer cross-talk. The session file is now keyed to `--sightmap-dir` (it lives at `<sightmap-dir>/.session`) instead of a single shared `$TMPDIR` file, so a second `browser start` only ever reuses the Chrome for *its own* corpus rather than piggybacking on another agent's browser. Free-port probing now checks the IPv4 loopback (where Chrome's CDP and the sightmap server bind), and the sightmap server binds `127.0.0.1`, so two daemons started on the default ports slide to non-overlapping ports instead of one daemon's server colliding with another's CDP port. Every session-aware `browser` command (including `console`/`network`, `status`/`stop`, `tabs`, and the low-level interactions) now accepts `--sightmap-dir` to select which session it talks to.

  Fix the Sightmap overlay getting stuck in a reload loop that flooded the captured console. The extension's version poll compared the raw `/sightmap/version` JSON text against the parsed version string, so it never matched and re-fetched every few seconds. The poll now parses the response and compares the `version` field. Also drop the post-install `chrome.runtime.reload()` step: a fresh `browser start` always relaunches Chrome with `--load-extension`, which loads the new unpacked extension directly, and the hot-reload could leave the overlay's content script uninjected until the next full restart.
</Update>

<Update label="sightmap 0.15.0" description="Released July 25, 2026" tags={["cli"]}>
  CLI teardown: reorganize the tooling around reusable library subsystems and add browser devtools.

  * Separate `snapshot` (observe: annotated tree + coverage) from `capture` (persist into a view's set); extract the `observe`, `coverage`, `viewset`, and `authoring` packages and merge the internal `Session` into `Corpus`.
  * Make the `browser start` daemon the single session owner: retire `--launch` and the `browser launch` subcommand (live commands attach to a started session).
  * Add `console` and `network` devtools commands, backed by a session-lifetime collector in the daemon that buffers console messages (with uncaught exceptions folded in) and network requests, with lazy response bodies.
  * Add screenshot clipping to `browser screenshot` (`--component` / `--selector` / `--expand-pct`).
</Update>
