@crvouga/atlas 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/dist/critics/src/contract.d.ts +113 -0
  3. package/dist/critics/src/contract.js +48 -0
  4. package/dist/critics/src/credentials.d.ts +72 -0
  5. package/dist/critics/src/credentials.js +136 -0
  6. package/dist/critics/src/critique.d.ts +128 -0
  7. package/dist/critics/src/critique.js +163 -0
  8. package/dist/critics/src/index.d.ts +21 -0
  9. package/dist/critics/src/index.js +21 -0
  10. package/dist/critics/src/judging.d.ts +153 -0
  11. package/dist/critics/src/judging.js +145 -0
  12. package/dist/critics/src/publish.d.ts +23 -0
  13. package/dist/critics/src/publish.js +21 -0
  14. package/dist/critics/src/runner.d.ts +104 -0
  15. package/dist/critics/src/runner.js +202 -0
  16. package/dist/critics/src/scope.d.ts +8 -0
  17. package/dist/critics/src/scope.js +26 -0
  18. package/dist/playwright/src/index.d.ts +4 -0
  19. package/dist/playwright/src/index.js +4 -0
  20. package/dist/playwright/src/ui-catalog.d.ts +51 -0
  21. package/dist/playwright/src/ui-catalog.js +44 -0
  22. package/dist/reports/src/compose.d.ts +2 -2
  23. package/dist/reports/src/compose.js +6 -5
  24. package/dist/reports/src/evidence-types.d.ts +3 -0
  25. package/dist/reports/src/evidence-view.d.ts +2 -1
  26. package/dist/reports/src/evidence-view.js +5 -2
  27. package/dist/reports/src/layer-merge.d.ts +16 -4
  28. package/dist/reports/src/layer-merge.js +23 -5
  29. package/dist/reports/src/report-index.js +9 -5
  30. package/dist/reports/src/report-status.d.ts +1 -1
  31. package/dist/reports/src/report-status.js +6 -4
  32. package/dist/reports/src/ui-catalog-publish.d.ts +92 -0
  33. package/dist/reports/src/ui-catalog-publish.js +68 -0
  34. package/dist/reports/src/ui-catalog.d.ts +229 -0
  35. package/dist/reports/src/ui-catalog.js +273 -0
  36. package/dist/reports/src/ui-state.d.ts +70 -0
  37. package/dist/reports/src/ui-state.js +66 -0
  38. package/dist/schema/src/api.d.ts +351 -0
  39. package/dist/schema/src/api.js +72 -1
  40. package/dist/schema/src/critique.d.ts +144 -0
  41. package/dist/schema/src/critique.js +96 -0
  42. package/dist/schema/src/index.d.ts +2 -1
  43. package/dist/schema/src/index.js +2 -1
  44. package/dist/schema/src/server-config.d.ts +7 -1
  45. package/dist/schema/src/server-config.js +6 -3
  46. package/dist/server/src/app.js +15 -0
  47. package/dist/server/src/auth.d.ts +1 -1
  48. package/dist/server/src/auth.js +2 -2
  49. package/dist/server/src/config.js +2 -2
  50. package/dist/server/src/openapi.d.ts +3 -3
  51. package/dist/server/src/openapi.js +29 -6
  52. package/dist/server/src/store-api.js +39 -1
  53. package/docs/agents.md +1 -0
  54. package/docs/critics.md +190 -0
  55. package/docs/extending.md +1 -0
  56. package/docs/reference/critics.md +840 -0
  57. package/docs/reference/http-api.md +52 -2
  58. package/docs/reference/playwright.md +63 -0
  59. package/docs/reference/reports.md +608 -8
  60. package/docs/reference/schema.md +152 -2
  61. package/docs/reference/server.md +3 -3
  62. package/docs/report-contract.md +6 -2
  63. package/docs/rich-evidence.md +5 -0
  64. package/docs/server.md +8 -4
  65. package/docs/ui-catalog.md +173 -0
  66. package/docs/ui.md +15 -0
  67. package/llms-full.txt +457 -8
  68. package/llms.txt +3 -0
  69. package/package.json +8 -2
  70. package/packages/critics/README.md +18 -0
  71. package/packages/schema/schemas/critique.schema.json +254 -0
  72. package/packages/schema/schemas/effective-delta.schema.json +73 -0
  73. package/packages/schema/schemas/effective-evidence.schema.json +73 -0
  74. package/packages/schema/schemas/layer-rows.schema.json +73 -0
  75. package/packages/schema/schemas/server-config.schema.json +2 -1
  76. package/packages/schema/schemas/server-info.schema.json +1 -0
  77. package/packages/server/openapi.json +1443 -80
  78. package/packages/visualizer/src/app/AppShell.tsx +36 -30
  79. package/packages/visualizer/src/app/router.tsx +10 -1
  80. package/packages/visualizer/src/catalog/UiCatalogFilters.tsx +65 -0
  81. package/packages/visualizer/src/catalog/UiCatalogPage.tsx +342 -0
  82. package/packages/visualizer/src/catalog/provider.tsx +25 -0
  83. package/packages/visualizer/src/catalog/search.ts +68 -0
  84. package/packages/visualizer/src/catalog/words.ts +26 -0
  85. package/packages/visualizer/src/components/Header.tsx +17 -4
