@crvouga/atlas 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/dist/core/src/cli.d.ts +2 -2
- package/dist/core/src/cli.js +7 -1
- package/dist/critics/src/catalog-client.d.ts +91 -0
- package/dist/critics/src/catalog-client.js +194 -0
- package/dist/critics/src/cli-calibrate.d.ts +4 -0
- package/dist/critics/src/cli-calibrate.js +211 -0
- package/dist/critics/src/cli-common.d.ts +108 -0
- package/dist/critics/src/cli-common.js +155 -0
- package/dist/critics/src/cli-review.d.ts +4 -0
- package/dist/critics/src/cli-review.js +174 -0
- package/dist/critics/src/cli-watch.d.ts +4 -0
- package/dist/critics/src/cli-watch.js +191 -0
- package/dist/critics/src/cli.d.ts +13 -0
- package/dist/critics/src/cli.js +490 -0
- package/dist/critics/src/index.d.ts +3 -0
- package/dist/critics/src/index.js +3 -0
- package/dist/critics/src/judging.d.ts +8 -0
- package/dist/critics/src/llm.d.ts +120 -0
- package/dist/critics/src/llm.js +266 -0
- package/dist/critics/src/marks.d.ts +74 -0
- package/dist/critics/src/marks.js +76 -0
- package/dist/critics/src/registry.d.ts +125 -0
- package/dist/critics/src/registry.js +203 -0
- package/dist/critics/src/spool.d.ts +32 -0
- package/dist/critics/src/spool.js +129 -0
- package/dist/critics/src/uiclip.d.ts +82 -0
- package/dist/critics/src/uiclip.js +167 -0
- package/dist/reports/src/compose.d.ts +23 -1
- package/dist/reports/src/compose.js +29 -19
- package/dist/reports/src/critique-review.d.ts +111 -0
- package/dist/reports/src/critique-review.js +123 -0
- package/dist/reports/src/critique-row.d.ts +24 -0
- package/dist/reports/src/critique-row.js +39 -0
- package/dist/reports/src/evidence-types.d.ts +5 -0
- package/dist/reports/src/layer-merge.js +5 -0
- package/dist/reports/src/report-cli.d.ts +13 -0
- package/dist/reports/src/report-cli.js +6 -4
- package/dist/reports/src/report-index.d.ts +6 -0
- package/dist/reports/src/report-index.js +12 -0
- package/dist/reports/src/ui-catalog-cli.d.ts +10 -0
- package/dist/reports/src/ui-catalog-cli.js +391 -0
- package/dist/reports/src/ui-catalog-diff.d.ts +104 -0
- package/dist/reports/src/ui-catalog-diff.js +130 -0
- package/dist/reports/src/ui-catalog-entries.d.ts +147 -0
- package/dist/reports/src/ui-catalog-entries.js +190 -0
- package/dist/reports/src/ui-catalog-export.d.ts +82 -0
- package/dist/reports/src/ui-catalog-export.js +221 -0
- package/dist/reports/src/ui-catalog-publish.js +2 -2
- package/dist/reports/src/ui-catalog.d.ts +2 -0
- package/dist/reports/src/ui-catalog.js +1 -1
- package/dist/reports/src/ui-state.d.ts +2 -0
- package/dist/reports/src/ui-state.js +2 -2
- package/dist/schema/src/api.d.ts +653 -1
- package/dist/schema/src/api.js +44 -0
- package/dist/schema/src/cli-results.d.ts +991 -1
- package/dist/schema/src/cli-results.js +265 -1
- package/dist/schema/src/critique.d.ts +31 -0
- package/dist/schema/src/critique.js +26 -0
- package/dist/schema/src/server-config.d.ts +3 -3
- package/dist/server/src/app.js +28 -1
- package/dist/server/src/openapi.js +19 -2
- package/docs/agents.md +16 -2
- package/docs/critics.md +273 -9
- package/docs/extending.md +1 -1
- package/docs/reference/cli.md +126 -1
- package/docs/reference/core.md +1 -1
- package/docs/reference/critics.md +1131 -29
- package/docs/reference/http-api.md +40 -0
- package/docs/reference/reports.md +855 -0
- package/docs/reference/schema.md +197 -1
- package/docs/report-contract.md +2 -1
- package/docs/server.md +2 -0
- package/docs/ui-catalog.md +110 -13
- package/docs/ui.md +69 -2
- package/llms-full.txt +636 -26
- package/llms.txt +1 -1
- package/package.json +9 -1
- package/packages/critics/README.md +1 -0
- package/packages/schema/schemas/critic-calibrate-result.schema.json +274 -0
- package/packages/schema/schemas/critic-doctor-result.schema.json +74 -0
- package/packages/schema/schemas/critic-list-result.schema.json +90 -0
- package/packages/schema/schemas/critic-review-result.schema.json +397 -0
- package/packages/schema/schemas/critic-run-result.schema.json +249 -0
- package/packages/schema/schemas/critic-setup-result.schema.json +34 -0
- package/packages/schema/schemas/critic-watch-result.schema.json +211 -0
- package/packages/schema/schemas/critique-mark.schema.json +81 -0
- package/packages/schema/schemas/effective-delta.schema.json +337 -0
- package/packages/schema/schemas/effective-evidence.schema.json +337 -0
- package/packages/schema/schemas/layer-rows.schema.json +337 -0
- package/packages/schema/schemas/ui-catalog-diff-result.schema.json +201 -0
- package/packages/schema/schemas/ui-catalog-export-result.schema.json +83 -0
- package/packages/schema/schemas/ui-catalog-publish-result.schema.json +116 -0
- package/packages/schema/schemas/ui-catalog-status.schema.json +197 -0
- package/packages/server/openapi.json +6755 -4738
- package/packages/visualizer/src/app/AppShell.tsx +3 -1
- package/packages/visualizer/src/app/router.tsx +20 -7
- package/packages/visualizer/src/catalog/Picture.tsx +32 -0
- package/packages/visualizer/src/catalog/UiCatalogCompare.tsx +289 -0
- package/packages/visualizer/src/catalog/UiCatalogPage.tsx +18 -28
- package/packages/visualizer/src/catalog/history.ts +61 -0
- package/packages/visualizer/src/catalog/search.ts +14 -3
- package/packages/visualizer/src/components/Header.tsx +16 -4
- package/packages/visualizer/src/critiques/CritiquesPage.tsx +402 -0
- package/packages/visualizer/src/critiques/FindingOverlay.tsx +50 -0
- package/packages/visualizer/src/critiques/ranking.ts +54 -0
- package/packages/visualizer/src/critiques/search.ts +41 -0
- package/packages/visualizer/src/evidence/applicability-view.tsx +11 -7
- package/packages/visualizer/src/evidence/behaviors-page.tsx +22 -15
- package/packages/visualizer/src/evidence/evidence-context.tsx +2 -1
- package/packages/visualizer/src/explorer/Explorer.tsx +5 -0
- package/packages/visualizer/src/explorer/TestDetail.tsx +4 -1
- package/packages/visualizer/src/explorer/parts.tsx +19 -1
- package/packages/visualizer/src/explorer/playlist.tsx +11 -7
package/llms-full.txt
CHANGED
|
@@ -113,6 +113,8 @@ The complete public export map is generated from the package manifest (wildcards
|
|
|
113
113
|
@crvouga/atlas/schema
|
|
114
114
|
@crvouga/atlas/server
|
|
115
115
|
@crvouga/atlas/critics
|
|
116
|
+
@crvouga/atlas/critics/uiclip
|
|
117
|
+
@crvouga/atlas/critics/llm
|
|
116
118
|
@crvouga/atlas/reports/*
|
|
117
119
|
@crvouga/atlas/schemas/*
|
|
118
120
|
@crvouga/atlas/openapi.json
|
|
@@ -160,7 +162,7 @@ and authentication. Keep synthetic failure checks in a scratch store rather than
|
|
|
160
162
|
## What an agent can rely on
|
|
161
163
|
|
|
162
164
|
**Machine-readable output.** Add `--format json` (alias `--json`) to `atlas init`, `atlas report publish`, `stream`,
|
|
163
|
-
`merge`, `doctor` and `
|
|
165
|
+
`merge`, `doctor`, `status` and `verify-recordings`, to `atlas ui-catalog`, and to `atlas critic list`, `setup`, `doctor`, `run`, `watch`, `review` and `calibrate`. Each prints one JSON document on standard output that validates against a published
|
|
164
166
|
JSON Schema (`@crvouga/atlas/schemas/<name>.schema.json`, draft 2020-12):
|
|
165
167
|
|
|
166
168
|
<!-- generated:json-command-schemas -->
|
|
@@ -173,6 +175,18 @@ JSON Schema (`@crvouga/atlas/schemas/<name>.schema.json`, draft 2020-12):
|
|
|
173
175
|
| `atlas report doctor` | `doctor-result` |
|
|
174
176
|
| `atlas report status` | `report-status` |
|
|
175
177
|
| `atlas report verify-recordings` | `verify-recordings-result` |
|
|
178
|
+
| `atlas critic list` | `critic-list-result` |
|
|
179
|
+
| `atlas critic setup` | `critic-setup-result` |
|
|
180
|
+
| `atlas critic doctor` | `critic-doctor-result` |
|
|
181
|
+
| `atlas critic run` | `critic-run-result` |
|
|
182
|
+
| `atlas critic watch` | `critic-watch-result` |
|
|
183
|
+
| `atlas critic review` | `critic-review-result` |
|
|
184
|
+
| `atlas critic calibrate` | `critic-calibrate-result` |
|
|
185
|
+
| `atlas ui-catalog plan` | `ui-catalog-publish-result` |
|
|
186
|
+
| `atlas ui-catalog publish` | `ui-catalog-publish-result` |
|
|
187
|
+
| `atlas ui-catalog status` | `ui-catalog-status` |
|
|
188
|
+
| `atlas ui-catalog diff` | `ui-catalog-diff-result` |
|
|
189
|
+
| `atlas ui-catalog export` | `ui-catalog-export-result` |
|
|
176
190
|
| `a command that was wrong (standard error)` | `command-error` |
|
|
177
191
|
<!-- /generated:json-command-schemas -->
|
|
178
192
|
|
|
@@ -181,7 +195,7 @@ pass, a store that needs attention, checks that could not be sent, a token that
|
|
|
181
195
|
itself was wrong (nothing was written). With `--format json` a wrong command is `{"ok":false,"error":{"message":…,"code":"usage"}}`
|
|
182
196
|
on standard error.
|
|
183
197
|
|
|
184
|
-
**No prompts, no interactive steps, no hidden state.** Configuration is flags and environment variables:
|
|
198
|
+
**No prompts, no interactive steps, no hidden state.** (`atlas critic` asks questions only when a person is at a terminal and never with `--non-interactive` or `--format json`.) Configuration is flags and environment variables:
|
|
185
199
|
`ATLAS_STORE` (a folder, `s3://…`, or the server's `https://…`), `ATLAS_TOKEN` (or `ATLAS_TOKEN_FILE`). A token is
|
|
186
200
|
a secret of one server and is never sent over plain `http://` beyond this machine.
|
|
187
201
|
For HTTP stores, `ATLAS_PROJECT` (or `--project <slug>` on report commands) selects the project. Native runner
|
|
@@ -1520,16 +1534,19 @@ thousands of captures can be watched while it happens and split across as many m
|
|
|
1520
1534
|
|
|
1521
1535
|
Nothing about it is new to the report contract. A state is an ordinary check with a screenshot, the expected set is an ordinary
|
|
1522
1536
|
layer, and the stream is the one every viewer already holds. This page is the convention that makes those usable as a catalog, the
|
|
1523
|
-
decisions behind it, and what
|
|
1537
|
+
decisions behind it, the command line, and what stays in your project.
|
|
1524
1538
|
|
|
1525
1539
|
- [How a state is written](#how-a-state-is-written)
|
|
1526
1540
|
- [What is expected](#what-is-expected)
|
|
1527
1541
|
- [Publishing from any collector](#publishing-from-any-collector)
|
|
1542
|
+
- [The command line](#the-command-line)
|
|
1528
1543
|
- [Sharding](#sharding)
|
|
1529
1544
|
- [Streaming and loading](#streaming-and-loading)
|
|
1530
1545
|
- [Exploring it](#exploring-it)
|
|
1546
|
+
- [Comparing two commits or two profiles](#comparing-two-commits-or-two-profiles)
|
|
1547
|
+
- [A single file that needs no server](#a-single-file-that-needs-no-server)
|
|
1531
1548
|
- [Decisions](#decisions)
|
|
1532
|
-
- [What stays in your project, and what
|
|
1549
|
+
- [What stays in your project, and what Atlas does not do](#what-stays-in-your-project-and-what-atlas-does-not-do)
|
|
1533
1550
|
|
|
1534
1551
|
## How a state is written
|
|
1535
1552
|
|
|
@@ -1541,7 +1558,7 @@ A state is one **check** in a layer whose part has `kind` **`ui-state`**.
|
|
|
1541
1558
|
| How the capture went | `status`: `passed` is **captured**, `failed` is attempted and failed (say why in `detail`), `running` is being taken, `not-run` is skipped. |
|
|
1542
1559
|
| The picture | An attachment with `role: "screenshot"` (else the first image), stored in the layer by content or given as an `https` `uri`. `width` and `height` help the viewer lay it out. |
|
|
1543
1560
|
| The profile | The layer's `environment` (`desktop-web`, `mobile-web`, `ios`, `android`) and `parameters` (`{ "viewport": "390x844", "scheme": "dark" }`). Parameters split a slot, so the same state at another viewport is **another result, side by side**, never a replacement. |
|
|
1544
|
-
| Facts the producer declares | The check's `extensions["atlas.ui-state"]`: `route`, `fixture`, `cohort`, `area`, `flags` (a list), `covers` (the product surfaces the state shows). All optional. |
|
|
1561
|
+
| Facts the producer declares | The check's `extensions["atlas.ui-state"]`: `route`, `fixture`, `cohort`, `area`, `kind` (the kind of UI state: `screen`, `overlay`, `loading`, `error`, `empty`… an open vocabulary that [critics](critics.md#reading-the-catalog) judge a state by), `flags` (a list), `covers` (the product surfaces the state shows). All optional. |
|
|
1545
1562
|
| Facts that describe without splitting | The check's `labels` (`shard`, `browser`) and `tags`. Labels never make a different slot. |
|
|
1546
1563
|
| Where it came from | The layer's `producer`, `sha`, `branch`, `runUrl`, `freshness`, `recordedAtMs`: shown with every capture, with the layer id and the backend that holds it. |
|
|
1547
1564
|
|
|
@@ -1614,6 +1631,35 @@ for a readiness check (bounded), photographs the page and returns the capture fo
|
|
|
1614
1631
|
a `failed` capture that says why (with a picture of what it showed), never an exception that ends the run. Nothing that reads,
|
|
1615
1632
|
composes or shows the catalog imports a collection framework, and a test keeps it that way.
|
|
1616
1633
|
|
|
1634
|
+
## The command line
|
|
1635
|
+
|
|
1636
|
+
`atlas ui-catalog` does the same without a script. Every subcommand prints a JSON document with `--format json`
|
|
1637
|
+
([`atlas ui-catalog --help`](reference/cli.md) lists every flag), and exits `0` it worked, `1` the answer is no, `2` the command was wrong.
|
|
1638
|
+
|
|
1639
|
+
```sh
|
|
1640
|
+
# The expected set under one profile. The file is {"states":[{"id":"checkout/payment","covers":["payment-form"]},{"id":"settings","inventory":true}]}.
|
|
1641
|
+
atlas ui-catalog plan --states plan.json --producer catalog-plan --environment mobile-web --param viewport=390x844 --param scheme=dark
|
|
1642
|
+
|
|
1643
|
+
# The states one collector or shard captured. "screenshot" names a file beside captures.json.
|
|
1644
|
+
atlas ui-catalog publish --captures captures.json --producer web-capture --environment mobile-web --param viewport=390x844 --param scheme=dark --shard 2/8
|
|
1645
|
+
|
|
1646
|
+
atlas ui-catalog status # exit 1 while anything failed, is missing, is still being captured or could not be read
|
|
1647
|
+
atlas ui-catalog diff --from 3f9a2c1 --to 8b41d70 # two commits (the whole id, or at least 7 digits)
|
|
1648
|
+
atlas ui-catalog export --out public/catalog.html # one file, no server
|
|
1649
|
+
```
|
|
1650
|
+
|
|
1651
|
+
`captures.json` is `{"states":[…]}` (or just the list); a state is `{"id","title"?,"status"?,"detail"?,"route"?,"fixture"?,"cohort"?,"area"?,
|
|
1652
|
+
"flags"?,"covers"?,"labels"?,"tags"?,"durationMs"?,"screenshot"?}` with `screenshot` a file name or `{"path"|"uri","width"?,"height"?,"contentType"?}`.
|
|
1653
|
+
A misspelled field is refused with its path, not ignored. A picture file is read from beside the file and from each `--attach-root`,
|
|
1654
|
+
never from a hidden file or a link out. **A `passed` state whose picture cannot be read is published as `failed`, with the reason**: a
|
|
1655
|
+
state without its picture is not shown as captured. `--recorded-at` defaults to the file's modification time, so publishing the same
|
|
1656
|
+
file twice is the same layer.
|
|
1657
|
+
|
|
1658
|
+
`status` is the gate: it exits `1` unless the catalog holds a state and nothing needs attention (a failed capture, a state a plan expects
|
|
1659
|
+
with nothing captured, a capture still in progress, a backend or layer that could not be read). Without a plan nothing is expected, so
|
|
1660
|
+
nothing can be missing: it says so. A skipped cell is counted, not a problem. Schemas: `ui-catalog-publish-result`,
|
|
1661
|
+
`ui-catalog-status`, `ui-catalog-diff-result`, `ui-catalog-export-result`.
|
|
1662
|
+
|
|
1617
1663
|
## Sharding
|
|
1618
1664
|
|
|
1619
1665
|
A shard is **any publisher that does not know the others**: by case, by profile, by area or by machine; Atlas does not care which.
|
|
@@ -1653,10 +1699,54 @@ and time. *Download the manifest* saves the filtered catalog as JSON.
|
|
|
1653
1699
|
curl "$ATLAS_SERVER_URL/api/reports/ui-catalog?coverage=failed,missing&profile=…" # JSON; add format=markdown for text
|
|
1654
1700
|
```
|
|
1655
1701
|
|
|
1702
|
+
A separate process that reviews the pictures (a [critic](critics.md#reading-the-catalog)) reads the same catalog as a flat list at
|
|
1703
|
+
`GET /api/reports/ui-catalog/entries`: cursor-paged, `since` a report version, with the hash of each stored picture.
|
|
1704
|
+
|
|
1656
1705
|
`GET /api/reports/ui-catalog` answers the same document the viewer computes (`uiCatalog` from
|
|
1657
1706
|
`@crvouga/atlas/reports/ui-catalog`), filtered by `q`, `coverage`, `profile`, `area`, `route`, `cohort`, `flag`, `surface` and
|
|
1658
1707
|
`producer`, paged with `limit` and `offset`, with an ETag. Its picture `path` is relative to `/api/reports`.
|
|
1659
1708
|
|
|
1709
|
+
## Comparing two commits or two profiles
|
|
1710
|
+
|
|
1711
|
+
The viewer's **Compare** button on `/catalog` (`?compare=commit&from=<commit>&to=<commit>`, or `?compare=profile&from=<profile>&to=<profile>`,
|
|
1712
|
+
so a link reproduces it) and `atlas ui-catalog diff` set one side against the other. Every pair is one state in one profile.
|
|
1713
|
+
|
|
1714
|
+
| A pair is | When |
|
|
1715
|
+
| --- | --- |
|
|
1716
|
+
| **Unchanged** | the same coverage and, when captured, the same stored picture |
|
|
1717
|
+
| **Picture changed** | captured on both sides, and the picture's bytes (SHA-256) differ. This is not a measure of how many pixels differ |
|
|
1718
|
+
| **Coverage changed** | it stands differently (captured against failed, failed against not captured yet…). A capture that did not pass is never compared by picture |
|
|
1719
|
+
| **Cannot tell** | captured on both sides, but a picture has no content hash (it is given only by address, or was left out) |
|
|
1720
|
+
| **Only after** / **Only before** | one side has no such cell. It may be new, renamed, or the other side's shard may not have run: it is not called a regression |
|
|
1721
|
+
|
|
1722
|
+
- **Two commits** compares the layers recorded at each commit. A layer replaces the older one of its slot, so the report keeps only the
|
|
1723
|
+
newest capture of every state; the comparison therefore reads the layers themselves (the viewer asks for the layer list and the rows of
|
|
1724
|
+
the two commits only; the command line reads the stores). Layers recorded from a checkout with uncommitted changes are not a commit's
|
|
1725
|
+
own result and are left out. A commit is the whole id or at least 7 digits that name exactly one commit; anything else is an error
|
|
1726
|
+
that lists the commits found, never a guess.
|
|
1727
|
+
- **Two profiles** sets each state's cell in one profile beside its cell in another (desktop against mobile, light against dark) in
|
|
1728
|
+
the report the viewer holds.
|
|
1729
|
+
- A layer or backend that could not be read is named, and what it held is unknown, never "unchanged": `diff` exits `1` for it.
|
|
1730
|
+
`--fail-on-change` also exits `1` for any difference, for a gate that must stay the same.
|
|
1731
|
+
- The filters (words, area, route, cohort, flag, surface, producer) narrow a comparison; coverage does not, because coverage is what is
|
|
1732
|
+
compared.
|
|
1733
|
+
|
|
1734
|
+
## A single file that needs no server
|
|
1735
|
+
|
|
1736
|
+
`atlas ui-catalog export --out catalog.html` writes the catalog as **one HTML file**: the pictures embedded as `data:` addresses, the
|
|
1737
|
+
manifest embedded as JSON (`<script type="application/json" id="ui-catalog">`), a search box and a coverage filter. Open it from a disk,
|
|
1738
|
+
attach it to a ticket or serve it as a file. It is the catalog at the moment it was made, and says so; it does not stream (for that,
|
|
1739
|
+
serve the store with `atlas serve` or build the site with `atlas client build`, which shows the same catalog).
|
|
1740
|
+
|
|
1741
|
+
- **Size.** Hosting platforms cap the size of one file. `--max-mb` (default 32) limits the embedded pictures; pictures past it are listed
|
|
1742
|
+
in the page as left out, with the reason, in the order the page shows them, and the manifest stays complete. `--images none` embeds none.
|
|
1743
|
+
`--manifest catalog.json` also writes the manifest as its own file.
|
|
1744
|
+
- **Safe to open.** The page is generated from data written by test runs, and treats it so: text is escaped, only PNG, JPEG, GIF and WebP
|
|
1745
|
+
bytes (by their first bytes, never SVG, never a layer's claimed type) are embedded, a picture given by address is linked only if it is
|
|
1746
|
+
`http` or `https`, and a Content-Security-Policy forbids every network request. Without scripts every state is simply shown.
|
|
1747
|
+
- It is a generated document, not the viewer: it carries its own small inline style and script because it must work with no build
|
|
1748
|
+
and no server, which [the viewer's UI conventions](ui.md) forbid inside the viewer.
|
|
1749
|
+
|
|
1660
1750
|
## Decisions
|
|
1661
1751
|
|
|
1662
1752
|
The questions the original proposal left open, and how they are answered here. Each is the simplest answer that stays inside the
|
|
@@ -1674,16 +1764,37 @@ The questions the original proposal left open, and how they are answered here. E
|
|
|
1674
1764
|
| What moves | Reading, composing, the viewer, the document and the API, the publish builders, a Playwright capture adapter. Staying with a product: its state declarations, its inventory, its fixtures and its runner. |
|
|
1675
1765
|
| Where it lives | `@crvouga/atlas/reports/ui-catalog` (model), `…/ui-catalog-publish` (builders), `…/ui-state` (convention); the viewer's `/catalog`; the server's `/api/reports/ui-catalog`; `@crvouga/atlas/playwright`'s `captureUiState`. |
|
|
1676
1766
|
| Exploration capabilities | Search, filters, profile comparison, coverage and surface coverage, failure diagnostics, deep links, manifest export. |
|
|
1677
|
-
| Static export | The catalog is computed from the layers a viewer holds, so
|
|
1767
|
+
| Static export | Two ways, neither needs a server. The catalog is computed from the layers a viewer holds, so the static site (`atlas client build`) shows it, without streaming. `atlas ui-catalog export` writes it as one self-contained HTML file with a size budget. |
|
|
1768
|
+
| Comparing | Two commits (from the layers recorded at each, because the report keeps only the newest) or two profiles; pictures by content hash, never by a pixel measure; one-sided states are not regressions. |
|
|
1769
|
+
| Command line | `atlas ui-catalog plan`, `publish`, `status`, `diff` and `export`, each with `--format json`. Running a collector is not one: that is the product's. |
|
|
1678
1770
|
|
|
1679
|
-
## What stays in your project, and what
|
|
1771
|
+
## What stays in your project, and what Atlas does not do
|
|
1680
1772
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1773
|
+
A project that had its own catalog moves each role it had to Atlas, and keeps only what is specific to its product. The eight roles, and
|
|
1774
|
+
where each now is:
|
|
1683
1775
|
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1776
|
+
| # | Role | In Atlas | Stays in your project |
|
|
1777
|
+
| --- | --- | --- | --- |
|
|
1778
|
+
| 1 | Declaration of the states to capture, and the plan of what is expected | The plan format (`ui-plan` layers), `uiPlanLayer`, `atlas ui-catalog plan` | **The list itself**: which states and profiles your product has |
|
|
1779
|
+
| 2 | A capture helper, with readiness checks and diagnostics | An optional adapter, `captureUiState` in `@crvouga/atlas/playwright`; any other tool writes the same layer | **The readiness condition of each screen**, and the helper for a framework Atlas has no adapter for |
|
|
1780
|
+
| 3 | An evidence store: one atomic record per capture, images by content | Layers and the stores that hold them (`publishChecks`, a directory, S3, a server) | Nothing |
|
|
1781
|
+
| 4 | An inventory of the product's surfaces, and reconciliation against it | Reconciliation: `inventory` plan entries and `covers`, the surface coverage table | **The inventory**: the list of surfaces your product has |
|
|
1782
|
+
| 5 | A manifest and report builder | `uiCatalog`, the JSON manifest, Markdown, the single HTML file | Nothing |
|
|
1783
|
+
| 6 | A viewer: as a single file, as a site, and live | `atlas ui-catalog export`, `atlas client build`, and `/catalog` on `atlas serve` | Nothing |
|
|
1784
|
+
| 7 | A command line (list, run, report, publish, watch) | `atlas ui-catalog plan\|publish\|status\|diff\|export`; watching is the live `/catalog` and `atlas report stream` | **Run**: the runner that drives your product to each state |
|
|
1785
|
+
| 8 | A publish step to a hosting platform | Not in Atlas, see below | **The deploy step** of your platform |
|
|
1786
|
+
|
|
1787
|
+
What stays is, in one list: the states and profiles to capture, the inventory of surfaces, the fixtures and test data, the runner that
|
|
1788
|
+
drives the product to each state, the readiness condition of each screen, and the deploy step of your hosting platform. Nothing else is
|
|
1789
|
+
specific to your product, so a project that installs a release holds none of the reporting and exploring code, only that.
|
|
1790
|
+
|
|
1791
|
+
**A hosting publish step is not a command, on purpose.** Publishing to a platform needs that platform's credentials and API, which Atlas
|
|
1792
|
+
neither holds nor should. Hand the platform what Atlas produces: the `export` file (or a folder of them) for any host that serves
|
|
1793
|
+
files, the output of `atlas client build` for a static site, or run `atlas serve` for a live one. Keep the credentials in the CI of the
|
|
1794
|
+
project, where they already are.
|
|
1795
|
+
|
|
1796
|
+
**Not built:** reconciliation against an external inventory beyond `inventory` entries and `covers`; a comparison of pictures by pixels
|
|
1797
|
+
(only by content); a comparison fetched from the server (the viewer and the command line read the layers themselves).
|
|
1687
1798
|
</doc>
|
|
1688
1799
|
|
|
1689
1800
|
<doc path="docs/server.md">
|
|
@@ -2234,6 +2345,8 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
|
|
|
2234
2345
|
| `GET /api/projects/{project}/reports/artifacts` | read | Recorded results that carry evidence, across the whole history. |
|
|
2235
2346
|
| `GET /api/reports/ui-catalog` | read | The UI catalog: captured UI states per profile, and what is still expected. |
|
|
2236
2347
|
| `GET /api/projects/{project}/reports/ui-catalog` | read | The UI catalog: captured UI states per profile, and what is still expected. |
|
|
2348
|
+
| `GET /api/reports/ui-catalog/entries` | read | The UI catalog as a list a critic iterates: captured states with their picture hash and critiques. |
|
|
2349
|
+
| `GET /api/projects/{project}/reports/ui-catalog/entries` | read | The UI catalog as a list a critic iterates: captured states with their picture hash and critiques. |
|
|
2237
2350
|
| `GET /api/reports/runs/detail` | read | Every test one run recorded. |
|
|
2238
2351
|
| `GET /api/projects/{project}/reports/runs/detail` | read | Every test one run recorded. |
|
|
2239
2352
|
| `GET /api/reports/outline` | read | The specs a report is laid over. |
|
|
@@ -3048,7 +3161,8 @@ anything else is recorded. A measurement over its allowance is reported and neve
|
|
|
3048
3161
|
|
|
3049
3162
|
Opinions (parts of kind `critique`, the verdicts of [automated UI reviewers](critics.md)) are never counted as checks,
|
|
3050
3163
|
even when they are all that was recorded, and are never part of the gate: a low score, or a critic that crashed, leaves
|
|
3051
|
-
`atlas report status` as it was. The `status` of a critique check says only whether the critic ran.
|
|
3164
|
+
`atlas report status` as it was. The `status` of a critique check says only whether the critic ran. A person's accept or dismiss of an
|
|
3165
|
+
opinion (`atlas.critique-mark`) is evidence in the same kind of part and has the same properties.
|
|
3052
3166
|
|
|
3053
3167
|
**Freshness never changes a status.** A layer whose spec fingerprint no longer matches, whose
|
|
3054
3168
|
checkout was modified, or whose related code changed still shows its recorded outcome, with a
|
|
@@ -3476,7 +3590,7 @@ Annotation `type`: `note`, `warning`, `deprecation`, `issue`, `accessibility`, `
|
|
|
3476
3590
|
| A kind of file Atlas has never seen | Attach it with its media type and a `role`. It is stored and offered for download today. |
|
|
3477
3591
|
| Structured data of your own | Put it in `extensions` under a name you own. It is kept, and shown as raw data. |
|
|
3478
3592
|
| A new annotation type, role or step kind | Use it. Open vocabularies need no registration. |
|
|
3479
|
-
| An automated reviewer's opinion of a captured UI | A [critique](critics.md): a part of kind `critique` with the `atlas.critique` extension. It is never counted and never gates. |
|
|
3593
|
+
| An automated reviewer's opinion of a captured UI | A [critique](critics.md): a part of kind `critique` with the `atlas.critique` extension. It is never counted and never gates. A person's accept or dismiss of it is the same kind of part with the `atlas.critique-mark` extension. |
|
|
3480
3594
|
| Something that should be shown well | Ask for a renderer (below). |
|
|
3481
3595
|
| A field every tool should have | Open an issue. It is added as an optional field, and the rules above guarantee nothing breaks. |
|
|
3482
3596
|
|
|
@@ -3513,10 +3627,20 @@ is not human review: it never changes whether a report passes.
|
|
|
3513
3627
|
- [What is available](#what-is-available)
|
|
3514
3628
|
- [The critic contract](#the-critic-contract)
|
|
3515
3629
|
- [A critique is a layer](#a-critique-is-a-layer)
|
|
3630
|
+
- [Reading the catalog](#reading-the-catalog)
|
|
3516
3631
|
- [Running critics](#running-critics)
|
|
3632
|
+
- [The `atlas critic` command](#the-atlas-critic-command)
|
|
3633
|
+
- [Watching the catalog](#watching-the-catalog)
|
|
3634
|
+
- [Reviewing and marking](#reviewing-and-marking)
|
|
3635
|
+
- [Calibrating](#calibrating)
|
|
3636
|
+
- [Configuring critics](#configuring-critics)
|
|
3637
|
+
- [The built-in UIClip critic](#the-built-in-uiclip-critic)
|
|
3638
|
+
- [The LLM critic and bringing your own AI](#the-llm-critic-and-bringing-your-own-ai)
|
|
3639
|
+
- [When the server goes away](#when-the-server-goes-away)
|
|
3517
3640
|
- [State-aware judging, calibration and modes](#state-aware-judging-calibration-and-modes)
|
|
3518
3641
|
- [Authentication and authorization](#authentication-and-authorization)
|
|
3519
3642
|
- [Credentials](#credentials)
|
|
3643
|
+
- [The viewer: needs polish](#the-viewer-needs-polish)
|
|
3520
3644
|
- [What is not here yet](#what-is-not-here-yet)
|
|
3521
3645
|
|
|
3522
3646
|
## What is available
|
|
@@ -3526,11 +3650,17 @@ Import `@crvouga/atlas/critics`. It needs no model, no provider SDK and no netwo
|
|
|
3526
3650
|
| You want | Use |
|
|
3527
3651
|
| --- | --- |
|
|
3528
3652
|
| Define a critic | `defineCritic` (the contract below) |
|
|
3653
|
+
| List, page through, follow and fetch the pictures of the UI catalog | `createCatalogClient` ([below](#reading-the-catalog)) |
|
|
3529
3654
|
| Judge many images with caching, retries, rate limits, timeouts and budgets | `runCritic` |
|
|
3530
3655
|
| Store the opinions | `critiqueLayer`, `publishCritiques` (a layer of parts of kind `critique`) |
|
|
3531
3656
|
| Read them back | `readCritique`, `critiquesOfLayer`, `critiqueCurrency` |
|
|
3532
3657
|
| Read a score in context, measure a threshold, decide an exit code | `relativeVerdict`, `calibrate`, `gateDecision` |
|
|
3533
3658
|
| Keep keys out of configuration and logs | `parseCredentialRef`, `resolveCredential`, `assertNoLiteralCredentials`, `createRedactor` |
|
|
3659
|
+
| Keep critiques that could not be sent and send them later | `spoolingStore`, `flushSpool` |
|
|
3660
|
+
| A UIClip critic, an LLM critic over any provider | `@crvouga/atlas/critics/uiclip`, `@crvouga/atlas/critics/llm` |
|
|
3661
|
+
| Run all of it from a terminal or CI | `atlas critic list \| setup \| doctor \| run \| watch \| review \| calibrate` ([below](#the-atlas-critic-command)) |
|
|
3662
|
+
| Record that a person agrees or disagrees with an opinion | `markLayer`, `publishMarks`, `markStanding` ([below](#reviewing-and-marking)) |
|
|
3663
|
+
| Rank states, compare critics | `rankCritiques`, `critiqueAgreement` (`@crvouga/atlas/reports/critique-review`) |
|
|
3534
3664
|
| Let a token publish critiques and nothing else | the `critic` role (below) |
|
|
3535
3665
|
|
|
3536
3666
|
The JSON Schema of the evidence is [`critique.schema.json`](../packages/schema/schemas/critique.schema.json). The
|
|
@@ -3609,6 +3739,226 @@ score does not fail it, and neither does a critic that crashed. A project that w
|
|
|
3609
3739
|
with `gateDecision` in `gate` mode, which is computed by the reader from critique data and never changes a check's
|
|
3610
3740
|
status, a slot or a winner.
|
|
3611
3741
|
|
|
3742
|
+
## Reading the catalog
|
|
3743
|
+
|
|
3744
|
+
A critic reads the [UI catalog](ui-catalog.md) from the server; it never reads a store directly and needs no browser or
|
|
3745
|
+
collection framework. `GET /api/reports/ui-catalog/entries` lists **entries**: one per captured state per profile that has a picture
|
|
3746
|
+
stored in its layer, each with the hash of that picture (`image.sha256`), where its bytes are (`image.path`), its `state` kind
|
|
3747
|
+
(`screen`, `overlay`, `loading`, `error`, `empty`, or `unknown` when the producer declared none: set `kind` in the state's
|
|
3748
|
+
`atlas.ui-state` facts) and the `critiques` already made of it, each marked `current` or `earlier-image`.
|
|
3749
|
+
|
|
3750
|
+
| A critic wants to | It asks |
|
|
3751
|
+
| --- | --- |
|
|
3752
|
+
| Iterate everything, a page at a time | `?limit=` and the opaque `nextCursor` of the previous page as `?cursor=`. Entries are in key order and the cursor names the last key, so each entry is seen once and in the same order |
|
|
3753
|
+
| Resume where it stopped | keep the `version` of the **first** page; later ask `?since=<version>` for the entries whose layer was added or changed since (an entry that is included but not new is harmless: the cache skips an image it has judged). A version the server does not remember is answered with the whole catalog and `since.honored: false` |
|
|
3754
|
+
| Judge only what it has not judged | `?unjudgedBy=<critic>@<version>`: entries with no current critique with a verdict from that critic (a failed critique is retried) |
|
|
3755
|
+
| Narrow | `?state=`, `?area=`, `?profile=`, `?route=`, `?q=` |
|
|
3756
|
+
| Be told something changed | hold `/api/events`; a `report` event carries the new version, then read `?since=`. A missed event costs nothing: the next read covers it |
|
|
3757
|
+
| Get a picture | `GET /api/reports` + `image.path`, the same safe attachment path every viewer uses; check the bytes against `image.sha256` |
|
|
3758
|
+
|
|
3759
|
+
```ts
|
|
3760
|
+
import { createCatalogClient } from '@crvouga/atlas/critics';
|
|
3761
|
+
|
|
3762
|
+
const catalog = createCatalogClient({ url: 'https://atlas.example.com', token: process.env.ATLAS_CRITIC_TOKEN });
|
|
3763
|
+
const { version, entries } = await catalog.read({ unjudgedBy: { name: 'uiclip', version: '1.0.0' } });
|
|
3764
|
+
for (const entry of entries) judge(entry, await catalog.image(entry)); // image() refuses bytes that are not the hash
|
|
3765
|
+
for await (const batch of catalog.follow({ since: version })) handle(batch.entries); // catch up on connecting and after every drop, none twice
|
|
3766
|
+
```
|
|
3767
|
+
|
|
3768
|
+
`follow` connects to the event stream, catches up with a `since` read, and then reads again after every `report` event. When the
|
|
3769
|
+
connection drops it reconnects after a pause and the next read starts from the version it holds, so nothing is missed; an entry
|
|
3770
|
+
is handed over once per picture. The token is sent as `Authorization: Bearer` and is never put in an address or an error. The
|
|
3771
|
+
list is a function of the report, so the same layers in any order give the same entries.
|
|
3772
|
+
|
|
3773
|
+
## The `atlas critic` command
|
|
3774
|
+
|
|
3775
|
+
`atlas critic list | setup | doctor | run | watch | review | calibrate` runs critics on this machine (a laptop, CI, a worker). Every
|
|
3776
|
+
command takes `--format json`; the documents validate against `critic-list-result`, `critic-setup-result`, `critic-doctor-result`,
|
|
3777
|
+
`critic-run-result`, `critic-watch-result`, `critic-review-result` and `critic-calibrate-result`. The full usage is in the
|
|
3778
|
+
[command reference](reference/cli.md).
|
|
3779
|
+
|
|
3780
|
+
```sh
|
|
3781
|
+
atlas critic list # what is configured, ready, and where images go
|
|
3782
|
+
atlas critic setup uiclip # fetch and verify the model checkpoint (the only download)
|
|
3783
|
+
atlas critic doctor # server, token scope, catalog, model files, credentials, providers
|
|
3784
|
+
atlas critic run --critic uiclip --unjudged # judge what uiclip has not judged yet
|
|
3785
|
+
atlas critic run --critic vision --all --dry-run # how many, and where images would go; sends and publishes nothing
|
|
3786
|
+
atlas critic watch --critic uiclip # a worker: judge new and changed entries as they arrive
|
|
3787
|
+
atlas critic review # the ranked needs-polish list; accept or dismiss what critics found
|
|
3788
|
+
atlas critic calibrate uiclip --good ok/ --bad broken/ --out calibration/uiclip.json
|
|
3789
|
+
```
|
|
3790
|
+
|
|
3791
|
+
- **Interactive when there is a terminal.** Missing choices (the critic, the server, the scope) are asked for, and a run that
|
|
3792
|
+
sends images to a third party names the destination and the number of images and waits for a yes.
|
|
3793
|
+
- **Never interactive without one** (no terminal, `--non-interactive`, or `--format json`): every choice is a flag, a missing one
|
|
3794
|
+
exits `2` and names the flags, and sending images to a third party needs `--yes`. Nothing is ever prompted for.
|
|
3795
|
+
- **Exit codes.** `0` it worked; `1` the answer is no (a critic failed on an entry, a gate was crossed, `doctor` found a
|
|
3796
|
+
problem); `2` the command was wrong (a missing flag, a gate refused for lack of evidence, a configuration that holds a
|
|
3797
|
+
credential). Stopping at a budget is not a failure.
|
|
3798
|
+
- **`doctor`** reports each problem separately with how to fix it: the server unreachable; the token missing or not accepted; its
|
|
3799
|
+
scope (a `read` token cannot publish, a `publish` or `admin` token can do more than a critic needs, and a server with no
|
|
3800
|
+
tokens is open, so **every caller is an administrator**); a server too old to list the catalog; and, for each configured
|
|
3801
|
+
critic, its model file and checksum, its optional dependencies, its credential, and whether its provider answers.
|
|
3802
|
+
- **`setup uiclip`** downloads the checkpoint only if it is missing, verifies it against the pinned SHA-256 (a bad checksum is
|
|
3803
|
+
an error, a bad download is discarded) and uses a verified file as it is. Judging never downloads anything and refuses a
|
|
3804
|
+
checkpoint that does not match.
|
|
3805
|
+
- **Scope.** `--all`, `--unjudged`, `--since <version>`, or filters (`--area`, `--profile`, `--state`, `--route`, `--q`); at least one
|
|
3806
|
+
is required. A score is read against the project's scores of the same state kind and against the entry's previous picture
|
|
3807
|
+
(`verdict.relative`).
|
|
3808
|
+
- **Cost.** `--max-images`, `--max-tokens`, `--max-cost`, `--concurrency`, `--timeout-ms`; see [running critics](#running-critics).
|
|
3809
|
+
- **Modes.** `--mode report|warn|gate`, `--threshold state=score`, `--calibration file.json`; see
|
|
3810
|
+
[modes](#state-aware-judging-calibration-and-modes). `gate` is refused (exit `2`, before anything is spent) without a calibration of
|
|
3811
|
+
this critic and version with held-out evidence for every state kind in scope.
|
|
3812
|
+
|
|
3813
|
+
## Watching the catalog
|
|
3814
|
+
|
|
3815
|
+
`atlas critic watch --critic <name>… --server <url>` is `run` as a worker. It follows the catalog (`createCatalogClient(…).follow`): it
|
|
3816
|
+
catches up on connecting, then judges what arrives, and after every announcement and every reconnection it reads again from the version
|
|
3817
|
+
it holds, so it misses no entry and judges none twice. A picture a critic has already judged is skipped by the cache without a call, so
|
|
3818
|
+
starting a watch on a judged catalog costs nothing.
|
|
3819
|
+
|
|
3820
|
+
```sh
|
|
3821
|
+
atlas critic watch --critic uiclip --critic vision --yes --max-cost 5 # until Ctrl-C, or the money is spent
|
|
3822
|
+
atlas critic watch --critic uiclip --since <version> --state overlay,loading # resume from the version the last watch printed
|
|
3823
|
+
```
|
|
3824
|
+
|
|
3825
|
+
- **Ends cleanly.** On Ctrl-C (or a termination signal), after `--max-batches n` batches, or when a budget is spent. What it finished
|
|
3826
|
+
is published as it goes; at the end it prints what it did and the report version to resume from (`--since`). A budget
|
|
3827
|
+
(`--max-images`, `--max-tokens`, `--max-cost`) is for the whole watch, not for each batch.
|
|
3828
|
+
- **Same promises as `run`.** Images that leave the machine need `--yes` (or a yes at the terminal, asked once before it starts);
|
|
3829
|
+
critiques the server cannot take are spooled and sent by the next invocation, in order, byte for byte; a score is read against its
|
|
3830
|
+
state kind; a failed entry is recorded with its reason and exits `1` at the end. It reports and never gates: there is no `--mode`.
|
|
3831
|
+
- `--dry-run` reads the catalog once and says what would be judged; nothing is sent or published. The one JSON document
|
|
3832
|
+
(`critic-watch-result`) is printed when the watch ends.
|
|
3833
|
+
- The server never runs a model and never holds a key: the critic runs where `watch` runs, such as a small worker beside CI.
|
|
3834
|
+
|
|
3835
|
+
## Reviewing and marking
|
|
3836
|
+
|
|
3837
|
+
`atlas critic review` is where people meet the opinions. It lists one critic's entries lowest first (by standing within the state kind
|
|
3838
|
+
when every score has one, as the viewer does), with every finding and the marks already made.
|
|
3839
|
+
|
|
3840
|
+
```sh
|
|
3841
|
+
atlas critic review --critic vision --limit 10 # the list; with a terminal it then asks about each entry
|
|
3842
|
+
atlas critic review --entry 'home@ios' --accept --finding f1 --note 'Really cramped'
|
|
3843
|
+
atlas critic review --entry 'home@ios' --dismiss # the whole opinion is wrong
|
|
3844
|
+
```
|
|
3845
|
+
|
|
3846
|
+
- **A mark says whether the UI needs polish.** *Accept*: the opinion is right (the finding is valid). *Dismiss*: it is not. A mark is
|
|
3847
|
+
about a whole opinion, or about one finding (`--finding <id>`, repeatable; a finding without an `id` can only be marked with the
|
|
3848
|
+
whole opinion). With a terminal and no `--entry`, `review` walks the list and asks `[a]ccept, [d]ismiss, [s]kip, [q]uit` for each
|
|
3849
|
+
finding; without one, every choice is a flag.
|
|
3850
|
+
- **A mark is a layer.** It is a check in a part of kind `critique` with the decision in `extensions["atlas.critique-mark"]`
|
|
3851
|
+
([schema](../packages/schema/schemas/critique-mark.schema.json)), published under the producer `critic.review.<reviewer>`
|
|
3852
|
+
(`--reviewer`, `ATLAS_REVIEWER`, else `reviewer`; a name the reviewer chooses, never an account). That is within what a `critic`-role
|
|
3853
|
+
token may publish, so marking needs no more power than critiquing (a `publish` or `admin` token may too, a `read` token may not, and
|
|
3854
|
+
the server answers `403` and stores nothing). The server may refuse a mark; it never alters one.
|
|
3855
|
+
- **Evidence, never a result.** A mark is in a part of kind `critique`, so it is never counted, never in `atlas report status`, and
|
|
3856
|
+
never changes a status, a slot or a winner. A reviewer holds one mark per critic and finding of an entry: a newer mark of theirs
|
|
3857
|
+
replaces the older, and reviewers who disagree are shown as disagreeing (`disputed`). A mark names the picture it was made about; when
|
|
3858
|
+
the picture changes, the mark is shown as about an earlier picture and counts for nothing now, like the critique it was about.
|
|
3859
|
+
- **Read back** on each entry of `GET /api/reports/ui-catalog/entries` as `marks`, in `atlas critic review --format json`
|
|
3860
|
+
(`critic-review-result`), by `markStanding`, and in the viewer. A server that predates marks sends none, which means none.
|
|
3861
|
+
- **It feeds calibration.** See [calibrating](#calibrating).
|
|
3862
|
+
|
|
3863
|
+
## Calibrating
|
|
3864
|
+
|
|
3865
|
+
`atlas critic calibrate <name>` measures a critic against labelled examples, per state kind (`calibrate` as a command), so a threshold is
|
|
3866
|
+
chosen from evidence and `gate` mode can be allowed.
|
|
3867
|
+
|
|
3868
|
+
```sh
|
|
3869
|
+
# Pictures in directories. A subdirectory's name is the state kind of the pictures in it; pictures directly in a directory get --state.
|
|
3870
|
+
atlas critic calibrate uiclip --good examples/ok --bad examples/broken --heldout-good holdout/ok --heldout-bad holdout/broken --out calibration/uiclip.json
|
|
3871
|
+
# Or what reviewers decided: an accepted whole opinion is a bad example, a dismissed one a good example.
|
|
3872
|
+
atlas critic calibrate uiclip --from-marks --server https://atlas.example.com --out calibration/uiclip.json
|
|
3873
|
+
atlas critic run --critic uiclip --all --mode gate --calibration calibration/uiclip.json
|
|
3874
|
+
```
|
|
3875
|
+
|
|
3876
|
+
- The critic judges the pictures here, with the same consent (`--yes` for a critic that sends images away), timeouts and budgets as `run`;
|
|
3877
|
+
`--dry-run` counts them and says where they would go. Nothing is published: the calibration is a document, with the critic's name,
|
|
3878
|
+
version, model and parameters, that you commit beside the configuration.
|
|
3879
|
+
- It records, per state kind, the sample sizes, how many are bad and good, the threshold chosen, precision and recall, and **whether the
|
|
3880
|
+
figures are in-sample**. A threshold is chosen only on examples that are not held out; `--heldout-*` pictures measure it. Marks are
|
|
3881
|
+
never held out, so figures from marks alone are in-sample.
|
|
3882
|
+
- Examples left out are named with the reason: the critic could not score the picture, reviewers disagree, the marks were about an
|
|
3883
|
+
earlier picture, or the critic judged it as another version. Finding-level marks are not examples.
|
|
3884
|
+
- The result says whether `gate` would accept it (`gate.eligible`) and, if not, what is missing per state kind (at least 10 bad and 10
|
|
3885
|
+
good examples, measured on held-out examples).
|
|
3886
|
+
|
|
3887
|
+
## Configuring critics
|
|
3888
|
+
|
|
3889
|
+
`atlas critic` reads `atlas.critics.json` (or `--config`). It may be committed: it names a credential by **reference**
|
|
3890
|
+
(`env:NAME`, `file:PATH`) and a literal credential in it is refused, with a message that says where and never repeats it.
|
|
3891
|
+
Paths are relative to the file.
|
|
3892
|
+
|
|
3893
|
+
```json
|
|
3894
|
+
{
|
|
3895
|
+
"server": { "url": "https://atlas.example.com", "token": "env:ATLAS_CRITIC_TOKEN" },
|
|
3896
|
+
"critics": {
|
|
3897
|
+
"uiclip": { "type": "uiclip", "checkpoint": { "path": ".atlas/models/uiclip.onnx", "sha256": "<64 hex digits you pinned>", "url": "https://models.example.com/uiclip.onnx" } },
|
|
3898
|
+
"vision": { "type": "llm", "baseUrl": "https://llm.example.com/v1", "apiKey": "env:LLM_API_KEY", "model": "<model id>", "rubricFile": "rubric.md" },
|
|
3899
|
+
"claude": { "type": "llm", "provider": "anthropic", "apiKey": "env:ANTHROPIC_API_KEY", "model": "<model id>" },
|
|
3900
|
+
"gemini": { "type": "llm", "provider": "gemini", "apiKey": "env:GEMINI_API_KEY", "model": "<model id>" },
|
|
3901
|
+
"contrast": { "type": "module", "module": "./critics/contrast.ts" }
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
```
|
|
3905
|
+
|
|
3906
|
+
The server is `--server`, `server.url`, `ATLAS_SERVER_URL` or an https `ATLAS_STORE`; the token is `server.token`,
|
|
3907
|
+
`ATLAS_CRITIC_TOKEN` or `ATLAS_TOKEN`, and is refused over plain `http://` beyond this machine. A `module` critic is any
|
|
3908
|
+
file whose default export is a `defineCritic` result (a heuristic, a classic model, a wrapper around your own service), on
|
|
3909
|
+
equal footing with the others; its `name` must equal its key, because the name is the producer of its layers. Critiques are
|
|
3910
|
+
published as `critic.<name>`.
|
|
3911
|
+
|
|
3912
|
+
## The built-in UIClip critic
|
|
3913
|
+
|
|
3914
|
+
`@crvouga/atlas/critics/uiclip` is a model-based critic that gives one score per image and keeps images on this machine. It is an
|
|
3915
|
+
**adapter**: importing it needs nothing installed, and the model runs in an ONNX Runtime session opened only when a run starts
|
|
3916
|
+
(`loadOnnxRuntime`, which needs the optional `onnxruntime-node` and `sharp` and names whichever is missing). A test or another
|
|
3917
|
+
runtime passes its own `UiclipRuntime` (`score(image)`), so no model is needed to test anything.
|
|
3918
|
+
|
|
3919
|
+
- **Pinned checkpoint.** You pin the SHA-256 of the checkpoint in the configuration. Atlas ships no checkpoint address: the
|
|
3920
|
+
weights' license must be checked before a project downloads them, and that choice is yours.
|
|
3921
|
+
- **`setupUiclip`** is the only place anything is downloaded (https only, hashed as it arrives, discarded on a mismatch);
|
|
3922
|
+
`verifyCheckpoint` refuses a missing, unpinned or different file; inference never downloads.
|
|
3923
|
+
- **Report-only by design.** A single global score cannot say what to fix, and ordinary loading overlays and bottom sheets score
|
|
3924
|
+
poorly. A run reports the score with its standing among the scores of the same state kind, so a loading overlay is compared with
|
|
3925
|
+
other loading overlays; calibrate before reading it as a threshold.
|
|
3926
|
+
- **Not validated against a particular checkpoint.** The preprocessing is CLIP's (resize to 224 by 224, normalise) and the score is
|
|
3927
|
+
the first output; measure the critic on your own labelled examples with `calibrate` before trusting it.
|
|
3928
|
+
|
|
3929
|
+
## The LLM critic and bringing your own AI
|
|
3930
|
+
|
|
3931
|
+
`@crvouga/atlas/critics/llm` asks a vision-capable model to review an image against a **rubric** and answer with structured
|
|
3932
|
+
findings. It is provider-agnostic: an `LlmProvider` is `complete(request)` plus declared capabilities, so a developer brings their own
|
|
3933
|
+
model and account. Three adapters ship, all over plain `fetch` so there is no SDK to install: `openAiCompatibleProvider` speaks the
|
|
3934
|
+
widely implemented chat-completions protocol (hosted services and local servers alike), `anthropicProvider` the Messages API and
|
|
3935
|
+
`geminiProvider` the Gemini API. In the configuration `provider` is `openai-compatible` (the default, needs `baseUrl`), `anthropic` or
|
|
3936
|
+
`gemini` (their own hosts by default; `apiKey` is required). A provider for anything else is a few lines.
|
|
3937
|
+
|
|
3938
|
+
- The rubric is recorded as a hash. The answer must be one JSON object with a `score` and optional `findings`; anything else is
|
|
3939
|
+
`invalid-output` and no score is invented. A provider's `429` (and Anthropic's `529`, overloaded) becomes a rate limit with its `Retry-After`; a timeout, a refused
|
|
3940
|
+
credential and an unreachable provider are failed critiques with a reason code.
|
|
3941
|
+
- The key is read from its reference when the critic is built, sent only in a request header (`Authorization`, `x-api-key` or `x-goog-api-key`, never in an address), and kept out of every result,
|
|
3942
|
+
progress line, error and JSON document (the runner redacts what it records). Plain `http` is allowed only for a provider on this
|
|
3943
|
+
machine.
|
|
3944
|
+
- **Images may leave the machine.** `describe().sendsImagesTo` is the provider's host, shown by `list`, `doctor` and `run --dry-run`
|
|
3945
|
+
and confirmed before a run that sends images there.
|
|
3946
|
+
|
|
3947
|
+
```ts
|
|
3948
|
+
import { createLlmCritic, openAiCompatibleProvider } from '@crvouga/atlas/critics/llm';
|
|
3949
|
+
|
|
3950
|
+
const critic = createLlmCritic({ model: 'my-vision-model', rubric: 'Judge spacing and contrast.', provider: openAiCompatibleProvider({ baseUrl: 'https://llm.example.com/v1', apiKey }) });
|
|
3951
|
+
```
|
|
3952
|
+
|
|
3953
|
+
## When the server goes away
|
|
3954
|
+
|
|
3955
|
+
A run that cannot reach the server while publishing does not lose its work. The objects of the layer (its files, the layer, its
|
|
3956
|
+
catalog entry) are written to the spool directory (`.atlas/critic-spool`, or `--spool`, or `spool` in the configuration); the run
|
|
3957
|
+
reports how many, and exits normally. The next `atlas critic run` sends them first, **byte for byte what was written and in the
|
|
3958
|
+
order it was written**, and removes each once the server has it. A layer is content-addressed and the store protocol is
|
|
3959
|
+
idempotent, so one that did arrive is not duplicated, and a spool that is empty is not an error. A refusal (`401`, `403`, `409`,
|
|
3960
|
+
`413`, `422`) is not spooled: it will not pass by waiting. `spoolingStore` and `flushSpool` do the same for your own code.
|
|
3961
|
+
|
|
3612
3962
|
## Running critics
|
|
3613
3963
|
|
|
3614
3964
|
`runCritic` owns everything around the judgement; a critic only judges one image.
|
|
@@ -3677,18 +4027,46 @@ run time (`resolveCredential`) and every message the runner records goes through
|
|
|
3677
4027
|
`createRedactor`) that removes them, in their URL-encoded and base64 forms too, and anything shaped like a provider key,
|
|
3678
4028
|
token or JWT. A credential never reaches a layer, a progress event or an error message.
|
|
3679
4029
|
|
|
4030
|
+
## The viewer: needs polish
|
|
4031
|
+
|
|
4032
|
+
`Needs polish` (`/critiques`, in the spec-type menu once any critic has recorded an opinion) ranks the captured states one critic
|
|
4033
|
+
judged, lowest first, so the likeliest problems are reviewed first. When every ranked score carries its standing within its state
|
|
4034
|
+
kind, that standing ranks them (a loading overlay is not compared with a full screen); otherwise the raw score does, read as
|
|
4035
|
+
higher-is-better. The address holds the critic, the state kind and the open entry.
|
|
4036
|
+
|
|
4037
|
+
- **Opinions, not results.** Critiques have their own place, wording and mark, never a passed or failed mark, and are never in a test
|
|
4038
|
+
count or the gate.
|
|
4039
|
+
- **Anchored to pixels.** A state whose picture changed since it was judged is listed apart as *not judged at the current picture*, its
|
|
4040
|
+
older opinion shown as judging an earlier picture; it is never ranked and never hidden.
|
|
4041
|
+
- **Failures are data.** A critic that could not judge says why (the reason code) and that this says nothing about the picture.
|
|
4042
|
+
- **Detail.** An open entry shows the verdict and its standing, every finding (category, severity, where, why, suggestion; unknown
|
|
4043
|
+
categories and severities are shown as they are) and where the opinion came from: critic, version, model, parameters, the hash of
|
|
4044
|
+
the picture it judged, the layer and the cost.
|
|
4045
|
+
- **Findings on the picture.** An open entry draws each finding that has a region as a numbered box on the picture, in its severity's
|
|
4046
|
+
color and never by color alone: the number matches the findings table, where *Locate* highlights one. Regions in `normalized`
|
|
4047
|
+
(0..1) or `px` (pixels of the stored picture, using the picture's size) units are drawn; a region in another unit, or none, is
|
|
4048
|
+
listed and not drawn. A critique that judged an earlier picture is not drawn on the new one.
|
|
4049
|
+
- **Agreement across critics.** The *Agreement across critics* tab compares each critic's opinion of the picture an entry has now. A
|
|
4050
|
+
critic *flags* a UI when its label says `needs-polish`, when it has findings, or, with neither, when its score is in the lowest quarter
|
|
4051
|
+
of its state kind; a bare score with no standing, or a critic that failed, gives *no view* and never makes critics agree. Entries that
|
|
4052
|
+
every critic flags come first, then those they disagree about; an entry only one critic judged is counted, not compared. It is a
|
|
4053
|
+
reading for people, not a verdict, a status or a gate.
|
|
4054
|
+
- **Marks.** The list and the open entry show what reviewers decided (accepted, dismissed, disputed, not reviewed), per finding and for
|
|
4055
|
+
the whole opinion, with their notes. The viewer only reads: a browser's sign-in box may look but not write, so it names the
|
|
4056
|
+
`atlas critic review` command that marks.
|
|
4057
|
+
|
|
3680
4058
|
## What is not here yet
|
|
3681
4059
|
|
|
3682
|
-
|
|
3683
|
-
|
|
4060
|
+
Out of scope until the open questions of [issue 50](https://github.com/crvouga/atlas/issues/50) are settled, because they need the
|
|
4061
|
+
weights, a license or a network that this repository does not have:
|
|
3684
4062
|
|
|
3685
|
-
- the **
|
|
3686
|
-
the
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
-
|
|
3691
|
-
|
|
4063
|
+
- the **UIClip critic's parity** with the in-house package it is meant to absorb: its exact preprocessing and score head, checked
|
|
4064
|
+
against a real checkpoint. No checkpoint address ships until the weights' license is settled, so the pin is yours; measure it
|
|
4065
|
+
on your own examples with `atlas critic calibrate` before trusting a score;
|
|
4066
|
+
- adapters for providers beyond OpenAI-compatible, Anthropic and Gemini, and access by a local command-line agent or a provider's
|
|
4067
|
+
OAuth sign-in (whether a consumer subscription may be used that way is per provider's terms);
|
|
4068
|
+
- heuristic and classic ML critics shipped in the package, a flow-level or before-and-after critic, and server-hosted execution (never
|
|
4069
|
+
planned: the server does not run models).
|
|
3692
4070
|
</doc>
|
|
3693
4071
|
|
|
3694
4072
|
<doc path="docs/report-backends.md">
|
|
@@ -4600,6 +4978,9 @@ same coverage state, revisions and recording in both.
|
|
|
4600
4978
|
- The search words (`q`), the coverage filter (`coverage`), the selection (`behavior`) and the expanded state (`workspace`) are in the
|
|
4601
4979
|
address. Selecting shows the Read / Watch / Inspect panel beside the list (below it on narrow screens), and *Read and watch* in the
|
|
4602
4980
|
selected-behavior bar is reachable before the list in keyboard order.
|
|
4981
|
+
- The same release controls as a feature page (*Release build*, *commit*, *platform*, and the *Stale or unverified* filter) sit above the list;
|
|
4982
|
+
they are the address (`release`, `releaseSha`, `releasePlatform`, `evidence=stale`), and the selected behavior and the workspace read their
|
|
4983
|
+
evidence against that release.
|
|
4603
4984
|
- **Read and watch workspace.** Expanding a behavior replaces the index with a page that uses the width: a two-column
|
|
4604
4985
|
`SimpleGrid` (one column below `md`) with *What should happen* (the steps, an outline's example filled in) beside *Recording* (the
|
|
4605
4986
|
player, status and provenance, promise and evidence revisions, applicability), the scoped summary below, and Inspect behind a
|
|
@@ -4616,6 +4997,39 @@ same coverage state, revisions and recording in both.
|
|
|
4616
4997
|
budget and breaches). Absent evidence is stated as unknown, never as good; the test's duration is not product latency, and the
|
|
4617
4998
|
summary says it describes the checks Atlas holds, not production. The full reliability and latency detail is one link away.
|
|
4618
4999
|
|
|
5000
|
+
## The review destination
|
|
5001
|
+
|
|
5002
|
+
A team asks four connected questions of one project: what behavior it promises (**Read**), what that looks like (**Watch**), what
|
|
5003
|
+
evidence supports it (**Inspect**) and where the gaps are. The viewer answers them in three places that lead to one another, and
|
|
5004
|
+
`tests/review-destination.test.tsx` walks the whole workflow against a real server, one test per acceptance behavior of the
|
|
5005
|
+
tracking issue (#20).
|
|
5006
|
+
|
|
5007
|
+
| Step | Where | Built by |
|
|
5008
|
+
| --- | --- | --- |
|
|
5009
|
+
| Find a promise by approximate words, a domain, a device and recordings; see each in its own coverage state | **Behaviors** (`/behaviors`) | #55, #74, #57, #44 |
|
|
5010
|
+
| Name the build and device to read the evidence against | the applicability controls of Behaviors and of a feature | #61, #73 |
|
|
5011
|
+
| Read what should happen beside the recording, with the scoped outcome, reliability and latency | the Read and watch workspace | #74, #60, #62, #63 |
|
|
5012
|
+
| Find recordings by tag and media type and play them as a labelled playlist | **Artifacts** (`?tab=artifacts`), Watch these N results | #49, #56, #59, #75 |
|
|
5013
|
+
| Go from a clip to its promise (**Read the promise**, also in a test's detail) or to its exact run (**Developer details**) | the playlist | #47, #54 |
|
|
5014
|
+
| See which run and execution a result came from, and which recordings never arrived or are gone | **Runs** (`?tab=runs`) | #45, #46, #48, #70, #72, #76 |
|
|
5015
|
+
| Seek to the step in a recording and share the moment | Timeline, Watch | #68 |
|
|
5016
|
+
| Freeze a selection with notes and gaps | **Collections** | #66 |
|
|
5017
|
+
| Compare two pinned runs | **Compare** | #67 |
|
|
5018
|
+
|
|
5019
|
+
How the pieces stay honest, and what a test in that file holds them to:
|
|
5020
|
+
|
|
5021
|
+
- The four states (not executed, API only, failed with a recording, demonstrated) are told apart in words and never counted as one
|
|
5022
|
+
pass. A recording that was expected and not uploaded, or stored and then lost, is shown beside its green tests, never in place of them.
|
|
5023
|
+
- Behaviors, Runs and Videos and artifacts link to one another (the header of Behaviors and the Specs block of the explorer), and a
|
|
5024
|
+
result in the playlist or in a test's detail links back to the exact check of its feature. Read names the check and no run;
|
|
5025
|
+
Inspect names the run (`layer`), so it never falls back to the latest combined evidence.
|
|
5026
|
+
- The address holds the scope: the release (`release`, `releaseSha`, `releasePlatform`) and `evidence=stale` on Behaviors as on a feature,
|
|
5027
|
+
the filters and the open playlist on Artifacts, the pinned runs on Compare, and the revision of a frozen collection.
|
|
5028
|
+
A frozen collection does not change when newer runs arrive; the filtered view it was frozen from does.
|
|
5029
|
+
- An older producer (no spec fingerprint, no recording expectation, no tags) or an older server (no artifact search, read-back,
|
|
5030
|
+
collections or health route) stays readable. What it cannot say is unknown: the revision is "not recorded", a run is not "complete",
|
|
5031
|
+
and an unreported behavior is "Not executed", never passed.
|
|
5032
|
+
|
|
4619
5033
|
## Catalog health
|
|
4620
5034
|
|
|
4621
5035
|
The **Catalog health** tab (`explorer/HealthView.tsx`, `?tab=health`; `@crvouga/atlas/reports/catalog-health`) explains why the Specs
|
|
@@ -4648,6 +5062,36 @@ it works without a server. Cards are Mantine `Card`s in a responsive `SimpleGrid
|
|
|
4648
5062
|
incomplete. Waiting follows the loading policy below, and layers still arriving are counted.
|
|
4649
5063
|
- Pictures are `loading="lazy"` and only the cards on screen are fetched. The grid is bounded with *Show more*.
|
|
4650
5064
|
- The open state shows every profile side by side with the layer, backend, producer, shard, commit and time of each picture.
|
|
5065
|
+
- **Compare** (`catalog/UiCatalogCompare.tsx`, `?compare=commit|profile&from&to`) sets two commits or two profiles against each other. Two
|
|
5066
|
+
commits need the layers a newer layer replaced, which the held report no longer has, so `catalog/history.ts` reads the layer list and
|
|
5067
|
+
the rows of the two commits only while the comparison is open (sharing the rows cache). Say what "changed" means (the stored bytes
|
|
5068
|
+
differ, not a pixel measure), never call a one-sided state a regression, and say a layer that could not be read is unknown. The
|
|
5069
|
+
coverage filter does not apply to a comparison.
|
|
5070
|
+
|
|
5071
|
+
## Needs polish
|
|
5072
|
+
|
|
5073
|
+
`/critiques` (`critiques/`, in the spec-type menu once a critic has recorded an opinion) ranks the captured UI states one critic
|
|
5074
|
+
judged, lowest first ([the guide](critics.md#the-viewer-needs-polish)). It is computed in the page from the layers the viewer already
|
|
5075
|
+
holds (`critiques/ranking.ts`), so a critique appears as its layer arrives.
|
|
5076
|
+
|
|
5077
|
+
- A critique is an opinion: no `StatusMark`, no passed or failed words, never in a test count. Use a Mantine `Badge` with the
|
|
5078
|
+
label the critic gave (an open string), the severity as text, and never a color alone.
|
|
5079
|
+
- A state judged at an earlier picture, or that the critic could not judge, is listed in its own table with the reason and is not
|
|
5080
|
+
ranked. Unknown categories and severities are shown as written.
|
|
5081
|
+
- The critic and the state kind are `NativeSelect`s whose values are the address (`critic`, `kind`, `entry`); the open entry is a
|
|
5082
|
+
`Drawer` with the findings in a `Table` and the provenance in an `Accordion`.
|
|
5083
|
+
- Pictures are `loading="lazy"`. Waiting, an unavailable backend and incomplete layers are named as on the UI catalog.
|
|
5084
|
+
- **Finding overlays** (`critiques/FindingOverlay.tsx`): in the open entry each finding with a region is a numbered box over the picture,
|
|
5085
|
+
positioned with Mantine style props (`pos`, `left`, `top`, `w`, `h` as percentages of the picture, `bd`) and a numbered `Badge`; no
|
|
5086
|
+
`style`, `className` or stylesheet. The number is the finding's row in the table (where a `Button` locates it, `aria-pressed`), so color
|
|
5087
|
+
is never the only signal; the boxes are `aria-hidden` because the table says the same in words, and a `Switch` hides them. A region in
|
|
5088
|
+
an unknown unit, or of a critique that judged an earlier picture, is not drawn.
|
|
5089
|
+
- **Agreement** is a `Tabs` view of the same page (`?view=agreement`, `keepMounted={false}`): a `Table` with one column per critic, each
|
|
5090
|
+
cell a `Badge` in words (*Flags it*, *Does not flag it*, *No view*) with a link to that critic's critique, and the outcome as text.
|
|
5091
|
+
The rule for a stance is stated on the page (`reports/critique-review`); a score without standing or a failure gives no view.
|
|
5092
|
+
- **Marks** are read, never written, by the viewer (a browser sign-in only looks): each finding and the whole opinion show a `Badge` in
|
|
5093
|
+
words (*Accepted by*, *Dismissed by*, *Reviewers disagree*, *Not reviewed*) with the reviewers' notes, and the page names
|
|
5094
|
+
`atlas critic review`. No mark uses a passed or failed word or a status color.
|
|
4651
5095
|
|
|
4652
5096
|
## Reliability
|
|
4653
5097
|
|
|
@@ -4736,7 +5180,8 @@ over the same filtered set the page lists, in the same order (`explorer/playlist
|
|
|
4736
5180
|
Speed, "Play the next clip when one ends" and "Repeat" are labelled controls. Captions (`text/vtt` attachments) are offered
|
|
4737
5181
|
as `<track>` elements.
|
|
4738
5182
|
- The scenario (title, recorded steps, why it failed) sits beside the player with the recording's scope badges
|
|
4739
|
-
(paced, scripted, silent, synthetic) and the sentence that the length is the recording's, not production latency.
|
|
5183
|
+
(paced, scripted, silent, synthetic) and the sentence that the length is the recording's, not production latency. **Read the promise** opens the feature on that
|
|
5184
|
+
exact check (the playlist stays in the address, so Back returns to the same clip); Developer
|
|
4740
5185
|
details opens the test pinned to that run's record and closes the playlist.
|
|
4741
5186
|
|
|
4742
5187
|
## Steps and moments in a recording
|
|
@@ -4865,7 +5310,7 @@ The narrowly scoped exceptions are:
|
|
|
4865
5310
|
| Area | Screens/interactions | Mantine composition |
|
|
4866
5311
|
| --- | --- | --- |
|
|
4867
5312
|
| Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
|
|
4868
|
-
| UI catalog | Captured UI states per profile as cards with coverage badges, filters in the index column, surface coverage, an open state with its profiles side by side and where each picture came from, manifest download | AppShell navbar, SimpleGrid, Card, Image, MultiSelect, TextInput, Drawer, Accordion, Table, Badge, Alert |
|
|
5313
|
+
| UI catalog | Captured UI states per profile as cards with coverage badges, filters in the index column, surface coverage, an open state with its profiles side by side and where each picture came from, manifest download, a Compare of two commits or profiles (SegmentedControl, NativeSelect, Switch) | AppShell navbar, SimpleGrid, Card, Image, MultiSelect, TextInput, Drawer, Accordion, Table, Badge, Alert |
|
|
4869
5314
|
| Project home | Tests/runs/artifacts/compare/catalog health tabs, a two-run comparison with promise, outcome, recording and figure tabs per behavior, a run's Artifacts (files of its layers, shared recordings, direct links), Watch playlist of a filtered set, metrics, search, status/environment/domain/artifact-tag filters, run and artifact facets with saved views, grouping, sorting, show more | Container, Tabs, SimpleGrid, Paper, TextInput, MultiSelect, Popover, Button, Accordion, Modal, Grid, Switch |
|
|
4870
5315
|
| Test detail | Current outcomes, statistics, duration graph, product latency with budgets and baselines, history, artifacts, recording; the `layer` address parameter pins it to one run's result, labelled "This run" against "Latest combined evidence" | Drawer, Table, Badge, Alert, Stage |
|
|
4871
5316
|
| Specs | All six existing kinds, overview/index with title search and folder or declared-domain grouping, Gherkin rules/scenarios/examples with a coverage summary and Read / Watch / Inspect per behavior, Quint declarations/traces, DMN tables, source text | SpecHeader, Accordion, Tabs, Table, CodeHighlightTabs, ResultRow |
|
|
@@ -5241,7 +5686,7 @@ carries it. Upgrade (`bun update @crvouga/atlas --latest`) and remove your worka
|
|
|
5241
5686
|
|
|
5242
5687
|
> Generated from the usage text each command prints (`atlas <command> --help`) by `bun run docs`. Do not edit.
|
|
5243
5688
|
|
|
5244
|
-
Add `--format json` (alias `--json`) to `init`, `report publish`, `stream`, `merge`, `doctor` and `
|
|
5689
|
+
Add `--format json` (alias `--json`) to `init`, `report publish`, `stream`, `merge`, `doctor`, `status`, every `ui-catalog` command and `critic list`, `setup`, `doctor`, `run`, `watch`, `review` and `calibrate` for a JSON document on standard output (schemas in `@crvouga/atlas/schemas`). Exit codes: `0` it worked, `1` it ran and the answer is no, `2` the command was wrong.
|
|
5245
5690
|
|
|
5246
5691
|
## atlas
|
|
5247
5692
|
|
|
@@ -5260,6 +5705,8 @@ atlas — executable statecharts
|
|
|
5260
5705
|
atlas dev [--port 5180] develop the Atlas server and client on one host
|
|
5261
5706
|
atlas init [--server <url>] [--write] integrate this project: detect its suites, print the plan (atlas init --help)
|
|
5262
5707
|
atlas report publish|stream|status|merge|doctor|policy … layered evidence for any kind of spec (atlas report --help)
|
|
5708
|
+
atlas critic list|setup|doctor|run … automated reviewers that mark which UIs need polish (atlas critic --help)
|
|
5709
|
+
atlas ui-catalog plan|publish|status|diff|export … the UI catalog of captured states (atlas ui-catalog --help)
|
|
5263
5710
|
|
|
5264
5711
|
--workers path attempts that run at once (default: one per CPU, capped by the driver)
|
|
5265
5712
|
--seed only paths that start from this seed ("start" for paths setup starts); repeatable
|
|
@@ -5411,6 +5858,129 @@ atlas report — layered evidence for any kind of spec
|
|
|
5411
5858
|
|
|
5412
5859
|
Exit codes: 0 ok, 1 the report does not pass (or a check failed), 2 the command was wrong.
|
|
5413
5860
|
```
|
|
5861
|
+
|
|
5862
|
+
## atlas ui-catalog
|
|
5863
|
+
|
|
5864
|
+
```text
|
|
5865
|
+
atlas ui-catalog — a browsable inventory of captured UI states, from any collector
|
|
5866
|
+
|
|
5867
|
+
atlas ui-catalog plan --states <file.json> --producer <name> [--store <dir|s3://bucket/prefix|https://…>]
|
|
5868
|
+
[--environment <name>] [--param key=value]... [--shard <label>]
|
|
5869
|
+
[--branch <b>] [--sha <s>] [--run-url <u>] [--run-id <id>] [--run-title <t>] [--recorded-at <iso|ms>]
|
|
5870
|
+
atlas ui-catalog publish --captures <file.json> --producer <name> [the same flags as plan]
|
|
5871
|
+
[--attach-root <dir>]... [--max-picture-mb <n>]
|
|
5872
|
+
atlas ui-catalog status [--store <dir|s3://…|id=url>]... [--backends <module>] [filters] [--branch <name>]...
|
|
5873
|
+
[--ignore-modified] [--format text|json|markdown]
|
|
5874
|
+
atlas ui-catalog diff --from <commit> --to <commit> [--store …]... [--backends <module>] [filters]
|
|
5875
|
+
[--fail-on-change] [--format text|json|markdown]
|
|
5876
|
+
atlas ui-catalog export --out <file.html> [--store …]... [--backends <module>] [filters]
|
|
5877
|
+
[--images inline|none] [--max-mb <n>] [--manifest <file.json>] [--title <t>]
|
|
5878
|
+
|
|
5879
|
+
filters (status, diff, export): --q <words> --coverage a,b --profile <key|label>... --area a --route r --cohort c
|
|
5880
|
+
--flag f --surface s --producer p
|
|
5881
|
+
|
|
5882
|
+
plan Write one layer of kind ui-plan: the states (and with "inventory": true, the surfaces) that should exist under
|
|
5883
|
+
this profile. Every entry is not-run, so a plan never counts as a pass. The file is {"states":[{"id":…,
|
|
5884
|
+
"title"?,"route"?,"fixture"?,"cohort"?,"area"?,"kind"?,"flags"?,"covers"?,"inventory"?}]} or just that list.
|
|
5885
|
+
publish Write one layer of kind ui-state: the states one collector or shard captured under this profile. The file is
|
|
5886
|
+
{"states":[{"id":…,"title"?,"status"?:"passed|failed|running|not-run","detail"?,"route"?,"fixture"?,"cohort"?,
|
|
5887
|
+
"area"?,"kind"?,"flags"?,"covers"?,"labels"?,"tags"?,"durationMs"?,"screenshot"?:"file.png"|{"path"|"uri",
|
|
5888
|
+
"width"?,"height"?,"contentType"?}}]}. A picture file is read from beside the file and each --attach-root, never
|
|
5889
|
+
from a hidden file or a link out. A passed state whose picture cannot be read is published as failed, with the
|
|
5890
|
+
reason: a state without its picture is not shown as captured.
|
|
5891
|
+
--environment and each --param key=value are the profile (a viewport, a color scheme, a device): another
|
|
5892
|
+
profile is another result side by side. --shard only describes the layer; it never splits a result.
|
|
5893
|
+
--recorded-at defaults to the modification time of the file, so publishing the same file twice is one layer.
|
|
5894
|
+
status Read every store and print the catalog's summary. Exit 1 unless the catalog holds a state and there is nothing
|
|
5895
|
+
to attend to: a failed capture, a state a plan expects with nothing captured, a capture still in progress, a
|
|
5896
|
+
backend or layer that could not be read. Without a plan nothing is expected, so nothing is missing.
|
|
5897
|
+
--profile takes a profile's key or its label, as printed by status.
|
|
5898
|
+
diff Set the catalog recorded at one commit (--from) against another (--to): each state in each profile is paired
|
|
5899
|
+
with itself and is unchanged, changed in coverage, changed in picture (the stored bytes differ), unverified (a
|
|
5900
|
+
side has no content hash), only in --to (added) or only in --from (removed). A commit is the whole id or at
|
|
5901
|
+
least 7 digits of it. Exit 0 unless a store could not be read, or, with --fail-on-change, anything differs.
|
|
5902
|
+
export Write the catalog as one HTML file with its pictures embedded and its manifest in it: no server, no network.
|
|
5903
|
+
It is the catalog at the moment it was made. --images none leaves the pictures out. --max-mb (default 32) limits
|
|
5904
|
+
the embedded pictures; those past it are listed in the page as left out, and the manifest stays complete.
|
|
5905
|
+
--manifest writes the catalog's JSON manifest too.
|
|
5906
|
+
|
|
5907
|
+
--format json (alias --json) every subcommand prints one JSON document instead of prose
|
|
5908
|
+
(schemas: ui-catalog-publish-result, ui-catalog-status, ui-catalog-diff-result, ui-catalog-export-result).
|
|
5909
|
+
A command that is wrong is {"ok":false,"error":{"message":…,"code":"usage"}} on standard error.
|
|
5910
|
+
A store is a directory, s3://bucket/prefix or an Atlas server (https://…), exactly as for atlas report; select a project
|
|
5911
|
+
with --project <slug> or ATLAS_PROJECT. When no --store is given, ATLAS_STORE is used.
|
|
5912
|
+
|
|
5913
|
+
Exit codes: 0 ok, 1 the answer is no (see each command), 2 the command was wrong.
|
|
5914
|
+
```
|
|
5915
|
+
|
|
5916
|
+
## atlas critic
|
|
5917
|
+
|
|
5918
|
+
```text
|
|
5919
|
+
atlas critic — automated reviewers of the UI catalog
|
|
5920
|
+
|
|
5921
|
+
atlas critic list [--config atlas.critics.json] [--format text|json]
|
|
5922
|
+
atlas critic setup <name> [--config atlas.critics.json] [--format text|json]
|
|
5923
|
+
atlas critic doctor [--server <url>] [--critic <name>]... [--config atlas.critics.json] [--format text|json]
|
|
5924
|
+
atlas critic run --critic <name>... ( --all | --unjudged | --since <version> | filters ) [--server <url>]
|
|
5925
|
+
[--area a,b] [--profile key] [--state screen,overlay] [--route r] [--q words]
|
|
5926
|
+
[--dry-run] [--yes] [--non-interactive] [--force]
|
|
5927
|
+
[--max-images n] [--max-tokens n] [--max-cost usd] [--concurrency n] [--timeout-ms n]
|
|
5928
|
+
[--mode report|warn|gate] [--threshold state=score]... [--calibration file.json]
|
|
5929
|
+
[--spool dir] [--project slug] [--config atlas.critics.json] [--format text|json]
|
|
5930
|
+
atlas critic watch --critic <name>... [--server <url>] [--since <version>] [filters] [--yes] [--dry-run]
|
|
5931
|
+
[--max-batches n] [--max-images n] [--max-tokens n] [--max-cost usd] [--concurrency n] [--timeout-ms n]
|
|
5932
|
+
[--spool dir] [--project slug] [--config atlas.critics.json] [--format text|json]
|
|
5933
|
+
atlas critic review [--server <url>] [--critic <name>] [--state kind] [--limit n] [--list] [--non-interactive]
|
|
5934
|
+
[--entry <key> ( --accept | --dismiss ) [--finding <id>]... [--note text] [--reviewer name] [--dry-run]]
|
|
5935
|
+
[--project slug] [--config atlas.critics.json] [--format text|json]
|
|
5936
|
+
atlas critic calibrate <name> ( --good <dir>... --bad <dir>... [--heldout-good <dir>...] [--heldout-bad <dir>...] [--state kind]
|
|
5937
|
+
| --from-marks [--server <url>] [--critic-version v] ) [--out file.json] [--dry-run] [--yes]
|
|
5938
|
+
[--max-images n] [--concurrency n] [--timeout-ms n] [--config atlas.critics.json] [--format text|json]
|
|
5939
|
+
|
|
5940
|
+
A critic is a producer: it runs here (on a laptop, in CI or in a worker), reads the catalog from an Atlas server, and publishes
|
|
5941
|
+
what it thinks as layers of critiques. The server never runs a model and never holds a provider key. A critique is an opinion,
|
|
5942
|
+
not a test result: it never changes whether a report passes.
|
|
5943
|
+
|
|
5944
|
+
Configuration is atlas.critics.json, which may be committed: it names credentials by reference (env:NAME, file:PATH) and a
|
|
5945
|
+
literal credential in it is refused. The server is --server, "server.url" there, ATLAS_SERVER_URL or an https ATLAS_STORE;
|
|
5946
|
+
the token is ATLAS_CRITIC_TOKEN (or ATLAS_TOKEN), never a flag, and is never sent over plain http beyond this machine.
|
|
5947
|
+
|
|
5948
|
+
list The critics available and configured: type, whether each is ready or needs setup, and where it sends images.
|
|
5949
|
+
setup Provision a critic: fetch and verify a model checkpoint against its pinned SHA-256 (a bad checksum is an error), or
|
|
5950
|
+
check that a credential reference resolves. This is the only place anything is downloaded; judging never downloads.
|
|
5951
|
+
doctor Check the server is reachable, the token is accepted and has the scope a critic needs (and warn when the server is
|
|
5952
|
+
open, so every caller is an administrator), that the server lists the catalog, and for each configured critic its
|
|
5953
|
+
model file, dependencies, credential and provider. Each problem is reported separately, with how to fix it.
|
|
5954
|
+
run Judge the entries in the chosen scope. An entry that already has a current critique of the same critic, version,
|
|
5955
|
+
model, parameters and image is skipped without a call; a changed image is judged again. Failures are recorded
|
|
5956
|
+
with a reason code and retried by the next run. Critiques that cannot be sent (the server went away) are kept in
|
|
5957
|
+
the spool directory (default .atlas/critic-spool) and sent, byte for byte, by the next invocation.
|
|
5958
|
+
--dry-run send and publish nothing: say how many entries would be judged or skipped, and where images would go
|
|
5959
|
+
--yes allow sending images to a third party without asking (required without a terminal)
|
|
5960
|
+
--max-images n stop cleanly after n images; --max-tokens and --max-cost likewise, from what critics report
|
|
5961
|
+
--mode report (default): scores are shown and the exit code is 0; warn: also list scores below their threshold;
|
|
5962
|
+
gate: exit 1 when a score is below its state kind's threshold, refused (exit 2) unless a calibration
|
|
5963
|
+
with held-out evidence of this critic and version is given with --calibration
|
|
5964
|
+
|
|
5965
|
+
watch A worker: run in a loop over the catalog. It catches up on connecting, judges new or changed entries as they arrive (a picture
|
|
5966
|
+
already judged is skipped without a call), publishes as it goes, keeps what the server cannot take and sends it next time.
|
|
5967
|
+
It stops on Ctrl-C, after --max-batches batches, or when a budget (for the whole watch) is spent, and prints the version to
|
|
5968
|
+
resume from (--since). It reports; it never gates. --dry-run reads the catalog once and sends and publishes nothing.
|
|
5969
|
+
review The ranked "needs polish" list of one critic (lowest first) with its findings and the marks already made. With a terminal it
|
|
5970
|
+
walks through the list and asks: accept (the opinion is right: the UI needs polish), dismiss (it is not), skip. With
|
|
5971
|
+
--entry and --accept or --dismiss it records one decision, for the whole opinion or for --finding ids. A mark is a layer
|
|
5972
|
+
under critic.review.<reviewer> (--reviewer, ATLAS_REVIEWER); it needs no more power than a critic token and never changes
|
|
5973
|
+
a result, a count or the gate.
|
|
5974
|
+
calibrate Judge labelled pictures (--good and --bad directories; a subdirectory's name is the state kind of the pictures in it) and/or
|
|
5975
|
+
read the whole-opinion marks reviewers made (--from-marks), and measure the critic per state kind: sample sizes, precision,
|
|
5976
|
+
recall, the threshold, and whether the figures are in-sample. --heldout-* pictures are never used to choose the threshold.
|
|
5977
|
+
--out writes the calibration that run --calibration reads. Nothing is published.
|
|
5978
|
+
|
|
5979
|
+
--format json prints one document (schemas: critic-list-result, critic-setup-result, critic-doctor-result, critic-run-result,
|
|
5980
|
+
critic-watch-result, critic-review-result, critic-calibrate-result).
|
|
5981
|
+
A command that is wrong is {"ok":false,"error":{"message":…,"code":"usage"}} on standard error.
|
|
5982
|
+
Exit codes: 0 ok, 1 the answer is no (a critic failed, a gate crossed, a problem found), 2 the command was wrong.
|
|
5983
|
+
```
|
|
5414
5984
|
</doc>
|
|
5415
5985
|
|
|
5416
5986
|
<doc path="docs/reference/http-api.md">
|
|
@@ -6442,6 +7012,46 @@ Scoped to one project. The same project may be selected on the unprefixed API us
|
|
|
6442
7012
|
|
|
6443
7013
|
Responses: `200` The catalog.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
|
|
6444
7014
|
|
|
7015
|
+
### The UI catalog as a list a critic iterates: captured states with their picture hash and critiques
|
|
7016
|
+
|
|
7017
|
+
`GET /api/reports/ui-catalog/entries` · role: read
|
|
7018
|
+
|
|
7019
|
+
The captured states of the UI catalog, one entry per state per profile that has a picture stored in its layer, in key order, for a process that reviews them (a critic: see the critics guide). Page with `limit` and the opaque `cursor` of the previous page (`nextCursor`): the cursor names the last key, so each entry is seen exactly once and in the same order. An entry carries `image.sha256` (the hash a critique is anchored to) and `image.path` (fetch the bytes from `/api/reports` + path, through the same safe attachment path as every file), its `state` kind, and `critiques`: those already made, newest first, each marked `current` or `earlier-image`. Keep the `version` of the first page; later ask `since=<that version>` to read only the entries whose layer was added or changed since. A `since` the server does not remember is answered with the whole catalog and `since.honored: false`. `unjudgedBy=<critic>@<version>` keeps entries with no current critique with a verdict from that critic (a failed critique is not a judgement). To be told that something changed, hold `/api/events`: a `report` event carries the new version; then read `since`. A critic-role token may read this.
|
|
7020
|
+
|
|
7021
|
+
- `limit` (query): At most this many entries per page (default 100, at most 1000).
|
|
7022
|
+
- `cursor` (query): The `nextCursor` of the previous page. Anything else is `400`.
|
|
7023
|
+
- `since` (query): A report version (`version` of an earlier answer): only entries whose layer was added or changed since. An unknown version is answered with everything.
|
|
7024
|
+
- `unjudgedBy` (query): `name` or `name@version` of a critic: only entries it has no current verdict for.
|
|
7025
|
+
- `q` (query): Words that must all appear in an entry's key, title, route or area.
|
|
7026
|
+
- `area` (query): Areas. Comma-separated.
|
|
7027
|
+
- `profile` (query): Profile keys, as listed in `profile.key`. Comma-separated.
|
|
7028
|
+
- `state` (query): State kinds: `screen`, `overlay`, `loading`, `error`, `empty`, `unknown`… Comma-separated.
|
|
7029
|
+
- `route` (query): Routes. Comma-separated.
|
|
7030
|
+
- `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
7031
|
+
|
|
7032
|
+
Responses: `200` One page of entries.; `304` Unchanged since the `If-None-Match` ETag.; `400` The cursor is not one this server made.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
|
|
7033
|
+
|
|
7034
|
+
### The UI catalog as a list a critic iterates: captured states with their picture hash and critiques
|
|
7035
|
+
|
|
7036
|
+
`GET /api/projects/{project}/reports/ui-catalog/entries` · role: read
|
|
7037
|
+
|
|
7038
|
+
The captured states of the UI catalog, one entry per state per profile that has a picture stored in its layer, in key order, for a process that reviews them (a critic: see the critics guide). Page with `limit` and the opaque `cursor` of the previous page (`nextCursor`): the cursor names the last key, so each entry is seen exactly once and in the same order. An entry carries `image.sha256` (the hash a critique is anchored to) and `image.path` (fetch the bytes from `/api/reports` + path, through the same safe attachment path as every file), its `state` kind, and `critiques`: those already made, newest first, each marked `current` or `earlier-image`. Keep the `version` of the first page; later ask `since=<that version>` to read only the entries whose layer was added or changed since. A `since` the server does not remember is answered with the whole catalog and `since.honored: false`. `unjudgedBy=<critic>@<version>` keeps entries with no current critique with a verdict from that critic (a failed critique is not a judgement). To be told that something changed, hold `/api/events`: a `report` event carries the new version; then read `since`. A critic-role token may read this.
|
|
7039
|
+
|
|
7040
|
+
Scoped to one project. The same project may be selected on the unprefixed API using ?project= or X-Atlas-Project. Unknown projects return 404 on reads and are created by an authenticated upload.
|
|
7041
|
+
|
|
7042
|
+
- `project` (path, required): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
7043
|
+
- `limit` (query): At most this many entries per page (default 100, at most 1000).
|
|
7044
|
+
- `cursor` (query): The `nextCursor` of the previous page. Anything else is `400`.
|
|
7045
|
+
- `since` (query): A report version (`version` of an earlier answer): only entries whose layer was added or changed since. An unknown version is answered with everything.
|
|
7046
|
+
- `unjudgedBy` (query): `name` or `name@version` of a critic: only entries it has no current verdict for.
|
|
7047
|
+
- `q` (query): Words that must all appear in an entry's key, title, route or area.
|
|
7048
|
+
- `area` (query): Areas. Comma-separated.
|
|
7049
|
+
- `profile` (query): Profile keys, as listed in `profile.key`. Comma-separated.
|
|
7050
|
+
- `state` (query): State kinds: `screen`, `overlay`, `loading`, `error`, `empty`, `unknown`… Comma-separated.
|
|
7051
|
+
- `route` (query): Routes. Comma-separated.
|
|
7052
|
+
|
|
7053
|
+
Responses: `200` One page of entries.; `304` Unchanged since the `If-None-Match` ETag.; `400` The cursor is not one this server made.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
|
|
7054
|
+
|
|
6445
7055
|
### Every test one run recorded
|
|
6446
7056
|
|
|
6447
7057
|
`GET /api/reports/runs/detail` · role: read
|