@crvouga/atlas 1.18.0 → 1.19.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 +8 -1
- package/dist/reports/src/evidence-detail.js +13 -3
- package/dist/reports/src/evidence-types.d.ts +4 -0
- package/dist/schema/src/api.d.ts +50 -0
- package/dist/schema/src/api.js +2 -0
- package/docs/reference/reports.md +4 -0
- package/docs/rich-evidence.md +1 -0
- package/docs/ui.md +40 -1
- package/llms-full.txt +41 -1
- package/package.json +1 -1
- package/packages/schema/schemas/effective-delta.schema.json +18 -0
- package/packages/schema/schemas/effective-evidence.schema.json +18 -0
- package/packages/schema/schemas/evidence-page.schema.json +9 -0
- package/packages/schema/schemas/layer-rows.schema.json +9 -0
- package/packages/schema/schemas/report-run.schema.json +9 -0
- package/packages/schema/schemas/review-collection.schema.json +9 -0
- package/packages/schema/schemas/review-manifest.schema.json +9 -0
- package/packages/schema/schemas/test-catalog.schema.json +9 -0
- package/packages/schema/schemas/test-history.schema.json +18 -0
- package/packages/server/openapi.json +126 -0
- package/packages/visualizer/src/app/AppShell.tsx +2 -2
- package/packages/visualizer/src/app/router.tsx +25 -1
- package/packages/visualizer/src/components/Header.tsx +15 -4
- package/packages/visualizer/src/evidence/behavior-index.test.ts +119 -0
- package/packages/visualizer/src/evidence/behavior-index.ts +162 -0
- package/packages/visualizer/src/evidence/behavior-panel.tsx +36 -22
- package/packages/visualizer/src/evidence/behavior-return.test.ts +41 -0
- package/packages/visualizer/src/evidence/behavior-return.ts +59 -0
- package/packages/visualizer/src/evidence/behavior.ts +32 -1
- package/packages/visualizer/src/evidence/behaviors-page.tsx +465 -0
- package/packages/visualizer/src/evidence/feature-page.tsx +5 -26
- package/packages/visualizer/src/evidence/scoped-summary-view.tsx +110 -0
- package/packages/visualizer/src/evidence/scoped-summary.test.ts +62 -0
- package/packages/visualizer/src/evidence/scoped-summary.ts +118 -0
- package/packages/visualizer/src/evidence/spec-texts.ts +88 -0
- package/packages/visualizer/src/explorer/ArtifactsView.tsx +4 -4
- package/packages/visualizer/src/explorer/Explorer.tsx +1 -1
- package/packages/visualizer/src/explorer/RunsView.tsx +4 -4
- package/packages/visualizer/src/explorer/collections.tsx +5 -5
- package/packages/visualizer/src/explorer/navigate.ts +17 -0
- package/packages/visualizer/src/explorer/playlist-entries.test.ts +10 -1
- package/packages/visualizer/src/explorer/playlist-entries.ts +10 -0
- package/packages/visualizer/src/explorer/playlist.tsx +43 -8
- package/packages/visualizer/src/explorer/search.ts +7 -1
- package/packages/visualizer/src/find/Finder.tsx +1 -62
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Changelog: @crvouga/atlas
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.19.0 (2026-10-29)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add a project-wide behavior index, a read-and-watch workspace and a scoped summary ([bb2d270](https://github.com/crvouga/atlas/commit/bb2d270fcfc2374c316db804e80af67885bdd8ee))
|
|
8
|
+
- restore the watch playlist from the address and show clip posters and lengths ([2dfc480](https://github.com/crvouga/atlas/commit/2dfc4806a72621f67f304cd9fda7619a9dbbea3f))
|
|
9
|
+
|
|
10
|
+
## 1.18.0 (2026-10-28)
|
|
4
11
|
|
|
5
12
|
### Features
|
|
6
13
|
|
|
@@ -62,7 +62,7 @@ function summarize(holders, owner, inherited, firstIsParent = false, unstored) {
|
|
|
62
62
|
for (const tag of normalizeTags(item.tags))
|
|
63
63
|
artifactTags.add(tag);
|
|
64
64
|
roles.add(item.role ?? roleOfType(item.contentType ?? contentTypeOf(item.name)));
|
|
65
|
-
const video = videoOf(item, by, where, effectiveScope(item.scope, ...scopes), unstored);
|
|
65
|
+
const video = videoOf(item, by, where, effectiveScope(item.scope, ...scopes), unstored, list);
|
|
66
66
|
if (video)
|
|
67
67
|
videos.push(video);
|
|
68
68
|
}
|
|
@@ -110,14 +110,24 @@ export function isVideoAttachment(attachment) {
|
|
|
110
110
|
export function attachmentIdentity(item) {
|
|
111
111
|
return item.sha256 ? `sha256:${item.sha256}` : item.bundleKey ?? item.uri ?? (item.text === undefined ? undefined : `inline:${item.name}`);
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
/** The bundle key of the image an attachment names as its poster among its siblings, when the layer's bundle holds it. */
|
|
114
|
+
function posterKey(item, siblings, unstored) {
|
|
115
|
+
if (item.poster === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
const poster = siblings?.find(other => other !== item && other.id === item.poster);
|
|
118
|
+
if (!poster?.bundleKey || unstored?.has(poster.bundleKey))
|
|
119
|
+
return undefined;
|
|
120
|
+
return (poster.contentType ?? contentTypeOf(poster.name)).toLowerCase().startsWith('image/') ? poster.bundleKey : undefined;
|
|
121
|
+
}
|
|
122
|
+
function videoOf(item, owner, where, scope, unstored, siblings) {
|
|
114
123
|
if (!isVideoAttachment(item))
|
|
115
124
|
return undefined;
|
|
116
125
|
// A bundle key the layer's bundle does not hold names bytes that are not there: only an address or inline text can still carry them, and the digest of lost bytes identifies nothing playable.
|
|
117
126
|
const lost = item.bundleKey !== undefined && unstored?.has(item.bundleKey) === true;
|
|
118
127
|
const recording = lost ? attachmentIdentity({ name: item.name, uri: item.uri, text: item.text }) : attachmentIdentity(item);
|
|
119
128
|
const tags = normalizeTags(item.tags);
|
|
120
|
-
|
|
129
|
+
const poster = posterKey(item, siblings, unstored);
|
|
130
|
+
return { name: item.name, owner, ...where, ...(tags.length ? { tags } : {}), ...(recording === undefined ? {} : { recording }), available: recording !== undefined, ...(lost && recording === undefined ? { reason: 'not-in-bundle' } : {}), ...(item.durationMs !== undefined && Number.isFinite(item.durationMs) ? { durationMs: item.durationMs } : {}), ...(poster === undefined ? {} : { poster }), ...(scope ? { scope } : {}) };
|
|
121
131
|
}
|
|
122
132
|
/**
|
|
123
133
|
* Counts references, distinct playable recordings and unplayable references. Undefined when there is no reference, so
|
|
@@ -60,6 +60,10 @@ export type VideoRef = {
|
|
|
60
60
|
readonly available: boolean;
|
|
61
61
|
/** Why it is unavailable, when the report can tell: `not-in-bundle` names a file the layer should hold (its bundle key) but does not, a recording that was acknowledged and is no longer there. Absent otherwise, and for an available video. An open vocabulary: treat an unknown reason as plain unavailable. */
|
|
62
62
|
readonly reason?: 'not-in-bundle';
|
|
63
|
+
/** How long the recording is, in milliseconds, as its producer declared (`Attachment.durationMs`): a hint to show before the file is opened, never measured here and never a latency. Absent when none was declared, and in documents of older servers. */
|
|
64
|
+
readonly durationMs?: number;
|
|
65
|
+
/** The bundle key, in the same layer, of the image the producer named as this recording's poster (`Attachment.poster`): a hint to show before the file is opened. Only an image the layer's bundle holds; absent when there is none, and in documents of older servers. */
|
|
66
|
+
readonly poster?: string;
|
|
63
67
|
/** What this recording shows, as declared for it: its own `scope`, else its attempt's, check's, part's or layer's. Absent when none declared one: its scope is unknown, not live, physical or audible. */
|
|
64
68
|
readonly scope?: EvidenceScope;
|
|
65
69
|
};
|
package/dist/schema/src/api.d.ts
CHANGED
|
@@ -91,6 +91,8 @@ export declare const VideoRefSchema: z.ZodObject<{
|
|
|
91
91
|
recording: z.ZodOptional<z.ZodString>;
|
|
92
92
|
available: z.ZodBoolean;
|
|
93
93
|
reason: z.ZodOptional<z.ZodString>;
|
|
94
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
95
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
94
96
|
scope: z.ZodOptional<z.ZodObject<{
|
|
95
97
|
surface: z.ZodOptional<z.ZodString>;
|
|
96
98
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -144,6 +146,8 @@ export declare const EvidenceSummarySchema: z.ZodObject<{
|
|
|
144
146
|
recording: z.ZodOptional<z.ZodString>;
|
|
145
147
|
available: z.ZodBoolean;
|
|
146
148
|
reason: z.ZodOptional<z.ZodString>;
|
|
149
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
150
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
147
151
|
scope: z.ZodOptional<z.ZodObject<{
|
|
148
152
|
surface: z.ZodOptional<z.ZodString>;
|
|
149
153
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -221,6 +225,8 @@ export declare const LayerHeaderSchema: z.ZodObject<{
|
|
|
221
225
|
recording: z.ZodOptional<z.ZodString>;
|
|
222
226
|
available: z.ZodBoolean;
|
|
223
227
|
reason: z.ZodOptional<z.ZodString>;
|
|
228
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
229
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
224
230
|
scope: z.ZodOptional<z.ZodObject<{
|
|
225
231
|
surface: z.ZodOptional<z.ZodString>;
|
|
226
232
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -309,6 +315,8 @@ export declare const LayerRowSchema: z.ZodObject<{
|
|
|
309
315
|
recording: z.ZodOptional<z.ZodString>;
|
|
310
316
|
available: z.ZodBoolean;
|
|
311
317
|
reason: z.ZodOptional<z.ZodString>;
|
|
318
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
319
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
312
320
|
scope: z.ZodOptional<z.ZodObject<{
|
|
313
321
|
surface: z.ZodOptional<z.ZodString>;
|
|
314
322
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -415,6 +423,8 @@ export declare const LayerRowsSchema: z.ZodObject<{
|
|
|
415
423
|
recording: z.ZodOptional<z.ZodString>;
|
|
416
424
|
available: z.ZodBoolean;
|
|
417
425
|
reason: z.ZodOptional<z.ZodString>;
|
|
426
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
427
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
418
428
|
scope: z.ZodOptional<z.ZodObject<{
|
|
419
429
|
surface: z.ZodOptional<z.ZodString>;
|
|
420
430
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -523,6 +533,8 @@ export declare const LayerEvidenceSchema: z.ZodObject<{
|
|
|
523
533
|
recording: z.ZodOptional<z.ZodString>;
|
|
524
534
|
available: z.ZodBoolean;
|
|
525
535
|
reason: z.ZodOptional<z.ZodString>;
|
|
536
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
537
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
526
538
|
scope: z.ZodOptional<z.ZodObject<{
|
|
527
539
|
surface: z.ZodOptional<z.ZodString>;
|
|
528
540
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -625,6 +637,8 @@ export declare const LayerEvidenceSchema: z.ZodObject<{
|
|
|
625
637
|
recording: z.ZodOptional<z.ZodString>;
|
|
626
638
|
available: z.ZodBoolean;
|
|
627
639
|
reason: z.ZodOptional<z.ZodString>;
|
|
640
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
641
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
628
642
|
scope: z.ZodOptional<z.ZodObject<{
|
|
629
643
|
surface: z.ZodOptional<z.ZodString>;
|
|
630
644
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -722,6 +736,8 @@ export declare const EffectiveEvidenceSchema: z.ZodObject<{
|
|
|
722
736
|
recording: z.ZodOptional<z.ZodString>;
|
|
723
737
|
available: z.ZodBoolean;
|
|
724
738
|
reason: z.ZodOptional<z.ZodString>;
|
|
739
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
740
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
725
741
|
scope: z.ZodOptional<z.ZodObject<{
|
|
726
742
|
surface: z.ZodOptional<z.ZodString>;
|
|
727
743
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -824,6 +840,8 @@ export declare const EffectiveEvidenceSchema: z.ZodObject<{
|
|
|
824
840
|
recording: z.ZodOptional<z.ZodString>;
|
|
825
841
|
available: z.ZodBoolean;
|
|
826
842
|
reason: z.ZodOptional<z.ZodString>;
|
|
843
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
844
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
827
845
|
scope: z.ZodOptional<z.ZodObject<{
|
|
828
846
|
surface: z.ZodOptional<z.ZodString>;
|
|
829
847
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -942,6 +960,8 @@ export declare const EffectiveDeltaSchema: z.ZodObject<{
|
|
|
942
960
|
recording: z.ZodOptional<z.ZodString>;
|
|
943
961
|
available: z.ZodBoolean;
|
|
944
962
|
reason: z.ZodOptional<z.ZodString>;
|
|
963
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
964
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
945
965
|
scope: z.ZodOptional<z.ZodObject<{
|
|
946
966
|
surface: z.ZodOptional<z.ZodString>;
|
|
947
967
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -1044,6 +1064,8 @@ export declare const EffectiveDeltaSchema: z.ZodObject<{
|
|
|
1044
1064
|
recording: z.ZodOptional<z.ZodString>;
|
|
1045
1065
|
available: z.ZodBoolean;
|
|
1046
1066
|
reason: z.ZodOptional<z.ZodString>;
|
|
1067
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
1068
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
1047
1069
|
scope: z.ZodOptional<z.ZodObject<{
|
|
1048
1070
|
surface: z.ZodOptional<z.ZodString>;
|
|
1049
1071
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -1154,6 +1176,8 @@ export declare const EvidencePageSchema: z.ZodObject<{
|
|
|
1154
1176
|
recording: z.ZodOptional<z.ZodString>;
|
|
1155
1177
|
available: z.ZodBoolean;
|
|
1156
1178
|
reason: z.ZodOptional<z.ZodString>;
|
|
1179
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
1180
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
1157
1181
|
scope: z.ZodOptional<z.ZodObject<{
|
|
1158
1182
|
surface: z.ZodOptional<z.ZodString>;
|
|
1159
1183
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -1735,6 +1759,8 @@ export declare const ReviewSnapshotSchema: z.ZodObject<{
|
|
|
1735
1759
|
recording: z.ZodOptional<z.ZodString>;
|
|
1736
1760
|
available: z.ZodBoolean;
|
|
1737
1761
|
reason: z.ZodOptional<z.ZodString>;
|
|
1762
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
1763
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
1738
1764
|
scope: z.ZodOptional<z.ZodObject<{
|
|
1739
1765
|
surface: z.ZodOptional<z.ZodString>;
|
|
1740
1766
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -1799,6 +1825,8 @@ export declare const ReviewEntrySchema: z.ZodObject<{
|
|
|
1799
1825
|
recording: z.ZodOptional<z.ZodString>;
|
|
1800
1826
|
available: z.ZodBoolean;
|
|
1801
1827
|
reason: z.ZodOptional<z.ZodString>;
|
|
1828
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
1829
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
1802
1830
|
scope: z.ZodOptional<z.ZodObject<{
|
|
1803
1831
|
surface: z.ZodOptional<z.ZodString>;
|
|
1804
1832
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -1882,6 +1910,8 @@ export declare const ReviewCollectionSchema: z.ZodObject<{
|
|
|
1882
1910
|
recording: z.ZodOptional<z.ZodString>;
|
|
1883
1911
|
available: z.ZodBoolean;
|
|
1884
1912
|
reason: z.ZodOptional<z.ZodString>;
|
|
1913
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
1914
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
1885
1915
|
scope: z.ZodOptional<z.ZodObject<{
|
|
1886
1916
|
surface: z.ZodOptional<z.ZodString>;
|
|
1887
1917
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -2042,6 +2072,8 @@ export declare const ReviewCollectionDetailSchema: z.ZodObject<{
|
|
|
2042
2072
|
recording: z.ZodOptional<z.ZodString>;
|
|
2043
2073
|
available: z.ZodBoolean;
|
|
2044
2074
|
reason: z.ZodOptional<z.ZodString>;
|
|
2075
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
2076
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
2045
2077
|
scope: z.ZodOptional<z.ZodObject<{
|
|
2046
2078
|
surface: z.ZodOptional<z.ZodString>;
|
|
2047
2079
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -2174,6 +2206,8 @@ export declare const ReviewManifestSchema: z.ZodObject<{
|
|
|
2174
2206
|
recording: z.ZodOptional<z.ZodString>;
|
|
2175
2207
|
available: z.ZodBoolean;
|
|
2176
2208
|
reason: z.ZodOptional<z.ZodString>;
|
|
2209
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
2210
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
2177
2211
|
scope: z.ZodOptional<z.ZodObject<{
|
|
2178
2212
|
surface: z.ZodOptional<z.ZodString>;
|
|
2179
2213
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -2303,6 +2337,8 @@ export declare const TestResultEntrySchema: z.ZodObject<{
|
|
|
2303
2337
|
recording: z.ZodOptional<z.ZodString>;
|
|
2304
2338
|
available: z.ZodBoolean;
|
|
2305
2339
|
reason: z.ZodOptional<z.ZodString>;
|
|
2340
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
2341
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
2306
2342
|
scope: z.ZodOptional<z.ZodObject<{
|
|
2307
2343
|
surface: z.ZodOptional<z.ZodString>;
|
|
2308
2344
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -2414,6 +2450,8 @@ export declare const TestSummarySchema: z.ZodObject<{
|
|
|
2414
2450
|
recording: z.ZodOptional<z.ZodString>;
|
|
2415
2451
|
available: z.ZodBoolean;
|
|
2416
2452
|
reason: z.ZodOptional<z.ZodString>;
|
|
2453
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
2454
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
2417
2455
|
scope: z.ZodOptional<z.ZodObject<{
|
|
2418
2456
|
surface: z.ZodOptional<z.ZodString>;
|
|
2419
2457
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -2721,6 +2759,8 @@ export declare const TestCatalogSchema: z.ZodObject<{
|
|
|
2721
2759
|
recording: z.ZodOptional<z.ZodString>;
|
|
2722
2760
|
available: z.ZodBoolean;
|
|
2723
2761
|
reason: z.ZodOptional<z.ZodString>;
|
|
2762
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
2763
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
2724
2764
|
scope: z.ZodOptional<z.ZodObject<{
|
|
2725
2765
|
surface: z.ZodOptional<z.ZodString>;
|
|
2726
2766
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -2960,6 +3000,8 @@ export declare const TestHistorySchema: z.ZodObject<{
|
|
|
2960
3000
|
recording: z.ZodOptional<z.ZodString>;
|
|
2961
3001
|
available: z.ZodBoolean;
|
|
2962
3002
|
reason: z.ZodOptional<z.ZodString>;
|
|
3003
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
3004
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
2963
3005
|
scope: z.ZodOptional<z.ZodObject<{
|
|
2964
3006
|
surface: z.ZodOptional<z.ZodString>;
|
|
2965
3007
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -3089,6 +3131,8 @@ export declare const TestHistorySchema: z.ZodObject<{
|
|
|
3089
3131
|
recording: z.ZodOptional<z.ZodString>;
|
|
3090
3132
|
available: z.ZodBoolean;
|
|
3091
3133
|
reason: z.ZodOptional<z.ZodString>;
|
|
3134
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
3135
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
3092
3136
|
scope: z.ZodOptional<z.ZodObject<{
|
|
3093
3137
|
surface: z.ZodOptional<z.ZodString>;
|
|
3094
3138
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -3497,6 +3541,8 @@ export declare const ArtifactResultSchema: z.ZodObject<{
|
|
|
3497
3541
|
recording: z.ZodOptional<z.ZodString>;
|
|
3498
3542
|
available: z.ZodBoolean;
|
|
3499
3543
|
reason: z.ZodOptional<z.ZodString>;
|
|
3544
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
3545
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
3500
3546
|
scope: z.ZodOptional<z.ZodObject<{
|
|
3501
3547
|
surface: z.ZodOptional<z.ZodString>;
|
|
3502
3548
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -3568,6 +3614,8 @@ export declare const ArtifactListSchema: z.ZodObject<{
|
|
|
3568
3614
|
recording: z.ZodOptional<z.ZodString>;
|
|
3569
3615
|
available: z.ZodBoolean;
|
|
3570
3616
|
reason: z.ZodOptional<z.ZodString>;
|
|
3617
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
3618
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
3571
3619
|
scope: z.ZodOptional<z.ZodObject<{
|
|
3572
3620
|
surface: z.ZodOptional<z.ZodString>;
|
|
3573
3621
|
deviceKind: z.ZodOptional<z.ZodString>;
|
|
@@ -3722,6 +3770,8 @@ export declare const RunDetailSchema: z.ZodObject<{
|
|
|
3722
3770
|
recording: z.ZodOptional<z.ZodString>;
|
|
3723
3771
|
available: z.ZodBoolean;
|
|
3724
3772
|
reason: z.ZodOptional<z.ZodString>;
|
|
3773
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
3774
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
3725
3775
|
scope: z.ZodOptional<z.ZodObject<{
|
|
3726
3776
|
surface: z.ZodOptional<z.ZodString>;
|
|
3727
3777
|
deviceKind: z.ZodOptional<z.ZodString>;
|
package/dist/schema/src/api.js
CHANGED
|
@@ -72,6 +72,8 @@ export const VideoRefSchema = z.looseObject({
|
|
|
72
72
|
recording: z.string().optional().describe('What identifies the bytes; references with the same value are one recording. Absent when it cannot be played'),
|
|
73
73
|
available: z.boolean().describe('False when the reference names no bytes: no bundle key, address or inline text, or a bundle key that the layer\'s own bundle does not hold'),
|
|
74
74
|
reason: z.string().optional().describe('Why it is unavailable, when the report can tell: `not-in-bundle` is a file the layer names but does not hold (stored, then gone). An open vocabulary: treat an unknown reason as plain unavailable'),
|
|
75
|
+
durationMs: z.number().nonnegative().optional().describe('How long the recording is, in milliseconds, as its producer declared it: a hint to show before the file is opened, never a latency. Absent when none was declared, and in documents of older servers'),
|
|
76
|
+
poster: z.string().optional().describe('The bundle key, in the same layer, of the image the producer named as this recording\'s poster: a hint to show before the file is opened. Absent when there is none or the layer\'s bundle does not hold it, and in documents of older servers'),
|
|
75
77
|
scope: EvidenceScopeSchema.optional().describe('What this recording shows as its producer declared it: its own `scope`, else its attempt\'s, check\'s, part\'s or layer\'s. Absent: the scope is unknown, never live, physical or audible')
|
|
76
78
|
});
|
|
77
79
|
/** Video references as counts: how many are referenced, how many distinct recordings can be played, how many cannot. */
|
|
@@ -2181,6 +2181,10 @@ export type VideoRef = {
|
|
|
2181
2181
|
readonly available: boolean;
|
|
2182
2182
|
/** Why it is unavailable, when the report can tell: `not-in-bundle` names a file the layer should hold (its bundle key) but does not, a recording that was acknowledged and is no longer there. Absent otherwise, and for an available video. An open vocabulary: treat an unknown reason as plain unavailable. */
|
|
2183
2183
|
readonly reason?: 'not-in-bundle';
|
|
2184
|
+
/** How long the recording is, in milliseconds, as its producer declared (`Attachment.durationMs`): a hint to show before the file is opened, never measured here and never a latency. Absent when none was declared, and in documents of older servers. */
|
|
2185
|
+
readonly durationMs?: number;
|
|
2186
|
+
/** The bundle key, in the same layer, of the image the producer named as this recording's poster (`Attachment.poster`): a hint to show before the file is opened. Only an image the layer's bundle holds; absent when there is none, and in documents of older servers. */
|
|
2187
|
+
readonly poster?: string;
|
|
2184
2188
|
/** What this recording shows, as declared for it: its own `scope`, else its attempt's, check's, part's or layer's. Absent when none declared one: its scope is unknown, not live, physical or audible. */
|
|
2185
2189
|
readonly scope?: EvidenceScope;
|
|
2186
2190
|
};
|
package/docs/rich-evidence.md
CHANGED
|
@@ -27,6 +27,7 @@ decided exactly as before ([the report contract](report-contract.md)). Evidence
|
|
|
27
27
|
| `attempts` | check | Every try of a retried check, with its own status, detail, steps and attachments. A check that passed after failing is flaky. |
|
|
28
28
|
| `tags`, `labels` | check, layer | Filters and groups (`smoke`, `flaky`, an owner, a browser). |
|
|
29
29
|
| `tags` | attachment | Marks one recording or screenshot, for example `demo`: shown as a `#demo` chip, and searchable. See [Tagging artifacts and demos](#tagging-artifacts-and-demos). |
|
|
30
|
+
| `poster` (the `id` of an image in the same list) and `durationMs` | attachment (video) | Shown beside the recording in a watch playlist before anything is opened: the report carries them as the optional `poster` (a bundle key) and `durationMs` of each video reference, so no evidence is read per entry. Neither is ever a latency. |
|
|
30
31
|
| `extensions['atlas.media']` | attachment (video) | Where the check's steps are in the recording, so a click on a step seeks the video. See [Place steps in a recording](#place-steps-in-a-recording). |
|
|
31
32
|
| `scope` | attachment, attempt, check, part, layer | What a recording does and does not show: where it ran, whether data and dependencies were real, whether audio was captured. See [Evidence scope](#evidence-scope). |
|
|
32
33
|
| `annotations`, `links` | check, step, attempt, layer | Notes, warnings and issue links next to the result. |
|
package/docs/ui.md
CHANGED
|
@@ -104,6 +104,37 @@ when Watch is on screen.
|
|
|
104
104
|
- The spec index searches titles, paths and declared domains, and can group by the domain from spec metadata (specs with
|
|
105
105
|
none are listed under "No domain declared"; the Tests view reports domain problems).
|
|
106
106
|
|
|
107
|
+
## Behaviors across the project
|
|
108
|
+
|
|
109
|
+
`/behaviors` (`evidence/behaviors-page.tsx`, **Behaviors** in the spec-type menu) lists every behavior of every Gherkin feature in one
|
|
110
|
+
place, so the question "what does this product promise?" needs no knowledge of which file holds the answer. It adds no source of
|
|
111
|
+
truth: `evidence/behavior-index.ts` reads each feature through the same `featureBehaviors` the feature page uses, so a behavior has the
|
|
112
|
+
same coverage state, revisions and recording in both.
|
|
113
|
+
|
|
114
|
+
- The list is searched with the shared matcher (`find/`: words, quoted phrases, `status:`, `domain:`, `tag:`, `has:video`), grouped by
|
|
115
|
+
the domain spec metadata declares ("No domain declared" when it declares none), or ranked best match first when words are typed.
|
|
116
|
+
A coverage state is a filter with its own count; `demonstrated` is always named. A long list is bounded with *Show more*.
|
|
117
|
+
- Features whose text has not arrived are counted ("3 of 12 read"), and features that cannot be parsed are named, so an empty answer is
|
|
118
|
+
never taken for "no such behavior" while it is still incomplete.
|
|
119
|
+
- The search words (`q`), the coverage filter (`coverage`), the selection (`behavior`) and the expanded state (`workspace`) are in the
|
|
120
|
+
address. Selecting shows the Read / Watch / Inspect panel beside the list (below it on narrow screens), and *Read and watch* in the
|
|
121
|
+
selected-behavior bar is reachable before the list in keyboard order.
|
|
122
|
+
- **Read and watch workspace.** Expanding a behavior replaces the index with a page that uses the width: a two-column
|
|
123
|
+
`SimpleGrid` (one column below `md`) with *What should happen* (the steps, an outline's example filled in) beside *Recording* (the
|
|
124
|
+
player, status and provenance, promise and evidence revisions, applicability), the scoped summary below, and Inspect behind a
|
|
125
|
+
disclosure that reads the technical record only when opened. Keyboard focus moves to the behavior's heading on arrival, and
|
|
126
|
+
*Previous behavior* / *Next behavior* move through the same search. A recording is labelled illustrative.
|
|
127
|
+
- **Returning** from the workspace restores the search words, the filter and the selection from the address, and the scroll position,
|
|
128
|
+
the number of rows shown and the focused row from `evidence/behavior-return.ts`, which remembers them for the session against the
|
|
129
|
+
scope they were taken in and gives them back once, only to that scope. Storage that is unavailable means the list opens at its top.
|
|
130
|
+
- **Scoped summary** (`evidence/scoped-summary.ts`, `scoped-summary-view.tsx`, on Watch and in the workspace) answers three questions
|
|
131
|
+
apart, for the check in the environment of the recording shown (environments are not pooled; with none named it says they are):
|
|
132
|
+
the **outcome** of the shown result (`outcomeHeadline`), **reliability** (`reports/reliability`: always with the independent
|
|
133
|
+
executions it rests on, "Insufficient evidence" below the minimum, first-try success only from recorded tries, the library's
|
|
134
|
+
warnings) and **performance** (`reports/latency`: only measurements named `product-latency`, percentiles withheld with the reason,
|
|
135
|
+
budget and breaches). Absent evidence is stated as unknown, never as good; the test's duration is not product latency, and the
|
|
136
|
+
summary says it describes the checks Atlas holds, not production. The full reliability and latency detail is one link away.
|
|
137
|
+
|
|
107
138
|
## Catalog health
|
|
108
139
|
|
|
109
140
|
The **Catalog health** tab (`explorer/HealthView.tsx`, `?tab=health`; `@crvouga/atlas/reports/catalog-health`) explains why the Specs
|
|
@@ -209,7 +240,14 @@ over the same filtered set the page lists, in the same order (`explorer/playlist
|
|
|
209
240
|
demonstrated. A set longer than the page's limit says it is only the newest results.
|
|
210
241
|
- Only the selected clip is read: the list is the metadata the catalog already sent, the selected result's evidence is read
|
|
211
242
|
when it is selected, and the one `<video>` uses `preload="metadata"`. Opening the playlist downloads no recordings in bulk.
|
|
212
|
-
|
|
243
|
+
An unselected entry still shows a poster and a length when the catalog already carries them: the producer's declared
|
|
244
|
+
`Attachment.durationMs` and `Attachment.poster` (an image of the same list that the layer's bundle holds) travel as the
|
|
245
|
+
optional `durationMs` and `poster` of the result's video reference. The poster is an `<img loading="lazy">`, fetched when
|
|
246
|
+
its row nears the screen; an entry whose producer declared neither shows neither, and nothing is read to find out.
|
|
247
|
+
- The open playlist is in the address (`watch=true`, and `watchClip` for the entry playing), so a link restores it over
|
|
248
|
+
the same filters, run or collection. Opening it adds a history entry (Back closes it); moving between clips and closing
|
|
249
|
+
it replace the current one. A `watchClip` the set no longer holds opens the first watchable entry and the address then
|
|
250
|
+
names that one.
|
|
213
251
|
- One player: selecting another clip replaces it, and the position of the clip left is kept for the session so returning
|
|
214
252
|
resumes there. Previous/next and advancing on end move over the watchable entries; a result with nothing to play is
|
|
215
253
|
selected from the list to read why.
|
|
@@ -350,6 +388,7 @@ The narrowly scoped exceptions are:
|
|
|
350
388
|
| 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 |
|
|
351
389
|
| 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 |
|
|
352
390
|
| 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 |
|
|
391
|
+
| Behaviors | Project-wide behavior index with search, coverage filters and a selected behavior's Read / Watch / Inspect; an expanded read-and-watch workspace with a scoped outcome, reliability and performance summary that restores scope, scroll, selection and focus on return | Container, Grid, SimpleGrid, NavLink, TextInput, Button, Paper, Accordion, Tabs |
|
|
353
392
|
| Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
|
|
354
393
|
| Evidence | Steps, retries, annotations, facts, unknown extensions, safe downloads and previews | List, Badge, Accordion, Code, Image, native media |
|
|
355
394
|
| Recordings | Pictures/video/timeline, actor selection, moments, failure jumps, timing, model-state differences | Tabs, Slider, Button, ScrollArea, Table, Progress |
|
package/llms-full.txt
CHANGED
|
@@ -1016,6 +1016,7 @@ decided exactly as before ([the report contract](report-contract.md)). Evidence
|
|
|
1016
1016
|
| `attempts` | check | Every try of a retried check, with its own status, detail, steps and attachments. A check that passed after failing is flaky. |
|
|
1017
1017
|
| `tags`, `labels` | check, layer | Filters and groups (`smoke`, `flaky`, an owner, a browser). |
|
|
1018
1018
|
| `tags` | attachment | Marks one recording or screenshot, for example `demo`: shown as a `#demo` chip, and searchable. See [Tagging artifacts and demos](#tagging-artifacts-and-demos). |
|
|
1019
|
+
| `poster` (the `id` of an image in the same list) and `durationMs` | attachment (video) | Shown beside the recording in a watch playlist before anything is opened: the report carries them as the optional `poster` (a bundle key) and `durationMs` of each video reference, so no evidence is read per entry. Neither is ever a latency. |
|
|
1019
1020
|
| `extensions['atlas.media']` | attachment (video) | Where the check's steps are in the recording, so a click on a step seeks the video. See [Place steps in a recording](#place-steps-in-a-recording). |
|
|
1020
1021
|
| `scope` | attachment, attempt, check, part, layer | What a recording does and does not show: where it ran, whether data and dependencies were real, whether audio was captured. See [Evidence scope](#evidence-scope). |
|
|
1021
1022
|
| `annotations`, `links` | check, step, attempt, layer | Notes, warnings and issue links next to the result. |
|
|
@@ -4560,6 +4561,37 @@ when Watch is on screen.
|
|
|
4560
4561
|
- The spec index searches titles, paths and declared domains, and can group by the domain from spec metadata (specs with
|
|
4561
4562
|
none are listed under "No domain declared"; the Tests view reports domain problems).
|
|
4562
4563
|
|
|
4564
|
+
## Behaviors across the project
|
|
4565
|
+
|
|
4566
|
+
`/behaviors` (`evidence/behaviors-page.tsx`, **Behaviors** in the spec-type menu) lists every behavior of every Gherkin feature in one
|
|
4567
|
+
place, so the question "what does this product promise?" needs no knowledge of which file holds the answer. It adds no source of
|
|
4568
|
+
truth: `evidence/behavior-index.ts` reads each feature through the same `featureBehaviors` the feature page uses, so a behavior has the
|
|
4569
|
+
same coverage state, revisions and recording in both.
|
|
4570
|
+
|
|
4571
|
+
- The list is searched with the shared matcher (`find/`: words, quoted phrases, `status:`, `domain:`, `tag:`, `has:video`), grouped by
|
|
4572
|
+
the domain spec metadata declares ("No domain declared" when it declares none), or ranked best match first when words are typed.
|
|
4573
|
+
A coverage state is a filter with its own count; `demonstrated` is always named. A long list is bounded with *Show more*.
|
|
4574
|
+
- Features whose text has not arrived are counted ("3 of 12 read"), and features that cannot be parsed are named, so an empty answer is
|
|
4575
|
+
never taken for "no such behavior" while it is still incomplete.
|
|
4576
|
+
- The search words (`q`), the coverage filter (`coverage`), the selection (`behavior`) and the expanded state (`workspace`) are in the
|
|
4577
|
+
address. Selecting shows the Read / Watch / Inspect panel beside the list (below it on narrow screens), and *Read and watch* in the
|
|
4578
|
+
selected-behavior bar is reachable before the list in keyboard order.
|
|
4579
|
+
- **Read and watch workspace.** Expanding a behavior replaces the index with a page that uses the width: a two-column
|
|
4580
|
+
`SimpleGrid` (one column below `md`) with *What should happen* (the steps, an outline's example filled in) beside *Recording* (the
|
|
4581
|
+
player, status and provenance, promise and evidence revisions, applicability), the scoped summary below, and Inspect behind a
|
|
4582
|
+
disclosure that reads the technical record only when opened. Keyboard focus moves to the behavior's heading on arrival, and
|
|
4583
|
+
*Previous behavior* / *Next behavior* move through the same search. A recording is labelled illustrative.
|
|
4584
|
+
- **Returning** from the workspace restores the search words, the filter and the selection from the address, and the scroll position,
|
|
4585
|
+
the number of rows shown and the focused row from `evidence/behavior-return.ts`, which remembers them for the session against the
|
|
4586
|
+
scope they were taken in and gives them back once, only to that scope. Storage that is unavailable means the list opens at its top.
|
|
4587
|
+
- **Scoped summary** (`evidence/scoped-summary.ts`, `scoped-summary-view.tsx`, on Watch and in the workspace) answers three questions
|
|
4588
|
+
apart, for the check in the environment of the recording shown (environments are not pooled; with none named it says they are):
|
|
4589
|
+
the **outcome** of the shown result (`outcomeHeadline`), **reliability** (`reports/reliability`: always with the independent
|
|
4590
|
+
executions it rests on, "Insufficient evidence" below the minimum, first-try success only from recorded tries, the library's
|
|
4591
|
+
warnings) and **performance** (`reports/latency`: only measurements named `product-latency`, percentiles withheld with the reason,
|
|
4592
|
+
budget and breaches). Absent evidence is stated as unknown, never as good; the test's duration is not product latency, and the
|
|
4593
|
+
summary says it describes the checks Atlas holds, not production. The full reliability and latency detail is one link away.
|
|
4594
|
+
|
|
4563
4595
|
## Catalog health
|
|
4564
4596
|
|
|
4565
4597
|
The **Catalog health** tab (`explorer/HealthView.tsx`, `?tab=health`; `@crvouga/atlas/reports/catalog-health`) explains why the Specs
|
|
@@ -4665,7 +4697,14 @@ over the same filtered set the page lists, in the same order (`explorer/playlist
|
|
|
4665
4697
|
demonstrated. A set longer than the page's limit says it is only the newest results.
|
|
4666
4698
|
- Only the selected clip is read: the list is the metadata the catalog already sent, the selected result's evidence is read
|
|
4667
4699
|
when it is selected, and the one `<video>` uses `preload="metadata"`. Opening the playlist downloads no recordings in bulk.
|
|
4668
|
-
|
|
4700
|
+
An unselected entry still shows a poster and a length when the catalog already carries them: the producer's declared
|
|
4701
|
+
`Attachment.durationMs` and `Attachment.poster` (an image of the same list that the layer's bundle holds) travel as the
|
|
4702
|
+
optional `durationMs` and `poster` of the result's video reference. The poster is an `<img loading="lazy">`, fetched when
|
|
4703
|
+
its row nears the screen; an entry whose producer declared neither shows neither, and nothing is read to find out.
|
|
4704
|
+
- The open playlist is in the address (`watch=true`, and `watchClip` for the entry playing), so a link restores it over
|
|
4705
|
+
the same filters, run or collection. Opening it adds a history entry (Back closes it); moving between clips and closing
|
|
4706
|
+
it replace the current one. A `watchClip` the set no longer holds opens the first watchable entry and the address then
|
|
4707
|
+
names that one.
|
|
4669
4708
|
- One player: selecting another clip replaces it, and the position of the clip left is kept for the session so returning
|
|
4670
4709
|
resumes there. Previous/next and advancing on end move over the watchable entries; a result with nothing to play is
|
|
4671
4710
|
selected from the list to read why.
|
|
@@ -4806,6 +4845,7 @@ The narrowly scoped exceptions are:
|
|
|
4806
4845
|
| 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 |
|
|
4807
4846
|
| 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 |
|
|
4808
4847
|
| 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 |
|
|
4848
|
+
| Behaviors | Project-wide behavior index with search, coverage filters and a selected behavior's Read / Watch / Inspect; an expanded read-and-watch workspace with a scoped outcome, reliability and performance summary that restores scope, scroll, selection and focus on return | Container, Grid, SimpleGrid, NavLink, TextInput, Button, Paper, Accordion, Tabs |
|
|
4809
4849
|
| Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
|
|
4810
4850
|
| Evidence | Steps, retries, annotations, facts, unknown extensions, safe downloads and previews | List, Badge, Accordion, Code, Image, native media |
|
|
4811
4851
|
| Recordings | Pictures/video/timeline, actor selection, moments, failure jumps, timing, model-state differences | Tabs, Slider, Button, ScrollArea, Table, Progress |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/atlas",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Atlas: specs of any kind, reported as layered evidence. Publish checks from any runner (JUnit, CTRF, a script), combine them across nodes, branches, environments and backends, gate CI on the result, and run statecharts against the real app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -268,6 +268,15 @@
|
|
|
268
268
|
"description": "Why it is unavailable, when the report can tell: `not-in-bundle` is a file the layer names but does not hold (stored, then gone). An open vocabulary: treat an unknown reason as plain unavailable",
|
|
269
269
|
"type": "string"
|
|
270
270
|
},
|
|
271
|
+
"durationMs": {
|
|
272
|
+
"description": "How long the recording is, in milliseconds, as its producer declared it: a hint to show before the file is opened, never a latency. Absent when none was declared, and in documents of older servers",
|
|
273
|
+
"type": "number",
|
|
274
|
+
"minimum": 0
|
|
275
|
+
},
|
|
276
|
+
"poster": {
|
|
277
|
+
"description": "The bundle key, in the same layer, of the image the producer named as this recording's poster: a hint to show before the file is opened. Absent when there is none or the layer's bundle does not hold it, and in documents of older servers",
|
|
278
|
+
"type": "string"
|
|
279
|
+
},
|
|
271
280
|
"scope": {
|
|
272
281
|
"description": "What this recording shows as its producer declared it: its own `scope`, else its attempt's, check's, part's or layer's. Absent: the scope is unknown, never live, physical or audible",
|
|
273
282
|
"type": "object",
|
|
@@ -751,6 +760,15 @@
|
|
|
751
760
|
"description": "Why it is unavailable, when the report can tell: `not-in-bundle` is a file the layer names but does not hold (stored, then gone). An open vocabulary: treat an unknown reason as plain unavailable",
|
|
752
761
|
"type": "string"
|
|
753
762
|
},
|
|
763
|
+
"durationMs": {
|
|
764
|
+
"description": "How long the recording is, in milliseconds, as its producer declared it: a hint to show before the file is opened, never a latency. Absent when none was declared, and in documents of older servers",
|
|
765
|
+
"type": "number",
|
|
766
|
+
"minimum": 0
|
|
767
|
+
},
|
|
768
|
+
"poster": {
|
|
769
|
+
"description": "The bundle key, in the same layer, of the image the producer named as this recording's poster: a hint to show before the file is opened. Absent when there is none or the layer's bundle does not hold it, and in documents of older servers",
|
|
770
|
+
"type": "string"
|
|
771
|
+
},
|
|
754
772
|
"scope": {
|
|
755
773
|
"description": "What this recording shows as its producer declared it: its own `scope`, else its attempt's, check's, part's or layer's. Absent: the scope is unknown, never live, physical or audible",
|
|
756
774
|
"type": "object",
|
|
@@ -268,6 +268,15 @@
|
|
|
268
268
|
"description": "Why it is unavailable, when the report can tell: `not-in-bundle` is a file the layer names but does not hold (stored, then gone). An open vocabulary: treat an unknown reason as plain unavailable",
|
|
269
269
|
"type": "string"
|
|
270
270
|
},
|
|
271
|
+
"durationMs": {
|
|
272
|
+
"description": "How long the recording is, in milliseconds, as its producer declared it: a hint to show before the file is opened, never a latency. Absent when none was declared, and in documents of older servers",
|
|
273
|
+
"type": "number",
|
|
274
|
+
"minimum": 0
|
|
275
|
+
},
|
|
276
|
+
"poster": {
|
|
277
|
+
"description": "The bundle key, in the same layer, of the image the producer named as this recording's poster: a hint to show before the file is opened. Absent when there is none or the layer's bundle does not hold it, and in documents of older servers",
|
|
278
|
+
"type": "string"
|
|
279
|
+
},
|
|
271
280
|
"scope": {
|
|
272
281
|
"description": "What this recording shows as its producer declared it: its own `scope`, else its attempt's, check's, part's or layer's. Absent: the scope is unknown, never live, physical or audible",
|
|
273
282
|
"type": "object",
|
|
@@ -751,6 +760,15 @@
|
|
|
751
760
|
"description": "Why it is unavailable, when the report can tell: `not-in-bundle` is a file the layer names but does not hold (stored, then gone). An open vocabulary: treat an unknown reason as plain unavailable",
|
|
752
761
|
"type": "string"
|
|
753
762
|
},
|
|
763
|
+
"durationMs": {
|
|
764
|
+
"description": "How long the recording is, in milliseconds, as its producer declared it: a hint to show before the file is opened, never a latency. Absent when none was declared, and in documents of older servers",
|
|
765
|
+
"type": "number",
|
|
766
|
+
"minimum": 0
|
|
767
|
+
},
|
|
768
|
+
"poster": {
|
|
769
|
+
"description": "The bundle key, in the same layer, of the image the producer named as this recording's poster: a hint to show before the file is opened. Absent when there is none or the layer's bundle does not hold it, and in documents of older servers",
|
|
770
|
+
"type": "string"
|
|
771
|
+
},
|
|
754
772
|
"scope": {
|
|
755
773
|
"description": "What this recording shows as its producer declared it: its own `scope`, else its attempt's, check's, part's or layer's. Absent: the scope is unknown, never live, physical or audible",
|
|
756
774
|
"type": "object",
|
|
@@ -255,6 +255,15 @@
|
|
|
255
255
|
"description": "Why it is unavailable, when the report can tell: `not-in-bundle` is a file the layer names but does not hold (stored, then gone). An open vocabulary: treat an unknown reason as plain unavailable",
|
|
256
256
|
"type": "string"
|
|
257
257
|
},
|
|
258
|
+
"durationMs": {
|
|
259
|
+
"description": "How long the recording is, in milliseconds, as its producer declared it: a hint to show before the file is opened, never a latency. Absent when none was declared, and in documents of older servers",
|
|
260
|
+
"type": "number",
|
|
261
|
+
"minimum": 0
|
|
262
|
+
},
|
|
263
|
+
"poster": {
|
|
264
|
+
"description": "The bundle key, in the same layer, of the image the producer named as this recording's poster: a hint to show before the file is opened. Absent when there is none or the layer's bundle does not hold it, and in documents of older servers",
|
|
265
|
+
"type": "string"
|
|
266
|
+
},
|
|
258
267
|
"scope": {
|
|
259
268
|
"description": "What this recording shows as its producer declared it: its own `scope`, else its attempt's, check's, part's or layer's. Absent: the scope is unknown, never live, physical or audible",
|
|
260
269
|
"type": "object",
|
|
@@ -259,6 +259,15 @@
|
|
|
259
259
|
"description": "Why it is unavailable, when the report can tell: `not-in-bundle` is a file the layer names but does not hold (stored, then gone). An open vocabulary: treat an unknown reason as plain unavailable",
|
|
260
260
|
"type": "string"
|
|
261
261
|
},
|
|
262
|
+
"durationMs": {
|
|
263
|
+
"description": "How long the recording is, in milliseconds, as its producer declared it: a hint to show before the file is opened, never a latency. Absent when none was declared, and in documents of older servers",
|
|
264
|
+
"type": "number",
|
|
265
|
+
"minimum": 0
|
|
266
|
+
},
|
|
267
|
+
"poster": {
|
|
268
|
+
"description": "The bundle key, in the same layer, of the image the producer named as this recording's poster: a hint to show before the file is opened. Absent when there is none or the layer's bundle does not hold it, and in documents of older servers",
|
|
269
|
+
"type": "string"
|
|
270
|
+
},
|
|
262
271
|
"scope": {
|
|
263
272
|
"description": "What this recording shows as its producer declared it: its own `scope`, else its attempt's, check's, part's or layer's. Absent: the scope is unknown, never live, physical or audible",
|
|
264
273
|
"type": "object",
|
|
@@ -384,6 +384,15 @@
|
|
|
384
384
|
"description": "Why it is unavailable, when the report can tell: `not-in-bundle` is a file the layer names but does not hold (stored, then gone). An open vocabulary: treat an unknown reason as plain unavailable",
|
|
385
385
|
"type": "string"
|
|
386
386
|
},
|
|
387
|
+
"durationMs": {
|
|
388
|
+
"description": "How long the recording is, in milliseconds, as its producer declared it: a hint to show before the file is opened, never a latency. Absent when none was declared, and in documents of older servers",
|
|
389
|
+
"type": "number",
|
|
390
|
+
"minimum": 0
|
|
391
|
+
},
|
|
392
|
+
"poster": {
|
|
393
|
+
"description": "The bundle key, in the same layer, of the image the producer named as this recording's poster: a hint to show before the file is opened. Absent when there is none or the layer's bundle does not hold it, and in documents of older servers",
|
|
394
|
+
"type": "string"
|
|
395
|
+
},
|
|
387
396
|
"scope": {
|
|
388
397
|
"description": "What this recording shows as its producer declared it: its own `scope`, else its attempt's, check's, part's or layer's. Absent: the scope is unknown, never live, physical or audible",
|
|
389
398
|
"type": "object",
|
|
@@ -200,6 +200,15 @@
|
|
|
200
200
|
"description": "Why it is unavailable, when the report can tell: `not-in-bundle` is a file the layer names but does not hold (stored, then gone). An open vocabulary: treat an unknown reason as plain unavailable",
|
|
201
201
|
"type": "string"
|
|
202
202
|
},
|
|
203
|
+
"durationMs": {
|
|
204
|
+
"description": "How long the recording is, in milliseconds, as its producer declared it: a hint to show before the file is opened, never a latency. Absent when none was declared, and in documents of older servers",
|
|
205
|
+
"type": "number",
|
|
206
|
+
"minimum": 0
|
|
207
|
+
},
|
|
208
|
+
"poster": {
|
|
209
|
+
"description": "The bundle key, in the same layer, of the image the producer named as this recording's poster: a hint to show before the file is opened. Absent when there is none or the layer's bundle does not hold it, and in documents of older servers",
|
|
210
|
+
"type": "string"
|
|
211
|
+
},
|
|
203
212
|
"scope": {
|
|
204
213
|
"description": "What this recording shows as its producer declared it: its own `scope`, else its attempt's, check's, part's or layer's. Absent: the scope is unknown, never live, physical or audible",
|
|
205
214
|
"type": "object",
|