@@ -414,7 +414,7 @@ Responses: `200` It exists.; `401` No token, or an unknown one. Send `Authorizat
414
414
 
415
415
  ### Upload an object
416
416
 
417
- `PUT /api/store/{key}` · role: publish
417
+ `PUT /api/store/{key}` · role: critic
418
418
 
419
419
  The write half of the `ObjectStore` interface. The server checks what it can from the bytes in hand and says no to what would corrupt the report for everyone:
420
420
 
@@ -426,6 +426,8 @@ The write half of the `ObjectStore` interface. The server checks what it can fro
426
426
 
427
427
  Upload in dependency order: blobs, then the manifest, then the catalog entry. `atlas report publish`, `atlas report stream` and `httpStore` do. Needs a `Bearer` token of role `publish` or `admin`; a browser's sign-in box is refused.
428
428
 
429
+ A token of role `critic` may upload only what a critic publishes: blobs, and layers (and their catalog entries) whose `producer` is `critic.<name>` and whose parts are all of kind `critique`. Anything else is `403` with the rule it broke, and nothing is stored.
430
+
429
431
  - `key` (path, required): An object key under `layers/`, such as `layers/manifests/<id>.json`. It contains slashes, which are not percent-encoded; every other character is.
430
432
  - `content-type` (header): Stored with the object and returned when it is read.
431
433
  - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
@@ -434,7 +436,7 @@ Responses: `200` Already there with the same bytes.; `201` Stored.; `401` No tok
434
436
 
435
437
  ### Upload an object
436
438
 
437
- `PUT /api/projects/{project}/store/{key}` · role: publish
439
+ `PUT /api/projects/{project}/store/{key}` · role: critic
438
440
 
439
441
  The write half of the `ObjectStore` interface. The server checks what it can from the bytes in hand and says no to what would corrupt the report for everyone:
440
442
 
@@ -446,6 +448,8 @@ The write half of the `ObjectStore` interface. The server checks what it can fro
446
448
 
447
449
  Upload in dependency order: blobs, then the manifest, then the catalog entry. `atlas report publish`, `atlas report stream` and `httpStore` do. Needs a `Bearer` token of role `publish` or `admin`; a browser's sign-in box is refused.
448
450
 
451
+ A token of role `critic` may upload only what a critic publishes: blobs, and layers (and their catalog entries) whose `producer` is `critic.<name>` and whose parts are all of kind `critique`. Anything else is `403` with the rule it broke, and nothing is stored.
452
+
449
453
  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.
450
454
 
451
455
  - `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.
@@ -922,6 +926,52 @@ Scoped to one project. The same project may be selected on the unprefixed API us
922
926
 
923
927
  Responses: `200` The matching results and the facets.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
924
928
 
