@crvouga/atlas 0.9.0 → 0.10.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 (53) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/playwright-reporter/src/evidence.d.ts +122 -0
  3. package/dist/playwright-reporter/src/evidence.js +327 -0
  4. package/dist/playwright-reporter/src/index.d.ts +114 -0
  5. package/dist/playwright-reporter/src/index.js +312 -0
  6. package/dist/reports/src/compose.js +7 -14
  7. package/dist/reports/src/recorded-specs.d.ts +33 -0
  8. package/dist/reports/src/recorded-specs.js +42 -0
  9. package/dist/reports/src/report-index.d.ts +6 -0
  10. package/dist/reports/src/report-index.js +14 -2
  11. package/dist/reports/src/test-catalog.d.ts +386 -0
  12. package/dist/reports/src/test-catalog.js +655 -0
  13. package/dist/schema/src/api.d.ts +585 -0
  14. package/dist/schema/src/api.js +105 -0
  15. package/dist/server/src/app.js +82 -4
  16. package/dist/server/src/llms.js +14 -0
  17. package/dist/server/src/openapi.d.ts +1 -1
  18. package/dist/server/src/openapi.js +60 -3
  19. package/docs/agents.md +1 -0
  20. package/docs/integrating.md +1 -0
  21. package/docs/reference/http-api.md +134 -2
  22. package/docs/reference/playwright-reporter.md +332 -0
  23. package/docs/reference/reports.md +632 -0
  24. package/docs/reference/schema.md +57 -0
  25. package/docs/rich-evidence.md +198 -0
  26. package/docs/server.md +41 -0
  27. package/llms-full.txt +378 -2
  28. package/llms.txt +2 -0
  29. package/package.json +6 -1
  30. package/packages/schema/schemas/report-run.schema.json +251 -0
  31. package/packages/schema/schemas/report-runs.schema.json +177 -0
  32. package/packages/schema/schemas/test-catalog.schema.json +1177 -0
  33. package/packages/schema/schemas/test-history.schema.json +696 -0
  34. package/packages/server/openapi.json +8676 -5351
  35. package/packages/visualizer/src/app/AppShell.tsx +7 -3
  36. package/packages/visualizer/src/app/router.tsx +26 -3
  37. package/packages/visualizer/src/components/Header.tsx +2 -2
  38. package/packages/visualizer/src/evidence/evidence-context.tsx +10 -1
  39. package/packages/visualizer/src/explorer/Explorer.tsx +433 -0
  40. package/packages/visualizer/src/explorer/ExplorerIndex.tsx +120 -0
  41. package/packages/visualizer/src/explorer/RunsView.tsx +164 -0
  42. package/packages/visualizer/src/explorer/TestDetail.tsx +214 -0
  43. package/packages/visualizer/src/explorer/catalog.tsx +274 -0
  44. package/packages/visualizer/src/explorer/explorer.module.css +1238 -0
  45. package/packages/visualizer/src/explorer/groups.test.ts +87 -0
  46. package/packages/visualizer/src/explorer/groups.ts +126 -0
  47. package/packages/visualizer/src/explorer/navigate.ts +36 -0
  48. package/packages/visualizer/src/explorer/parts.tsx +59 -0
  49. package/packages/visualizer/src/explorer/search.ts +97 -0
  50. package/packages/visualizer/src/find/Finder.tsx +2 -0
  51. package/packages/visualizer/src/find/corpus.ts +13 -6
  52. package/packages/visualizer/src/find/rank.ts +2 -0
  53. package/packages/visualizer/src/evidence/project-home.tsx +0 -45
@@ -4713,6 +4713,39 @@ Streams a test as events: its report, then each frame file the report names as i
4713
4713
  the report names but the store lacks is skipped. Text media (`html`, `json`, `text`) arrive as inline text, the rest
4714
4714
  as base64.
4715
4715
 
