@crvouga/atlas 0.2.0 → 0.4.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 (111) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +21 -2
  3. package/dist/reports/src/evidence-live.d.ts +6 -0
  4. package/dist/reports/src/evidence-live.js +35 -3
  5. package/dist/reports/src/evidence-types.d.ts +2 -0
  6. package/dist/reports/src/evidence-view.d.ts +15 -2
  7. package/dist/reports/src/evidence-view.js +31 -3
  8. package/dist/reports/src/export-evidence.js +2 -10
  9. package/dist/reports/src/frames.d.ts +3 -1
  10. package/dist/reports/src/frames.js +21 -0
  11. package/dist/reports/src/layer-axes.d.ts +18 -0
  12. package/dist/reports/src/layer-axes.js +51 -0
  13. package/dist/reports/src/layer-catalog.js +1 -1
  14. package/dist/reports/src/layer-merge.d.ts +1 -1
  15. package/dist/reports/src/layer-merge.js +9 -4
  16. package/dist/reports/src/layer-source.js +3 -0
  17. package/dist/reports/src/layer-types.d.ts +2 -0
  18. package/dist/reports/src/layers-route.js +2 -5
  19. package/dist/reports/src/protocol.d.ts +4 -0
  20. package/dist/reports/src/publish.d.ts +2 -0
  21. package/dist/reports/src/publish.js +6 -11
  22. package/dist/reports/src/recording-keys.js +4 -0
  23. package/dist/reports/src/report-parts.js +3 -0
  24. package/dist/reports/src/report-types.d.ts +19 -2
  25. package/dist/reports/src/source-entries.js +2 -12
  26. package/package.json +5 -4
  27. package/packages/reports/README.md +4 -2
  28. package/packages/reports/STORAGE.md +1 -1
  29. package/packages/visualizer/README.md +9 -6
  30. package/packages/visualizer/example/specs/coffee/ordering/cart.qnt +7 -0
  31. package/packages/visualizer/example/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  32. package/packages/visualizer/example/specs/coffee/ordering/drink-price.dmn +24 -0
  33. package/packages/visualizer/example/specs/coffee/ordering/loyalty.feature +7 -0
  34. package/packages/visualizer/example/specs/coffee/ordering/ordering.feature +36 -0
  35. package/packages/visualizer/example/specs/coffee/ordering/payment.als +4 -0
  36. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/cart.qnt +7 -0
  37. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  38. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/drink-price.dmn +24 -0
  39. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/loyalty.feature +7 -0
  40. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/ordering.feature +36 -0
  41. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/payment.als +4 -0
  42. package/packages/visualizer/fixtures/full/specs/coffee/ordering/cart.qnt +7 -0
  43. package/packages/visualizer/fixtures/full/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  44. package/packages/visualizer/fixtures/full/specs/coffee/ordering/drink-price.dmn +24 -0
  45. package/packages/visualizer/fixtures/full/specs/coffee/ordering/loyalty.feature +7 -0
  46. package/packages/visualizer/fixtures/full/specs/coffee/ordering/ordering.feature +36 -0
  47. package/packages/visualizer/fixtures/full/specs/coffee/ordering/payment.als +4 -0
  48. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/cart.qnt +7 -0
  49. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  50. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/drink-price.dmn +24 -0
  51. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/loyalty.feature +7 -0
  52. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/ordering.feature +36 -0
  53. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/payment.als +4 -0
  54. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/cart.qnt +7 -0
  55. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  56. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/drink-price.dmn +24 -0
  57. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/loyalty.feature +7 -0
  58. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/ordering.feature +36 -0
  59. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/payment.als +4 -0
  60. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/cart.qnt +7 -0
  61. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  62. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/drink-price.dmn +24 -0
  63. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/loyalty.feature +7 -0
  64. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/ordering.feature +36 -0
  65. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/payment.als +4 -0
  66. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/cart.qnt +7 -0
  67. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  68. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/drink-price.dmn +24 -0
  69. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/loyalty.feature +7 -0
  70. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/ordering.feature +36 -0
  71. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/payment.als +4 -0
  72. package/packages/visualizer/scripts/make-fixtures.ts +2 -0
  73. package/packages/visualizer/scripts/publish-data.ts +1 -2
  74. package/packages/visualizer/src/app/AppShell.tsx +11 -12
  75. package/packages/visualizer/src/app/gazetteer.module.css +12 -2
  76. package/packages/visualizer/src/app/gazetteer.tsx +38 -26
  77. package/packages/visualizer/src/components/Header.module.css +20 -4
  78. package/packages/visualizer/src/components/Header.tsx +7 -4
  79. package/packages/visualizer/src/data/model/build.ts +17 -5
  80. package/packages/visualizer/src/data/model/types.ts +10 -1
  81. package/packages/visualizer/src/evidence/api.ts +6 -1
  82. package/packages/visualizer/src/evidence/check-detail.tsx +35 -3
  83. package/packages/visualizer/src/evidence/evidence.module.css +20 -0
  84. package/packages/visualizer/src/evidence/marks.tsx +2 -0
  85. package/packages/visualizer/src/evidence/overview.tsx +3 -3
  86. package/packages/visualizer/src/evidence/recording-page.tsx +19 -2
  87. package/packages/visualizer/src/evidence/recording.module.css +4 -8
  88. package/packages/visualizer/src/evidence/recording.tsx +74 -83
  89. package/packages/visualizer/src/evidence/snapshot.tsx +5 -4
  90. package/packages/visualizer/src/evidence/sources-panel.tsx +88 -34
  91. package/packages/visualizer/src/evidence/spec-index.tsx +3 -11
  92. package/packages/visualizer/src/find/Finder.module.css +168 -0
  93. package/packages/visualizer/src/find/Finder.tsx +303 -0
  94. package/packages/visualizer/src/find/corpus.test.ts +77 -0
  95. package/packages/visualizer/src/find/corpus.ts +368 -0
  96. package/packages/visualizer/src/find/open.ts +51 -0
  97. package/packages/visualizer/src/find/rank.test.ts +237 -0
  98. package/packages/visualizer/src/find/rank.ts +405 -0
  99. package/packages/visualizer/src/find/semantic.test.ts +62 -0
  100. package/packages/visualizer/src/find/semantic.ts +93 -0
  101. package/packages/visualizer/src/find/semantic.worker.ts +40 -0
  102. package/packages/visualizer/src/looks/Stage.tsx +322 -0
  103. package/packages/visualizer/src/looks/collect.test.ts +158 -0
  104. package/packages/visualizer/src/looks/collect.ts +245 -0
  105. package/packages/visualizer/src/looks/stage.module.css +300 -0
  106. package/packages/visualizer/src/reachable.test.ts +1 -1
  107. package/packages/visualizer/src/state/ui-store.ts +6 -2
  108. package/packages/visualizer/src/views/SidePanel.tsx +39 -49
  109. package/packages/visualizer/vite/example-reports.ts +475 -0
  110. package/packages/visualizer/vite.config.ts +9 -1
  111. package/packages/visualizer/src/app/search-query.tsx +0 -14