929
+ ### The UI catalog: captured UI states per profile, and what is still expected
930
+
931
+ `GET /api/reports/ui-catalog` · role: read
932
+
933
+ A browsable inventory of the product's user interface, computed from the winning results of the `ui-state` and `ui-plan` parts of every layer (see the UI catalog guide). Each state is a check id; each cell is one profile of it (the layer's environment and parameters) with its coverage: `captured` only when the winning capture passed, `failed`, `capturing`, `skipped`, or `missing` when a plan expects it and nothing has been captured. A picture is read from the cell's `capture.image.path` under the reports API root, or `uri`. What is expected is itself a layer, so a shard that never published leaves its states `missing`, never captured. The document equals what the viewer computes from the same report. Narrow it by `q` (words), `coverage`, `profile` (a profile `key`), `area`, `route`, `cohort`, `flag`, `surface` and `producer`: AND across parameters, OR within one; a state keeps only the cells that match `profile` and `coverage`. Page the states with `limit` and `offset`. Viewers need not poll: they hold the report over `/api/events` and read changes with `/api/reports/evidence/effective?since=`.
934
+
935
+ - `limit` (query): At most this many states (default 200).
936
+ - `offset` (query): Skip this many states (default 0).
937
+ - `q` (query): Words that must all appear in a state's id, title, route, fixture, cohort, area, flags, surfaces or producers.
938
+ - `coverage` (query): Coverage values: `captured`, `failed`, `capturing`, `skipped`, `missing`. Comma-separated.
939
+ - `profile` (query): Profile keys, as listed in `profiles[].key`.
940
+ - `area` (query): Areas.
941
+ - `route` (query): Routes.
942
+ - `cohort` (query): Cohorts.
943
+ - `flag` (query): Feature flags a state was captured with.
944
+ - `surface` (query): Surfaces a state covers.
945
+ - `producer` (query): Producers that captured or planned a state.
946
+ - `format` (query): `markdown` for Markdown instead of JSON.
947
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
948
+
949
+ Responses: `200` The catalog.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
950
+
951
+ ### The UI catalog: captured UI states per profile, and what is still expected
952
+
953
+ `GET /api/projects/{project}/reports/ui-catalog` · role: read
954
+
955
+ A browsable inventory of the product's user interface, computed from the winning results of the `ui-state` and `ui-plan` parts of every layer (see the UI catalog guide). Each state is a check id; each cell is one profile of it (the layer's environment and parameters) with its coverage: `captured` only when the winning capture passed, `failed`, `capturing`, `skipped`, or `missing` when a plan expects it and nothing has been captured. A picture is read from the cell's `capture.image.path` under the reports API root, or `uri`. What is expected is itself a layer, so a shard that never published leaves its states `missing`, never captured. The document equals what the viewer computes from the same report. Narrow it by `q` (words), `coverage`, `profile` (a profile `key`), `area`, `route`, `cohort`, `flag`, `surface` and `producer`: AND across parameters, OR within one; a state keeps only the cells that match `profile` and `coverage`. Page the states with `limit` and `offset`. Viewers need not poll: they hold the report over `/api/events` and read changes with `/api/reports/evidence/effective?since=`.
956
+
957
+ 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.
958
+
959
+ - `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.
960
+ - `limit` (query): At most this many states (default 200).
961
+ - `offset` (query): Skip this many states (default 0).
962
+ - `q` (query): Words that must all appear in a state's id, title, route, fixture, cohort, area, flags, surfaces or producers.
963
+ - `coverage` (query): Coverage values: `captured`, `failed`, `capturing`, `skipped`, `missing`. Comma-separated.
964
+ - `profile` (query): Profile keys, as listed in `profiles[].key`.
965
+ - `area` (query): Areas.
966
+ - `route` (query): Routes.
967
+ - `cohort` (query): Cohorts.
968
+ - `flag` (query): Feature flags a state was captured with.
969
+ - `surface` (query): Surfaces a state covers.
970
+ - `producer` (query): Producers that captured or planned a state.
971
+ - `format` (query): `markdown` for Markdown instead of JSON.
972
+
973
+ Responses: `200` The catalog.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
974
+
925
975
  ### Every test one run recorded
926
976
 
927
977
  `GET /api/reports/runs/detail` · role: read
@@ -104,7 +104,10 @@ recognise screens and tap or type (`screen`, `recognize`, `locate`, the signal c
104
104
  the media tools (`captureScreen`, `ScreencastRecorder`, `ffmpeg`, `PHONE` and the pacing presets). Needs the
105
105
  `playwright` package; clips and thumbnails need FFmpeg on the PATH.
106
106
 
107
+ Also `captureUiState`, the optional Playwright adapter of the UI catalog (it needs no Playwright import itself).
108
+
107
109
  See docs/statecharts.md
110
+ See docs/ui-catalog.md
108
111
 
109
112
  ### Playwright screenshots, clips and paced actions
110
113
 
@@ -301,3 +304,63 @@ const states = {
301
304
  'No todos': screen({ all: [text('Nothing to do yet')], none: [role('listitem')] })
302
305
  };
303
306
  ```