4716
+ ### Specs the layers recorded
4717
+
4718
+ Source: `packages/reports/src/recorded-specs.ts`. Import: `@crvouga/atlas/reports/recorded-specs`
4719
+
4720
+ When no spec set was published, a report is laid over the specs its layers say they ran against (each layer's
4721
+ `specs[]`). `RecordedSpecs` keeps one entry per spec path: the one from the newest layer, and at the same time the
4722
+ one with the larger fingerprint, so the outline it gives does not depend on the order layers are read in. The CLI's
4723
+ composition and the server's live report use it alike, so the two cannot disagree about which specs a report has.
4724
+
4725
+ See docs/report-contract.md#how-layers-compose
4726
+
4727
+ #### `RecordedSpecs`
4728
+
4729
+ ```ts
4730
+ export class RecordedSpecs
4731
+ ```
4732
+
4733
+ The specs layers recorded, newest first per path. Add every included layer's `specs[]` with its `recordedAtMs`;
4734
+ `entries()` is the outline, sorted by path.
4735
+
4736
+ **Example**
4737
+
4738
+ ```ts
4739
+ const recorded = new RecordedSpecs();
4740
+ for (const layer of layers) recorded.add(layer.specs, layer.recordedAtMs);
4741
+ const outline = { specs: recorded.entries(), sha: null, changedFiles: [] };
4742
+ ```
4743
+
4744
+ - `add(specs: readonly SpecEntry[], at: number): void` — Takes in the specs one layer recorded at `at` (epoch ms). A spec whose path is empty is ignored.
4745
+ - `get(path: string): SpecEntry | undefined` — The recorded spec at a path (as `specPath` normalises it), or undefined.
4746
+ - `get size(): number { return this.#byPath.size; }` — How many distinct specs were recorded.
4747
+ - `entries(): readonly SpecEntry[]` — Every recorded spec, sorted by path.
4748
+
4716
4749
  ### In-memory recorded test
4717
4750
 
4718
4751
  Source: `packages/reports/src/recording-bundle.ts`. Import: `@crvouga/atlas/reports/recording-bundle`
@@ -5061,6 +5094,7 @@ It reads only immutable layers (`layers/index/…`), which is what makes "read o
5061
5094
  - `snapshot(): EffectiveEvidence` — The current report, ready to send. Built once per change, and only the layers that changed are rebuilt.
5062
5095
  - `json(): { readonly version: string; readonly text: string }` — The report as JSON text: exactly what `JSON.stringify({ ...snapshot(), active: [] })` writes, put together from the text of each layer, which is written once. `active` is what a store without live runs always sends: none.
5063
5096
  - `delta(since: string): { readonly text: string; readonly version: string } | null` — What differs between the report a viewer holds (named by its version) and the current one: the layers that changed in full, and the ids of those that no longer hold a winner. Null when that version is not remembered, and the viewer should ask for the whole report. See `applyEffectiveDelta`.
5097
+ - `get history(): TestHistory { return this.#history; }` — Every recorded result of every test and every run, as layers were folded in: what the test catalog, a test's history and the run list are built from. Read-only for callers; `history.rev` changes whenever a layer is taken in.
5064
5098
  - `stats()` — For `/readyz` and logs.
5065
5099
 
5066
5100
  ### Test loading state