@@ -18,8 +18,12 @@ export type ReportPerformanceOptions = {
18
18
  /** Diagnostic threshold, not a portable CPU or paint-time assertion. */
19
19
  readonly slowStepMs: number;
20
20
  };
21
- /** How a captured value is encoded on disk and rendered in the report. */
22
- export type SnapshotMedia = 'json' | 'html' | 'png' | 'text' | 'bytes';
21
+ /**
22
+ * How a captured value is encoded on disk and rendered in the report.
23
+ * `png` is a still, `html` is a page snapshot, and `video` is an mp4 of the moment or the whole test.
24
+ * A runner may record any mix. The viewer treats a still, a video, and a timeline of frames as the same kind of evidence.
25
+ */
26
+ export type SnapshotMedia = 'json' | 'html' | 'png' | 'text' | 'bytes' | 'video';
23
27
  export type ReportStepStatus = 'passed' | 'failed';
24
28
  /** Test outcome after the harness (e.g. the test runner) has weighed in. */
25
29
  export type ReportTestStatus = 'passed' | 'failed' | 'skipped';
@@ -102,10 +106,23 @@ export type RunReportMeta = {
102
106
  readonly error?: string;
103
107
  readonly errorStack?: string;
104
108
  };
109
+ /**
110
+ * A file attached to the whole test. A runner that can capture each step puts stills and page
111
+ * snapshots on `steps[].frames` instead, and the viewer builds a timeline from those. This list
112
+ * is for what the runner can attach when it cannot: a video of the test, a few screenshots, a log.
113
+ */
114
+ export type ReportArtifact = {
115
+ readonly name: string;
116
+ readonly media: SnapshotMedia;
117
+ readonly file: string;
118
+ readonly poster?: string;
119
+ readonly captions?: string;
120
+ };
105
121
  /** In-memory / persisted timeline for one test run. */
106
122
  export type RunReport = {
107
123
  readonly meta: RunReportMeta;
108
124
  readonly steps: readonly ReportStep[];
125
+ readonly artifacts?: readonly ReportArtifact[];
109
126
  };