307
+
308
+ ### Playwright adapter for the UI catalog
309
+
310
+ Source: `packages/playwright/src/ui-catalog.ts`
311
+
312
+ The optional Playwright side of the UI catalog: take a page to a state, wait until it is ready, photograph it, and hand the
313
+ result to `uiStateLayer` (`@crvouga/atlas/reports/ui-catalog-publish`) as a capture. This is the only Playwright-specific code
314
+ of the feature; reading, composing and exploring the catalog know nothing about it, and any other program that writes the
315
+ same layer produces the same catalog. The page is typed by the three methods used, so no `playwright` import is needed here
316
+ and a Playwright Test `page`, a `Page` of the driver, or a stub all fit.
317
+
318
+ See docs/ui-catalog.md
319
+
320
+ #### `CapturePage`
321
+
322
+ ```ts
323
+ export type CapturePage = {
324
+ screenshot(options?: { readonly fullPage?: boolean; readonly animations?: 'disabled' | 'allow'; readonly type?: 'png' }): Promise<Uint8Array>;
325
+ viewportSize(): { readonly width: number; readonly height: number } | null;
326
+ };
327
+ ```
328
+
329
+ The part of a Playwright `Page` that capturing uses.
330
+
331
+ #### `CaptureUiStateOptions`
332
+
333
+ ```ts
334
+ export type CaptureUiStateOptions = UiStateFacts & {
335
+ /** The state's stable id: how a later capture replaces this one. */
336
+ readonly id: string;
337
+ readonly title?: string;
338
+ /** Resolves when the page shows the state (a locator is visible, the network is idle). A rejection or a timeout makes the capture `failed`, with the reason as its detail. */
339
+ readonly ready?: (page: CapturePage) => Promise<void>;
340
+ /** How long to wait for `ready`; default 15 seconds. */
341
+ readonly readyTimeoutMs?: number;
342
+ /** Photograph the whole scrollable page rather than the viewport. */
343
+ readonly fullPage?: boolean;
344
+ /** Extra labels for the capture (`browser`, `os`), which never split it. */
345
+ readonly labels?: Readonly<Record<string, string>>;
346
+ };
347
+ ```
348
+
349
+ What to capture and how to know the page is ready.
350
+
351
+ #### `captureUiState`
352
+
353
+ ```ts
354
+ export async function captureUiState(page: CapturePage, options: CaptureUiStateOptions): Promise<UiStateCapture>
355
+ ```
356
+
357
+ Wait for the page to be ready, photograph it, and return the capture to publish. Never throws for the page's sake: a page
358
+ that never became ready, or could not be photographed, is a `failed` capture that says why, so a broken state shows in the
359
+ catalog instead of ending the run. A readiness failure still attaches a picture of what the page showed, for diagnosis.
360
+
361
+ **Example**
362
+
363
+ ```ts
364
+ const shot = await captureUiState(page, { id: 'checkout/payment', route: '/checkout', ready: p => expectVisible(p, '#total') });
365
+ await publishChecks(store, uiStateLayer({ producer: 'playwright', recordedAtMs: Date.now(), environment: 'desktop-web', profile: { viewport: '1280x800' }, states: [shot] }));
366
+ ```