@crvouga/atlas 1.12.0 → 1.14.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 +13 -1
- package/dist/critics/src/contract.d.ts +113 -0
- package/dist/critics/src/contract.js +48 -0
- package/dist/critics/src/credentials.d.ts +72 -0
- package/dist/critics/src/credentials.js +136 -0
- package/dist/critics/src/critique.d.ts +128 -0
- package/dist/critics/src/critique.js +163 -0
- package/dist/critics/src/index.d.ts +21 -0
- package/dist/critics/src/index.js +21 -0
- package/dist/critics/src/judging.d.ts +153 -0
- package/dist/critics/src/judging.js +145 -0
- package/dist/critics/src/publish.d.ts +23 -0
- package/dist/critics/src/publish.js +21 -0
- package/dist/critics/src/runner.d.ts +104 -0
- package/dist/critics/src/runner.js +202 -0
- package/dist/critics/src/scope.d.ts +8 -0
- package/dist/critics/src/scope.js +26 -0
- package/dist/playwright/src/index.d.ts +4 -0
- package/dist/playwright/src/index.js +4 -0
- package/dist/playwright/src/ui-catalog.d.ts +51 -0
- package/dist/playwright/src/ui-catalog.js +44 -0
- package/dist/reports/src/compose.d.ts +2 -2
- package/dist/reports/src/compose.js +6 -5
- package/dist/reports/src/evidence-types.d.ts +3 -0
- package/dist/reports/src/evidence-view.d.ts +2 -1
- package/dist/reports/src/evidence-view.js +5 -2
- package/dist/reports/src/layer-merge.d.ts +16 -4
- package/dist/reports/src/layer-merge.js +23 -5
- package/dist/reports/src/report-index.js +9 -5
- package/dist/reports/src/report-status.d.ts +1 -1
- package/dist/reports/src/report-status.js +6 -4
- package/dist/reports/src/ui-catalog-publish.d.ts +92 -0
- package/dist/reports/src/ui-catalog-publish.js +68 -0
- package/dist/reports/src/ui-catalog.d.ts +229 -0
- package/dist/reports/src/ui-catalog.js +273 -0
- package/dist/reports/src/ui-state.d.ts +70 -0
- package/dist/reports/src/ui-state.js +66 -0
- package/dist/schema/src/api.d.ts +351 -0
- package/dist/schema/src/api.js +72 -1
- package/dist/schema/src/critique.d.ts +144 -0
- package/dist/schema/src/critique.js +96 -0
- package/dist/schema/src/index.d.ts +2 -1
- package/dist/schema/src/index.js +2 -1
- package/dist/schema/src/server-config.d.ts +7 -1
- package/dist/schema/src/server-config.js +6 -3
- package/dist/server/src/app.js +15 -0
- package/dist/server/src/auth.d.ts +1 -1
- package/dist/server/src/auth.js +2 -2
- package/dist/server/src/config.js +2 -2
- package/dist/server/src/openapi.d.ts +3 -3
- package/dist/server/src/openapi.js +29 -6
- package/dist/server/src/store-api.js +39 -1
- package/docs/agents.md +1 -0
- package/docs/critics.md +190 -0
- package/docs/extending.md +1 -0
- package/docs/reference/critics.md +840 -0
- package/docs/reference/http-api.md +52 -2
- package/docs/reference/playwright.md +63 -0
- package/docs/reference/reports.md +608 -8
- package/docs/reference/schema.md +152 -2
- package/docs/reference/server.md +3 -3
- package/docs/report-contract.md +6 -2
- package/docs/rich-evidence.md +5 -0
- package/docs/server.md +8 -4
- package/docs/ui-catalog.md +173 -0
- package/docs/ui.md +15 -0
- package/llms-full.txt +457 -8
- package/llms.txt +3 -0
- package/package.json +8 -2
- package/packages/critics/README.md +18 -0
- package/packages/schema/schemas/critique.schema.json +254 -0
- package/packages/schema/schemas/effective-delta.schema.json +73 -0
- package/packages/schema/schemas/effective-evidence.schema.json +73 -0
- package/packages/schema/schemas/layer-rows.schema.json +73 -0
- package/packages/schema/schemas/server-config.schema.json +2 -1
- package/packages/schema/schemas/server-info.schema.json +1 -0
- package/packages/server/openapi.json +1443 -80
- package/packages/visualizer/src/app/AppShell.tsx +36 -30
- package/packages/visualizer/src/app/router.tsx +10 -1
- package/packages/visualizer/src/catalog/UiCatalogFilters.tsx +65 -0
- package/packages/visualizer/src/catalog/UiCatalogPage.tsx +342 -0
- package/packages/visualizer/src/catalog/provider.tsx +25 -0
- package/packages/visualizer/src/catalog/search.ts +68 -0
- package/packages/visualizer/src/catalog/words.ts +26 -0
- package/packages/visualizer/src/components/Header.tsx +17 -4
package/llms-full.txt
CHANGED
|
@@ -112,6 +112,7 @@ The complete public export map is generated from the package manifest (wildcards
|
|
|
112
112
|
@crvouga/atlas/vitest
|
|
113
113
|
@crvouga/atlas/schema
|
|
114
114
|
@crvouga/atlas/server
|
|
115
|
+
@crvouga/atlas/critics
|
|
115
116
|
@crvouga/atlas/reports/*
|
|
116
117
|
@crvouga/atlas/schemas/*
|
|
117
118
|
@crvouga/atlas/openapi.json
|
|
@@ -1178,6 +1179,11 @@ time. Every field after `unit` is optional and none of them decides a status, a
|
|
|
1178
1179
|
- **Reading it from the API.** `GET /api/reports/tests/history` entries carry `latency` (their product-latency
|
|
1179
1180
|
measurements) next to `durationMs`. The pure functions are in `@crvouga/atlas/reports/latency`.
|
|
1180
1181
|
|
|
1182
|
+
## A catalog of UI states
|
|
1183
|
+
|
|
1184
|
+
A screenshot per screen, per viewport and color scheme, is a *UI catalog*: check ids are the states, the viewport and scheme are
|
|
1185
|
+
`parameters`, and the viewer's `/catalog` shows them with what is still to be captured. See [the UI catalog](ui-catalog.md).
|
|
1186
|
+
|
|
1181
1187
|
## Finding runs and artifacts
|
|
1182
1188
|
|
|
1183
1189
|
The viewer's **Runs** and **Artifacts** tabs and the API answer one query, so a link means the same thing everywhere.
|
|
@@ -1463,6 +1469,182 @@ This is a statement about publication. Atlas does not check that a stored video
|
|
|
1463
1469
|
re-check later that a stored file is still readable.
|
|
1464
1470
|
</doc>
|
|
1465
1471
|
|
|
1472
|
+
<doc path="docs/ui-catalog.md">
|
|
1473
|
+
# The UI catalog
|
|
1474
|
+
|
|
1475
|
+
A browsable inventory of a product's user interface: each **state** (a screen, or one state of a screen) captured once per
|
|
1476
|
+
**profile** (a viewport, a color scheme, a device), with the facts needed to find and trust it, and what should exist but has not
|
|
1477
|
+
been captured yet. Any program that can write a layer can feed it, and the catalog fills in as results stream in, so a run of
|
|
1478
|
+
thousands of captures can be watched while it happens and split across as many machines as needed.
|
|
1479
|
+
|
|
1480
|
+
Nothing about it is new to the report contract. A state is an ordinary check with a screenshot, the expected set is an ordinary
|
|
1481
|
+
layer, and the stream is the one every viewer already holds. This page is the convention that makes those usable as a catalog, the
|
|
1482
|
+
decisions behind it, and what is not built yet.
|
|
1483
|
+
|
|
1484
|
+
- [How a state is written](#how-a-state-is-written)
|
|
1485
|
+
- [What is expected](#what-is-expected)
|
|
1486
|
+
- [Publishing from any collector](#publishing-from-any-collector)
|
|
1487
|
+
- [Sharding](#sharding)
|
|
1488
|
+
- [Streaming and loading](#streaming-and-loading)
|
|
1489
|
+
- [Exploring it](#exploring-it)
|
|
1490
|
+
- [Decisions](#decisions)
|
|
1491
|
+
- [What stays in your project, and what is not built yet](#what-stays-in-your-project-and-what-is-not-built-yet)
|
|
1492
|
+
|
|
1493
|
+
## How a state is written
|
|
1494
|
+
|
|
1495
|
+
A state is one **check** in a layer whose part has `kind` **`ui-state`**.
|
|
1496
|
+
|
|
1497
|
+
| Part of the idea | Where it goes |
|
|
1498
|
+
| --- | --- |
|
|
1499
|
+
| The state's identity | The check `id`. Make it stable (`checkout/payment`, `home?cohort=new`): a new capture replaces an old one only if the id is the same. |
|
|
1500
|
+
| 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. |
|
|
1501
|
+
| 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. |
|
|
1502
|
+
| 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. |
|
|
1503
|
+
| 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. |
|
|
1504
|
+
| Facts that describe without splitting | The check's `labels` (`shard`, `browser`) and `tags`. Labels never make a different slot. |
|
|
1505
|
+
| 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. |
|
|
1506
|
+
|
|
1507
|
+
```json
|
|
1508
|
+
{
|
|
1509
|
+
"producer": "device-farm", "kind": "ui-state", "environment": "mobile-web",
|
|
1510
|
+
"parameters": { "viewport": "390x844", "scheme": "dark" }, "recordedAtMs": 1760000000000,
|
|
1511
|
+
"checks": [{
|
|
1512
|
+
"id": "checkout/payment", "title": "Payment, card declined", "status": "passed",
|
|
1513
|
+
"labels": { "shard": "2/8" },
|
|
1514
|
+
"extensions": { "atlas.ui-state": { "route": "/checkout", "fixture": "declined-card", "cohort": "new-customer", "flags": ["new-nav"], "covers": ["payment-form"] } },
|
|
1515
|
+
"attachments": [{ "name": "payment.png", "role": "screenshot", "contentType": "image/png", "path": "payment.png" }]
|
|
1516
|
+
}]
|
|
1517
|
+
}
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
`atlas report publish --checks file.json --store …` publishes that file, reading `payment.png` from beside it. Nothing in it
|
|
1521
|
+
mentions how the page was driven. Because a state is a check, the gate counts it (a `failed` capture fails `atlas report status`),
|
|
1522
|
+
`atlas report status` lists it, and a reader that does not know the catalog still shows a check with a screenshot. Evidence never
|
|
1523
|
+
decides a result: a picture that cannot be read leaves the check, its status and its slot as they were.
|
|
1524
|
+
|
|
1525
|
+
## What is expected
|
|
1526
|
+
|
|
1527
|
+
What should exist is a **layer too**, of kind **`ui-plan`**: the same ids, the same profiles (environment and parameters), every
|
|
1528
|
+
check `not-run`, published by a planner, by each shard for the states it was given, or both. A plan fills a slot of its own (the
|
|
1529
|
+
kind is part of the slot), so a capture never erases it and it never hides a capture; and `not-run` is never a pass.
|
|
1530
|
+
|
|
1531
|
+
| A cell (one state in one profile) is | When |
|
|
1532
|
+
| --- | --- |
|
|
1533
|
+
| **Captured** | the winning capture passed |
|
|
1534
|
+
| **Capture failed** | the winning capture failed (its `detail` says why, and a diagnostic picture may be attached) |
|
|
1535
|
+
| **Capturing** | the winning result is `running` |
|
|
1536
|
+
| **Skipped** | a producer recorded it as `not-run` |
|
|
1537
|
+
| **Not captured yet** | a plan expects it and nothing has been recorded |
|
|
1538
|
+
|
|
1539
|
+
A captured cell that no plan names is shown as **not in the expected set**. **Without any plan, nothing is "not captured yet" and
|
|
1540
|
+
nothing is "not in the expected set"**: what should exist is then unknown, and the catalog says so instead of guessing. "Not yet
|
|
1541
|
+
captured" is therefore different from "never declared".
|
|
1542
|
+
|
|
1543
|
+
Coverage against an **inventory of the product's surfaces** is a plan entry with `"inventory": true` (the id is the surface;
|
|
1544
|
+
nothing is captured for it). A state says which surfaces it shows in `covers`. The catalog lists every surface as *covered* (a
|
|
1545
|
+
state that shows it is captured), *states cover it, none captured yet*, or *no state covers it*.
|
|
1546
|
+
|
|
1547
|
+
## Publishing from any collector
|
|
1548
|
+
|
|
1549
|
+
Any program that writes the layer format feeds the catalog: a shell script, a Go test, a Storybook crawler, a device farm.
|
|
1550
|
+
|
|
1551
|
+
```ts
|
|
1552
|
+
import { publishChecks } from '@crvouga/atlas/reports/publish-checks';
|
|
1553
|
+
import { uiPlanLayer, uiStateLayer } from '@crvouga/atlas/reports/ui-catalog-publish';
|
|
1554
|
+
|
|
1555
|
+
// The planner, once: what the product should have, in every profile.
|
|
1556
|
+
await publishChecks(store, uiPlanLayer({
|
|
1557
|
+
producer: 'catalog-plan', recordedAtMs: Date.now(), environment: 'mobile-web', profile: { viewport: '390x844', scheme: 'dark' },
|
|
1558
|
+
states: [{ id: 'checkout/payment', covers: ['payment-form'] }, { id: 'settings', inventory: true }]
|
|
1559
|
+
}));
|
|
1560
|
+
|
|
1561
|
+
// Each collector or shard, with its own captures and no knowledge of the others.
|
|
1562
|
+
await publishChecks(store, uiStateLayer({
|
|
1563
|
+
producer: 'web-capture', recordedAtMs: Date.now(), environment: 'mobile-web', profile: { viewport: '390x844', scheme: 'dark' }, shard: '2/8',
|
|
1564
|
+
states: [{ id: 'checkout/payment', route: '/checkout', covers: ['payment-form'], screenshot: { data: png, width: 390, height: 844 } }]
|
|
1565
|
+
}));
|
|
1566
|
+
```
|
|
1567
|
+
|
|
1568
|
+
`uiStateLayer` and `uiPlanLayer` only build the input of `publishChecks`; they use no browser and no test framework. `store` can be
|
|
1569
|
+
a directory, a bucket or the server (`httpStore`), exactly as for any layer.
|
|
1570
|
+
|
|
1571
|
+
**Playwright is one adapter and not a dependency.** `captureUiState(page, { id, ready })` from `@crvouga/atlas/playwright` waits
|
|
1572
|
+
for a readiness check (bounded), photographs the page and returns the capture for `uiStateLayer`. A page that never became ready is
|
|
1573
|
+
a `failed` capture that says why (with a picture of what it showed), never an exception that ends the run. Nothing that reads,
|
|
1574
|
+
composes or shows the catalog imports a collection framework, and a test keeps it that way.
|
|
1575
|
+
|
|
1576
|
+
## Sharding
|
|
1577
|
+
|
|
1578
|
+
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.
|
|
1579
|
+
Each publishes its own layers (a `shard` label records which, and is shown with each capture). The guarantees are those of every
|
|
1580
|
+
layer ([the contract](report-contract.md#guarantees)), which is what the tests prove for the catalog:
|
|
1581
|
+
|
|
1582
|
+
- The captures of N shards (N is 1 or more), combined, equal the catalog one unsharded run produces.
|
|
1583
|
+
- Running a shard again replaces only the cells it produces (a newer `recordedAtMs` wins its slot); every other cell is untouched.
|
|
1584
|
+
- A shard that never publishes leaves its cells **not captured yet** when a plan names them, and absent otherwise. They are never
|
|
1585
|
+
shown as captured, and the other shards' cells are unaffected.
|
|
1586
|
+
- Atlas does not need the shard count. Completeness is the plan: a cell is expected because a plan says so.
|
|
1587
|
+
|
|
1588
|
+
## Streaming and loading
|
|
1589
|
+
|
|
1590
|
+
- **Streaming.** There is no second stream. A viewer holds the report over `/api/events` and reads what changed with
|
|
1591
|
+
`/api/reports/evidence/effective?since=<version>`; the catalog is computed from the report it holds, so a capture appears when
|
|
1592
|
+
its layer arrives, with no reload and no file rebuilt. Events carry metadata only; the picture is fetched when a card is on screen.
|
|
1593
|
+
- **Reconnecting.** A viewer that was offline or whose stream dropped reads the delta since the version it holds (or the whole report
|
|
1594
|
+
when the server no longer remembers it). Cells are keyed by state and profile, so nothing is missing and nothing is duplicated.
|
|
1595
|
+
- **No stream.** A viewer without the stream (or a static export) polls, as the rest of the viewer does, and shows everything that exists.
|
|
1596
|
+
- **Slow and failing backends.** Each backend is listed and read on its own, so a slow one never holds back the others: their
|
|
1597
|
+
captures are shown, and the slow backend's appear when it answers. A backend that fails, or a layer that cannot be read, is named
|
|
1598
|
+
on the page and in the document (`sources`); a capture that depended on it is not shown as captured.
|
|
1599
|
+
- **Images.** A picture is a file in the layer, addressed by content (`/layers/<layer>/blobs/attachments/<sha256>`), loaded lazily
|
|
1600
|
+
for the cards on screen, and served under the same untrusted-bytes rules as every attachment. A picture given as a `uri` is
|
|
1601
|
+
followed only if it is `http` or `https`. A producer may leave the bytes out (`omitted`), which is told apart from never taken.
|
|
1602
|
+
|
|
1603
|
+
## Exploring it
|
|
1604
|
+
|
|
1605
|
+
The viewer's **UI catalog** (`/catalog`, in the menu beside the spec types once the report holds a state or a plan) shows a card per
|
|
1606
|
+
state with its picture and a coverage badge per profile, and filters by words, coverage, profile, area, route, cohort, feature flag,
|
|
1607
|
+
surface and producer. The address holds the filters and the open state, so a link reproduces the view. Opening a state shows its
|
|
1608
|
+
profiles side by side with the declared facts, the failure reason, and for each picture the layer, backend, producer, shard, commit
|
|
1609
|
+
and time. *Download the manifest* saves the filtered catalog as JSON.
|
|
1610
|
+
|
|
1611
|
+
```sh
|
|
1612
|
+
curl "$ATLAS_SERVER_URL/api/reports/ui-catalog?coverage=failed,missing&profile=…" # JSON; add format=markdown for text
|
|
1613
|
+
```
|
|
1614
|
+
|
|
1615
|
+
`GET /api/reports/ui-catalog` answers the same document the viewer computes (`uiCatalog` from
|
|
1616
|
+
`@crvouga/atlas/reports/ui-catalog`), filtered by `q`, `coverage`, `profile`, `area`, `route`, `cohort`, `flag`, `surface` and
|
|
1617
|
+
`producer`, paged with `limit` and `offset`, with an ETag. Its picture `path` is relative to `/api/reports`.
|
|
1618
|
+
|
|
1619
|
+
## Decisions
|
|
1620
|
+
|
|
1621
|
+
The questions the original proposal left open, and how they are answered here. Each is the simplest answer that stays inside the
|
|
1622
|
+
[stable contracts](report-contract.md#versioning-and-stability): optional fields only, no change to a slot or a winner.
|
|
1623
|
+
|
|
1624
|
+
| Question | Decision |
|
|
1625
|
+
| --- | --- |
|
|
1626
|
+
| Layer and event schema | An existing check in a `ui-state` part, a `screenshot` attachment, facts under the `atlas.ui-state` extension. No new part type, no new schema version. The report row gained one optional field, `state`, only for `ui-state` and `ui-plan` rows. |
|
|
1627
|
+
| Identity and slot | The check id is the state; the slot is the usual (producer, kind, spec, id, environment, parameters). Profile dimensions are `parameters` because they must split the slot; labels never do. |
|
|
1628
|
+
| Expected versus captured | A `ui-plan` layer. `ui-plan` is a diagnostic kind (like `performance`): it qualifies a check and is not counted as one when the check has any other result. |
|
|
1629
|
+
| Coverage against an inventory | `inventory: true` plan entries plus each state's `covers`. |
|
|
1630
|
+
| Carrying images | By content in the layer (`bundleKey`) or by `uri`; loaded lazily; the existing size limits of an upload apply. |
|
|
1631
|
+
| The event stream | No new stream or events: the existing `report` event and `?since=` deltas. |
|
|
1632
|
+
| Sharding | No shard concept in the format; a shard is any publisher. A `shard` label is provenance. Completeness comes from the plan, so Atlas needs no shard count. |
|
|
1633
|
+
| 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. |
|
|
1634
|
+
| 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`. |
|
|
1635
|
+
| Exploration capabilities | Search, filters, profile comparison, coverage and surface coverage, failure diagnostics, deep links, manifest export. |
|
|
1636
|
+
| Static export | The catalog is computed from the layers a viewer holds, so a static export (`atlas client build`) shows it; it just does not stream. |
|
|
1637
|
+
|
|
1638
|
+
## What stays in your project, and what is not built yet
|
|
1639
|
+
|
|
1640
|
+
**Stays in a project** (it is specific to the product): the list of states and profiles to capture, the inventory of surfaces,
|
|
1641
|
+
fixtures and test data, the runner that drives the product to each state, and the readiness conditions of each screen.
|
|
1642
|
+
|
|
1643
|
+
**Not built yet:** a command line to list and publish a catalog without a script (use `atlas report publish --checks` today), a
|
|
1644
|
+
dedicated static single-file export, diff views that compare two commits of a state, and a hosting-platform publish step beyond
|
|
1645
|
+
`atlas serve`. The first slice makes the catalog explorable, streaming, shardable and independent of how it was collected.
|
|
1646
|
+
</doc>
|
|
1647
|
+
|
|
1466
1648
|
<doc path="docs/server.md">
|
|
1467
1649
|
# The Atlas server
|
|
1468
1650
|
|
|
@@ -1671,7 +1853,7 @@ is said in words and the server does not start. The file has a published
|
|
|
1671
1853
|
| How often (seconds) the backends are looked at for layers written by something else | `refreshSeconds` | `--refresh <seconds>` | `ATLAS_REFRESH_SECONDS` | `10` |
|
|
1672
1854
|
| Let anyone look, not upload | `auth.anonymousRead` | `--anonymous-read` | `ATLAS_ANONYMOUS_READ` | no |
|
|
1673
1855
|
| Listen publicly with no tokens (something in front signs people in) | `auth.insecure` | `--insecure` | `ATLAS_INSECURE` | no |
|
|
1674
|
-
| The admin token (also `ATLAS_PUBLISH_TOKEN` and `ATLAS_READ_TOKEN` for the other roles, or `auth.tokens` in the file) | `auth.tokens` | | `ATLAS_TOKEN` | none: the server is open and listens on this machine only |
|
|
1856
|
+
| The admin token (also `ATLAS_PUBLISH_TOKEN`, `ATLAS_CRITIC_TOKEN` and `ATLAS_READ_TOKEN` for the other roles, or `auth.tokens` in the file) | `auth.tokens` | | `ATLAS_TOKEN` | none: the server is open and listens on this machine only |
|
|
1675
1857
|
| The configuration file | `(the file itself)` | `--config <file>` | `ATLAS_CONFIG` | `./atlas.server.json` when it exists |
|
|
1676
1858
|
| Largest uploaded object in MiB | `limits.maxObjectMiB` | | `ATLAS_MAX_OBJECT_MIB` | `256` |
|
|
1677
1859
|
| Largest JSON upload in MiB | `limits.maxJsonMiB` | | `ATLAS_MAX_JSON_MIB` | `32` |
|
|
@@ -1704,16 +1886,18 @@ backend in the client and `/readyz`, and recovers by itself.
|
|
|
1704
1886
|
| Role | May |
|
|
1705
1887
|
| --- | --- |
|
|
1706
1888
|
| `read` | look: the client, the report, `/api/status` |
|
|
1889
|
+
| `critic` | also publish [critiques](critics.md#authentication-and-authorization) and nothing else: blobs and layers whose producer is `critic.<name>` and whose parts are all of kind `critique`. It cannot post test results, start live runs or change settings |
|
|
1707
1890
|
| `publish` | also upload layers, specs and runs, and remove the scratch objects `atlas report doctor` writes |
|
|
1708
1891
|
| `admin` | all server operations |
|
|
1709
1892
|
|
|
1710
1893
|
Tokens are at least 12 characters (`openssl rand -hex 24`) and different from one another. Give them as
|
|
1711
|
-
`ATLAS_TOKEN` (admin), `ATLAS_PUBLISH_TOKEN`, `ATLAS_READ_TOKEN`, or under `auth.tokens` in the file, preferably
|
|
1894
|
+
`ATLAS_TOKEN` (admin), `ATLAS_PUBLISH_TOKEN`, `ATLAS_CRITIC_TOKEN`, `ATLAS_READ_TOKEN`, or under `auth.tokens` in the file, preferably
|
|
1712
1895
|
with `tokenEnv` so the file holds no secret.
|
|
1713
1896
|
|
|
1714
1897
|
- **Sending a token:** `Authorization: Bearer <token>`. A browser's sign-in box also works for *looking* (any
|
|
1715
1898
|
user name, the token as the password). **Uploading needs the Bearer header, never the sign-in box**: a browser
|
|
1716
1899
|
sends those credentials by itself to any page that asks, which would let another site upload with them.
|
|
1900
|
+
- **Run critics against a server with tokens.** With no tokens every caller is an administrator, a critic included.
|
|
1717
1901
|
- **No tokens** means open, and then the server refuses to listen beyond this machine unless told
|
|
1718
1902
|
`--insecure` (for when a proxy in front does the signing in).
|
|
1719
1903
|
- `/healthz` and `/readyz` never need a token, so a load balancer can use them.
|
|
@@ -1956,8 +2140,8 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
|
|
|
1956
2140
|
| `GET /api/projects/{project}/store/{key}` | read | Read an object. |
|
|
1957
2141
|
| `HEAD /api/store/{key}` | read | Does an object exist. |
|
|
1958
2142
|
| `HEAD /api/projects/{project}/store/{key}` | read | Does an object exist. |
|
|
1959
|
-
| `PUT /api/store/{key}` |
|
|
1960
|
-
| `PUT /api/projects/{project}/store/{key}` |
|
|
2143
|
+
| `PUT /api/store/{key}` | critic | Upload an object. |
|
|
2144
|
+
| `PUT /api/projects/{project}/store/{key}` | critic | Upload an object. |
|
|
1961
2145
|
| `DELETE /api/store/{key}` | publish | Remove a scratch object. |
|
|
1962
2146
|
| `DELETE /api/projects/{project}/store/{key}` | publish | Remove a scratch object. |
|
|
1963
2147
|
| `GET /api/reports/tests` | read | Every test, with insights, facets and history statistics. |
|
|
@@ -1986,6 +2170,8 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
|
|
|
1986
2170
|
| `GET /api/projects/{project}/reports/runs` | read | The runs that recorded the report. |
|
|
1987
2171
|
| `GET /api/reports/artifacts` | read | Recorded results that carry evidence, across the whole history. |
|
|
1988
2172
|
| `GET /api/projects/{project}/reports/artifacts` | read | Recorded results that carry evidence, across the whole history. |
|
|
2173
|
+
| `GET /api/reports/ui-catalog` | read | The UI catalog: captured UI states per profile, and what is still expected. |
|
|
2174
|
+
| `GET /api/projects/{project}/reports/ui-catalog` | read | The UI catalog: captured UI states per profile, and what is still expected. |
|
|
1989
2175
|
| `GET /api/reports/runs/detail` | read | Every test one run recorded. |
|
|
1990
2176
|
| `GET /api/projects/{project}/reports/runs/detail` | read | Every test one run recorded. |
|
|
1991
2177
|
| `GET /api/reports/outline` | read | The specs a report is laid over. |
|
|
@@ -2652,7 +2838,7 @@ Zod schema as `LayerDocumentSchema` from `@crvouga/atlas/schema`.
|
|
|
2652
2838
|
| `freshness` | `modified`: recorded from a checkout with uncommitted changes. `unknown`: origin not captured. Advisory only; see [Statuses](#statuses-and-the-gate). |
|
|
2653
2839
|
| `run` | Optional `{ id, title?, startedAtMs?, endedAtMs? }`: the execution a local or manual producer names. Layers with the same `id` in one project are one run in the run list, however many shards or batches uploaded them; a later invocation at the same branch and commit gets another `id`. Without it a run is its `runUrl`, else `branch@sha`, as before. `title` is for people. Grouping and display only: it is **never part of the slot**, never decides a winner or a status, and never hides `sha`, `branch` or `freshness`. A `run` a reader cannot read is treated as absent. |
|
|
2654
2840
|
| `specs` | The specs the layer was recorded against, with a `fingerprint` of their text, so a reader can warn when a spec changed since. Optional. |
|
|
2655
|
-
| `parts[].kind` | An open category (`behavior`, `performance`, `accessibility`, …). A new kind needs no change to any reader. Part of the slot. |
|
|
2841
|
+
| `parts[].kind` | An open category (`behavior`, `performance`, `accessibility`, …). A new kind needs no change to any reader. Part of the slot. `ui-state` and `ui-plan` are the [UI catalog](ui-catalog.md)'s: a captured UI state, and the states that are expected. |
|
|
2656
2842
|
| `parts[].checks[]` | `id` (required, stable), `title`, `status`, `detail`, `specPath`, `measurements`, `durationMs`. |
|
|
2657
2843
|
| `tags`, `labels`, `links`, `attachments`, `annotations`, `extensions` | Evidence, on the layer, a part or a check: what a result carries beyond its outcome. All optional and open. See [evidence](#evidence) and [the guide](extending.md). |
|
|
2658
2844
|
| `checks[].steps`, `checks[].attempts` | A check's timeline, and every try when it was retried. Optional. |
|
|
@@ -2795,9 +2981,13 @@ are part of the contract:
|
|
|
2795
2981
|
Tools map to these four. CTRF `skipped`, `pending` and `other`, and JUnit `<skipped>`, are `not-run`.
|
|
2796
2982
|
An Atlas path that passed on retry (`flaky`) is `passed`, with the detail "Passed on retry (flaky)".
|
|
2797
2983
|
|
|
2798
|
-
Timings that only qualify a check (parts of kind `performance`) are not counted as checks when
|
|
2984
|
+
Timings that only qualify a check (parts of kind `performance`), and what a UI catalog expects (`ui-plan`), are not counted as checks when
|
|
2799
2985
|
anything else is recorded. A measurement over its allowance is reported and never fails a check.
|
|
2800
2986
|
|
|
2987
|
+
Opinions (parts of kind `critique`, the verdicts of [automated UI reviewers](critics.md)) are never counted as checks,
|
|
2988
|
+
even when they are all that was recorded, and are never part of the gate: a low score, or a critic that crashed, leaves
|
|
2989
|
+
`atlas report status` as it was. The `status` of a critique check says only whether the critic ran.
|
|
2990
|
+
|
|
2801
2991
|
**Freshness never changes a status.** A layer whose spec fingerprint no longer matches, whose
|
|
2802
2992
|
checkout was modified, or whose related code changed still shows its recorded outcome, with a
|
|
2803
2993
|
warning ("Potentially outdated: spec changed"). Atlas favours quick, possibly stale evidence over
|
|
@@ -3224,6 +3414,7 @@ Annotation `type`: `note`, `warning`, `deprecation`, `issue`, `accessibility`, `
|
|
|
3224
3414
|
| 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. |
|
|
3225
3415
|
| Structured data of your own | Put it in `extensions` under a name you own. It is kept, and shown as raw data. |
|
|
3226
3416
|
| A new annotation type, role or step kind | Use it. Open vocabularies need no registration. |
|
|
3417
|
+
| 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. |
|
|
3227
3418
|
| Something that should be shown well | Ask for a renderer (below). |
|
|
3228
3419
|
| A field every tool should have | Open an issue. It is added as an optional field, and the rules above guarantee nothing breaks. |
|
|
3229
3420
|
|
|
@@ -3245,6 +3436,199 @@ listed with its name, role, type and size, and offered for download, and everyth
|
|
|
3245
3436
|
unaffected.
|
|
3246
3437
|
</doc>
|
|
3247
3438
|
|
|
3439
|
+
<doc path="docs/critics.md">
|
|
3440
|
+
# Critics: automated reviewers that mark which UIs need polish
|
|
3441
|
+
|
|
3442
|
+
A **critic** looks at one captured UI (an image and what is known about it) and says how polished it is, and ideally
|
|
3443
|
+
where and why it is not. A critic may be a pretrained model that gives a score, a vision-capable language model that
|
|
3444
|
+
follows a rubric, or a classic measure such as a contrast ratio. All three are the same thing to Atlas, through one small
|
|
3445
|
+
contract, so people review the likeliest problems first.
|
|
3446
|
+
|
|
3447
|
+
**Critics are producers that publish layers.** Atlas supplies the contract, the runner, the evidence format and the
|
|
3448
|
+
credential. It never executes a model on the server and never holds a provider key. A critique is not a test result and
|
|
3449
|
+
is not human review: it never changes whether a report passes.
|
|
3450
|
+
|
|
3451
|
+
- [What is available](#what-is-available)
|
|
3452
|
+
- [The critic contract](#the-critic-contract)
|
|
3453
|
+
- [A critique is a layer](#a-critique-is-a-layer)
|
|
3454
|
+
- [Running critics](#running-critics)
|
|
3455
|
+
- [State-aware judging, calibration and modes](#state-aware-judging-calibration-and-modes)
|
|
3456
|
+
- [Authentication and authorization](#authentication-and-authorization)
|
|
3457
|
+
- [Credentials](#credentials)
|
|
3458
|
+
- [What is not here yet](#what-is-not-here-yet)
|
|
3459
|
+
|
|
3460
|
+
## What is available
|
|
3461
|
+
|
|
3462
|
+
Import `@crvouga/atlas/critics`. It needs no model, no provider SDK and no network; a critic brings its own.
|
|
3463
|
+
|
|
3464
|
+
| You want | Use |
|
|
3465
|
+
| --- | --- |
|
|
3466
|
+
| Define a critic | `defineCritic` (the contract below) |
|
|
3467
|
+
| Judge many images with caching, retries, rate limits, timeouts and budgets | `runCritic` |
|
|
3468
|
+
| Store the opinions | `critiqueLayer`, `publishCritiques` (a layer of parts of kind `critique`) |
|
|
3469
|
+
| Read them back | `readCritique`, `critiquesOfLayer`, `critiqueCurrency` |
|
|
3470
|
+
| Read a score in context, measure a threshold, decide an exit code | `relativeVerdict`, `calibrate`, `gateDecision` |
|
|
3471
|
+
| Keep keys out of configuration and logs | `parseCredentialRef`, `resolveCredential`, `assertNoLiteralCredentials`, `createRedactor` |
|
|
3472
|
+
| Let a token publish critiques and nothing else | the `critic` role (below) |
|
|
3473
|
+
|
|
3474
|
+
The JSON Schema of the evidence is [`critique.schema.json`](../packages/schema/schemas/critique.schema.json). The
|
|
3475
|
+
function-level reference is generated from the code: [critics](reference/critics.md).
|
|
3476
|
+
|
|
3477
|
+
## The critic contract
|
|
3478
|
+
|
|
3479
|
+
```ts
|
|
3480
|
+
import { defineCritic } from '@crvouga/atlas/critics';
|
|
3481
|
+
|
|
3482
|
+
export default defineCritic({
|
|
3483
|
+
name: 'contrast', version: '1.0.0', kind: 'heuristic', // kind is an open string: model, llm, ml, heuristic…
|
|
3484
|
+
describe: () => ({ sendsImagesTo: null, needsSetup: false }),
|
|
3485
|
+
evaluate: async ({ image, imageSha256, entry, signal }) => ({
|
|
3486
|
+
verdict: { score: measureContrast(image), scale: '0..1, higher is better' }
|
|
3487
|
+
})
|
|
3488
|
+
});
|
|
3489
|
+
```
|
|
3490
|
+
|
|
3491
|
+
A critic only judges one image. `describe()` says where images are sent (`sendsImagesTo`, `null` when they never leave
|
|
3492
|
+
the machine), the model, and the fixed parameters (temperature, the SHA-256 of the rubric): together with the critic's
|
|
3493
|
+
`name` and `version` and the image hash they are the identity of a judgement. `evaluate` returns a `verdict` (a score
|
|
3494
|
+
and how to read it), `findings`, or both, and may throw: throw a `RateLimitedError` (with the provider's `Retry-After` in
|
|
3495
|
+
milliseconds), an `InvalidOutputError`, or any `CriticError` with your own reason code. The runner records the failure
|
|
3496
|
+
and the next run retries it. A failure never becomes a score.
|
|
3497
|
+
|
|
3498
|
+
## A critique is a layer
|
|
3499
|
+
|
|
3500
|
+
A critique is a check inside a part of kind `critique`, in a layer whose `producer` is `critic.<name>`. The check's
|
|
3501
|
+
`status` says only whether the critic ran: `passed` a verdict was produced, `failed` the critic could not produce one.
|
|
3502
|
+
The opinion is evidence, by the rules of [extending.md](extending.md#the-rules-that-keep-it-compatible-forever), so it
|
|
3503
|
+
can never decide a result. It lives in `extensions["atlas.critique"]` and is repeated as standard evidence, so a viewer
|
|
3504
|
+
that has never heard of critics still shows it: the score is a measurement named `polish`, and each finding is an
|
|
3505
|
+
annotation of type `critique`.
|
|
3506
|
+
|
|
3507
|
+
```json
|
|
3508
|
+
{
|
|
3509
|
+
"producer": "critic.vision-llm",
|
|
3510
|
+
"kind": "critique",
|
|
3511
|
+
"checks": [{
|
|
3512
|
+
"id": "critique/checkout/payment-sheet",
|
|
3513
|
+
"status": "passed",
|
|
3514
|
+
"measurements": [{ "name": "polish", "value": 0.62, "unit": "score" }],
|
|
3515
|
+
"annotations": [{ "type": "critique", "level": "warning", "message": "spacing: Label touches both edges of the button. (suggestion: Increase horizontal padding.)" }],
|
|
3516
|
+
"extensions": { "atlas.critique": {
|
|
3517
|
+
"schema": 1,
|
|
3518
|
+
"subject": { "entryKey": "checkout/payment-sheet", "imageSha256": "…", "state": "overlay" },
|
|
3519
|
+
"critic": { "name": "vision-llm", "kind": "llm", "version": "1.0.0" },
|
|
3520
|
+
"model": { "id": "…", "provider": "…" },
|
|
3521
|
+
"parameters": { "temperature": 0, "rubric": "<sha256 of the rubric>" },
|
|
3522
|
+
"verdict": { "score": 0.62, "scale": "0..1, higher is better", "label": "needs-polish", "confidence": 0.7 },
|
|
3523
|
+
"findings": [{ "id": "f1", "category": "spacing", "severity": "minor", "region": { "unit": "px", "x": 24, "y": 612, "w": 342, "h": 48 }, "rationale": "Label touches both edges of the button.", "suggestion": "Increase horizontal padding." }]
|
|
3524
|
+
} }
|
|
3525
|
+
}]
|
|
3526
|
+
}
|
|
3527
|
+
```
|
|
3528
|
+
|
|
3529
|
+
- A single score (a model-based critic) is a valid critique: everything except `schema`, `subject` and `critic` is optional.
|
|
3530
|
+
- `kind`, `category`, `severity`, `label` and failure `reason` are open strings, and every object is open. A value or field
|
|
3531
|
+
nobody has heard of is kept and shown generically, never rejected.
|
|
3532
|
+
- A reader that cannot read the whole extension (a missing `subject`, a score that is not a number, a `schema` version it
|
|
3533
|
+
does not know) drops the extension and keeps the check and its status. An unreadable opinion is unknown.
|
|
3534
|
+
- **Anchored to pixels.** `subject.imageSha256` is the hash of the image that was judged. When an entry's image changes,
|
|
3535
|
+
`critiqueCurrency` says the old critique judged an earlier image: it is shown, never hidden, and the entry counts as not
|
|
3536
|
+
yet judged.
|
|
3537
|
+
- **Slots.** The producer is the critic's name; the critic and model versions are labels. A newer version of a critic
|
|
3538
|
+
replaces an older one's critique of the same entry. Put the version in `parameters` of `critiqueLayer` to compare
|
|
3539
|
+
versions side by side instead.
|
|
3540
|
+
- **The prompt is recorded as a hash** (`parameters.rubric`), not as text.
|
|
3541
|
+
- **Failures are data.** A crash, a timeout, a rate limit that never cleared and an unparseable answer are `failed`
|
|
3542
|
+
critiques with `failure.reason` (`error`, `timeout`, `rate-limited`, `invalid-output`, `image-mismatch`…) and a message.
|
|
3543
|
+
|
|
3544
|
+
**Critiques never change the gate.** Parts of kind `critique` are left out of every count and of `atlas report status`,
|
|
3545
|
+
even when they are the only thing recorded (a report of opinions alone has no test results, so the gate says so). A low
|
|
3546
|
+
score does not fail it, and neither does a critic that crashed. A project that wants a score to fail something opts in
|
|
3547
|
+
with `gateDecision` in `gate` mode, which is computed by the reader from critique data and never changes a check's
|
|
3548
|
+
status, a slot or a winner.
|
|
3549
|
+
|
|
3550
|
+
## Running critics
|
|
3551
|
+
|
|
3552
|
+
`runCritic` owns everything around the judgement; a critic only judges one image.
|
|
3553
|
+
|
|
3554
|
+
- **Caching.** An entry is skipped, without fetching its image, when a critique with a verdict already exists for the same
|
|
3555
|
+
critic, version, model, parameters and image hash (`existing`, read with `critiquesOfLayer`). Identical images across
|
|
3556
|
+
runs are judged once; only a changed image is judged again. A failed critique is not a cache hit. `force` judges again.
|
|
3557
|
+
Layers are content-addressed, so publishing the same outcomes twice is harmless.
|
|
3558
|
+
- **Concurrency, timeout, rate limits.** A bounded number of calls in flight. A call over `timeoutMs` is aborted and
|
|
3559
|
+
recorded as `timeout`; the run goes on. A rate-limit answer waits at least `Retry-After`, with exponential backoff and
|
|
3560
|
+
jitter, for a bounded number of attempts, and then is recorded as `rate-limited`.
|
|
3561
|
+
- **Budgets.** `budget: { maxImages, maxTokens, maxCostUsd }`. When one is reached the run stops cleanly and returns what
|
|
3562
|
+
it finished with `stoppedBy: 'budget'`. Cost and tokens come from the `usage` a critic reports.
|
|
3563
|
+
- **Dry run.** `dryRun` loads and sends nothing and returns what would be judged and skipped, and where images would go
|
|
3564
|
+
(`sendsImagesTo`).
|
|
3565
|
+
- **Reproducibility.** Results come back in the order of the entries, so the same inputs publish the same layer; a
|
|
3566
|
+
deterministic critic gives identical scores on every run. Atlas records what explains a result (critic, version,
|
|
3567
|
+
model, rubric hash, parameters) and does not promise that a language model answers the same twice. A re-run adds a
|
|
3568
|
+
newer layer instead of changing an old one.
|
|
3569
|
+
- **Images may leave the machine.** `describe().sendsImagesTo` is the thing to show a person before a run that sends
|
|
3570
|
+
images to a third party, and to require explicit confirmation for.
|
|
3571
|
+
|
|
3572
|
+
```ts
|
|
3573
|
+
import { critiquesOfLayer, publishCritiques, runCritic } from '@crvouga/atlas/critics';
|
|
3574
|
+
|
|
3575
|
+
const result = await runCritic({
|
|
3576
|
+
critic, entries, loadImage: (entry) => fetchImage(entry),
|
|
3577
|
+
existing: layers.flatMap((layer) => critiquesOfLayer(layer).map((found) => found.critique)),
|
|
3578
|
+
budget: { maxImages: 200 }, secrets: [providerKey]
|
|
3579
|
+
});
|
|
3580
|
+
await publishCritiques(store, { critic, outcomes: result.outcomes, recordedAtMs: Date.now() });
|
|
3581
|
+
```
|
|
3582
|
+
|
|
3583
|
+
## State-aware judging, calibration and modes
|
|
3584
|
+
|
|
3585
|
+
A raw score is not absolute: a loading overlay is not a full screen.
|
|
3586
|
+
|
|
3587
|
+
- `relativeVerdict` reads a score against the project's scores of the **same state kind** (a percentile within the kind)
|
|
3588
|
+
and against the same entry's previous image (`delta`), and goes into `verdict.relative`.
|
|
3589
|
+
- `calibrate` measures a critic against labelled examples **per state kind**: sample sizes, precision, recall, the
|
|
3590
|
+
threshold chosen, and `inSample`, which is true unless held-out examples were given. In-sample figures flatter a critic
|
|
3591
|
+
and are labelled as such.
|
|
3592
|
+
- `gateDecision` turns scores into an exit code. Modes: `report` (default: shown, always exit 0), `warn` (a warning for each
|
|
3593
|
+
score below its threshold, exit 0), `gate` (exit 1 when a threshold is crossed). `gate` is **refused** (exit 2, with the
|
|
3594
|
+
reasons) unless every state kind present has a calibration of this critic and version with a threshold, at least 10 bad
|
|
3595
|
+
and 10 good examples, and held-out figures. An explicit threshold never waives that evidence.
|
|
3596
|
+
|
|
3597
|
+
## Authentication and authorization
|
|
3598
|
+
|
|
3599
|
+
Run critics against a server that has tokens: a server with no tokens is open and every caller is an administrator, a
|
|
3600
|
+
critic included.
|
|
3601
|
+
|
|
3602
|
+
A token of role `critic` (`ATLAS_CRITIC_TOKEN`, or `role: "critic"` under `auth.tokens`) may do what `read` may, and
|
|
3603
|
+
publish critiques, and nothing else. Through the store protocol (`PUT /api/store/…`) it may upload blobs and layers, and
|
|
3604
|
+
the catalog entries that make them visible, whose `producer` is `critic.<name>` and whose parts are all of kind
|
|
3605
|
+
`critique`. Anything else is `403` naming the rule it broke, and the layer is not stored. It cannot post test results,
|
|
3606
|
+
start live runs, change settings or remove anything. `publish` and `admin` tokens may publish critiques too. As for every
|
|
3607
|
+
token: send it as `Authorization: Bearer`, never in a URL, never over plain `http` beyond this machine.
|
|
3608
|
+
|
|
3609
|
+
## Credentials
|
|
3610
|
+
|
|
3611
|
+
Developers bring their own models and AI accounts. Configuration names a credential by **reference**
|
|
3612
|
+
(`env:SOME_API_KEY`, `file:/run/secrets/key`) and never contains one, so it can be committed;
|
|
3613
|
+
`assertNoLiteralCredentials` refuses a configuration with a literal key and says where, never what. Credentials are read at
|
|
3614
|
+
run time (`resolveCredential`) and every message the runner records goes through a redactor (`secrets` in `runCritic`,
|
|
3615
|
+
`createRedactor`) that removes them, in their URL-encoded and base64 forms too, and anything shaped like a provider key,
|
|
3616
|
+
token or JWT. A credential never reaches a layer, a progress event or an error message.
|
|
3617
|
+
|
|
3618
|
+
## What is not here yet
|
|
3619
|
+
|
|
3620
|
+
This release delivers the contract, the evidence format, the runner, state-aware judging and calibration, credential
|
|
3621
|
+
handling and the scoped `critic` role. Still to come, tracked in [issue 50](https://github.com/crvouga/atlas/issues/50):
|
|
3622
|
+
|
|
3623
|
+
- the **catalog read surface** for critics (cursor pagination, `since`, realtime events, images by hash), which builds on
|
|
3624
|
+
the UI catalog of [issue 43](https://github.com/crvouga/atlas/issues/43);
|
|
3625
|
+
- the **`atlas critic` command** (`list`, `setup`, `doctor`, `run`, `watch`, `review`, `calibrate`), its interactive mode
|
|
3626
|
+
and its `--format json` documents;
|
|
3627
|
+
- the **built-in UIClip critic** and the **LLM critic** with provider adapters;
|
|
3628
|
+
- the **viewer**: a ranked "needs polish" list, finding overlays and agreement across critics. Until then critiques are
|
|
3629
|
+
stored, composed and kept out of test counts, and the viewer lists only test results.
|
|
3630
|
+
</doc>
|
|
3631
|
+
|
|
3248
3632
|
<doc path="docs/report-backends.md">
|
|
3249
3633
|
|
|
3250
3634
|
</doc>
|
|
@@ -4131,6 +4515,20 @@ when Watch is on screen.
|
|
|
4131
4515
|
- The spec index searches titles, paths and declared domains, and can group by the domain from spec metadata (specs with
|
|
4132
4516
|
none are listed under "No domain declared"; the Tests view reports domain problems).
|
|
4133
4517
|
|
|
4518
|
+
## UI catalog
|
|
4519
|
+
|
|
4520
|
+
`/catalog` (`catalog/`) is the viewer's inventory of captured UI states ([the guide](ui-catalog.md)). It is computed in the page from
|
|
4521
|
+
the layers the viewer already holds (`catalog/provider.tsx`, only while the page is open), so states appear as results stream in and
|
|
4522
|
+
it works without a server. Cards are Mantine `Card`s in a responsive `SimpleGrid`; the filters live in the shell's navigation column
|
|
4523
|
+
(`UiCatalogFilters`) and the open state in a `Drawer`, and both are the address (`catalog/search.ts`).
|
|
4524
|
+
|
|
4525
|
+
- Only `captured` is called captured. Every other coverage (`Capture failed`, `Capturing`, `Skipped`, `Not captured yet`) has its own
|
|
4526
|
+
words and mark, and a cell without a picture says so in words instead of showing an empty frame. Never show a plan as a result.
|
|
4527
|
+
- A backend that is not available, a notice, and layers that could not be read are shown above the grid; the catalog says it may be
|
|
4528
|
+
incomplete. Waiting follows the loading policy below, and layers still arriving are counted.
|
|
4529
|
+
- Pictures are `loading="lazy"` and only the cards on screen are fetched. The grid is bounded with *Show more*.
|
|
4530
|
+
- The open state shows every profile side by side with the layer, backend, producer, shard, commit and time of each picture.
|
|
4531
|
+
|
|
4134
4532
|
## Reliability
|
|
4135
4533
|
|
|
4136
4534
|
An open test shows its reliability (`explorer/Reliability.tsx`, worked out by `reports/reliability` from the history the page
|
|
@@ -4311,6 +4709,7 @@ The narrowly scoped exceptions are:
|
|
|
4311
4709
|
| Area | Screens/interactions | Mantine composition |
|
|
4312
4710
|
| --- | --- | --- |
|
|
4313
4711
|
| Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
|
|
4712
|
+
| 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 |
|
|
4314
4713
|
| Project home | Tests/runs/artifacts tabs, 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 |
|
|
4315
4714
|
| 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 |
|
|
4316
4715
|
| 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 |
|
|
@@ -5265,7 +5664,7 @@ Responses: `200` It exists.; `401` No token, or an unknown one. Send `Authorizat
|
|
|
5265
5664
|
|
|
5266
5665
|
### Upload an object
|
|
5267
5666
|
|
|
5268
|
-
`PUT /api/store/{key}` · role:
|
|
5667
|
+
`PUT /api/store/{key}` · role: critic
|
|
5269
5668
|
|
|
5270
5669
|
The write half of the `ObjectStore` interface. The server checks what it can from the bytes in hand and says no to what would corrupt the report for everyone:
|
|
5271
5670
|
|
|
@@ -5277,6 +5676,8 @@ The write half of the `ObjectStore` interface. The server checks what it can fro
|
|
|
5277
5676
|
|
|
5278
5677
|
Upload in dependency order: blobs, then the manifest, then the catalog entry. `atlas report publish`, `atlas report stream` and `httpStore` do. Needs a `Bearer` token of role `publish` or `admin`; a browser's sign-in box is refused.
|
|
5279
5678
|
|
|
5679
|
+
A token of role `critic` may upload only what a critic publishes: blobs, and layers (and their catalog entries) whose `producer` is `critic.<name>` and whose parts are all of kind `critique`. Anything else is `403` with the rule it broke, and nothing is stored.
|
|
5680
|
+
|
|
5280
5681
|
- `key` (path, required): An object key under `layers/`, such as `layers/manifests/<id>.json`. It contains slashes, which are not percent-encoded; every other character is.
|
|
5281
5682
|
- `content-type` (header): Stored with the object and returned when it is read.
|
|
5282
5683
|
- `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
@@ -5285,7 +5686,7 @@ Responses: `200` Already there with the same bytes.; `201` Stored.; `401` No tok
|
|
|
5285
5686
|
|
|
5286
5687
|
### Upload an object
|
|
5287
5688
|
|
|
5288
|
-
`PUT /api/projects/{project}/store/{key}` · role:
|
|
5689
|
+
`PUT /api/projects/{project}/store/{key}` · role: critic
|
|
5289
5690
|
|
|
5290
5691
|
The write half of the `ObjectStore` interface. The server checks what it can from the bytes in hand and says no to what would corrupt the report for everyone:
|
|
5291
5692
|
|
|
@@ -5297,6 +5698,8 @@ The write half of the `ObjectStore` interface. The server checks what it can fro
|
|
|
5297
5698
|
|
|
5298
5699
|
Upload in dependency order: blobs, then the manifest, then the catalog entry. `atlas report publish`, `atlas report stream` and `httpStore` do. Needs a `Bearer` token of role `publish` or `admin`; a browser's sign-in box is refused.
|
|
5299
5700
|
|
|
5701
|
+
A token of role `critic` may upload only what a critic publishes: blobs, and layers (and their catalog entries) whose `producer` is `critic.<name>` and whose parts are all of kind `critique`. Anything else is `403` with the rule it broke, and nothing is stored.
|
|
5702
|
+
|
|
5300
5703
|
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.
|
|
5301
5704
|
|
|
5302
5705
|
- `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.
|
|
@@ -5773,6 +6176,52 @@ Scoped to one project. The same project may be selected on the unprefixed API us
|
|
|
5773
6176
|
|
|
5774
6177
|
Responses: `200` The matching results and the facets.; `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`.
|
|
5775
6178
|
|
|
6179
|
+
### The UI catalog: captured UI states per profile, and what is still expected
|
|
6180
|
+
|
|
6181
|
+
`GET /api/reports/ui-catalog` · role: read
|
|
6182
|
+
|
|
6183
|
+
A browsable inventory of the product's user interface, computed from the winning results of the `ui-state` and `ui-plan` parts of every layer (see the UI catalog guide). Each state is a check id; each cell is one profile of it (the layer's environment and parameters) with its coverage: `captured` only when the winning capture passed, `failed`, `capturing`, `skipped`, or `missing` when a plan expects it and nothing has been captured. A picture is read from the cell's `capture.image.path` under the reports API root, or `uri`. What is expected is itself a layer, so a shard that never published leaves its states `missing`, never captured. The document equals what the viewer computes from the same report. Narrow it by `q` (words), `coverage`, `profile` (a profile `key`), `area`, `route`, `cohort`, `flag`, `surface` and `producer`: AND across parameters, OR within one; a state keeps only the cells that match `profile` and `coverage`. Page the states with `limit` and `offset`. Viewers need not poll: they hold the report over `/api/events` and read changes with `/api/reports/evidence/effective?since=`.
|
|
6184
|
+
|
|
6185
|
+
- `limit` (query): At most this many states (default 200).
|
|
6186
|
+
- `offset` (query): Skip this many states (default 0).
|
|
6187
|
+
- `q` (query): Words that must all appear in a state's id, title, route, fixture, cohort, area, flags, surfaces or producers.
|
|
6188
|
+
- `coverage` (query): Coverage values: `captured`, `failed`, `capturing`, `skipped`, `missing`. Comma-separated.
|
|
6189
|
+
- `profile` (query): Profile keys, as listed in `profiles[].key`.
|
|
6190
|
+
- `area` (query): Areas.
|
|
6191
|
+
- `route` (query): Routes.
|
|
6192
|
+
- `cohort` (query): Cohorts.
|
|
6193
|
+
- `flag` (query): Feature flags a state was captured with.
|
|
6194
|
+
- `surface` (query): Surfaces a state covers.
|
|
6195
|
+
- `producer` (query): Producers that captured or planned a state.
|
|
6196
|
+
- `format` (query): `markdown` for Markdown instead of JSON.
|
|
6197
|
+
- `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
6198
|
+
|
|
6199
|
+
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`.
|
|
6200
|
+
|
|
6201
|
+
### The UI catalog: captured UI states per profile, and what is still expected
|
|
6202
|
+
|
|
6203
|
+
`GET /api/projects/{project}/reports/ui-catalog` · role: read
|
|
6204
|
+
|
|
6205
|
+
A browsable inventory of the product's user interface, computed from the winning results of the `ui-state` and `ui-plan` parts of every layer (see the UI catalog guide). Each state is a check id; each cell is one profile of it (the layer's environment and parameters) with its coverage: `captured` only when the winning capture passed, `failed`, `capturing`, `skipped`, or `missing` when a plan expects it and nothing has been captured. A picture is read from the cell's `capture.image.path` under the reports API root, or `uri`. What is expected is itself a layer, so a shard that never published leaves its states `missing`, never captured. The document equals what the viewer computes from the same report. Narrow it by `q` (words), `coverage`, `profile` (a profile `key`), `area`, `route`, `cohort`, `flag`, `surface` and `producer`: AND across parameters, OR within one; a state keeps only the cells that match `profile` and `coverage`. Page the states with `limit` and `offset`. Viewers need not poll: they hold the report over `/api/events` and read changes with `/api/reports/evidence/effective?since=`.
|
|
6206
|
+
|
|
6207
|
+
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.
|
|
6208
|
+
|
|
6209
|
+
- `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.
|
|
6210
|
+
- `limit` (query): At most this many states (default 200).
|
|
6211
|
+
- `offset` (query): Skip this many states (default 0).
|
|
6212
|
+
- `q` (query): Words that must all appear in a state's id, title, route, fixture, cohort, area, flags, surfaces or producers.
|
|
6213
|
+
- `coverage` (query): Coverage values: `captured`, `failed`, `capturing`, `skipped`, `missing`. Comma-separated.
|
|
6214
|
+
- `profile` (query): Profile keys, as listed in `profiles[].key`.
|
|
6215
|
+
- `area` (query): Areas.
|
|
6216
|
+
- `route` (query): Routes.
|
|
6217
|
+
- `cohort` (query): Cohorts.
|
|
6218
|
+
- `flag` (query): Feature flags a state was captured with.
|
|
6219
|
+
- `surface` (query): Surfaces a state covers.
|
|
6220
|
+
- `producer` (query): Producers that captured or planned a state.
|
|
6221
|
+
- `format` (query): `markdown` for Markdown instead of JSON.
|
|
6222
|
+
|
|
6223
|
+
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`.
|
|
6224
|
+
|
|
5776
6225
|
### Every test one run recorded
|
|
5777
6226
|
|
|
5778
6227
|
`GET /api/reports/runs/detail` · role: read
|
package/llms.txt
CHANGED
|
@@ -11,6 +11,7 @@ Given only the repository link, start at docs/agents.md: install `@crvouga/atlas
|
|
|
11
11
|
- [Integrating Atlas into a project](https://raw.githubusercontent.com/crvouga/atlas/main/docs/integrating.md): This guide takes a project from nothing to a layered, gated report of its specs.
|
|
12
12
|
- [Native test runner reporters](https://raw.githubusercontent.com/crvouga/atlas/main/docs/runners.md): Projects upload results to a shared Atlas node under their own project slug.
|
|
13
13
|
- [Send rich evidence](https://raw.githubusercontent.com/crvouga/atlas/main/docs/rich-evidence.md): A check that says only its title and passed answers one question:
|
|
14
|
+
- [The UI catalog](https://raw.githubusercontent.com/crvouga/atlas/main/docs/ui-catalog.md): A browsable inventory of a product's user interface:
|
|
14
15
|
|
|
15
16
|
## Run and use the server
|
|
16
17
|
|
|
@@ -21,6 +22,7 @@ Given only the repository link, start at docs/agents.md: install `@crvouga/atlas
|
|
|
21
22
|
|
|
22
23
|
- [The report contract](https://raw.githubusercontent.com/crvouga/atlas/main/docs/report-contract.md): This is the normative description of what Atlas reads and writes when it reports on specs of any kind:
|
|
23
24
|
- [Evidence: attachments, timelines, snapshots and everything else](https://raw.githubusercontent.com/crvouga/atlas/main/docs/extending.md): Atlas is opinionated about specs and open about evidence.
|
|
25
|
+
- [Critics: automated reviewers that mark which UIs need polish](https://raw.githubusercontent.com/crvouga/atlas/main/docs/critics.md): A critic looks at one captured UI (an image and what is known about it) and says how polished it is, and ideally where and why it is not.
|
|
24
26
|
- [docs/report-backends.md](https://raw.githubusercontent.com/crvouga/atlas/main/docs/report-backends.md):
|
|
25
27
|
- [Executable statecharts](https://raw.githubusercontent.com/crvouga/atlas/main/docs/statecharts.md): Write down how a product behaves as statecharts in open formats, then let Atlas lint them, plan paths that cover every transition, run those paths against the real product through any driver (a browser, a phone, a pla…
|
|
26
28
|
|
|
@@ -46,6 +48,7 @@ Given only the repository link, start at docs/agents.md: install `@crvouga/atlas
|
|
|
46
48
|
- [TypeScript API: detox](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/detox.md): The Detox driver for statechart runs of React Native apps.
|
|
47
49
|
- [TypeScript API: runners](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/runners.md): Native reporters that upload to the project server.
|
|
48
50
|
- [TypeScript API: vitest](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/vitest.md): Run a statechart plan as Vitest tests.
|
|
51
|
+
- [TypeScript API: critics](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/critics.md): Automated reviewers of the UI catalog: the critic contract, the runner (caching, retries, rate limits, budgets), credential references and redaction, the `atlas.critique` evidence as layers, calibration and report, warn and gate modes.
|
|
49
52
|
- [TypeScript API: playwright-reporter](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/playwright-reporter.md): A Playwright Test reporter that streams every test to Atlas with its duration, device labels, steps, retries, videos, screenshots and traces.
|
|
50
53
|
- [JSON Schemas](https://unpkg.com/@crvouga/atlas/packages/schema/schemas/): the layer format, checks file, report status, command results, server responses, server configuration.
|
|
51
54
|
|