110
127
  export type SuiteTestSummary = {
111
128
  readonly testId: string;
@@ -1,15 +1,7 @@
1
1
  import { testFrameKey } from './recording-keys.js';
2
+ import { capturedFiles } from './frames.js';
2
3
  import { remoteTestFileKey, remoteTestReportKey } from './keys.js';
3
4
  const JSON_TYPE = 'application/json; charset=utf-8';
4
- function frameFiles(steps, out) {
5
- for (const step of steps) {
6
- for (const frame of step.frames) {
7
- for (const blob of frame.snapshots)
8
- out.add(blob.file);
9
- }
10
- frameFiles(step.steps, out);
11
- }
12
- }
13
5
  export async function* sourceEntries(args, source, tests, runId) {
14
6
  for (const { report } of [...tests].sort((a, b) => a.report.meta.testId < b.report.meta.testId ? -1 : 1)) {
15
7
  const { testId } = report.meta;
@@ -18,9 +10,7 @@ export async function* sourceEntries(args, source, tests, runId) {
18
10
  bytes: new TextEncoder().encode(JSON.stringify(report)),
19
11
  contentType: JSON_TYPE,
20
12
  };
21
- const files = new Set();
22
- frameFiles(report.steps, files);
23
- for (const file of [...files].sort()) {
13
+ for (const file of capturedFiles(report).sort()) {
24
14
  const obj = await args.local.get(testFrameKey(runId, testId, file));
25
15
  if (obj === null)
26
16
  throw new Error(`Missing report snapshot: ${file}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crvouga/atlas",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Atlas: executable statecharts. Spec a product's flows as statecharts, run them against the real app with any driver, and browse what happened.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -107,7 +107,7 @@
107
107
  "test": "bun test && bun test --cwd packages/visualizer --preload ./tests/preload.ts tests",
108
108
  "type-check": "for dir in packages/* scripts examples/todo; do tsc --noEmit -p \"$dir\" || exit 1; done",
109
109
  "schemas": "bun packages/schema/scripts/emit-json-schemas.ts",
110
- "viz:dev": "cd packages/visualizer && INIT_CWD=\"$OLDPWD\" bun --bun vite --configLoader runner",
110
+ "viz:dev": "bun packages/visualizer/bin/atlas-visualizer.mjs dev --specs \"$(pwd)/packages/visualizer/fixtures/full/specs\" --runs \"$(pwd)/packages/visualizer/fixtures/full/runs\"",
111
111
  "viz:build": "cd packages/visualizer && INIT_CWD=\"$OLDPWD\" bun --bun vite build --configLoader runner",
112
112
  "viz:publish-data": "bun packages/visualizer/scripts/publish-data.ts",
113
113
  "viz:fixtures": "bun packages/visualizer/scripts/make-fixtures.ts",
@@ -115,11 +115,11 @@
115
115
  "example:lint": "bun --cwd examples/todo ../../packages/core/bin/atlas.mjs lint --require-implementations",
116
116
  "example:plan": "bun --cwd examples/todo ../../packages/core/bin/atlas.mjs plan",
117
117
  "example:fast": "bun --cwd examples/todo ../../packages/core/bin/atlas.mjs run --mode fast",
118
- "example:run": "bun --cwd examples/todo ../../packages/core/bin/atlas.mjs run --mode showcase",
118
+ "example:run": "bun examples/todo/run-all.ts",
119
119
  "example:clients": "bun --cwd examples/todo ../../packages/core/bin/atlas.mjs run --config clients/atlas.config.ts --mode showcase",
120
120
  "example:clients:bun": "ATLAS_DESKTOP=bun bun --cwd examples/todo ../../packages/core/bin/atlas.mjs run --config clients/atlas.config.ts --mode showcase",
121
121
  "atlas:run": "status=0; bun run example:clients || status=$?; bun run example:run || status=$?; exit $status",
122
- "atlas:viz": "bun packages/visualizer/bin/atlas-visualizer.mjs dev --specs examples/todo/specs --runs examples/todo/atlas-runs",
122
+ "atlas:viz": "ATLAS_REPORTS_MODULE=\"$(pwd)/examples/todo/reports-dev.ts\" bun packages/visualizer/bin/atlas-visualizer.mjs dev --specs examples/todo/specs --runs examples/todo/atlas-runs",
123
123
  "release:plan": "bun scripts/release/plan.ts",
124
124
  "release:publish": "bun scripts/release/publish.ts",
125
125
  "release:smoke": "bun scripts/release/consumer-smoke.ts",
@@ -171,6 +171,7 @@
171
171
  "devDependencies": {
172
172
  "@commitlint/cli": "19.8.1",
173
173
  "@commitlint/config-conventional": "19.8.1",
174
+ "@informalsystems/quint": "^0.33.0",
174
175
  "@types/bun": "1.4.3",
175
176
  "@types/node": "^22.20.3",
176
177
  "@types/react": "19.2.7",
@@ -10,11 +10,11 @@ The Atlas visualizer shows each kind of spec in its own tab, with every spec's r
10
10
 
11
11
  ## Combining evidence
12
12
 
13
- Current specs define the outline. `combineEvidence` fills it with the newest completed result for each producer, check and parameter set, across backends, branches and commits. Equal-time conflicts favor failure. Unfinished runs do not erase completed outcomes. Missing specs stay visible and removed specs retain their evidence outside the outline. `evidenceView` groups those results by check: the outcomes, the recorded timings that qualify them, and a summary per spec. A timing over its allowance never fails a check. `checkHistory` returns every layer's own record of one check, so a superseded result stays readable. Leaving a backend, a branch or one layer out (`contributes`) is how earlier outcomes are inspected.
13
+ Current specs define the outline. A layer is one focused recording: the checks it actually ran, on one environment, from one version and branch, in one store. `combineEvidence` fills the outline from every layer. The newest completed result wins only its own producer, check, environment and parameter set. Desktop web, mobile web, iOS and Android of the same check all stay visible, and any other recorded environment does too. A layer that ran one scenario leaves every other scenario as the newest layer that did run it, so the report fills without one suite that runs everything. Equal-time conflicts favor failure. Unfinished runs do not erase completed outcomes. Missing specs stay visible and removed specs retain their evidence outside the outline. `evidenceView` groups those results by check: the outcomes, the recorded timings that qualify them, and a summary per spec. A timing over its allowance never fails a check. `checkHistory` returns every layer's own record of one check, so a superseded result stays readable. Leaving a backend, an environment, a branch, a version or one layer out (`contributes`) is how earlier outcomes are inspected.
14
14
 
15
15
  ## Backends
16
16
 
17
- `createReportsApp` combines layers from several stores. `localStore` is the backend `local` ("This machine"), `store` is `shared` ("Shared reports"), and `backends` adds any number of further named stores, such as another team's bucket or a device lab. Every layer says which backend it came from. An unavailable backend is named in the page's notices and leaves the others usable; `GET /evidence?retry=1` asks it again without waiting.
17
+ `createReportsApp` combines layers from several stores. `localStore` is the backend `local` ("This machine"), `store` is `shared` ("Shared reports"), and `backends` adds any number of further named stores. A backend is any `ObjectStore`: the local disk, a shared bucket, S3, or a device lab. Every layer says which backend, version, branch and environment it came from. An unavailable backend is named in the page's notices and leaves the others usable; `GET /evidence?retry=1` asks it again without waiting.
18
18
 
19
19
  | Route | Returns |
20
20
  | --- | --- |
@@ -40,6 +40,8 @@ Freshness warnings preserve the recorded outcome. Changed spec fingerprints or r
40
40
 
41
41
  Configure `ATLAS_REPORTS_MODULE` with a host composition module exporting `reportsDevFetch(request, repoRoot)`. Atlas's Vite middleware serves it at `/api/reports`, alongside specs and chart runs. `INIT_CWD` identifies the consuming repository. Credentials stay in that server module.
42
42
 
43
+ When that module is unset and the open specs are the coffee example or the todo example, the visualizer dev server serves an in-memory layered report for those documents instead. Any other spec set is left alone. The todo example's own module serves that same layered report and adds the desktop web layer `example:run` writes.
44
+
43
45
  For a separately hosted reports API, set `VITE_ATLAS_REPORTS_URL` when building the visualizer. Otherwise a built site reads `data/evidence/`, populated with `exportEvidence`. Atlas still browses specs if evidence is absent.
44
46
 
45
47
  ## Verification
@@ -7,7 +7,7 @@ storage and time ports from its caller.
7
7
 
8
8
  `layers/index/<inverted-time>-<digest>.json` discovers immutable manifests at `layers/manifests/<digest>.json`. Each records producer, origin, parameters, checks, spec fingerprints and a recording bundle. The manifest is written after its blobs and the index last. Distinct results at the same timestamp retain separate identities. Compressed blobs under `layers/blobs/` are addressed by content and reused across runs; ordinary source publication shares its bundle directly with its layer without recompressing snapshots.
9
9
 
10
- Git commits are provenance and exact historical recording addresses, never a report partition. `combineEvidence` chooses completed results per check and parameters, retaining gaps from older layers. `publishSource` accepts `parameters`, and standalone parts may include them too. Inventory must be published before results for fingerprint comparison to be available.
10
+ Git commits are provenance and exact historical recording addresses, never a report partition. `combineEvidence` chooses the newest completed result per check, environment and parameters, and keeps every other cell. An environment is the client that was checked (`desktop-web`, `mobile-web`, `ios`, `android`, or any other recorded name). The same check on two environments contributes twice. A layer that checks one scenario fills only that scenario. `publishSource` accepts `environment` and `parameters`, and a part may name its environment in `parameters.environment`. Inventory must be published before results for fingerprint comparison to be available.
11
11
 
12
12
  ## Commit inventory
13
13
 
@@ -11,7 +11,9 @@ reads two independent sources and works with whatever exists:
11
11
 
12
12
  The explorer is one header, an open index and one plate. The index lists every place and journey of
13
13
  the product; each kind of spec (Gherkin, Quint, DMN, Alloy, OpenSLO) has its own tab, with the
14
- recorded result of every spec in its index. Local directories, published HTTP files and HTTP APIs
14
+ recorded result of every spec in its index. The header finds a flow in ordinary language across
15
+ charts, specs and recorded runs. ⌘K (Ctrl+K on other keyboards) opens it, and `/` does the same
16
+ when you are not already typing. Local directories, published HTTP files and HTTP APIs
15
17
  can supply reports simultaneously. Reports update through file watching, polling or server events.
16
18
  Latest includes running reports; one unavailable backend leaves the others usable.
17
19
 
@@ -36,8 +38,8 @@ atlas-visualizer dev --specs ./specs --runs ./atlas-runs
36
38
  | `--port <n>`, `--host <host>` | `dev`: where to listen (default `localhost:5180`, or the next free port). |
37
39
 
38
40
  Relative paths resolve against the directory the command was run from. Without flags the
39
- environment is read: `ATLAS_SPECS_ROOT`, `ATLAS_RUNS_ROOT` (relative to `INIT_CWD` when your package
40
- manager sets it to where you ran the command, or the current directory) and `ATLAS_FIXTURE`. With nothing configured and no
41
+ environment is read: `ATLAS_SPECS_ROOT` and `ATLAS_RUNS_ROOT` (relative to `INIT_CWD` when your package
42
+ manager sets it to where you ran the command, or the current directory). With nothing configured and no
41
43
  `specs/` directory, the `full` example is shown.
42
44
 
43
45
  Editing a spec file or a run publishing a snapshot updates the open page in about a second, without a reload,
@@ -60,8 +62,7 @@ sets how often the site checks for new runs (default 60 s, plus when the tab reg
60
62
  ### Inside this repository
61
63
 
62
64
  ```sh
63
- bun run viz:dev # ATLAS_SPECS_ROOT / ATLAS_RUNS_ROOT, or the example
64
- ATLAS_FIXTURE=partial bun run viz:dev # one of the fixtures
65
+ bun run viz:dev # the full coffee example, charts and layered report
65
66
  bun test packages/visualizer/src packages/visualizer/vite # parsing, composition, statuses, every fixture
66
67
  bun run viz:fixtures # regenerate the fixtures (needs ImageMagick, cwebp, ffmpeg)
67
68
  ```
@@ -147,7 +148,9 @@ MIT
147
148
 
148
149
  Beside the statechart map, every other kind of spec has its own tab: Gherkin features, Quint models, DMN decision tables, Alloy structures and OpenSLO objectives. A tab appears when the specs folder has files of its kind. The index lists every spec with what was recorded for it, and filters to those with results, those failing, or those with none. A spec page shows each scenario, trace or check with its outcome; opening one shows the recorded steps, the error, the timings, every other run of the same check, and its recording.
149
150
 
150
- Results come from the Atlas reports API (`/api/reports` in dev, `VITE_ATLAS_REPORTS_URL` when hosted elsewhere) or from a static `data/evidence` export. They combine across backends, branches and runs; **Sources** in the header switches any of them off. While a spec suite is running, the header says so and each finished test appears within a couple of seconds. See [Atlas reports](../reports/README.md) for the API and host configuration.
151
+ Results come from the Atlas reports API (`/api/reports` in dev, `VITE_ATLAS_REPORTS_URL` when hosted elsewhere) or from a static `data/evidence` export. They combine across backends, branches, versions, environments and layers; **Sources** in the header switches any of them off. While a spec suite is running, the header says so and each finished test appears within a couple of seconds. See [Atlas reports](../reports/README.md) for the API and host configuration.
152
+
153
+ The coffee example ships those documents and a layered report. `bun run viz:dev` opens that full example, and the same report is the fallback when a working directory has no `specs/` and `ATLAS_REPORTS_MODULE` is unset. `atlas:viz` serves the same kind of report for the todo example, and adds the desktop web layer `example:run` writes. Each built-in report is five layers: an older desktop web run, a mobile web branch, Android from a device lab, a focused iOS run, and a newer desktop web run that repeated one scenario. The same check stays visible on each environment. A feature with no layer stays empty.
151
154
 
152
155
  The header title is the product's name, taken from `atlas.yaml`'s `title` up to its first dash or colon, or "Atlas".
153
156
 
@@ -0,0 +1,7 @@
1
+ # Cart
2
+ module cart {
3
+ var items: int
4
+ var paid: bool
5
+ action add = items' = items + 1
6
+ action pay = paid' = true
7
+ }
@@ -0,0 +1,10 @@
1
+ # Checkout latency
2
+ apiVersion: openslo/v1
3
+ kind: SLO
4
+ metadata:
5
+ name: checkout-latency
6
+ spec:
7
+ description: Checkout answers within a second.
8
+ objectives:
9
+ - displayName: Checkout latency
10
+ target: 0.99
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/">
3
+ <decision id="drink-price" name="Drink price">
4
+ <decisionTable id="Drink price" hitPolicy="UNIQUE">
5
+ <input label="Size">
6
+ <inputExpression typeRef="string"><text>size</text></inputExpression>
7
+ </input>
8
+ <input label="Milk">
9
+ <inputExpression typeRef="string"><text>milk</text></inputExpression>
10
+ </input>
11
+ <output label="Price" name="price" />
12
+ <rule>
13
+ <inputEntry><text>"Small"</text></inputEntry>
14
+ <inputEntry><text>"None"</text></inputEntry>
15
+ <outputEntry><text>3</text></outputEntry>
16
+ </rule>
17
+ <rule>
18
+ <inputEntry><text>"Large"</text></inputEntry>
19
+ <inputEntry><text>"Oat"</text></inputEntry>
20
+ <outputEntry><text>5</text></outputEntry>
21
+ </rule>
22
+ </decisionTable>
23
+ </decision>
24
+ </definitions>
@@ -0,0 +1,7 @@
1
+ Feature: Loyalty stamps
2
+ A regular earns a free drink. No layer has recorded this yet.
3
+
4
+ Scenario: Tenth drink is free
5
+ Given nine stamps
6
+ When the customer orders
7
+ Then the drink is free
@@ -0,0 +1,36 @@
1
+ Feature: Order a drink
2
+ A customer picks a drink, pays, and picks it up.
3
+
4
+ Rule: Paying for the order
5
+
6
+ Scenario: Pays by card
7
+ Given a drink in the cart
8
+ When the customer pays by card
9
+ Then the barista can start the drink
10
+
11
+ Scenario: Pays with a wallet
12
+ Given a drink in the cart
13
+ When the customer pays with a wallet
14
+ Then the order is paid
15
+
16
+ Scenario: Card declined, then a wallet works
17
+ Given a drink in the cart
18
+ When the card is declined
19
+ And the customer pays with a wallet
20
+ Then the order is paid
21
+
22
+ Scenario: Slow payment
23
+ Given a drink in the cart
24
+ When the payment takes longer than a minute
25
+ Then the customer can check the payment again
26
+
27
+ Scenario: Leaves checkout and comes back
28
+ Given a drink in the cart
29
+ When the customer closes checkout
30
+ And the customer starts checkout again
31
+ Then checkout is open
32
+
33
+ Scenario: Drink left too long
34
+ Given a finished drink
35
+ When nobody picks it up
36
+ Then the drink is thrown away and refunded
@@ -0,0 +1,4 @@
1
+ // An order is paid once.
2
+ sig Order { paid: lone Payment }
3
+ sig Payment {}
4
+ fact { all o: Order | one o.paid }
@@ -0,0 +1,7 @@
1
+ # Cart
2
+ module cart {
3
+ var items: int
4
+ var paid: bool
5
+ action add = items' = items + 1
6
+ action pay = paid' = true
7
+ }
@@ -0,0 +1,10 @@
1
+ # Checkout latency
2
+ apiVersion: openslo/v1
3
+ kind: SLO
4
+ metadata:
5
+ name: checkout-latency
6
+ spec:
7
+ description: Checkout answers within a second.
8
+ objectives:
9
+ - displayName: Checkout latency
10
+ target: 0.99
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/">
3
+ <decision id="drink-price" name="Drink price">
4
+ <decisionTable id="Drink price" hitPolicy="UNIQUE">
5
+ <input label="Size">
6
+ <inputExpression typeRef="string"><text>size</text></inputExpression>
7
+ </input>
8
+ <input label="Milk">
9
+ <inputExpression typeRef="string"><text>milk</text></inputExpression>
10
+ </input>
11
+ <output label="Price" name="price" />
12
+ <rule>
13
+ <inputEntry><text>"Small"</text></inputEntry>
14
+ <inputEntry><text>"None"</text></inputEntry>
15
+ <outputEntry><text>3</text></outputEntry>
16
+ </rule>
17
+ <rule>
18
+ <inputEntry><text>"Large"</text></inputEntry>
19
+ <inputEntry><text>"Oat"</text></inputEntry>
20
+ <outputEntry><text>5</text></outputEntry>
21
+ </rule>
22
+ </decisionTable>
23
+ </decision>
24
+ </definitions>
@@ -0,0 +1,7 @@
1
+ Feature: Loyalty stamps
2
+ A regular earns a free drink. No layer has recorded this yet.
3
+
4
+ Scenario: Tenth drink is free
5
+ Given nine stamps
6
+ When the customer orders
7
+ Then the drink is free
@@ -0,0 +1,36 @@
1
+ Feature: Order a drink
2
+ A customer picks a drink, pays, and picks it up.
3
+
4
+ Rule: Paying for the order
5
+
6
+ Scenario: Pays by card
7
+ Given a drink in the cart
8
+ When the customer pays by card
9
+ Then the barista can start the drink
10
+
11
+ Scenario: Pays with a wallet
12
+ Given a drink in the cart
13
+ When the customer pays with a wallet
14
+ Then the order is paid
15
+
16
+ Scenario: Card declined, then a wallet works
17
+ Given a drink in the cart
18
+ When the card is declined
19
+ And the customer pays with a wallet
20
+ Then the order is paid
21
+
22
+ Scenario: Slow payment
23
+ Given a drink in the cart
24
+ When the payment takes longer than a minute
25
+ Then the customer can check the payment again
26
+
27
+ Scenario: Leaves checkout and comes back
28
+ Given a drink in the cart
29
+ When the customer closes checkout
30
+ And the customer starts checkout again
31
+ Then checkout is open
32
+
33
+ Scenario: Drink left too long
34
+ Given a finished drink
35
+ When nobody picks it up
36
+ Then the drink is thrown away and refunded
@@ -0,0 +1,4 @@
1
+ // An order is paid once.
2
+ sig Order { paid: lone Payment }
3
+ sig Payment {}
4
+ fact { all o: Order | one o.paid }
@@ -0,0 +1,7 @@
1
+ # Cart
2
+ module cart {
3
+ var items: int
4
+ var paid: bool
5
+ action add = items' = items + 1
6
+ action pay = paid' = true
7
+ }
@@ -0,0 +1,10 @@
1
+ # Checkout latency
2
+ apiVersion: openslo/v1
3
+ kind: SLO
4
+ metadata:
5
+ name: checkout-latency
6
+ spec:
7
+ description: Checkout answers within a second.
8
+ objectives:
9
+ - displayName: Checkout latency
10
+ target: 0.99
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/">
3
+ <decision id="drink-price" name="Drink price">
4
+ <decisionTable id="Drink price" hitPolicy="UNIQUE">
5
+ <input label="Size">
6
+ <inputExpression typeRef="string"><text>size</text></inputExpression>
7
+ </input>
8
+ <input label="Milk">
9
+ <inputExpression typeRef="string"><text>milk</text></inputExpression>
10
+ </input>
11
+ <output label="Price" name="price" />
12
+ <rule>
13
+ <inputEntry><text>"Small"</text></inputEntry>
14
+ <inputEntry><text>"None"</text></inputEntry>
15
+ <outputEntry><text>3</text></outputEntry>
16
+ </rule>
17
+ <rule>
18
+ <inputEntry><text>"Large"</text></inputEntry>
19
+ <inputEntry><text>"Oat"</text></inputEntry>
20
+ <outputEntry><text>5</text></outputEntry>
21
+ </rule>
22
+ </decisionTable>
23
+ </decision>
24
+ </definitions>
@@ -0,0 +1,7 @@
1
+ Feature: Loyalty stamps
2
+ A regular earns a free drink. No layer has recorded this yet.
3
+
4
+ Scenario: Tenth drink is free
5
+ Given nine stamps
6
+ When the customer orders
7
+ Then the drink is free
@@ -0,0 +1,36 @@
1
+ Feature: Order a drink
2
+ A customer picks a drink, pays, and picks it up.
3
+
4
+ Rule: Paying for the order
5
+
6
+ Scenario: Pays by card
7
+ Given a drink in the cart
8
+ When the customer pays by card
9
+ Then the barista can start the drink
10
+
11
+ Scenario: Pays with a wallet
12
+ Given a drink in the cart
13
+ When the customer pays with a wallet
14
+ Then the order is paid
15
+
16
+ Scenario: Card declined, then a wallet works
17
+ Given a drink in the cart
18
+ When the card is declined
19
+ And the customer pays with a wallet
20
+ Then the order is paid
21
+
22
+ Scenario: Slow payment
23
+ Given a drink in the cart
24
+ When the payment takes longer than a minute
25
+ Then the customer can check the payment again
26
+
27
+ Scenario: Leaves checkout and comes back
28
+ Given a drink in the cart
29
+ When the customer closes checkout
30
+ And the customer starts checkout again
31
+ Then checkout is open
32
+
33
+ Scenario: Drink left too long
34
+ Given a finished drink
35
+ When nobody picks it up
36
+ Then the drink is thrown away and refunded
@@ -0,0 +1,4 @@
1
+ // An order is paid once.
2
+ sig Order { paid: lone Payment }
3
+ sig Payment {}
4
+ fact { all o: Order | one o.paid }
@@ -0,0 +1,7 @@
1
+ # Cart
2
+ module cart {
3
+ var items: int
4
+ var paid: bool
5
+ action add = items' = items + 1
6
+ action pay = paid' = true
7
+ }
@@ -0,0 +1,10 @@
1
+ # Checkout latency
2
+ apiVersion: openslo/v1
3
+ kind: SLO
4
+ metadata:
5
+ name: checkout-latency
6
+ spec:
7
+ description: Checkout answers within a second.
8
+ objectives:
9
+ - displayName: Checkout latency
10
+ target: 0.99
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/">
3
+ <decision id="drink-price" name="Drink price">
4
+ <decisionTable id="Drink price" hitPolicy="UNIQUE">
5
+ <input label="Size">
6
+ <inputExpression typeRef="string"><text>size</text></inputExpression>
7
+ </input>
8
+ <input label="Milk">
9
+ <inputExpression typeRef="string"><text>milk</text></inputExpression>
10
+ </input>
11
+ <output label="Price" name="price" />
12
+ <rule>
13
+ <inputEntry><text>"Small"</text></inputEntry>
14
+ <inputEntry><text>"None"</text></inputEntry>
15
+ <outputEntry><text>3</text></outputEntry>
16
+ </rule>
17
+ <rule>
18
+ <inputEntry><text>"Large"</text></inputEntry>
19
+ <inputEntry><text>"Oat"</text></inputEntry>
20
+ <outputEntry><text>5</text></outputEntry>
21
+ </rule>
22
+ </decisionTable>
23
+ </decision>
24
+ </definitions>
@@ -0,0 +1,7 @@
1
+ Feature: Loyalty stamps
2
+ A regular earns a free drink. No layer has recorded this yet.
3
+
4
+ Scenario: Tenth drink is free
5
+ Given nine stamps
6
+ When the customer orders
7
+ Then the drink is free
@@ -0,0 +1,36 @@
1
+ Feature: Order a drink
2
+ A customer picks a drink, pays, and picks it up.
3
+
4
+ Rule: Paying for the order
5
+
6
+ Scenario: Pays by card
7
+ Given a drink in the cart
8
+ When the customer pays by card
9
+ Then the barista can start the drink
10
+
11
+ Scenario: Pays with a wallet
12
+ Given a drink in the cart
13
+ When the customer pays with a wallet
14
+ Then the order is paid
15
+
16
+ Scenario: Card declined, then a wallet works
17
+ Given a drink in the cart
18
+ When the card is declined
19
+ And the customer pays with a wallet
20
+ Then the order is paid
21
+
22
+ Scenario: Slow payment
23
+ Given a drink in the cart
24
+ When the payment takes longer than a minute
25
+ Then the customer can check the payment again
26
+
27
+ Scenario: Leaves checkout and comes back
28
+ Given a drink in the cart
29
+ When the customer closes checkout
30
+ And the customer starts checkout again
31
+ Then checkout is open
32
+
33
+ Scenario: Drink left too long
34
+ Given a finished drink
35
+ When nobody picks it up
36
+ Then the drink is thrown away and refunded
@@ -0,0 +1,4 @@
1
+ // An order is paid once.
2
+ sig Order { paid: lone Payment }
3
+ sig Payment {}
4
+ fact { all o: Order | one o.paid }
@@ -0,0 +1,7 @@
1
+ # Cart
2
+ module cart {
3
+ var items: int
4
+ var paid: bool
5
+ action add = items' = items + 1
6
+ action pay = paid' = true
7
+ }
@@ -0,0 +1,10 @@
1
+ # Checkout latency
2
+ apiVersion: openslo/v1
3
+ kind: SLO
4
+ metadata:
5
+ name: checkout-latency
6
+ spec:
7
+ description: Checkout answers within a second.
8
+ objectives:
9
+ - displayName: Checkout latency
10
+ target: 0.99
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/">
3
+ <decision id="drink-price" name="Drink price">
4
+ <decisionTable id="Drink price" hitPolicy="UNIQUE">
5
+ <input label="Size">
6
+ <inputExpression typeRef="string"><text>size</text></inputExpression>
7
+ </input>
8
+ <input label="Milk">
9
+ <inputExpression typeRef="string"><text>milk</text></inputExpression>
10
+ </input>
11
+ <output label="Price" name="price" />
12
+ <rule>
13
+ <inputEntry><text>"Small"</text></inputEntry>
14
+ <inputEntry><text>"None"</text></inputEntry>
15
+ <outputEntry><text>3</text></outputEntry>
16
+ </rule>
17
+ <rule>
18
+ <inputEntry><text>"Large"</text></inputEntry>
19
+ <inputEntry><text>"Oat"</text></inputEntry>
20
+ <outputEntry><text>5</text></outputEntry>
21
+ </rule>
22
+ </decisionTable>
23
+ </decision>
24
+ </definitions>