@crvouga/atlas 1.9.0 → 1.11.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/dist/reports/src/review-collection-store.d.ts +47 -0
  3. package/dist/reports/src/review-collection-store.js +214 -0
  4. package/dist/reports/src/review-collection.d.ts +88 -0
  5. package/dist/reports/src/review-collection.js +144 -0
  6. package/dist/reports/src/test-catalog.d.ts +2 -0
  7. package/dist/reports/src/test-catalog.js +1 -1
  8. package/dist/schema/src/api.d.ts +574 -0
  9. package/dist/schema/src/api.js +152 -1
  10. package/dist/server/src/app.js +4 -1
  11. package/dist/server/src/openapi.js +46 -1
  12. package/dist/server/src/review-collections.d.ts +39 -0
  13. package/dist/server/src/review-collections.js +179 -0
  14. package/docs/extending.md +1 -1
  15. package/docs/reference/http-api.md +122 -0
  16. package/docs/reference/reports.md +294 -0
  17. package/docs/reference/schema.md +131 -0
  18. package/docs/reference/server.md +44 -0
  19. package/docs/rich-evidence.md +38 -0
  20. package/docs/server.md +41 -0
  21. package/docs/ui.md +67 -1
  22. package/llms-full.txt +269 -2
  23. package/package.json +1 -1
  24. package/packages/schema/schemas/review-collection.schema.json +398 -0
  25. package/packages/schema/schemas/review-manifest.schema.json +475 -0
  26. package/packages/server/openapi.json +6729 -4084
  27. package/packages/visualizer/src/app/router.tsx +6 -1
  28. package/packages/visualizer/src/evidence/applicability-view.tsx +233 -0
  29. package/packages/visualizer/src/evidence/applicability.test.ts +131 -0
  30. package/packages/visualizer/src/evidence/applicability.ts +241 -0
  31. package/packages/visualizer/src/evidence/artifact-tags.tsx +3 -2
  32. package/packages/visualizer/src/evidence/behavior-panel.tsx +11 -1
  33. package/packages/visualizer/src/evidence/feature-page.tsx +25 -4
  34. package/packages/visualizer/src/explorer/ArtifactsView.tsx +26 -2
  35. package/packages/visualizer/src/explorer/Explorer.tsx +7 -2
  36. package/packages/visualizer/src/explorer/RecordingTextSearch.tsx +355 -0
  37. package/packages/visualizer/src/explorer/RunsView.tsx +11 -1
  38. package/packages/visualizer/src/explorer/collection-model.test.ts +31 -0
  39. package/packages/visualizer/src/explorer/collection-model.ts +45 -0
  40. package/packages/visualizer/src/explorer/collections.tsx +513 -0
  41. package/packages/visualizer/src/explorer/playlist-entries.test.ts +51 -0
  42. package/packages/visualizer/src/explorer/playlist-entries.ts +163 -0
  43. package/packages/visualizer/src/explorer/playlist.tsx +500 -0
  44. package/packages/visualizer/src/explorer/recording-text.test.ts +149 -0
  45. package/packages/visualizer/src/explorer/recording-text.ts +490 -0
  46. package/packages/visualizer/src/explorer/search.ts +10 -2
