@crvouga/atlas 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/dist/core/src/cli.d.ts +2 -2
- package/dist/core/src/cli.js +7 -1
- package/dist/critics/src/catalog-client.d.ts +91 -0
- package/dist/critics/src/catalog-client.js +194 -0
- package/dist/critics/src/cli-calibrate.d.ts +4 -0
- package/dist/critics/src/cli-calibrate.js +211 -0
- package/dist/critics/src/cli-common.d.ts +108 -0
- package/dist/critics/src/cli-common.js +155 -0
- package/dist/critics/src/cli-review.d.ts +4 -0
- package/dist/critics/src/cli-review.js +174 -0
- package/dist/critics/src/cli-watch.d.ts +4 -0
- package/dist/critics/src/cli-watch.js +191 -0
- package/dist/critics/src/cli.d.ts +13 -0
- package/dist/critics/src/cli.js +490 -0
- package/dist/critics/src/index.d.ts +3 -0
- package/dist/critics/src/index.js +3 -0
- package/dist/critics/src/judging.d.ts +8 -0
- package/dist/critics/src/llm.d.ts +120 -0
- package/dist/critics/src/llm.js +266 -0
- package/dist/critics/src/marks.d.ts +74 -0
- package/dist/critics/src/marks.js +76 -0
- package/dist/critics/src/registry.d.ts +125 -0
- package/dist/critics/src/registry.js +203 -0
- package/dist/critics/src/spool.d.ts +32 -0
- package/dist/critics/src/spool.js +129 -0
- package/dist/critics/src/uiclip.d.ts +82 -0
- package/dist/critics/src/uiclip.js +167 -0
- package/dist/reports/src/compose.d.ts +23 -1
- package/dist/reports/src/compose.js +29 -19
- package/dist/reports/src/critique-review.d.ts +111 -0
- package/dist/reports/src/critique-review.js +123 -0
- package/dist/reports/src/critique-row.d.ts +24 -0
- package/dist/reports/src/critique-row.js +39 -0
- package/dist/reports/src/evidence-types.d.ts +5 -0
- package/dist/reports/src/layer-merge.js +5 -0
- package/dist/reports/src/report-cli.d.ts +13 -0
- package/dist/reports/src/report-cli.js +6 -4
- package/dist/reports/src/report-index.d.ts +6 -0
- package/dist/reports/src/report-index.js +12 -0
- package/dist/reports/src/ui-catalog-cli.d.ts +10 -0
- package/dist/reports/src/ui-catalog-cli.js +391 -0
- package/dist/reports/src/ui-catalog-diff.d.ts +104 -0
- package/dist/reports/src/ui-catalog-diff.js +130 -0
- package/dist/reports/src/ui-catalog-entries.d.ts +147 -0
- package/dist/reports/src/ui-catalog-entries.js +190 -0
- package/dist/reports/src/ui-catalog-export.d.ts +82 -0
- package/dist/reports/src/ui-catalog-export.js +221 -0
- package/dist/reports/src/ui-catalog-publish.js +2 -2
- package/dist/reports/src/ui-catalog.d.ts +2 -0
- package/dist/reports/src/ui-catalog.js +1 -1
- package/dist/reports/src/ui-state.d.ts +2 -0
- package/dist/reports/src/ui-state.js +2 -2
- package/dist/schema/src/api.d.ts +653 -1
- package/dist/schema/src/api.js +44 -0
- package/dist/schema/src/cli-results.d.ts +991 -1
- package/dist/schema/src/cli-results.js +265 -1
- package/dist/schema/src/critique.d.ts +31 -0
- package/dist/schema/src/critique.js +26 -0
- package/dist/schema/src/server-config.d.ts +3 -3
- package/dist/server/src/app.js +28 -1
- package/dist/server/src/openapi.js +19 -2
- package/docs/agents.md +16 -2
- package/docs/critics.md +273 -9
- package/docs/extending.md +1 -1
- package/docs/reference/cli.md +126 -1
- package/docs/reference/core.md +1 -1
- package/docs/reference/critics.md +1131 -29
- package/docs/reference/http-api.md +40 -0
- package/docs/reference/reports.md +855 -0
- package/docs/reference/schema.md +197 -1
- package/docs/report-contract.md +2 -1
- package/docs/server.md +2 -0
- package/docs/ui-catalog.md +110 -13
- package/docs/ui.md +69 -2
- package/llms-full.txt +636 -26
- package/llms.txt +1 -1
- package/package.json +9 -1
- package/packages/critics/README.md +1 -0
- package/packages/schema/schemas/critic-calibrate-result.schema.json +274 -0
- package/packages/schema/schemas/critic-doctor-result.schema.json +74 -0
- package/packages/schema/schemas/critic-list-result.schema.json +90 -0
- package/packages/schema/schemas/critic-review-result.schema.json +397 -0
- package/packages/schema/schemas/critic-run-result.schema.json +249 -0
- package/packages/schema/schemas/critic-setup-result.schema.json +34 -0
- package/packages/schema/schemas/critic-watch-result.schema.json +211 -0
- package/packages/schema/schemas/critique-mark.schema.json +81 -0
- package/packages/schema/schemas/effective-delta.schema.json +337 -0
- package/packages/schema/schemas/effective-evidence.schema.json +337 -0
- package/packages/schema/schemas/layer-rows.schema.json +337 -0
- package/packages/schema/schemas/ui-catalog-diff-result.schema.json +201 -0
- package/packages/schema/schemas/ui-catalog-export-result.schema.json +83 -0
- package/packages/schema/schemas/ui-catalog-publish-result.schema.json +116 -0
- package/packages/schema/schemas/ui-catalog-status.schema.json +197 -0
- package/packages/server/openapi.json +6755 -4738
- package/packages/visualizer/src/app/AppShell.tsx +3 -1
- package/packages/visualizer/src/app/router.tsx +20 -7
- package/packages/visualizer/src/catalog/Picture.tsx +32 -0
- package/packages/visualizer/src/catalog/UiCatalogCompare.tsx +289 -0
- package/packages/visualizer/src/catalog/UiCatalogPage.tsx +18 -28
- package/packages/visualizer/src/catalog/history.ts +61 -0
- package/packages/visualizer/src/catalog/search.ts +14 -3
- package/packages/visualizer/src/components/Header.tsx +16 -4
- package/packages/visualizer/src/critiques/CritiquesPage.tsx +402 -0
- package/packages/visualizer/src/critiques/FindingOverlay.tsx +50 -0
- package/packages/visualizer/src/critiques/ranking.ts +54 -0
- package/packages/visualizer/src/critiques/search.ts +41 -0
- package/packages/visualizer/src/evidence/applicability-view.tsx +11 -7
- package/packages/visualizer/src/evidence/behaviors-page.tsx +22 -15
- package/packages/visualizer/src/evidence/evidence-context.tsx +2 -1
- package/packages/visualizer/src/explorer/Explorer.tsx +5 -0
- package/packages/visualizer/src/explorer/TestDetail.tsx +4 -1
- package/packages/visualizer/src/explorer/parts.tsx +19 -1
- package/packages/visualizer/src/explorer/playlist.tsx +11 -7
package/docs/reference/schema.md
CHANGED
|
@@ -572,11 +572,27 @@ export const UiCatalogSchema = …
|
|
|
572
572
|
`GET /api/reports/ui-catalog`: the UI catalog, computed from the winning results of the `ui-state` and `ui-plan` parts
|
|
573
573
|
of every layer. States are in id order and cells in profile order, so the same layers always give the same document.
|
|
574
574
|
|
|
575
|
+
#### `CatalogEntrySchema`
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
export const CatalogEntrySchema = …
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
An entry of the catalog as a critic reads it: one captured state in one profile, with the hash of its stored picture and the critiques already made of it.
|
|
582
|
+
|
|
583
|
+
#### `CatalogEntriesSchema`
|
|
584
|
+
|
|
585
|
+
```ts
|
|
586
|
+
export const CatalogEntriesSchema = …
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
`GET /api/reports/ui-catalog/entries`: the UI catalog as a flat, cursor-paged list of captured states for a critic.
|
|
590
|
+
|
|
575
591
|
### Command results (`--format json`)
|
|
576
592
|
|
|
577
593
|
Source: `packages/schema/src/cli-results.ts`
|
|
578
594
|
|
|
579
|
-
What `atlas report publish`, `stream`, `merge`, `doctor` and `verify-recordings` print on standard output with `--format json` (alias
|
|
595
|
+
What `atlas report publish`, `stream`, `merge`, `doctor` and `verify-recordings`, `atlas ui-catalog plan`, `publish`, `status`, `diff` and `export`, and `atlas critic list`, `setup`, `doctor` and `run`, print on standard output with `--format json` (alias
|
|
580
596
|
`--json`), and what any `atlas` command prints on standard error for a command that was wrong: one JSON document,
|
|
581
597
|
so an agent or a script never has to parse prose. (`status` and `policy` already print documents:
|
|
582
598
|
`ReportStatusSchema`, and an IAM policy.) Each is also published as a JSON Schema.
|
|
@@ -686,6 +702,142 @@ export const InitPlanSchema = …
|
|
|
686
702
|
What `atlas init --format json` prints: everything found about the project, and everything it would change.
|
|
687
703
|
Without `--write` nothing is changed. With `--write`, the files are written and `written` says which.
|
|
688
704
|
|
|
705
|
+
#### `CriticListEntrySchema`
|
|
706
|
+
|
|
707
|
+
```ts
|
|
708
|
+
export const CriticListEntrySchema = …
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
One critic as `atlas critic list` shows it.
|
|
712
|
+
|
|
713
|
+
#### `CriticListResultSchema`
|
|
714
|
+
|
|
715
|
+
```ts
|
|
716
|
+
export const CriticListResultSchema = …
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
What `atlas critic list --format json` prints.
|
|
720
|
+
|
|
721
|
+
#### `CriticSetupResultSchema`
|
|
722
|
+
|
|
723
|
+
```ts
|
|
724
|
+
export const CriticSetupResultSchema = …
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
What `atlas critic setup --format json` prints.
|
|
728
|
+
|
|
729
|
+
#### `CriticDoctorCheckSchema`
|
|
730
|
+
|
|
731
|
+
```ts
|
|
732
|
+
export const CriticDoctorCheckSchema = …
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
One thing `atlas critic doctor` checked.
|
|
736
|
+
|
|
737
|
+
#### `CriticDoctorResultSchema`
|
|
738
|
+
|
|
739
|
+
```ts
|
|
740
|
+
export const CriticDoctorResultSchema = …
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
What `atlas critic doctor --format json` prints.
|
|
744
|
+
|
|
745
|
+
#### `CriticRunCriticSchema`
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
export const CriticRunCriticSchema = …
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
What `atlas critic run` did for one critic.
|
|
752
|
+
|
|
753
|
+
#### `CriticRunResultSchema`
|
|
754
|
+
|
|
755
|
+
```ts
|
|
756
|
+
export const CriticRunResultSchema = …
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
What `atlas critic run --format json` prints. A dry run publishes and sends nothing.
|
|
760
|
+
|
|
761
|
+
#### `CriticWatchCriticSchema`
|
|
762
|
+
|
|
763
|
+
```ts
|
|
764
|
+
export const CriticWatchCriticSchema = …
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
What `atlas critic watch` did for one critic.
|
|
768
|
+
|
|
769
|
+
#### `CriticWatchResultSchema`
|
|
770
|
+
|
|
771
|
+
```ts
|
|
772
|
+
export const CriticWatchResultSchema = …
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
What `atlas critic watch --format json` prints, once, when the watch ends.
|
|
776
|
+
|
|
777
|
+
#### `CriticMarkStandingSchema`
|
|
778
|
+
|
|
779
|
+
```ts
|
|
780
|
+
export const CriticMarkStandingSchema = …
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
What reviewers decided about one opinion, as `atlas critic review` reports it.
|
|
784
|
+
|
|
785
|
+
#### `CriticReviewResultSchema`
|
|
786
|
+
|
|
787
|
+
```ts
|
|
788
|
+
export const CriticReviewResultSchema = …
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
What `atlas critic review --format json` prints.
|
|
792
|
+
|
|
793
|
+
#### `CriticCalibrateResultSchema`
|
|
794
|
+
|
|
795
|
+
```ts
|
|
796
|
+
export const CriticCalibrateResultSchema = …
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
What `atlas critic calibrate --format json` prints. A dry run measures nothing and writes nothing.
|
|
800
|
+
|
|
801
|
+
#### `UiCatalogPublishResultSchema`
|
|
802
|
+
|
|
803
|
+
```ts
|
|
804
|
+
export const UiCatalogPublishResultSchema = …
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
What `atlas ui-catalog plan --format json` and `atlas ui-catalog publish --format json` print: the one layer that was written.
|
|
808
|
+
|
|
809
|
+
#### `UiCatalogProblemSchema`
|
|
810
|
+
|
|
811
|
+
```ts
|
|
812
|
+
export const UiCatalogProblemSchema = …
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
One thing that keeps a UI catalog from being complete and good.
|
|
816
|
+
|
|
817
|
+
#### `UiCatalogStatusSchema`
|
|
818
|
+
|
|
819
|
+
```ts
|
|
820
|
+
export const UiCatalogStatusSchema = …
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
What `atlas ui-catalog status --format json` prints.
|
|
824
|
+
|
|
825
|
+
#### `UiCatalogDiffResultSchema`
|
|
826
|
+
|
|
827
|
+
```ts
|
|
828
|
+
export const UiCatalogDiffResultSchema = …
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
What `atlas ui-catalog diff --format json` prints.
|
|
832
|
+
|
|
833
|
+
#### `UiCatalogExportResultSchema`
|
|
834
|
+
|
|
835
|
+
```ts
|
|
836
|
+
export const UiCatalogExportResultSchema = …
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
What `atlas ui-catalog export --format json` prints.
|
|
840
|
+
|
|
689
841
|
#### `CLI_RESULTS`
|
|
690
842
|
|
|
691
843
|
```ts
|
|
@@ -983,6 +1135,50 @@ export type CritiqueFinding = z.output<typeof CritiqueFindingSchema>;
|
|
|
983
1135
|
|
|
984
1136
|
One finding of a critique.
|
|
985
1137
|
|
|
1138
|
+
#### `CRITIQUE_MARK_EXTENSION`
|
|
1139
|
+
|
|
1140
|
+
```ts
|
|
1141
|
+
export const CRITIQUE_MARK_EXTENSION = …
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
The key under which a reviewer's mark of a critique is stored in a check's `extensions`.
|
|
1145
|
+
|
|
1146
|
+
#### `CRITIQUE_MARK_SCHEMA_VERSION`
|
|
1147
|
+
|
|
1148
|
+
```ts
|
|
1149
|
+
export const CRITIQUE_MARK_SCHEMA_VERSION = …
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
The version of the `atlas.critique-mark` extension a reader of this release understands. A mark of another version is unknown, not wrong.
|
|
1153
|
+
|
|
1154
|
+
#### `CRITIQUE_MARK_PRODUCER_PREFIX`
|
|
1155
|
+
|
|
1156
|
+
```ts
|
|
1157
|
+
export const CRITIQUE_MARK_PRODUCER_PREFIX = …
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
The prefix of the `producer` of a layer of marks: `critic.review.<reviewer>`. It is within what a critic-role token may publish.
|
|
1161
|
+
|
|
1162
|
+
#### `CritiqueMarkSchema`
|
|
1163
|
+
|
|
1164
|
+
```ts
|
|
1165
|
+
export const CritiqueMarkSchema = …
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
The `atlas.critique-mark` extension, version 1: a person's verdict on a critic's opinion. `accept` says the opinion was right
|
|
1169
|
+
(the finding is valid, the UI needs polish); `dismiss` says it was not. A mark names the entry and the exact picture it was made
|
|
1170
|
+
about, the critic whose opinion it marks, and optionally one finding of it; without `finding` it marks the critique as a whole.
|
|
1171
|
+
Like the critique it is evidence in a part of kind `critique`: it is never counted and never changes a status, a slot or the gate.
|
|
1172
|
+
A newer mark of the same reviewer for the same subject replaces an older one. Emitted as `critique-mark.schema.json`.
|
|
1173
|
+
|
|
1174
|
+
#### `CritiqueMark`
|
|
1175
|
+
|
|
1176
|
+
```ts
|
|
1177
|
+
export type CritiqueMark = z.output<typeof CritiqueMarkSchema>;
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
A mark as stored in a check's `extensions["atlas.critique-mark"]`.
|
|
1181
|
+
|
|
986
1182
|
### Schema package entry point (`@crvouga/atlas/schema`)
|
|
987
1183
|
|
|
988
1184
|
Source: `packages/schema/src/index.ts`. Import: `@crvouga/atlas/schema`
|
package/docs/report-contract.md
CHANGED
|
@@ -232,7 +232,8 @@ anything else is recorded. A measurement over its allowance is reported and neve
|
|
|
232
232
|
|
|
233
233
|
Opinions (parts of kind `critique`, the verdicts of [automated UI reviewers](critics.md)) are never counted as checks,
|
|
234
234
|
even when they are all that was recorded, and are never part of the gate: a low score, or a critic that crashed, leaves
|
|
235
|
-
`atlas report status` as it was. The `status` of a critique check says only whether the critic ran.
|
|
235
|
+
`atlas report status` as it was. The `status` of a critique check says only whether the critic ran. A person's accept or dismiss of an
|
|
236
|
+
opinion (`atlas.critique-mark`) is evidence in the same kind of part and has the same properties.
|
|
236
237
|
|
|
237
238
|
**Freshness never changes a status.** A layer whose spec fingerprint no longer matches, whose
|
|
238
239
|
checkout was modified, or whose related code changed still shows its recorded outcome, with a
|
package/docs/server.md
CHANGED
|
@@ -545,6 +545,8 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
|
|
|
545
545
|
| `GET /api/projects/{project}/reports/artifacts` | read | Recorded results that carry evidence, across the whole history. |
|
|
546
546
|
| `GET /api/reports/ui-catalog` | read | The UI catalog: captured UI states per profile, and what is still expected. |
|
|
547
547
|
| `GET /api/projects/{project}/reports/ui-catalog` | read | The UI catalog: captured UI states per profile, and what is still expected. |
|
|
548
|
+
| `GET /api/reports/ui-catalog/entries` | read | The UI catalog as a list a critic iterates: captured states with their picture hash and critiques. |
|
|
549
|
+
| `GET /api/projects/{project}/reports/ui-catalog/entries` | read | The UI catalog as a list a critic iterates: captured states with their picture hash and critiques. |
|
|
548
550
|
| `GET /api/reports/runs/detail` | read | Every test one run recorded. |
|
|
549
551
|
| `GET /api/projects/{project}/reports/runs/detail` | read | Every test one run recorded. |
|
|
550
552
|
| `GET /api/reports/outline` | read | The specs a report is laid over. |
|
package/docs/ui-catalog.md
CHANGED
|
@@ -7,16 +7,19 @@ thousands of captures can be watched while it happens and split across as many m
|
|
|
7
7
|
|
|
8
8
|
Nothing about it is new to the report contract. A state is an ordinary check with a screenshot, the expected set is an ordinary
|
|
9
9
|
layer, and the stream is the one every viewer already holds. This page is the convention that makes those usable as a catalog, the
|
|
10
|
-
decisions behind it, and what
|
|
10
|
+
decisions behind it, the command line, and what stays in your project.
|
|
11
11
|
|
|
12
12
|
- [How a state is written](#how-a-state-is-written)
|
|
13
13
|
- [What is expected](#what-is-expected)
|
|
14
14
|
- [Publishing from any collector](#publishing-from-any-collector)
|
|
15
|
+
- [The command line](#the-command-line)
|
|
15
16
|
- [Sharding](#sharding)
|
|
16
17
|
- [Streaming and loading](#streaming-and-loading)
|
|
17
18
|
- [Exploring it](#exploring-it)
|
|
19
|
+
- [Comparing two commits or two profiles](#comparing-two-commits-or-two-profiles)
|
|
20
|
+
- [A single file that needs no server](#a-single-file-that-needs-no-server)
|
|
18
21
|
- [Decisions](#decisions)
|
|
19
|
-
- [What stays in your project, and what
|
|
22
|
+
- [What stays in your project, and what Atlas does not do](#what-stays-in-your-project-and-what-atlas-does-not-do)
|
|
20
23
|
|
|
21
24
|
## How a state is written
|
|
22
25
|
|
|
@@ -28,7 +31,7 @@ A state is one **check** in a layer whose part has `kind` **`ui-state`**.
|
|
|
28
31
|
| 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. |
|
|
29
32
|
| 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. |
|
|
30
33
|
| 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. |
|
|
31
|
-
| 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. |
|
|
34
|
+
| Facts the producer declares | The check's `extensions["atlas.ui-state"]`: `route`, `fixture`, `cohort`, `area`, `kind` (the kind of UI state: `screen`, `overlay`, `loading`, `error`, `empty`… an open vocabulary that [critics](critics.md#reading-the-catalog) judge a state by), `flags` (a list), `covers` (the product surfaces the state shows). All optional. |
|
|
32
35
|
| Facts that describe without splitting | The check's `labels` (`shard`, `browser`) and `tags`. Labels never make a different slot. |
|
|
33
36
|
| 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. |
|
|
34
37
|
|
|
@@ -101,6 +104,35 @@ for a readiness check (bounded), photographs the page and returns the capture fo
|
|
|
101
104
|
a `failed` capture that says why (with a picture of what it showed), never an exception that ends the run. Nothing that reads,
|
|
102
105
|
composes or shows the catalog imports a collection framework, and a test keeps it that way.
|
|
103
106
|
|
|
107
|
+
## The command line
|
|
108
|
+
|
|
109
|
+
`atlas ui-catalog` does the same without a script. Every subcommand prints a JSON document with `--format json`
|
|
110
|
+
([`atlas ui-catalog --help`](reference/cli.md) lists every flag), and exits `0` it worked, `1` the answer is no, `2` the command was wrong.
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
# The expected set under one profile. The file is {"states":[{"id":"checkout/payment","covers":["payment-form"]},{"id":"settings","inventory":true}]}.
|
|
114
|
+
atlas ui-catalog plan --states plan.json --producer catalog-plan --environment mobile-web --param viewport=390x844 --param scheme=dark
|
|
115
|
+
|
|
116
|
+
# The states one collector or shard captured. "screenshot" names a file beside captures.json.
|
|
117
|
+
atlas ui-catalog publish --captures captures.json --producer web-capture --environment mobile-web --param viewport=390x844 --param scheme=dark --shard 2/8
|
|
118
|
+
|
|
119
|
+
atlas ui-catalog status # exit 1 while anything failed, is missing, is still being captured or could not be read
|
|
120
|
+
atlas ui-catalog diff --from 3f9a2c1 --to 8b41d70 # two commits (the whole id, or at least 7 digits)
|
|
121
|
+
atlas ui-catalog export --out public/catalog.html # one file, no server
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`captures.json` is `{"states":[…]}` (or just the list); a state is `{"id","title"?,"status"?,"detail"?,"route"?,"fixture"?,"cohort"?,"area"?,
|
|
125
|
+
"flags"?,"covers"?,"labels"?,"tags"?,"durationMs"?,"screenshot"?}` with `screenshot` a file name or `{"path"|"uri","width"?,"height"?,"contentType"?}`.
|
|
126
|
+
A misspelled field is refused with its path, not ignored. A picture file is read from beside the file and from each `--attach-root`,
|
|
127
|
+
never from a hidden file or a link out. **A `passed` state whose picture cannot be read is published as `failed`, with the reason**: a
|
|
128
|
+
state without its picture is not shown as captured. `--recorded-at` defaults to the file's modification time, so publishing the same
|
|
129
|
+
file twice is the same layer.
|
|
130
|
+
|
|
131
|
+
`status` is the gate: it exits `1` unless the catalog holds a state and nothing needs attention (a failed capture, a state a plan expects
|
|
132
|
+
with nothing captured, a capture still in progress, a backend or layer that could not be read). Without a plan nothing is expected, so
|
|
133
|
+
nothing can be missing: it says so. A skipped cell is counted, not a problem. Schemas: `ui-catalog-publish-result`,
|
|
134
|
+
`ui-catalog-status`, `ui-catalog-diff-result`, `ui-catalog-export-result`.
|
|
135
|
+
|
|
104
136
|
## Sharding
|
|
105
137
|
|
|
106
138
|
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.
|
|
@@ -140,10 +172,54 @@ and time. *Download the manifest* saves the filtered catalog as JSON.
|
|
|
140
172
|
curl "$ATLAS_SERVER_URL/api/reports/ui-catalog?coverage=failed,missing&profile=…" # JSON; add format=markdown for text
|
|
141
173
|
```
|
|
142
174
|
|
|
175
|
+
A separate process that reviews the pictures (a [critic](critics.md#reading-the-catalog)) reads the same catalog as a flat list at
|
|
176
|
+
`GET /api/reports/ui-catalog/entries`: cursor-paged, `since` a report version, with the hash of each stored picture.
|
|
177
|
+
|
|
143
178
|
`GET /api/reports/ui-catalog` answers the same document the viewer computes (`uiCatalog` from
|
|
144
179
|
`@crvouga/atlas/reports/ui-catalog`), filtered by `q`, `coverage`, `profile`, `area`, `route`, `cohort`, `flag`, `surface` and
|
|
145
180
|
`producer`, paged with `limit` and `offset`, with an ETag. Its picture `path` is relative to `/api/reports`.
|
|
146
181
|
|
|
182
|
+
## Comparing two commits or two profiles
|
|
183
|
+
|
|
184
|
+
The viewer's **Compare** button on `/catalog` (`?compare=commit&from=<commit>&to=<commit>`, or `?compare=profile&from=<profile>&to=<profile>`,
|
|
185
|
+
so a link reproduces it) and `atlas ui-catalog diff` set one side against the other. Every pair is one state in one profile.
|
|
186
|
+
|
|
187
|
+
| A pair is | When |
|
|
188
|
+
| --- | --- |
|
|
189
|
+
| **Unchanged** | the same coverage and, when captured, the same stored picture |
|
|
190
|
+
| **Picture changed** | captured on both sides, and the picture's bytes (SHA-256) differ. This is not a measure of how many pixels differ |
|
|
191
|
+
| **Coverage changed** | it stands differently (captured against failed, failed against not captured yet…). A capture that did not pass is never compared by picture |
|
|
192
|
+
| **Cannot tell** | captured on both sides, but a picture has no content hash (it is given only by address, or was left out) |
|
|
193
|
+
| **Only after** / **Only before** | one side has no such cell. It may be new, renamed, or the other side's shard may not have run: it is not called a regression |
|
|
194
|
+
|
|
195
|
+
- **Two commits** compares the layers recorded at each commit. A layer replaces the older one of its slot, so the report keeps only the
|
|
196
|
+
newest capture of every state; the comparison therefore reads the layers themselves (the viewer asks for the layer list and the rows of
|
|
197
|
+
the two commits only; the command line reads the stores). Layers recorded from a checkout with uncommitted changes are not a commit's
|
|
198
|
+
own result and are left out. A commit is the whole id or at least 7 digits that name exactly one commit; anything else is an error
|
|
199
|
+
that lists the commits found, never a guess.
|
|
200
|
+
- **Two profiles** sets each state's cell in one profile beside its cell in another (desktop against mobile, light against dark) in
|
|
201
|
+
the report the viewer holds.
|
|
202
|
+
- A layer or backend that could not be read is named, and what it held is unknown, never "unchanged": `diff` exits `1` for it.
|
|
203
|
+
`--fail-on-change` also exits `1` for any difference, for a gate that must stay the same.
|
|
204
|
+
- The filters (words, area, route, cohort, flag, surface, producer) narrow a comparison; coverage does not, because coverage is what is
|
|
205
|
+
compared.
|
|
206
|
+
|
|
207
|
+
## A single file that needs no server
|
|
208
|
+
|
|
209
|
+
`atlas ui-catalog export --out catalog.html` writes the catalog as **one HTML file**: the pictures embedded as `data:` addresses, the
|
|
210
|
+
manifest embedded as JSON (`<script type="application/json" id="ui-catalog">`), a search box and a coverage filter. Open it from a disk,
|
|
211
|
+
attach it to a ticket or serve it as a file. It is the catalog at the moment it was made, and says so; it does not stream (for that,
|
|
212
|
+
serve the store with `atlas serve` or build the site with `atlas client build`, which shows the same catalog).
|
|
213
|
+
|
|
214
|
+
- **Size.** Hosting platforms cap the size of one file. `--max-mb` (default 32) limits the embedded pictures; pictures past it are listed
|
|
215
|
+
in the page as left out, with the reason, in the order the page shows them, and the manifest stays complete. `--images none` embeds none.
|
|
216
|
+
`--manifest catalog.json` also writes the manifest as its own file.
|
|
217
|
+
- **Safe to open.** The page is generated from data written by test runs, and treats it so: text is escaped, only PNG, JPEG, GIF and WebP
|
|
218
|
+
bytes (by their first bytes, never SVG, never a layer's claimed type) are embedded, a picture given by address is linked only if it is
|
|
219
|
+
`http` or `https`, and a Content-Security-Policy forbids every network request. Without scripts every state is simply shown.
|
|
220
|
+
- It is a generated document, not the viewer: it carries its own small inline style and script because it must work with no build
|
|
221
|
+
and no server, which [the viewer's UI conventions](ui.md) forbid inside the viewer.
|
|
222
|
+
|
|
147
223
|
## Decisions
|
|
148
224
|
|
|
149
225
|
The questions the original proposal left open, and how they are answered here. Each is the simplest answer that stays inside the
|
|
@@ -161,13 +237,34 @@ The questions the original proposal left open, and how they are answered here. E
|
|
|
161
237
|
| 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. |
|
|
162
238
|
| 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`. |
|
|
163
239
|
| Exploration capabilities | Search, filters, profile comparison, coverage and surface coverage, failure diagnostics, deep links, manifest export. |
|
|
164
|
-
| Static export | The catalog is computed from the layers a viewer holds, so
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
240
|
+
| Static export | Two ways, neither needs a server. The catalog is computed from the layers a viewer holds, so the static site (`atlas client build`) shows it, without streaming. `atlas ui-catalog export` writes it as one self-contained HTML file with a size budget. |
|
|
241
|
+
| Comparing | Two commits (from the layers recorded at each, because the report keeps only the newest) or two profiles; pictures by content hash, never by a pixel measure; one-sided states are not regressions. |
|
|
242
|
+
| Command line | `atlas ui-catalog plan`, `publish`, `status`, `diff` and `export`, each with `--format json`. Running a collector is not one: that is the product's. |
|
|
243
|
+
|
|
244
|
+
## What stays in your project, and what Atlas does not do
|
|
245
|
+
|
|
246
|
+
A project that had its own catalog moves each role it had to Atlas, and keeps only what is specific to its product. The eight roles, and
|
|
247
|
+
where each now is:
|
|
248
|
+
|
|
249
|
+
| # | Role | In Atlas | Stays in your project |
|
|
250
|
+
| --- | --- | --- | --- |
|
|
251
|
+
| 1 | Declaration of the states to capture, and the plan of what is expected | The plan format (`ui-plan` layers), `uiPlanLayer`, `atlas ui-catalog plan` | **The list itself**: which states and profiles your product has |
|
|
252
|
+
| 2 | A capture helper, with readiness checks and diagnostics | An optional adapter, `captureUiState` in `@crvouga/atlas/playwright`; any other tool writes the same layer | **The readiness condition of each screen**, and the helper for a framework Atlas has no adapter for |
|
|
253
|
+
| 3 | An evidence store: one atomic record per capture, images by content | Layers and the stores that hold them (`publishChecks`, a directory, S3, a server) | Nothing |
|
|
254
|
+
| 4 | An inventory of the product's surfaces, and reconciliation against it | Reconciliation: `inventory` plan entries and `covers`, the surface coverage table | **The inventory**: the list of surfaces your product has |
|
|
255
|
+
| 5 | A manifest and report builder | `uiCatalog`, the JSON manifest, Markdown, the single HTML file | Nothing |
|
|
256
|
+
| 6 | A viewer: as a single file, as a site, and live | `atlas ui-catalog export`, `atlas client build`, and `/catalog` on `atlas serve` | Nothing |
|
|
257
|
+
| 7 | A command line (list, run, report, publish, watch) | `atlas ui-catalog plan\|publish\|status\|diff\|export`; watching is the live `/catalog` and `atlas report stream` | **Run**: the runner that drives your product to each state |
|
|
258
|
+
| 8 | A publish step to a hosting platform | Not in Atlas, see below | **The deploy step** of your platform |
|
|
259
|
+
|
|
260
|
+
What stays is, in one list: the states and profiles to capture, the inventory of surfaces, the fixtures and test data, the runner that
|
|
261
|
+
drives the product to each state, the readiness condition of each screen, and the deploy step of your hosting platform. Nothing else is
|
|
262
|
+
specific to your product, so a project that installs a release holds none of the reporting and exploring code, only that.
|
|
263
|
+
|
|
264
|
+
**A hosting publish step is not a command, on purpose.** Publishing to a platform needs that platform's credentials and API, which Atlas
|
|
265
|
+
neither holds nor should. Hand the platform what Atlas produces: the `export` file (or a folder of them) for any host that serves
|
|
266
|
+
files, the output of `atlas client build` for a static site, or run `atlas serve` for a live one. Keep the credentials in the CI of the
|
|
267
|
+
project, where they already are.
|
|
268
|
+
|
|
269
|
+
**Not built:** reconciliation against an external inventory beyond `inventory` entries and `covers`; a comparison of pictures by pixels
|
|
270
|
+
(only by content); a comparison fetched from the server (the viewer and the command line read the layers themselves).
|
package/docs/ui.md
CHANGED
|
@@ -119,6 +119,9 @@ same coverage state, revisions and recording in both.
|
|
|
119
119
|
- The search words (`q`), the coverage filter (`coverage`), the selection (`behavior`) and the expanded state (`workspace`) are in the
|
|
120
120
|
address. Selecting shows the Read / Watch / Inspect panel beside the list (below it on narrow screens), and *Read and watch* in the
|
|
121
121
|
selected-behavior bar is reachable before the list in keyboard order.
|
|
122
|
+
- The same release controls as a feature page (*Release build*, *commit*, *platform*, and the *Stale or unverified* filter) sit above the list;
|
|
123
|
+
they are the address (`release`, `releaseSha`, `releasePlatform`, `evidence=stale`), and the selected behavior and the workspace read their
|
|
124
|
+
evidence against that release.
|
|
122
125
|
- **Read and watch workspace.** Expanding a behavior replaces the index with a page that uses the width: a two-column
|
|
123
126
|
`SimpleGrid` (one column below `md`) with *What should happen* (the steps, an outline's example filled in) beside *Recording* (the
|
|
124
127
|
player, status and provenance, promise and evidence revisions, applicability), the scoped summary below, and Inspect behind a
|
|
@@ -135,6 +138,39 @@ same coverage state, revisions and recording in both.
|
|
|
135
138
|
budget and breaches). Absent evidence is stated as unknown, never as good; the test's duration is not product latency, and the
|
|
136
139
|
summary says it describes the checks Atlas holds, not production. The full reliability and latency detail is one link away.
|
|
137
140
|
|
|
141
|
+
## The review destination
|
|
142
|
+
|
|
143
|
+
A team asks four connected questions of one project: what behavior it promises (**Read**), what that looks like (**Watch**), what
|
|
144
|
+
evidence supports it (**Inspect**) and where the gaps are. The viewer answers them in three places that lead to one another, and
|
|
145
|
+
`tests/review-destination.test.tsx` walks the whole workflow against a real server, one test per acceptance behavior of the
|
|
146
|
+
tracking issue (#20).
|
|
147
|
+
|
|
148
|
+
| Step | Where | Built by |
|
|
149
|
+
| --- | --- | --- |
|
|
150
|
+
| Find a promise by approximate words, a domain, a device and recordings; see each in its own coverage state | **Behaviors** (`/behaviors`) | #55, #74, #57, #44 |
|
|
151
|
+
| Name the build and device to read the evidence against | the applicability controls of Behaviors and of a feature | #61, #73 |
|
|
152
|
+
| Read what should happen beside the recording, with the scoped outcome, reliability and latency | the Read and watch workspace | #74, #60, #62, #63 |
|
|
153
|
+
| Find recordings by tag and media type and play them as a labelled playlist | **Artifacts** (`?tab=artifacts`), Watch these N results | #49, #56, #59, #75 |
|
|
154
|
+
| Go from a clip to its promise (**Read the promise**, also in a test's detail) or to its exact run (**Developer details**) | the playlist | #47, #54 |
|
|
155
|
+
| See which run and execution a result came from, and which recordings never arrived or are gone | **Runs** (`?tab=runs`) | #45, #46, #48, #70, #72, #76 |
|
|
156
|
+
| Seek to the step in a recording and share the moment | Timeline, Watch | #68 |
|
|
157
|
+
| Freeze a selection with notes and gaps | **Collections** | #66 |
|
|
158
|
+
| Compare two pinned runs | **Compare** | #67 |
|
|
159
|
+
|
|
160
|
+
How the pieces stay honest, and what a test in that file holds them to:
|
|
161
|
+
|
|
162
|
+
- The four states (not executed, API only, failed with a recording, demonstrated) are told apart in words and never counted as one
|
|
163
|
+
pass. A recording that was expected and not uploaded, or stored and then lost, is shown beside its green tests, never in place of them.
|
|
164
|
+
- Behaviors, Runs and Videos and artifacts link to one another (the header of Behaviors and the Specs block of the explorer), and a
|
|
165
|
+
result in the playlist or in a test's detail links back to the exact check of its feature. Read names the check and no run;
|
|
166
|
+
Inspect names the run (`layer`), so it never falls back to the latest combined evidence.
|
|
167
|
+
- The address holds the scope: the release (`release`, `releaseSha`, `releasePlatform`) and `evidence=stale` on Behaviors as on a feature,
|
|
168
|
+
the filters and the open playlist on Artifacts, the pinned runs on Compare, and the revision of a frozen collection.
|
|
169
|
+
A frozen collection does not change when newer runs arrive; the filtered view it was frozen from does.
|
|
170
|
+
- An older producer (no spec fingerprint, no recording expectation, no tags) or an older server (no artifact search, read-back,
|
|
171
|
+
collections or health route) stays readable. What it cannot say is unknown: the revision is "not recorded", a run is not "complete",
|
|
172
|
+
and an unreported behavior is "Not executed", never passed.
|
|
173
|
+
|
|
138
174
|
## Catalog health
|
|
139
175
|
|
|
140
176
|
The **Catalog health** tab (`explorer/HealthView.tsx`, `?tab=health`; `@crvouga/atlas/reports/catalog-health`) explains why the Specs
|
|
@@ -167,6 +203,36 @@ it works without a server. Cards are Mantine `Card`s in a responsive `SimpleGrid
|
|
|
167
203
|
incomplete. Waiting follows the loading policy below, and layers still arriving are counted.
|
|
168
204
|
- Pictures are `loading="lazy"` and only the cards on screen are fetched. The grid is bounded with *Show more*.
|
|
169
205
|
- The open state shows every profile side by side with the layer, backend, producer, shard, commit and time of each picture.
|
|
206
|
+
- **Compare** (`catalog/UiCatalogCompare.tsx`, `?compare=commit|profile&from&to`) sets two commits or two profiles against each other. Two
|
|
207
|
+
commits need the layers a newer layer replaced, which the held report no longer has, so `catalog/history.ts` reads the layer list and
|
|
208
|
+
the rows of the two commits only while the comparison is open (sharing the rows cache). Say what "changed" means (the stored bytes
|
|
209
|
+
differ, not a pixel measure), never call a one-sided state a regression, and say a layer that could not be read is unknown. The
|
|
210
|
+
coverage filter does not apply to a comparison.
|
|
211
|
+
|
|
212
|
+
## Needs polish
|
|
213
|
+
|
|
214
|
+
`/critiques` (`critiques/`, in the spec-type menu once a critic has recorded an opinion) ranks the captured UI states one critic
|
|
215
|
+
judged, lowest first ([the guide](critics.md#the-viewer-needs-polish)). It is computed in the page from the layers the viewer already
|
|
216
|
+
holds (`critiques/ranking.ts`), so a critique appears as its layer arrives.
|
|
217
|
+
|
|
218
|
+
- A critique is an opinion: no `StatusMark`, no passed or failed words, never in a test count. Use a Mantine `Badge` with the
|
|
219
|
+
label the critic gave (an open string), the severity as text, and never a color alone.
|
|
220
|
+
- A state judged at an earlier picture, or that the critic could not judge, is listed in its own table with the reason and is not
|
|
221
|
+
ranked. Unknown categories and severities are shown as written.
|
|
222
|
+
- The critic and the state kind are `NativeSelect`s whose values are the address (`critic`, `kind`, `entry`); the open entry is a
|
|
223
|
+
`Drawer` with the findings in a `Table` and the provenance in an `Accordion`.
|
|
224
|
+
- Pictures are `loading="lazy"`. Waiting, an unavailable backend and incomplete layers are named as on the UI catalog.
|
|
225
|
+
- **Finding overlays** (`critiques/FindingOverlay.tsx`): in the open entry each finding with a region is a numbered box over the picture,
|
|
226
|
+
positioned with Mantine style props (`pos`, `left`, `top`, `w`, `h` as percentages of the picture, `bd`) and a numbered `Badge`; no
|
|
227
|
+
`style`, `className` or stylesheet. The number is the finding's row in the table (where a `Button` locates it, `aria-pressed`), so color
|
|
228
|
+
is never the only signal; the boxes are `aria-hidden` because the table says the same in words, and a `Switch` hides them. A region in
|
|
229
|
+
an unknown unit, or of a critique that judged an earlier picture, is not drawn.
|
|
230
|
+
- **Agreement** is a `Tabs` view of the same page (`?view=agreement`, `keepMounted={false}`): a `Table` with one column per critic, each
|
|
231
|
+
cell a `Badge` in words (*Flags it*, *Does not flag it*, *No view*) with a link to that critic's critique, and the outcome as text.
|
|
232
|
+
The rule for a stance is stated on the page (`reports/critique-review`); a score without standing or a failure gives no view.
|
|
233
|
+
- **Marks** are read, never written, by the viewer (a browser sign-in only looks): each finding and the whole opinion show a `Badge` in
|
|
234
|
+
words (*Accepted by*, *Dismissed by*, *Reviewers disagree*, *Not reviewed*) with the reviewers' notes, and the page names
|
|
235
|
+
`atlas critic review`. No mark uses a passed or failed word or a status color.
|
|
170
236
|
|
|
171
237
|
## Reliability
|
|
172
238
|
|
|
@@ -255,7 +321,8 @@ over the same filtered set the page lists, in the same order (`explorer/playlist
|
|
|
255
321
|
Speed, "Play the next clip when one ends" and "Repeat" are labelled controls. Captions (`text/vtt` attachments) are offered
|
|
256
322
|
as `<track>` elements.
|
|
257
323
|
- The scenario (title, recorded steps, why it failed) sits beside the player with the recording's scope badges
|
|
258
|
-
(paced, scripted, silent, synthetic) and the sentence that the length is the recording's, not production latency.
|
|
324
|
+
(paced, scripted, silent, synthetic) and the sentence that the length is the recording's, not production latency. **Read the promise** opens the feature on that
|
|
325
|
+
exact check (the playlist stays in the address, so Back returns to the same clip); Developer
|
|
259
326
|
details opens the test pinned to that run's record and closes the playlist.
|
|
260
327
|
|
|
261
328
|
## Steps and moments in a recording
|
|
@@ -384,7 +451,7 @@ The narrowly scoped exceptions are:
|
|
|
384
451
|
| Area | Screens/interactions | Mantine composition |
|
|
385
452
|
| --- | --- | --- |
|
|
386
453
|
| Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
|
|
387
|
-
| 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 |
|
|
454
|
+
| UI catalog | Captured UI states per profile as cards with coverage badges, filters in the index column, surface coverage, an open state with its profiles side by side and where each picture came from, manifest download, a Compare of two commits or profiles (SegmentedControl, NativeSelect, Switch) | AppShell navbar, SimpleGrid, Card, Image, MultiSelect, TextInput, Drawer, Accordion, Table, Badge, Alert |
|
|
388
455
|
| Project home | Tests/runs/artifacts/compare/catalog health tabs, a two-run comparison with promise, outcome, recording and figure tabs per behavior, a run's Artifacts (files of its layers, shared recordings, direct links), Watch playlist of a filtered set, metrics, search, status/environment/domain/artifact-tag filters, run and artifact facets with saved views, grouping, sorting, show more | Container, Tabs, SimpleGrid, Paper, TextInput, MultiSelect, Popover, Button, Accordion, Modal, Grid, Switch |
|
|
389
456
|
| 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 |
|
|
390
457
|
| 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 |
|