@@ -7093,6 +7127,604 @@ file; the package (the part of the viewer test id's source before `--`, or `repo
7093
7127
  line of its error as the detail. `search` joins the title, ids, paths, system, runtime, package and error for text
7094
7128
  matching. `href` is `null`; the caller sets where a row links to.
7095
7129
 
7130
+ ### Test catalog, history and runs
7131
+
7132
+ Source: `packages/reports/src/test-catalog.ts`. Import: `@crvouga/atlas/reports/test-catalog`
7133
+
7134
+ What a person or an agent asks of a report once it holds more than a handful of checks: every test with where it ran,
7135
+ how it went over time and how long it takes; the specs, domains, environments and producers they fall under; the CI
7136
+ runs that produced them and which tests each one ran.
7137
+
7138
+ - `TestHistory` keeps every recorded result of every test (bounded per test) and every run's results, folded in one
7139
+ layer at a time, in any order, with the same outcome. It never decides a status: the current status of a test is
7140
+ always the report's winner (`combineEvidence`), and history only describes how it got there.
7141
+ - `testCatalog` turns the report's winners (plus a history, when there is one) into a `TestCatalog`: a summary with
7142
+ insights (slowest, flaky, failing), facets to filter by, and the tests that match a `TestQuery`.
7143
+ - `testHistory`, `runList` and `runDetail` answer for one test, every run, and one run.
7144
+ - `catalogMarkdown`, `historyMarkdown`, `runsMarkdown` and `runMarkdown` say the same in Markdown, for an agent or a
7145
+ terminal.
7146
+
7147
+ A test is a check identity (its spec path and id) regardless of producer, environment and parameters; each of those
7148
+ combinations is one of its results. A result that is not `passed` is never counted as a pass, and a test with no
7149
+ completed result has no pass rate.
7150
+
7151
+ See docs/server.md#reading-the-report-as-an-agent
7152
+
7153
+ #### `TEST_CATALOG_VERSION`
7154
+
7155
+ ```ts
7156
+ export const TEST_CATALOG_VERSION = …
7157
+ ```
7158
+
7159
+ The version of the documents this module writes (`TestCatalog`, `TestHistoryDocument`, `RunList`, `RunDetail`). Only optional fields are added within it.
7160
+
7161
+ #### `HISTORY_PER_TEST`
7162
+
7163
+ ```ts
7164
+ export const HISTORY_PER_TEST = …
7165
+ ```
7166
+
7167
+ Recorded results kept per test, newest first. Older ones are counted in no statistic.
7168
+
7169
+ #### `TestResultEntry`
7170
+
7171
+ ```ts
7172
+ export type TestResultEntry = {
7173
+ /** When it was recorded, epoch ms. */
7174
+ readonly at: number;
7175
+ readonly status: EvidenceStatus;
7176
+ /** How long it took, ms, or null when the producer did not measure it. */
7177
+ readonly durationMs: number | null;
7178
+ /** The client it checked (`desktop-web`, `api`, `ios`…), or null. */
7179
+ readonly environment: string | null;
7180
+ readonly producer: string;
7181
+ /** The kind of the part that held it (`checks`, `behavior`, `performance`…). */
7182
+ readonly kind: string;
7183
+ /** Other choices that made it its own result (a device size, a locale…). */
7184
+ readonly parameters: Readonly<Record<string, string>>;
7185
+ readonly branch: string | null;
7186
+ readonly sha: string | null;
7187
+ readonly runUrl: string | null;
7188
+ /** The key of the run it belongs to (see `runKey`). */
7189
+ readonly run: string;
7190
+ /** The id of the layer that recorded it. */
7191
+ readonly layer: string;
7192
+ readonly backend: string;
7193
+ /** Why it has its status: the first 500 characters of the check's detail. */
7194
+ readonly detail: string;
7195
+ /** What it carried beyond its outcome (attachments, steps, retries, tags, labels), when anything. */
7196
+ readonly evidence?: EvidenceSummary;
7197
+ };
7198
+ ```
7199
+
7200
+ One recorded result of a test: when, where, how it went and how long it took, and the layer and run it came from.
7201
+
7202
+ #### `TestOutcome`
7203
+
7204
+ ```ts
7205
+ export type TestOutcome = TestResultEntry;
7206
+ ```
7207
+
7208
+ The current result of a test in one producer, environment and parameter set: the report's winner for that slot.
7209
+
7210
+ #### `DurationStats`
7211
+
7212
+ ```ts
7213
+ export type DurationStats = {
7214
+ readonly count: number;
7215
+ readonly totalMs: number;
7216
+ readonly avgMs: number;
7217
+ readonly p50Ms: number;
7218
+ readonly p95Ms: number;
7219
+ readonly minMs: number;
7220
+ readonly maxMs: number;
7221
+ /** The newest recorded duration. */
7222
+ readonly lastMs: number;
7223
+ };
7224
+ ```
7225
+
7226
+ Durations of a set of results, ms. Only results that recorded one are counted.
7227
+
7228
+ #### `TestStats`
7229
+
7230
+ ```ts
7231
+ export type TestStats = {
7232
+ /** Recorded results: passed, failed and not-run (progress reported as `running` is not history). */
7233
+ readonly results: number;
7234
+ readonly passed: number;
7235
+ readonly failed: number;
7236
+ /** passed / (passed + failed), or null with no completed result. */
7237
+ readonly passRate: number | null;
7238
+ /** True when the same code both passed and failed, a retry was needed to pass, or the producer said so. */
7239
+ readonly flaky: boolean;
7240
+ /** Why it is flaky, for a person; empty when it is not. */
7241
+ readonly flakyReason: string;
7242
+ /** How many times the completed outcome changed, per result, newest history first. */
7243
+ readonly flips: number;
7244
+ readonly durations: DurationStats | null;
7245
+ readonly firstSeenMs: number | null;
7246
+ readonly lastSeenMs: number | null;
7247
+ readonly lastPassedAtMs: number | null;
7248
+ readonly lastFailedAtMs: number | null;
7249
+ /** Distinct runs it was recorded in. */
7250
+ readonly runs: number;
7251
+ /** Branches it was recorded on, most recent first, at most ten. */
7252
+ readonly branches: readonly string[];
7253
+ };
7254
+ ```
7255
+
7256
+ How a test has gone across its recorded history (at most `HISTORY_PER_TEST` results).
7257
+
7258
+ #### `CatalogSpec`
7259
+
7260
+ ```ts
7261
+ export type CatalogSpec = {
7262
+ readonly path: string;
7263
+ readonly title: string;
7264
+ /** The group it belongs to: the spec's own domain, else the folder under `specs/`. */
7265
+ readonly domain: string;
7266
+ readonly kind: string;
7267
+ };
7268
+ ```
7269
+
7270
+ A spec as the catalog shows it: recorded by a layer or published in a spec set, or derived from the path alone.
7271
+
7272
+ #### `TestSummary`
7273
+
7274
+ ```ts
7275
+ export type TestSummary = {
7276
+ /** The check identity, `JSON.stringify([specPath, id])`. Stable; use it to ask for the test's history. */
7277
+ readonly key: string;
7278
+ readonly id: string;
7279
+ readonly title: string;
7280
+ readonly specPath: string | null;
7281
+ readonly spec: CatalogSpec | null;
7282
+ /** The worst current outcome across its results (failed, then running, then not-run, then passed). */
7283
+ readonly status: EvidenceStatus;
7284
+ readonly environments: readonly string[];
7285
+ readonly producers: readonly string[];
7286
+ /** The current result per producer, environment and parameter set, newest first. */
7287
+ readonly outcomes: readonly TestOutcome[];
7288
+ readonly stats: TestStats;
7289
+ readonly tags: readonly string[];
7290
+ readonly labels: Readonly<Record<string, string>>;
7291
+ /** What its current results carry: attachments, steps, retries, and the kinds of attachment. */
7292
+ readonly evidence: { readonly attachments: number; readonly steps: number; readonly attempts: number; readonly roles: readonly string[] };
7293
+ /** The newest time any of its results was recorded. */
7294
+ readonly lastRecordedAtMs: number;
7295
+ /** True while one of its results comes from a run still in progress. */
7296
+ readonly live: boolean;
7297
+ };
7298
+ ```
7299
+
7300
+ One test: what it is, its current status per environment, and how it has gone.
7301
+
7302
+ #### `TestRef`
7303
+
7304
+ ```ts
7305
+ export type TestRef = {
7306
+ readonly key: string;
7307
+ readonly title: string;
7308
+ readonly specPath: string | null;
7309
+ readonly status: EvidenceStatus;
7310
+ /** Average duration (ms) for `slowest`, flips for `flaky`, failures for `failing`. */
7311
+ readonly value: number;
7312
+ };
7313
+ ```
7314
+
7315
+ A test named in an insight list, with the number that put it there.
7316
+
7317
+ #### `CatalogGroup`
7318
+
7319
+ ```ts
7320
+ export type CatalogGroup = {
7321
+ readonly name: string;
7322
+ readonly tests: number;
7323
+ readonly counts: StatusCounts;
7324
+ /** Average of its tests' average durations, ms, or null when none was measured. */
7325
+ readonly avgMs: number | null;
7326
+ };
7327
+ ```
7328
+
7329
+ A group in the summary (an environment, a producer, a domain, a spec): how many tests, by status (for an environment, by how they went on it), and their timing.
7330
+
7331
+ #### `CatalogSummary`
7332
+
7333
+ ```ts
7334
+ export type CatalogSummary = {
7335
+ readonly tests: number;
7336
+ /** Tests by their current status. */
7337
+ readonly counts: StatusCounts;
7338
+ /** Current results (a test checked on two environments has two). */
7339
+ readonly outcomes: number;
7340
+ readonly flaky: number;
7341
+ /** Over each test's average duration. */
7342
+ readonly durations: DurationStats | null;
7343
+ /** Tests with at least one measured duration. */
7344
+ readonly measured: number;
7345
+ readonly withAttachments: number;
7346
+ readonly environments: readonly CatalogGroup[];
7347
+ readonly producers: readonly CatalogGroup[];
7348
+ readonly domains: readonly CatalogGroup[];
7349
+ readonly slowest: readonly TestRef[];
7350
+ readonly flakiest: readonly TestRef[];
7351
+ readonly failing: readonly TestRef[];
7352
+ /** Runs the history knows of. */
7353
+ readonly runs: number;
7354
+ readonly lastRecordedAtMs: number | null;
7355
+ /** True when statistics come from recorded history, false when only the current results were available. */
7356
+ readonly history: boolean;
7357
+ };
7358
+ ```
7359
+
7360
+ The report at a glance: counts, timing and insights over every test (not only those a query matched).
7361
+
7362
+ #### `FacetValue`
7363
+
7364
+ ```ts
7365
+ export type FacetValue = { readonly value: string; readonly count: number };
7366
+ ```
7367
+
7368
+ A value to filter by, and how many tests have it.
7369
+
7370
+ #### `CatalogFacets`
7371
+
7372
+ ```ts
7373
+ export type CatalogFacets = {
7374
+ readonly statuses: readonly FacetValue[];
7375
+ readonly environments: readonly FacetValue[];
7376
+ readonly producers: readonly FacetValue[];
7377
+ readonly domains: readonly FacetValue[];
7378
+ readonly specs: readonly (CatalogSpec & { readonly tests: number; readonly counts: StatusCounts })[];
7379
+ readonly tags: readonly FacetValue[];
7380
+ readonly branches: readonly FacetValue[];
7381
+ readonly labels: Readonly<Record<string, readonly FacetValue[]>>;
7382
+ };
7383
+ ```
7384
+
7385
+ Everything the tests can be filtered and grouped by, with counts over every test.
7386
+
7387
+ #### `TEST_SORTS`
7388
+
7389
+ ```ts
7390
+ export const TEST_SORTS = …
7391
+ ```
7392
+
7393
+ How tests are ordered.
7394
+
7395
+ #### `TestSort`
7396
+
7397
+ ```ts
7398
+ export type TestSort = (typeof TEST_SORTS)[number];
7399
+ ```
7400
+
7401
+ One of `TEST_SORTS`. `spec` (the default) orders by domain, spec and title.
7402
+
7403
+ #### `TestQuery`
7404
+
7405
+ ```ts
7406
+ export type TestQuery = {
7407
+ /** Words that must all appear in the title, id, spec, domain, tags, labels, environments or producers. Case does not matter. */
7408
+ readonly q?: string;
7409
+ /** Current status, or `flaky`. */
7410
+ readonly status?: readonly string[];
7411
+ readonly environment?: readonly string[];
7412
+ readonly producer?: readonly string[];
7413
+ readonly domain?: readonly string[];
7414
+ /** Spec paths (`specs/...`). */
7415
+ readonly spec?: readonly string[];
7416
+ readonly tag?: readonly string[];
7417
+ /** Only tests recorded in this run (a key from `runList`). */
7418
+ readonly run?: string;
7419
+ readonly sort?: TestSort;
7420
+ readonly offset?: number;
7421
+ /** At most this many tests (default 500, at most 20000). */
7422
+ readonly limit?: number;
7423
+ };
7424
+ ```
7425
+
7426
+ Which tests to list. Every field is optional; several values of one field match any of them, and fields combine.
7427
+
7428
+ #### `TestCatalog`
7429
+
7430
+ ```ts
7431
+ export type TestCatalog = {
7432
+ readonly schemaVersion: typeof TEST_CATALOG_VERSION;
7433
+ /** Changes whenever the report or its history does. */
7434
+ readonly version: string;
7435
+ readonly generatedAtMs: number;
7436
+ readonly summary: CatalogSummary;
7437
+ readonly facets: CatalogFacets;
7438
+ /** Tests that matched, before `offset` and `limit`. */
7439
+ readonly total: number;
7440
+ readonly offset: number;
7441
+ readonly limit: number;
7442
+ readonly tests: readonly TestSummary[];
7443
+ };
7444
+ ```
7445
+
7446
+ `GET /api/reports/tests`: the summary, the facets and the tests that match a query.
7447
+
7448
+ #### `TestHistoryDocument`
7449
+
7450
+ ```ts
7451
+ export type TestHistoryDocument = {
7452
+ readonly schemaVersion: typeof TEST_CATALOG_VERSION;
7453
+ readonly version: string;
7454
+ readonly test: TestSummary;
7455
+ readonly history: readonly TestResultEntry[];
7456
+ };
7457
+ ```
7458
+
7459
+ `GET /api/reports/tests/history`: one test and every result recorded for it, newest first.
7460
+
7461
+ #### `RunSummary`
7462
+
7463
+ ```ts
7464
+ export type RunSummary = {
7465
+ /** Stable key: the run URL, or `branch@sha`. */
7466
+ readonly key: string;
7467
+ readonly runUrl: string | null;
7468
+ readonly branch: string | null;
7469
+ readonly sha: string | null;
7470
+ readonly startedAtMs: number;
7471
+ readonly endedAtMs: number;
7472
+ /** From its first to its last recorded result, ms. */
7473
+ readonly spanMs: number;
7474
+ readonly producers: readonly string[];
7475
+ readonly environments: readonly string[];
7476
+ readonly layers: number;
7477
+ /** Distinct tests it recorded. */
7478
+ readonly tests: number;
7479
+ /** Its results by status (its last word on each test, environment and producer). */
7480
+ readonly counts: StatusCounts;
7481
+ /** Sum of its results' durations, ms, when any was measured. */
7482
+ readonly totalDurationMs: number | null;
7483
+ /** Up to twenty tests that failed in it. */
7484
+ readonly failing: readonly { readonly key: string; readonly title: string; readonly environment: string | null; readonly detail: string }[];
7485
+ };
7486
+ ```
7487
+
7488
+ A CI run (or any group of layers sharing a run URL, else a branch and commit): when, where, and how its tests went.
7489
+
7490
+ #### `RunList`
7491
+
7492
+ ```ts
7493
+ export type RunList = {
7494
+ readonly schemaVersion: typeof TEST_CATALOG_VERSION;
7495
+ readonly version: string;
7496
+ readonly total: number;
7497
+ readonly runs: readonly RunSummary[];
7498
+ };
7499
+ ```
7500
+
7501
+ `GET /api/reports/runs`: runs, newest first.
7502
+
7503
+ #### `RunResult`
7504
+
7505
+ ```ts
7506
+ export type RunResult = {
7507
+ readonly key: string;
7508
+ readonly id: string;
7509
+ readonly title: string;
7510
+ readonly specPath: string | null;
7511
+ readonly environment: string | null;
7512
+ readonly producer: string;
7513
+ readonly status: EvidenceStatus;
7514
+ readonly durationMs: number | null;
7515
+ readonly at: number;
7516
+ readonly layer: string;
7517
+ readonly detail: string;
7518
+ };
7519
+ ```
7520
+
7521
+ One test's last result within one run.
7522
+
7523
+ #### `RunDetail`
7524
+
7525
+ ```ts
7526
+ export type RunDetail = {
7527
+ readonly schemaVersion: typeof TEST_CATALOG_VERSION;
7528
+ readonly version: string;
7529
+ readonly run: RunSummary;
7530
+ readonly results: readonly RunResult[];
7531
+ };
7532
+ ```
7533
+
7534
+ `GET /api/reports/runs/detail`: one run and every result it recorded.
7535
+
7536
+ #### `runKey`
7537
+
7538
+ ```ts
7539
+ export function runKey(layer: Pick<LayerHeader, 'runUrl' | 'branch' | 'sha'>): string
7540
+ ```
7541
+
7542
+ The run a layer belongs to: its run URL, else `branch@sha`, else `local`. Layers with the same key are one run.
7543
+
7544
+ #### `TestHistory`
7545
+
7546
+ ```ts
7547
+ export class TestHistory
7548
+ ```
7549
+
7550
+ Every recorded result of every test (the newest `HISTORY_PER_TEST` per test, leaving out `running` progress) and the
7551
+ runs they came from. A run's results are read from that history, so memory follows the number of tests and not the age
7552
+ of the project: a run older than a test's last hundred results no longer lists that test. Add each layer once with
7553
+ `add`; the order layers arrive in does not change what it holds. It never decides a status.
7554
+
7555
+ **Example**
7556
+
7557
+ ```ts
7558
+ const history = new TestHistory();
7559
+ for (const { layer, rows } of layers) history.add(layer, rows, specs, layer.backend);
7560
+ const catalog = testCatalog(catalogModel(layers, { history }));
7561
+ ```
7562
+
7563
+ - `get rev(): number { return this.#rev; }` — Changes each time a layer is added.
7564
+ - `get specs(): RecordedSpecs { return this.#specs; }` — The specs the added layers recorded.
7565
+ - `get layers(): number { return this.#seen.size; }` — How many layers were added.
7566
+ - `add(layer: LayerHeader, rows: readonly LayerRow[], specs: readonly SpecEntry[] = [], backend = layer.backend): void` — Takes in one layer: its header, its rows, the specs it recorded and the backend it was read from. A layer id already added is ignored.
7567
+ - `of(key: string): readonly TestResultEntry[]` — The recorded results of one test (by `TestSummary.key`), newest first. Empty when none.
7568
+ - `get runCount(): number { return this.#runs.size; }` — How many runs it knows of.
7569
+ - `runs(): RunSummary[]` — Every run, newest first.
7570
+ - `run(key: string): { readonly run: RunSummary; readonly results: RunResult[] } | null` — One run and its results (failed first, then by spec and title), or null.
7571
+ - `testsIn(key: string): ReadonlySet<string>` — The keys of the tests one run recorded.
7572
+
7573
+ #### `durationStats`
7574
+
7575
+ ```ts
7576
+ export function durationStats(values: readonly number[]): DurationStats | null
7577
+ ```
7578
+
7579
+ Durations of a list of numbers (ms), or null when empty. `lastMs` is the first value, so give them newest first.
7580
+
7581
+ #### `catalogSpec`
7582
+
7583
+ ```ts
7584
+ export function catalogSpec(path: string | null, known: (path: string) => SpecEntry | undefined): CatalogSpec | null
7585
+ ```
7586
+
7587
+ The spec a path names: the recorded one, or one derived from the path (`specs/<domain>/<name>.<ext>`).
7588
+
7589
+ #### `CatalogSources`
7590
+
7591
+ ```ts
7592
+ export type CatalogSources = {
7593
+ /** Recorded results; without it, statistics describe only the current results. */
7594
+ readonly history?: TestHistory | null;
7595
+ /** Specs by path, published or recorded. Defaults to those the history recorded. */
7596
+ readonly specs?: readonly SpecEntry[];
7597
+ /** The report's version, echoed in the documents. */
7598
+ readonly version?: string;
7599
+ readonly now?: number;
7600
+ };
7601
+ ```
7602
+
7603
+ Where the catalog's statistics and specs come from.
7604
+
7605
+ #### `CatalogModel`
7606
+
7607
+ ```ts
7608
+ export type CatalogModel = {
7609
+ readonly version: string;
7610
+ readonly generatedAtMs: number;
7611
+ readonly tests: readonly TestSummary[];
7612
+ readonly byKey: ReadonlyMap<string, TestSummary>;
7613
+ readonly summary: CatalogSummary;
7614
+ readonly facets: CatalogFacets;
7615
+ readonly haystacks: ReadonlyMap<string, string>;
7616
+ readonly history: TestHistory | null;
7617
+ };
7618
+ ```
7619
+
7620
+ The tests of a report, built once and queried many times: what `testCatalog` and `testHistory` read from.
7621
+
7622
+ #### `haystackOf`
7623
+
7624
+ ```ts
7625
+ export function haystackOf(test: TestSummary): string
7626
+ ```
7627
+
7628
+ The lowercase text a search matches a test against: title, id, spec, domain, tags, labels, environments and producers.
7629
+
7630
+ #### `catalogModel`
7631
+
7632
+ ```ts
7633
+ export function catalogModel(layers: readonly LayerEvidence[], sources: CatalogSources = {}): CatalogModel
7634
+ ```
7635
+
7636
+ Builds the tests of a report from its winners (`layers`, as the effective report holds them: each layer with the rows
7637
+ that win) and, when given, a history. The model is what `testCatalog` and `testHistory` query; build it once per
7638
+ version and reuse it.
7639
+
7640
+ #### `matchesQuery`
7641
+
7642
+ ```ts
7643
+ export function matchesQuery(model: CatalogModel, test: TestSummary, query: TestQuery, inRun?: ReadonlySet<string> | null): boolean
7644
+ ```
7645
+
7646
+ Whether one test matches a query (everything but sorting and paging).
7647
+
7648
+ #### `testCatalog`
7649
+
7650
+ ```ts
7651
+ export function testCatalog(model: CatalogModel, query: TestQuery = {}): TestCatalog
7652
+ ```
7653
+
7654
+ The catalog document for a query: the model's summary and facets, and the matching tests, sorted and paged.
7655
+
7656
+ #### `testHistory`
7657
+
7658
+ ```ts
7659
+ export function testHistory(model: CatalogModel, key: string): TestHistoryDocument | null
7660
+ ```
7661
+
7662
+ One test and its recorded results, or null when the report has no test with that key.
7663
+
7664
+ #### `runList`
7665
+
7666
+ ```ts
7667
+ export function runList(history: TestHistory, version: string, limit = 100): RunList
7668
+ ```
7669
+
7670
+ Every run the history knows of, newest first, `limit` at most (default 100).
7671
+
7672
+ #### `runDetail`
7673
+
7674
+ ```ts
7675
+ export function runDetail(history: TestHistory, version: string, key: string): RunDetail | null
7676
+ ```
7677
+
7678
+ One run with every result it recorded, or null when no run has that key.
7679
+
7680
+ #### `parseTestQuery`
7681
+
7682
+ ```ts
7683
+ export function parseTestQuery(params: URLSearchParams): TestQuery
7684
+ ```
7685
+
7686
+ Reads a `TestQuery` from URL search parameters: `q`, `sort`, `offset`, `limit`, `run`, and repeatable or comma-separated `status`, `environment`, `producer`, `domain`, `spec`, `tag`.
7687
+
7688
+ #### `formatMs`
7689
+
7690
+ ```ts
7691
+ export function formatMs(ms: number | null | undefined): string
7692
+ ```
7693
+
7694
+ A duration for a person: `850 ms`, `12.3 s`, `4m 05s`.
7695
+
7696
+ #### `catalogMarkdown`
7697
+
7698
+ ```ts
7699
+ export function catalogMarkdown(catalog: TestCatalog): string
7700
+ ```
7701
+
7702
+ The catalog as Markdown: the summary, insights, groups and the listed tests as a table.
7703
+
7704
+ #### `historyMarkdown`
7705
+
7706
+ ```ts
7707
+ export function historyMarkdown(document: TestHistoryDocument): string
7708
+ ```
7709
+
7710
+ One test's history as Markdown.
7711
+
7712
+ #### `runsMarkdown`
7713
+
7714
+ ```ts
7715
+ export function runsMarkdown(list: RunList): string
7716
+ ```
7717
+
7718
+ The run list as Markdown.
7719
+
7720
+ #### `runMarkdown`
7721
+
7722
+ ```ts
7723
+ export function runMarkdown(detail: RunDetail): string
7724
+ ```
7725
+
7726
+ One run as Markdown.
7727
+
7096
7728
  ### Report viewer data
7097
7729
 
7098
7730
  Source: `packages/reports/src/types.ts`. Import: `@crvouga/atlas/reports/types`