package/docs/server.md CHANGED
@@ -335,6 +335,37 @@ edit, and the server's time. Nothing about the layer, its results or the report'
335
335
  See [Tagging artifacts and demos](rich-evidence.md#tagging-artifacts-and-demos) for the tag rules. Reviewer tags are
336
336
  shown in the viewer's evidence; the tests API (`artifactTag`) searches the producers' tags.
337
337
 
338
+ ## Review collections
339
+
340
+ A team can publish a named, curated review of a few behaviors: recordings from several explicit executions, notes on
341
+ what each is for, and the gaps nobody recorded yet. A **review collection** is kept beside the report under
342
+ `curation/review-collections/` (never as a layer, a check or a run), so it cannot pass or fail anything, decides no
343
+ slot or winner, and never claims its entries are one execution: every entry keeps the layer, run, commit, environment and
344
+ status it was recorded with.
345
+
346
+ - **Freeze.** `POST /api/reports/review-collections` (publish) takes a title, optional `purpose`, `audience` and `tags`
347
+ (such as `demo`), the ordered `entries` (`layer` and `check` from `GET /api/reports/artifacts`, optionally `kind`
348
+ and one `recording`, and a `caption`) and optional `gaps`. The viewer's **Freeze as review collection** button on the
349
+ Artifacts tab sends the filtered set as it is listed. The server resolves each entry to the result it holds and
350
+ records what the report says about it; an entry it cannot resolve refuses the whole request (`422`, naming the entry).
351
+ The order is the curator's: a failed, retried or incomplete result stays beside a later passing replay, each with its
352
+ own status, time and tries.
353
+ - **Immutable.** A revision is never changed or deleted by the server. Later runs and changed tags do not alter it. To
354
+ change a collection, publish a new revision with `basedOn: { id, revision }` naming the newest (`409` otherwise); older
355
+ revisions stay readable (`?revision=`).
356
+ - **Notes and marks** (`POST .../review-collections/reviews`, publish) are separate immutable records with the token's name
357
+ and the time: a `note`, a `gap` note (optionally at a `timecodeMs` of the entry's recording), `reviewed` and `unreviewed`.
358
+ They are curation: they change no outcome, layer or winner, and a reviewed mark is not an acceptance sign-off.
359
+ - **When a source goes away** (`GET .../review-collections/detail`) the entry stays where it was with its frozen snapshot
360
+ and an `availability` of `unavailable` (its layer is gone, for example collected) or `unverified` (held, but the report
361
+ no longer lists it), with the reason. Nothing is removed or replaced.
362
+ - **Sharing** adds no access. The share URL is the viewer's `?tab=collections&collection=<id>`; reading needs the same
363
+ read access as the report, and a visitor can Read, Watch and Inspect only what the attachment routes already allow them.
364
+ Nothing is public by default.
365
+ - **Export.** `GET .../review-collections/export` is a portable manifest: the exact sources, each file with its recorded
366
+ name, a safe name and path, its `sha256` when the report holds one (`null` is unknown, not verified), and every missing
367
+ source or file. It holds no bytes. `capabilities.reviewCollections` on `/api/server` says a server has all this.
368
+
338
369
  ## Streaming
339
370
 
340
371
  `GET /api/events` is a server-sent-events stream:
@@ -475,6 +506,16 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
475
506
  | `GET /api/projects/{project}/reports/artifact-tags` | read | Reviewer tags of attachments: every recorded edit. |
476
507
  | `POST /api/reports/artifact-tags` | publish | Tag or untag one attachment after upload. |
477
508
  | `POST /api/projects/{project}/reports/artifact-tags` | publish | Tag or untag one attachment after upload. |
509
+ | `GET /api/reports/review-collections` | read | Review collections: curated sets of recordings, newest first. |
510
+ | `GET /api/projects/{project}/reports/review-collections` | read | Review collections: curated sets of recordings, newest first. |
511
+ | `POST /api/reports/review-collections` | publish | Publish an immutable review collection (or a new revision of one). |
512
+ | `POST /api/projects/{project}/reports/review-collections` | publish | Publish an immutable review collection (or a new revision of one). |
513
+ | `GET /api/reports/review-collections/detail` | read | One review collection revision with how its sources stand now. |
514
+ | `GET /api/projects/{project}/reports/review-collections/detail` | read | One review collection revision with how its sources stand now. |
515
+ | `POST /api/reports/review-collections/reviews` | publish | Add a note, a time-coded gap note or a reviewed mark to a collection entry. |
516
+ | `POST /api/projects/{project}/reports/review-collections/reviews` | publish | Add a note, a time-coded gap note or a reviewed mark to a collection entry. |
517
+ | `GET /api/reports/review-collections/export` | read | Portable index (manifest) of one review collection revision. |
518
+ | `GET /api/projects/{project}/reports/review-collections/export` | read | Portable index (manifest) of one review collection revision. |
478
519
  | `GET /api/reports/runs` | read | The runs that recorded the report. |
479
520
  | `GET /api/projects/{project}/reports/runs` | read | The runs that recorded the report. |
480
521
  | `GET /api/reports/artifacts` | read | Recorded results that carry evidence, across the whole history. |
package/docs/ui.md CHANGED
@@ -82,6 +82,15 @@ when Watch is on screen.
82
82
  - The promise's revision (the spec's fingerprint now) and the evidence's revision (the fingerprint the layer recorded)
83
83
  are shown separately. A known difference demotes a recording to "Recording is older" with an alert; a missing
84
84
  fingerprint is "not recorded", never a match.
85
+ - Whether the evidence applies is a separate reading (`evidence/applicability.ts`), shown on Watch and Inspect and as a
86
+ row badge when it is stale (or unknown against a named release): `applies`, `stale` (an earlier promise or another
87
+ build), `unknown`, or `none`. It never changes a status or a coverage state. Age is when the checks ran
88
+ (`recordedAtMs`), never when the layer was uploaded or annotated. A release is named in the address (`release` build,
89
+ `releaseSha`, `releasePlatform`); a branch name or a passing run never supplies one. Another platform, a missing
90
+ build or commit, or a checkout with local changes (`freshness`) is `unknown`, never an exact match. Producers say
91
+ what they know with labels: `build`, `platform`, and `applies-to` with `applies-to-by` (the policy that asserts the
92
+ evidence still holds for those builds; without it the assertion is refused). The "Stale or unverified" filter
93
+ (`evidence=stale`) lists every behavior whose evidence does not apply, including those with none.
85
94
  - A recording is always labelled illustrative: it shows one run and does not approve a behavior for production.
86
95
  `demo`/`representative` tags only add a "Representative demo" label.
87
96
  - The spec index searches titles, paths and declared domains, and can group by the domain from spec metadata (specs with
@@ -120,6 +129,63 @@ environment) is unknown unless the producer declared it under the `atlas.failure
120
129
  wording. Records of one source whose outcome changed are listed oldest first, and a later pass is never called a fix.
121
130
  In the explorer the chosen source is the `source` search parameter (a layer id; unlike `layer` it does not pin a run).
122
131
 
132
+ ## Watch a filtered set as a playlist
133
+
134
+ The Artifacts tab and an opened run have a **Watch these N results** button. It opens a Mantine `Modal` (`explorer/playlist.tsx`)
135
+ over the same filtered set the page lists, in the same order (`explorer/playlist-entries.ts` builds the entries).
136
+
137
+ - Every result of the set is an entry. A failed one is never dropped, and one with no recording, an API-only one or one
138
+ whose file is missing says so in words (`Recording not available`, `API only`, `No recording`); none of these is shown as
139
+ demonstrated. A set longer than the page's limit says it is only the newest results.
140
+ - Only the selected clip is read: the list is the metadata the catalog already sent, the selected result's evidence is read
141
+ when it is selected, and the one `<video>` uses `preload="metadata"`. Opening the playlist downloads no recordings in bulk.
142
+ Posters and durations of unselected entries are not shown for that reason.
143
+ - One player: selecting another clip replaces it, and the position of the clip left is kept for the session so returning
144
+ resumes there. Previous/next and advancing on end move over the watchable entries; a result with nothing to play is
145
+ selected from the list to read why.
146
+ - A clip that fails to load, or whose autoplay the browser blocked, stays in the list and offers Retry, Play and Next clip.
147
+ Speed, "Play the next clip when one ends" and "Repeat" are labelled controls. Captions (`text/vtt` attachments) are offered
148
+ as `<track>` elements.
149
+ - The scenario (title, recorded steps, why it failed) sits beside the player with the recording's scope badges
150
+ (paced, scripted, silent, synthetic) and the sentence that the length is the recording's, not production latency. Developer
151
+ details opens the test pinned to that run's record and closes the playlist.
152
+
153
+ ## Review collections
154
+
155
+ The **Collections** tab (`explorer/collections.tsx`, `?tab=collections&collection=<id>&rev=<n>`) lists and opens review
156
+ collections, and the Artifacts tab's **Freeze as review collection** button (a Mantine `Modal`) freezes the filtered set as it is
157
+ listed. A collection is curation, not a test run (`@crvouga/atlas/reports/review-collection`, `docs/server.md#review-collections`).
158
+
159
+ - The page never shows one status for the whole collection. Every entry shows the status it was **recorded** with ("Failed as
160
+ recorded"), its time, run, commit, layer and tries in order, so entries from several runs are never read as one execution; the
161
+ note at the top says how many runs they come from. The order is the curator's: a failed or incomplete take stays beside a
162
+ later passing replay and is never collapsed to the newest pass.
163
+ - A revision is frozen. Opening an older revision says a newer one exists; a change is a new revision, never an edit.
164
+ - An entry whose source is gone (`unavailable`) or cannot be checked (`unverified`) stays in its place with the reason and
165
+ what it was when frozen, and its recording says it is not available. Nothing is removed or replaced.
166
+ - Notes, time-coded gap notes (`m:ss`, or seconds; a mistake is said, not guessed) and the **Reviewed** switch are separate
167
+ records with who and when. They change no outcome and the page says a mark is not an approval. Writing needs a token that
168
+ may publish, held only in this tab; a read-only visitor is refused with the server's reason and can still Read, Watch and
169
+ Inspect. **Download manifest** is the portable index; the share link adds no access.
170
+
171
+ ## Search inside recordings
172
+
173
+ The Artifacts tab has a **Search inside recordings** section (`explorer/RecordingTextSearch.tsx`; words in `inside`). It
174
+ applies the shared matcher (`find/`) to the transcript, caption and screen-text segments producers attached
175
+ (`explorer/recording-text.ts`; format in [rich evidence](rich-evidence.md#words-in-recordings)), so phrases, close
176
+ spellings, meaning and typed filters (`status:failed`) behave as in every search box.
177
+
178
+ - Nothing is read until words are typed; then each listed result's evidence and text files are read a few at a time
179
+ through the report API (the same access rules as playing the recording). The video is never read. A file the viewer
180
+ may not read is not indexed and is reported as not searched, never counted or quoted. There is no stored index.
181
+ - A result shows the indexed words exactly as indexed, why it matched (exact, close spelling, meaning), the source (spoken,
182
+ on screen, caption), generated text with its generator, confidence and a warning, the time range, the owning result and
183
+ retry attempt, and the result's own outcome. A match is never described as the behavior passing.
184
+ - "No match" is only said about searched recordings. Recordings with no text (`Not indexed`), silent or unsupported ones
185
+ and unreadable text (`Failed`) are counted separately and listed under **Recordings that were not searched**.
186
+ - A result opens the playlist on that result, that attempt's video and, when its time is usable and reachable, that time.
187
+ With no usable time the recording opens at its start and a note says seeking is unavailable.
188
+
123
189
  ## Icons and notation marks
124
190
 
125
191
  Use named imports from `@tabler/icons-react` for all UI glyphs. The icon package supplies SVG paths;
@@ -188,7 +254,7 @@ The narrowly scoped exceptions are:
188
254
  | Area | Screens/interactions | Mantine composition |
189
255
  | --- | --- | --- |
190
256
  | Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
191
- | Project home | Tests/runs/artifacts tabs, a run's Artifacts (files of its layers, shared recordings, direct links), 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 |
257
+ | 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 |
192
258
  | 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 |
193
259
  | 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 |
194
260
  | Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
package/llms-full.txt CHANGED
@@ -1170,6 +1170,44 @@ curl "$ATLAS_SERVER_URL/api/reports/artifacts?artifactTag=demo&type=video&domain
1170
1170
  **live query**: opening it asks again and shows what matches the newest report. It is not a snapshot; an immutable
1171
1171
  review collection is a separate feature. *Copy link* gives the address, which holds the whole query.
1172
1172
 
1173
+ ## Words in recordings
1174
+
1175
+ A reviewer who remembers a phrase that was spoken or shown in a video can search for it on the **Artifacts** tab
1176
+ (**Search inside recordings**, the `inside` address parameter). Atlas never transcribes video or reads a screen: it
1177
+ searches text you attach, and nothing is read from a recording until words are typed. Attach it beside the video, at the
1178
+ same level (the check, or the retry attempt that recorded the video):
1179
+
1180
+ ```ts
1181
+ attachments: [
1182
+ { id: 'video', name: 'checkout.webm', contentType: 'video/webm', data: video },
1183
+ // Spoken words: WebVTT, SubRip, JSON segments or plain text.
1184
+ { name: 'checkout.vtt', role: 'transcript', contentType: 'text/vtt', data: transcript,
1185
+ extensions: { 'atlas.text': { recording: 'video', generated: true, generator: 'my-speech-tool' } } },
1186
+ // Text shown on screen, with its own timing.
1187
+ { name: 'screen.json', role: 'ocr', contentType: 'application/json', data: JSON.stringify({ segments: [{ start: 2, end: 4, text: 'Order total', confidence: 0.9 }] }) }
1188
+ ]
1189
+ ```
1190
+
1191
+ - **Which attachment is text.** One with `extensions["atlas.text"]`, a `role` of `transcript`, `ocr`, `screen-text`,
1192
+ `captions` or `subtitles`, or a WebVTT or SubRip type. `extensions["atlas.text"].kind` (`speech`, `screen`, `caption`)
1193
+ says what it is; otherwise the role does. It belongs to the video that lists its `id` in `captions`, or that
1194
+ `extensions["atlas.text"].recording` names by `id`, else to the only video at the same level.
1195
+ - **Segments.** WebVTT and SubRip cues; JSON as `{ "segments": [...] }` (or `cues`, `results`, `lines`, or a bare list) of
1196
+ `{ text, start, end }` in seconds or `{ text, startMs, endMs }`, with an optional `confidence` between 0 and 1. A
1197
+ segment whose time is missing or invalid is still searched and is shown with no time; the viewer offers the recording
1198
+ and says seeking is unavailable, and never guesses a moment.
1199
+ - **Honesty about the text.** `generated: true` (with `generator`) marks text a tool produced; results show it with its
1200
+ confidence and say it may contain errors. A result always shows the indexed words, never a reworded quotation. A
1201
+ match finds a moment; it is not a new assertion, and the result's own outcome and the retry attempt stay beside it.
1202
+ - **States are different things.** Searched with no match; not indexed (no text was supplied); unsupported (the video is
1203
+ silent by its `scope.audioCaptured: false`, or the text is in a form that is not read, or sits at a `uri`); failed
1204
+ (`extensions["atlas.text"].state: "failed"` with a `reason`, a file left out with `omitted`, or text that did not
1205
+ parse). The ones not searched are listed apart and never counted as "no match".
1206
+ - **Access.** The text is read through the same report API, and so the same access rules, that play the recording. A file
1207
+ the viewer may not read is not indexed and appears in no snippet or count. The index exists only in the open page: there
1208
+ is no stored or cross-project index. Sending evidence to a transcription or OCR service is the producer's explicit
1209
+ decision and happens outside Atlas.
1210
+
1173
1211
  ## Labels describe, parameters split
1174
1212
 
1175
1213
  A result's **slot** is its producer, check id, part kind, spec, environment and **parameters**. The newest completed
@@ -1724,6 +1762,37 @@ edit, and the server's time. Nothing about the layer, its results or the report'
1724
1762
  See [Tagging artifacts and demos](rich-evidence.md#tagging-artifacts-and-demos) for the tag rules. Reviewer tags are
1725
1763
  shown in the viewer's evidence; the tests API (`artifactTag`) searches the producers' tags.
1726
1764
 
1765
+ ## Review collections
1766
+
1767
+ A team can publish a named, curated review of a few behaviors: recordings from several explicit executions, notes on
1768
+ what each is for, and the gaps nobody recorded yet. A **review collection** is kept beside the report under
1769
+ `curation/review-collections/` (never as a layer, a check or a run), so it cannot pass or fail anything, decides no
1770
+ slot or winner, and never claims its entries are one execution: every entry keeps the layer, run, commit, environment and
1771
+ status it was recorded with.
1772
+
1773
+ - **Freeze.** `POST /api/reports/review-collections` (publish) takes a title, optional `purpose`, `audience` and `tags`
1774
+ (such as `demo`), the ordered `entries` (`layer` and `check` from `GET /api/reports/artifacts`, optionally `kind`
1775
+ and one `recording`, and a `caption`) and optional `gaps`. The viewer's **Freeze as review collection** button on the
1776
+ Artifacts tab sends the filtered set as it is listed. The server resolves each entry to the result it holds and
1777
+ records what the report says about it; an entry it cannot resolve refuses the whole request (`422`, naming the entry).
1778
+ The order is the curator's: a failed, retried or incomplete result stays beside a later passing replay, each with its
1779
+ own status, time and tries.
1780
+ - **Immutable.** A revision is never changed or deleted by the server. Later runs and changed tags do not alter it. To
1781
+ change a collection, publish a new revision with `basedOn: { id, revision }` naming the newest (`409` otherwise); older
1782
+ revisions stay readable (`?revision=`).
1783
+ - **Notes and marks** (`POST .../review-collections/reviews`, publish) are separate immutable records with the token's name
1784
+ and the time: a `note`, a `gap` note (optionally at a `timecodeMs` of the entry's recording), `reviewed` and `unreviewed`.
1785
+ They are curation: they change no outcome, layer or winner, and a reviewed mark is not an acceptance sign-off.
1786
+ - **When a source goes away** (`GET .../review-collections/detail`) the entry stays where it was with its frozen snapshot
1787
+ and an `availability` of `unavailable` (its layer is gone, for example collected) or `unverified` (held, but the report
1788
+ no longer lists it), with the reason. Nothing is removed or replaced.
1789
+ - **Sharing** adds no access. The share URL is the viewer's `?tab=collections&collection=<id>`; reading needs the same
1790
+ read access as the report, and a visitor can Read, Watch and Inspect only what the attachment routes already allow them.
1791
+ Nothing is public by default.
1792
+ - **Export.** `GET .../review-collections/export` is a portable manifest: the exact sources, each file with its recorded
1793
+ name, a safe name and path, its `sha256` when the report holds one (`null` is unknown, not verified), and every missing
1794
+ source or file. It holds no bytes. `capabilities.reviewCollections` on `/api/server` says a server has all this.
1795
+
1727
1796
  ## Streaming
1728
1797
 
1729
1798
  `GET /api/events` is a server-sent-events stream:
@@ -1864,6 +1933,16 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
1864
1933
  | `GET /api/projects/{project}/reports/artifact-tags` | read | Reviewer tags of attachments: every recorded edit. |
1865
1934
  | `POST /api/reports/artifact-tags` | publish | Tag or untag one attachment after upload. |
1866
1935
  | `POST /api/projects/{project}/reports/artifact-tags` | publish | Tag or untag one attachment after upload. |
1936
+ | `GET /api/reports/review-collections` | read | Review collections: curated sets of recordings, newest first. |
1937
+ | `GET /api/projects/{project}/reports/review-collections` | read | Review collections: curated sets of recordings, newest first. |
1938
+ | `POST /api/reports/review-collections` | publish | Publish an immutable review collection (or a new revision of one). |
1939
+ | `POST /api/projects/{project}/reports/review-collections` | publish | Publish an immutable review collection (or a new revision of one). |
1940
+ | `GET /api/reports/review-collections/detail` | read | One review collection revision with how its sources stand now. |
1941
+ | `GET /api/projects/{project}/reports/review-collections/detail` | read | One review collection revision with how its sources stand now. |
1942
+ | `POST /api/reports/review-collections/reviews` | publish | Add a note, a time-coded gap note or a reviewed mark to a collection entry. |
1943
+ | `POST /api/projects/{project}/reports/review-collections/reviews` | publish | Add a note, a time-coded gap note or a reviewed mark to a collection entry. |
1944
+ | `GET /api/reports/review-collections/export` | read | Portable index (manifest) of one review collection revision. |
1945
+ | `GET /api/projects/{project}/reports/review-collections/export` | read | Portable index (manifest) of one review collection revision. |
1867
1946
  | `GET /api/reports/runs` | read | The runs that recorded the report. |
1868
1947
  | `GET /api/projects/{project}/reports/runs` | read | The runs that recorded the report. |
1869
1948
  | `GET /api/reports/artifacts` | read | Recorded results that carry evidence, across the whole history. |
@@ -2887,7 +2966,7 @@ carry any of these. Every one is optional.
2887
2966
  | `annotations[]` | Notes about the result: a warning, a deprecation, an accessibility finding, a coverage figure. |
2888
2967
  | `attempts[]` | Every try, when the check was retried. |
2889
2968
  | `tags[]` | Free labels to filter by: `smoke`, `payments`. |
2890
- | `labels{}` | Facts about the result that are not its identity: `owner`, `severity`, `browser`, `build`. |
2969
+ | `labels{}` | Facts about the result that are not its identity: `owner`, `severity`, `browser`, `build`. The viewer reads `build`, `platform`, `applies-to` and `applies-to-by` to tell whether evidence applies to a release (see [the viewer conventions](ui.md#behaviors-read-watch-inspect)). |
2891
2970
  | `scope{}` | What the evidence does and does not show: surface, device kind, synthetic data, scripted dependencies, audio, pacing, what was asserted and its limits. Unknown when absent. See [Evidence scope](rich-evidence.md#evidence-scope). |
2892
2971
  | `links[]` | Where to read more: the CI run, the issue, the trace client. |
2893
2972
  | `extensions{}` | Structured data of your own or a tool's, under a name you own. |
@@ -3992,6 +4071,15 @@ when Watch is on screen.
3992
4071
  - The promise's revision (the spec's fingerprint now) and the evidence's revision (the fingerprint the layer recorded)
3993
4072
  are shown separately. A known difference demotes a recording to "Recording is older" with an alert; a missing
3994
4073
  fingerprint is "not recorded", never a match.
4074
+ - Whether the evidence applies is a separate reading (`evidence/applicability.ts`), shown on Watch and Inspect and as a
4075
+ row badge when it is stale (or unknown against a named release): `applies`, `stale` (an earlier promise or another
4076
+ build), `unknown`, or `none`. It never changes a status or a coverage state. Age is when the checks ran
4077
+ (`recordedAtMs`), never when the layer was uploaded or annotated. A release is named in the address (`release` build,
4078
+ `releaseSha`, `releasePlatform`); a branch name or a passing run never supplies one. Another platform, a missing
4079
+ build or commit, or a checkout with local changes (`freshness`) is `unknown`, never an exact match. Producers say
4080
+ what they know with labels: `build`, `platform`, and `applies-to` with `applies-to-by` (the policy that asserts the
4081
+ evidence still holds for those builds; without it the assertion is refused). The "Stale or unverified" filter
4082
+ (`evidence=stale`) lists every behavior whose evidence does not apply, including those with none.
3995
4083
  - A recording is always labelled illustrative: it shows one run and does not approve a behavior for production.
3996
4084
  `demo`/`representative` tags only add a "Representative demo" label.
3997
4085
  - The spec index searches titles, paths and declared domains, and can group by the domain from spec metadata (specs with
@@ -4030,6 +4118,63 @@ environment) is unknown unless the producer declared it under the `atlas.failure
4030
4118
  wording. Records of one source whose outcome changed are listed oldest first, and a later pass is never called a fix.
4031
4119
  In the explorer the chosen source is the `source` search parameter (a layer id; unlike `layer` it does not pin a run).
4032
4120
 
4121
+ ## Watch a filtered set as a playlist
4122
+
4123
+ The Artifacts tab and an opened run have a **Watch these N results** button. It opens a Mantine `Modal` (`explorer/playlist.tsx`)
4124
+ over the same filtered set the page lists, in the same order (`explorer/playlist-entries.ts` builds the entries).
4125
+
4126
+ - Every result of the set is an entry. A failed one is never dropped, and one with no recording, an API-only one or one
4127
+ whose file is missing says so in words (`Recording not available`, `API only`, `No recording`); none of these is shown as
4128
+ demonstrated. A set longer than the page's limit says it is only the newest results.
4129
+ - Only the selected clip is read: the list is the metadata the catalog already sent, the selected result's evidence is read
4130
+ when it is selected, and the one `<video>` uses `preload="metadata"`. Opening the playlist downloads no recordings in bulk.
4131
+ Posters and durations of unselected entries are not shown for that reason.
4132
+ - One player: selecting another clip replaces it, and the position of the clip left is kept for the session so returning
4133
+ resumes there. Previous/next and advancing on end move over the watchable entries; a result with nothing to play is
4134
+ selected from the list to read why.
4135
+ - A clip that fails to load, or whose autoplay the browser blocked, stays in the list and offers Retry, Play and Next clip.
4136
+ Speed, "Play the next clip when one ends" and "Repeat" are labelled controls. Captions (`text/vtt` attachments) are offered
4137
+ as `<track>` elements.
4138
+ - The scenario (title, recorded steps, why it failed) sits beside the player with the recording's scope badges
4139
+ (paced, scripted, silent, synthetic) and the sentence that the length is the recording's, not production latency. Developer
4140
+ details opens the test pinned to that run's record and closes the playlist.
4141
+
4142
+ ## Review collections
4143
+
4144
+ The **Collections** tab (`explorer/collections.tsx`, `?tab=collections&collection=<id>&rev=<n>`) lists and opens review
4145
+ collections, and the Artifacts tab's **Freeze as review collection** button (a Mantine `Modal`) freezes the filtered set as it is
4146
+ listed. A collection is curation, not a test run (`@crvouga/atlas/reports/review-collection`, `docs/server.md#review-collections`).
4147
+
4148
+ - The page never shows one status for the whole collection. Every entry shows the status it was **recorded** with ("Failed as
4149
+ recorded"), its time, run, commit, layer and tries in order, so entries from several runs are never read as one execution; the
4150
+ note at the top says how many runs they come from. The order is the curator's: a failed or incomplete take stays beside a
4151
+ later passing replay and is never collapsed to the newest pass.
4152
+ - A revision is frozen. Opening an older revision says a newer one exists; a change is a new revision, never an edit.
4153
+ - An entry whose source is gone (`unavailable`) or cannot be checked (`unverified`) stays in its place with the reason and
4154
+ what it was when frozen, and its recording says it is not available. Nothing is removed or replaced.
4155
+ - Notes, time-coded gap notes (`m:ss`, or seconds; a mistake is said, not guessed) and the **Reviewed** switch are separate
4156
+ records with who and when. They change no outcome and the page says a mark is not an approval. Writing needs a token that
4157
+ may publish, held only in this tab; a read-only visitor is refused with the server's reason and can still Read, Watch and
4158
+ Inspect. **Download manifest** is the portable index; the share link adds no access.
4159
+
4160
+ ## Search inside recordings
4161
+
4162
+ The Artifacts tab has a **Search inside recordings** section (`explorer/RecordingTextSearch.tsx`; words in `inside`). It
4163
+ applies the shared matcher (`find/`) to the transcript, caption and screen-text segments producers attached
4164
+ (`explorer/recording-text.ts`; format in [rich evidence](rich-evidence.md#words-in-recordings)), so phrases, close
4165
+ spellings, meaning and typed filters (`status:failed`) behave as in every search box.
4166
+
4167
+ - Nothing is read until words are typed; then each listed result's evidence and text files are read a few at a time
4168
+ through the report API (the same access rules as playing the recording). The video is never read. A file the viewer
4169
+ may not read is not indexed and is reported as not searched, never counted or quoted. There is no stored index.
4170
+ - A result shows the indexed words exactly as indexed, why it matched (exact, close spelling, meaning), the source (spoken,
4171
+ on screen, caption), generated text with its generator, confidence and a warning, the time range, the owning result and
4172
+ retry attempt, and the result's own outcome. A match is never described as the behavior passing.
4173
+ - "No match" is only said about searched recordings. Recordings with no text (`Not indexed`), silent or unsupported ones
4174
+ and unreadable text (`Failed`) are counted separately and listed under **Recordings that were not searched**.
4175
+ - A result opens the playlist on that result, that attempt's video and, when its time is usable and reachable, that time.
4176
+ With no usable time the recording opens at its start and a note says seeking is unavailable.
4177
+
4033
4178
  ## Icons and notation marks
4034
4179
 
4035
4180
  Use named imports from `@tabler/icons-react` for all UI glyphs. The icon package supplies SVG paths;
@@ -4098,7 +4243,7 @@ The narrowly scoped exceptions are:
4098
4243
  | Area | Screens/interactions | Mantine composition |
4099
4244
  | --- | --- | --- |
4100
4245
  | Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
4101
- | Project home | Tests/runs/artifacts tabs, a run's Artifacts (files of its layers, shared recordings, direct links), 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 |
4246
+ | 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 |
4102
4247
  | 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 |
4103
4248
  | 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 |
4104
4249
  | Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
@@ -5328,6 +5473,128 @@ Scoped to one project. The same project may be selected on the unprefixed API us
5328
5473
 
5329
5474
  Responses: `201` The recorded entry.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `413` Request too large.; `422` Not a tag edit: a bad target, an empty or over-long tag, or nothing to change.
5330
5475
 
5476
+ ### Review collections: curated sets of recordings, newest first
5477
+
5478
+ `GET /api/reports/review-collections` · role: read
5479
+
5480
+ The newest revision of every review collection, in brief: id, revision and how many exist, title, purpose, audience, tags, how many entries and gaps it has, who published it and when. A collection is curation kept beside the report; it is not a layer, a check or a run, and it has no status. Open one with `/api/reports/review-collections/detail?id=`. Its share URL is the viewer's `?tab=collections&collection=<id>` and needs the same read access as the rest of the report.
5481
+
5482
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
5483
+
5484
+ Responses: `200` The collections.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.
5485
+
5486
+ ### Review collections: curated sets of recordings, newest first
5487
+
5488
+ `GET /api/projects/{project}/reports/review-collections` · role: read
5489
+
5490
+ The newest revision of every review collection, in brief: id, revision and how many exist, title, purpose, audience, tags, how many entries and gaps it has, who published it and when. A collection is curation kept beside the report; it is not a layer, a check or a run, and it has no status. Open one with `/api/reports/review-collections/detail?id=`. Its share URL is the viewer's `?tab=collections&collection=<id>` and needs the same read access as the rest of the report.
5491
+
5492
+ 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.
5493
+
5494
+ - `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.
5495
+
5496
+ Responses: `200` The collections.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.
5497
+
5498
+ ### Publish an immutable review collection (or a new revision of one)
5499
+
5500
+ `POST /api/reports/review-collections` · role: publish
5501
+
5502
+ Freezes an ordered set of references to recorded results as one named collection, recorded under the name of the publish (or admin) token that sends it. Take each entry's `layer` and `check` (and `kind`) from `GET /api/reports/artifacts`, and optionally one of its recordings (`videos[].recording`). The server resolves every entry to the result the report holds and records what the report says about it (status, time, run, commit, environment, tries, videos): the request does not claim them. An entry it cannot resolve refuses the whole request (`422`, naming the entry). The order is yours and is never re-sorted; failed, rejected and incomplete results stay in it beside a later passing replay.
5503
+
5504
+ Entries from different runs are never one execution: each keeps its own layer, run and status. A collection has no status of its own, adds no check and cannot change any result or winner. A revision is immutable. To change a collection, publish a new revision with `basedOn: { id, revision }` naming the newest revision (`409` otherwise); the earlier ones stay readable. Notes and reviewed marks are separate (`/reviews`). A reader-only token, a browser sign-in box and an anonymous reader are refused; nothing is shared publicly by default.
5505
+
5506
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
5507
+
5508
+ Responses: `201` The recorded revision.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `404` `basedOn` names no collection.; `409` `basedOn.revision` is not the newest.; `413` Request too large.; `422` Not a collection, or an entry the report does not hold.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
5509
+
5510
+ ### Publish an immutable review collection (or a new revision of one)
5511
+
5512
+ `POST /api/projects/{project}/reports/review-collections` · role: publish
5513
+
5514
+ Freezes an ordered set of references to recorded results as one named collection, recorded under the name of the publish (or admin) token that sends it. Take each entry's `layer` and `check` (and `kind`) from `GET /api/reports/artifacts`, and optionally one of its recordings (`videos[].recording`). The server resolves every entry to the result the report holds and records what the report says about it (status, time, run, commit, environment, tries, videos): the request does not claim them. An entry it cannot resolve refuses the whole request (`422`, naming the entry). The order is yours and is never re-sorted; failed, rejected and incomplete results stay in it beside a later passing replay.
5515
+
5516
+ Entries from different runs are never one execution: each keeps its own layer, run and status. A collection has no status of its own, adds no check and cannot change any result or winner. A revision is immutable. To change a collection, publish a new revision with `basedOn: { id, revision }` naming the newest revision (`409` otherwise); the earlier ones stay readable. Notes and reviewed marks are separate (`/reviews`). A reader-only token, a browser sign-in box and an anonymous reader are refused; nothing is shared publicly by default.
5517
+
5518
+ 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.
5519
+
5520
+ - `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.
5521
+
5522
+ Responses: `201` The recorded revision.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `404` `basedOn` names no collection.; `409` `basedOn.revision` is not the newest.; `413` Request too large.; `422` Not a collection, or an entry the report does not hold.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
5523
+
5524
+ ### One review collection revision with how its sources stand now
5525
+
5526
+ `GET /api/reports/review-collections/detail` · role: read
5527
+
5528
+ The revision (the newest without `revision`), every revision number, the review notes and reviewed marks of that revision, and for each entry `availability`: `available` (its layer is held and the report lists the result), `unverified` (the layer is held but the report no longer lists the result or recording) or `unavailable` (the layer is gone, for example deleted by garbage collection), with the reason. An entry whose source is not available stays in its place with its frozen snapshot; it is never removed or replaced. Entries keep the status each layer recorded, with their own times, in the order the curator chose. A reviewed mark is curation: it changes no outcome and is not an approval.
5529
+
5530
+ - `id` (query, required): A collection id from the list.
5531
+ - `revision` (query): A revision number. Default: the newest.
5532
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
5533
+
5534
+ Responses: `200` The revision.; `400` No `id`, or a bad `revision`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No such collection or revision.
5535
+
5536
+ ### One review collection revision with how its sources stand now
5537
+
5538
+ `GET /api/projects/{project}/reports/review-collections/detail` · role: read
5539
+
5540
+ The revision (the newest without `revision`), every revision number, the review notes and reviewed marks of that revision, and for each entry `availability`: `available` (its layer is held and the report lists the result), `unverified` (the layer is held but the report no longer lists the result or recording) or `unavailable` (the layer is gone, for example deleted by garbage collection), with the reason. An entry whose source is not available stays in its place with its frozen snapshot; it is never removed or replaced. Entries keep the status each layer recorded, with their own times, in the order the curator chose. A reviewed mark is curation: it changes no outcome and is not an approval.
5541
+
5542
+ 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.
5543
+
5544
+ - `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.
5545
+ - `id` (query, required): A collection id from the list.
5546
+ - `revision` (query): A revision number. Default: the newest.
5547
+
5548
+ Responses: `200` The revision.; `400` No `id`, or a bad `revision`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No such collection or revision.
5549
+
5550
+ ### Add a note, a time-coded gap note or a reviewed mark to a collection entry
5551
+
5552
+ `POST /api/reports/review-collections/reviews` · role: publish
5553
+
5554
+ Records one immutable entry, by the name of the publish (or admin) token and the server's time: a `note` or a `gap` (text required; with `entry` and optionally `timecodeMs`, a moment in that entry's recording), or a `reviewed` / `unreviewed` mark for one entry. It changes no check outcome, no layer, no collection revision and no report winner; a mark is not an acceptance sign-off. The notes of a revision are in its `detail`.
5555
+
5556
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
5557
+
5558
+ Responses: `201` The recorded note.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `404` No such collection or revision.; `413` Request too large.; `422` Not a note: missing text, an unknown entry, or a mark without an entry.
5559
+
5560
+ ### Add a note, a time-coded gap note or a reviewed mark to a collection entry
5561
+
5562
+ `POST /api/projects/{project}/reports/review-collections/reviews` · role: publish
5563
+
5564
+ Records one immutable entry, by the name of the publish (or admin) token and the server's time: a `note` or a `gap` (text required; with `entry` and optionally `timecodeMs`, a moment in that entry's recording), or a `reviewed` / `unreviewed` mark for one entry. It changes no check outcome, no layer, no collection revision and no report winner; a mark is not an acceptance sign-off. The notes of a revision are in its `detail`.
5565
+
5566
+ 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.
5567
+
5568
+ - `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.
5569
+
5570
+ Responses: `201` The recorded note.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `404` No such collection or revision.; `413` Request too large.; `422` Not a note: missing text, an unknown entry, or a mark without an entry.
5571
+
5572
+ ### Portable index (manifest) of one review collection revision
5573
+
5574
+ `GET /api/reports/review-collections/export` · role: read
5575
+
5576
+ A manifest an archive can be built from: the collection, each entry with its exact source (layer, check, kind, recording), the status and run it was recorded with, how it stands now, whether it is marked reviewed, and the files it references with their name as recorded, a safe name and path (`files/<position>-<safeName>`), and the recorded `sha256` when the report holds one (`null` is unknown, not verified). A source that cannot be read now is listed under `missing` and its files are marked `missing`; nothing is left out silently. The manifest contains no bytes; recordings are fetched from the attachment routes, which keep their own access control. Sent as an attachment.
5577
+
5578
+ - `id` (query, required): A collection id.
5579
+ - `revision` (query): A revision number. Default: the newest.
5580
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
5581
+
5582
+ Responses: `200` The manifest.; `400` No `id`, or a bad `revision`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No such collection or revision.
5583
+
5584
+ ### Portable index (manifest) of one review collection revision
5585
+
5586
+ `GET /api/projects/{project}/reports/review-collections/export` · role: read
5587
+
5588
+ A manifest an archive can be built from: the collection, each entry with its exact source (layer, check, kind, recording), the status and run it was recorded with, how it stands now, whether it is marked reviewed, and the files it references with their name as recorded, a safe name and path (`files/<position>-<safeName>`), and the recorded `sha256` when the report holds one (`null` is unknown, not verified). A source that cannot be read now is listed under `missing` and its files are marked `missing`; nothing is left out silently. The manifest contains no bytes; recordings are fetched from the attachment routes, which keep their own access control. Sent as an attachment.
5589
+
5590
+ 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.
5591
+
5592
+ - `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.
5593
+ - `id` (query, required): A collection id.
5594
+ - `revision` (query): A revision number. Default: the newest.
5595
+
5596
+ Responses: `200` The manifest.; `400` No `id`, or a bad `revision`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No such collection or revision.
5597
+
5331
5598
  ### The runs that recorded the report
5332
5599
 
5333
5600
  `GET /api/reports/runs` · role: read
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crvouga/atlas",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Atlas: specs of any kind, reported as layered evidence. Publish checks from any runner (JUnit, CTRF, a script), combine them across nodes, branches, environments and backends, gate CI on the result, and run statecharts against the real app.",
5
5
  "license": "MIT",
6
6
  "type": "module",