@crvouga/atlas 0.4.0 → 0.6.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 +13 -1
- package/README.md +191 -611
- package/dist/bun/src/driver.d.ts +41 -7
- package/dist/bun/src/driver.js +47 -6
- package/dist/bun/src/index.d.ts +8 -0
- package/dist/bun/src/index.js +8 -0
- package/dist/core/src/cli.d.ts +14 -1
- package/dist/core/src/cli.js +35 -1
- package/dist/core/src/config.d.ts +77 -4
- package/dist/core/src/config.js +110 -17
- package/dist/core/src/events/cloudevents.d.ts +54 -9
- package/dist/core/src/events/cloudevents.js +60 -9
- package/dist/core/src/graph.d.ts +21 -0
- package/dist/core/src/graph.js +26 -0
- package/dist/core/src/index.d.ts +11 -0
- package/dist/core/src/index.js +11 -0
- package/dist/core/src/lint.d.ts +30 -0
- package/dist/core/src/lint.js +13 -0
- package/dist/core/src/plan.d.ts +39 -1
- package/dist/core/src/plan.js +28 -1
- package/dist/core/src/privacy.d.ts +29 -2
- package/dist/core/src/privacy.js +19 -2
- package/dist/core/src/report/formats.d.ts +34 -4
- package/dist/core/src/report/formats.js +24 -4
- package/dist/core/src/report/markdown.d.ts +15 -0
- package/dist/core/src/report/markdown.js +8 -0
- package/dist/core/src/run/clients.d.ts +44 -5
- package/dist/core/src/run/clients.js +46 -2
- package/dist/core/src/run/execute.d.ts +75 -16
- package/dist/core/src/run/execute.js +71 -18
- package/dist/core/src/run/frames.d.ts +13 -4
- package/dist/core/src/run/frames.js +19 -4
- package/dist/core/src/run/http.d.ts +34 -1
- package/dist/core/src/run/http.js +14 -1
- package/dist/core/src/run/types.d.ts +76 -3
- package/dist/core/src/run/types.js +19 -3
- package/dist/core/src/signals.d.ts +49 -5
- package/dist/core/src/signals.js +22 -5
- package/dist/core/src/spec/compose.d.ts +23 -1
- package/dist/core/src/spec/compose.js +10 -1
- package/dist/core/src/spec/index.d.ts +9 -1
- package/dist/core/src/spec/index.js +9 -1
- package/dist/core/src/spec/load.d.ts +22 -1
- package/dist/core/src/spec/load.js +31 -1
- package/dist/core/src/spec/parse.d.ts +16 -2
- package/dist/core/src/spec/parse.js +25 -2
- package/dist/core/src/spec/scxml.d.ts +25 -4
- package/dist/core/src/spec/scxml.js +36 -4
- package/dist/core/src/spec/types.d.ts +52 -1
- package/dist/core/src/spec/types.js +24 -0
- package/dist/detox/src/index.d.ts +54 -11
- package/dist/detox/src/index.js +45 -7
- package/dist/playwright/src/driver.d.ts +39 -3
- package/dist/playwright/src/driver.js +27 -3
- package/dist/playwright/src/index.d.ts +10 -0
- package/dist/playwright/src/index.js +10 -0
- package/dist/playwright/src/media.d.ts +50 -4
- package/dist/playwright/src/media.js +55 -4
- package/dist/playwright/src/signals.d.ts +44 -5
- package/dist/playwright/src/signals.js +29 -5
- package/dist/reports/src/attachment-files.d.ts +44 -0
- package/dist/reports/src/attachment-files.js +121 -0
- package/dist/reports/src/attachment-response.d.ts +31 -0
- package/dist/reports/src/attachment-response.js +55 -0
- package/dist/reports/src/aws-credentials.d.ts +48 -0
- package/dist/reports/src/aws-credentials.js +221 -0
- package/dist/reports/src/browser-navigation.d.ts +14 -0
- package/dist/reports/src/browser-navigation.js +7 -0
- package/dist/reports/src/bundle.d.ts +34 -2
- package/dist/reports/src/bundle.js +38 -2
- package/dist/reports/src/catalog.d.ts +34 -3
- package/dist/reports/src/catalog.js +17 -3
- package/dist/reports/src/cli-stores.d.ts +36 -0
- package/dist/reports/src/cli-stores.js +73 -0
- package/dist/reports/src/clock.d.ts +7 -2
- package/dist/reports/src/clock.js +5 -2
- package/dist/reports/src/completion-order.d.ts +18 -5
- package/dist/reports/src/completion-order.js +18 -5
- package/dist/reports/src/compose.d.ts +80 -0
- package/dist/reports/src/compose.js +117 -0
- package/dist/reports/src/copy-store.d.ts +29 -0
- package/dist/reports/src/copy-store.js +52 -0
- package/dist/reports/src/directory-outline.d.ts +38 -0
- package/dist/reports/src/directory-outline.js +103 -0
- package/dist/reports/src/directory-store.d.ts +20 -0
- package/dist/reports/src/directory-store.js +166 -0
- package/dist/reports/src/doctor.d.ts +29 -0
- package/dist/reports/src/doctor.js +85 -0
- package/dist/reports/src/effective-delta.d.ts +34 -0
- package/dist/reports/src/effective-delta.js +25 -0
- package/dist/reports/src/evidence-detail.d.ts +64 -0
- package/dist/reports/src/evidence-detail.js +92 -0
- package/dist/reports/src/evidence-live.d.ts +35 -5
- package/dist/reports/src/evidence-live.js +18 -2
- package/dist/reports/src/evidence-types.d.ts +68 -1
- package/dist/reports/src/evidence-types.js +5 -1
- package/dist/reports/src/evidence-view.d.ts +43 -4
- package/dist/reports/src/evidence-view.js +13 -2
- package/dist/reports/src/explorer-types.d.ts +40 -3
- package/dist/reports/src/explorer-types.js +8 -0
- package/dist/reports/src/explorer-versions.d.ts +9 -1
- package/dist/reports/src/explorer-versions.js +18 -1
- package/dist/reports/src/export-evidence.d.ts +16 -1
- package/dist/reports/src/export-evidence.js +52 -1
- package/dist/reports/src/fetcher.d.ts +27 -3
- package/dist/reports/src/fetcher.js +11 -3
- package/dist/reports/src/format-date.d.ts +6 -0
- package/dist/reports/src/format-date.js +6 -0
- package/dist/reports/src/format.d.ts +16 -0
- package/dist/reports/src/format.js +10 -0
- package/dist/reports/src/frames.d.ts +36 -3
- package/dist/reports/src/frames.js +22 -3
- package/dist/reports/src/hash.d.ts +18 -0
- package/dist/reports/src/hash.js +13 -0
- package/dist/reports/src/host.d.ts +29 -0
- package/dist/reports/src/host.js +64 -0
- package/dist/reports/src/html-frame.d.ts +5 -6
- package/dist/reports/src/html-frame.js +12 -1
- package/dist/reports/src/http-store.d.ts +56 -0
- package/dist/reports/src/http-store.js +148 -0
- package/dist/reports/src/http.d.ts +7 -0
- package/dist/reports/src/http.js +6 -0
- package/dist/reports/src/ingest.d.ts +54 -0
- package/dist/reports/src/ingest.js +210 -0
- package/dist/reports/src/init.d.ts +40 -0
- package/dist/reports/src/init.js +369 -0
- package/dist/reports/src/junit.d.ts +33 -6
- package/dist/reports/src/junit.js +87 -21
- package/dist/reports/src/keyboard.d.ts +20 -0
- package/dist/reports/src/keyboard.js +11 -0
- package/dist/reports/src/keys.d.ts +21 -3
- package/dist/reports/src/keys.js +21 -3
- package/dist/reports/src/layer-axes.d.ts +35 -5
- package/dist/reports/src/layer-axes.js +25 -5
- package/dist/reports/src/layer-catalog.d.ts +28 -1
- package/dist/reports/src/layer-catalog.js +18 -1
- package/dist/reports/src/layer-merge.d.ts +98 -9
- package/dist/reports/src/layer-merge.js +147 -31
- package/dist/reports/src/layer-source.d.ts +18 -0
- package/dist/reports/src/layer-source.js +11 -0
- package/dist/reports/src/layer-store.d.ts +42 -0
- package/dist/reports/src/layer-store.js +63 -5
- package/dist/reports/src/layer-types.d.ts +32 -1
- package/dist/reports/src/layers-route.d.ts +47 -4
- package/dist/reports/src/layers-route.js +86 -7
- package/dist/reports/src/live-reporter.d.ts +34 -0
- package/dist/reports/src/live-reporter.js +122 -0
- package/dist/reports/src/media-types.d.ts +11 -0
- package/dist/reports/src/media-types.js +22 -0
- package/dist/reports/src/namespace-guard.d.ts +10 -1
- package/dist/reports/src/namespace-guard.js +10 -1
- package/dist/reports/src/navigation.d.ts +12 -0
- package/dist/reports/src/navigation.js +7 -0
- package/dist/reports/src/object-io.d.ts +11 -4
- package/dist/reports/src/object-io.js +4 -4
- package/dist/reports/src/object-store-conformance.d.ts +27 -0
- package/dist/reports/src/object-store-conformance.js +87 -0
- package/dist/reports/src/object-store.d.ts +33 -24
- package/dist/reports/src/object-store.js +17 -24
- package/dist/reports/src/parse-with.d.ts +16 -0
- package/dist/reports/src/parse-with.js +14 -0
- package/dist/reports/src/performance-part.d.ts +25 -1
- package/dist/reports/src/performance-part.js +15 -1
- package/dist/reports/src/performance.d.ts +22 -2
- package/dist/reports/src/performance.js +12 -2
- package/dist/reports/src/persist.d.ts +12 -3
- package/dist/reports/src/persist.js +12 -3
- package/dist/reports/src/phone.d.ts +18 -1
- package/dist/reports/src/phone.js +18 -1
- package/dist/reports/src/protocol.d.ts +26 -4
- package/dist/reports/src/protocol.js +15 -4
- package/dist/reports/src/publication.d.ts +21 -1
- package/dist/reports/src/publication.js +21 -1
- package/dist/reports/src/publish-checks.d.ts +35 -0
- package/dist/reports/src/publish-checks.js +122 -0
- package/dist/reports/src/publish.d.ts +42 -9
- package/dist/reports/src/publish.js +36 -7
- package/dist/reports/src/read.d.ts +46 -16
- package/dist/reports/src/read.js +38 -14
- package/dist/reports/src/recording-bundle.d.ts +13 -2
- package/dist/reports/src/recording-bundle.js +4 -2
- package/dist/reports/src/recording-keys.d.ts +15 -1
- package/dist/reports/src/recording-keys.js +15 -1
- package/dist/reports/src/remote-report.d.ts +28 -7
- package/dist/reports/src/remote-report.js +15 -7
- package/dist/reports/src/report-cli.d.ts +51 -0
- package/dist/reports/src/report-cli.js +486 -0
- package/dist/reports/src/report-index.d.ts +99 -0
- package/dist/reports/src/report-index.js +337 -0
- package/dist/reports/src/report-loader.d.ts +10 -0
- package/dist/reports/src/report-loader.js +6 -0
- package/dist/reports/src/report-parts.d.ts +29 -3
- package/dist/reports/src/report-parts.js +37 -3
- package/dist/reports/src/report-status.d.ts +48 -0
- package/dist/reports/src/report-status.js +156 -0
- package/dist/reports/src/report-types.d.ts +73 -0
- package/dist/reports/src/report-types.js +3 -0
- package/dist/reports/src/reporter.d.ts +67 -0
- package/dist/reports/src/reporter.js +128 -0
- package/dist/reports/src/reports-explorer-route.d.ts +541 -0
- package/dist/reports/src/reports-explorer-route.js +11 -0
- package/dist/reports/src/reports-list-route.d.ts +9 -0
- package/dist/reports/src/reports-list-route.js +9 -0
- package/dist/reports/src/reports-route.d.ts +561 -22
- package/dist/reports/src/reports-route.js +11 -3
- package/dist/reports/src/reports-store.d.ts +19 -2
- package/dist/reports/src/reports-store.js +10 -2
- package/dist/reports/src/resilient-cache.d.ts +14 -1
- package/dist/reports/src/resilient-cache.js +8 -1
- package/dist/reports/src/retention.d.ts +17 -1
- package/dist/reports/src/retention.js +5 -1
- package/dist/reports/src/run-bundle.d.ts +63 -0
- package/dist/reports/src/run-bundle.js +159 -0
- package/dist/reports/src/s3-store.d.ts +79 -0
- package/dist/reports/src/s3-store.js +295 -0
- package/dist/reports/src/scenario.d.ts +14 -4
- package/dist/reports/src/scenario.js +5 -4
- package/dist/reports/src/sigv4.d.ts +72 -0
- package/dist/reports/src/sigv4.js +96 -0
- package/dist/reports/src/snapshot-bundle.d.ts +24 -1
- package/dist/reports/src/snapshot-bundle.js +27 -2
- package/dist/reports/src/snapshot-surface.d.ts +20 -9
- package/dist/reports/src/snapshot-surface.js +32 -19
- package/dist/reports/src/source-content.d.ts +23 -0
- package/dist/reports/src/source-content.js +30 -1
- package/dist/reports/src/source-entries.d.ts +13 -0
- package/dist/reports/src/source-entries.js +6 -0
- package/dist/reports/src/source-part.d.ts +15 -0
- package/dist/reports/src/source-part.js +8 -0
- package/dist/reports/src/source-reader.d.ts +11 -0
- package/dist/reports/src/source-reader.js +5 -0
- package/dist/reports/src/spec-evidence.d.ts +12 -2
- package/dist/reports/src/spec-evidence.js +6 -2
- package/dist/reports/src/spec-inventory.d.ts +15 -1
- package/dist/reports/src/spec-inventory.js +22 -1
- package/dist/reports/src/spec-link.d.ts +28 -0
- package/dist/reports/src/spec-link.js +70 -0
- package/dist/reports/src/spec-set.d.ts +63 -0
- package/dist/reports/src/spec-set.js +177 -0
- package/dist/reports/src/status.d.ts +10 -0
- package/dist/reports/src/status.js +5 -0
- package/dist/reports/src/store-prefix.d.ts +28 -0
- package/dist/reports/src/store-prefix.js +22 -0
- package/dist/reports/src/store-url.d.ts +89 -0
- package/dist/reports/src/store-url.js +160 -0
- package/dist/reports/src/test-catalog-entity.d.ts +11 -0
- package/dist/reports/src/test-catalog-entity.js +6 -0
- package/dist/reports/src/types.d.ts +6 -1
- package/dist/reports/src/use-report-nav.d.ts +35 -0
- package/dist/reports/src/use-report-nav.js +12 -0
- package/dist/runners/src/codecept.d.ts +14 -0
- package/dist/runners/src/codecept.js +16 -0
- package/dist/runners/src/jest.d.ts +50 -0
- package/dist/runners/src/jest.js +52 -0
- package/dist/runners/src/mocha.d.ts +40 -0
- package/dist/runners/src/mocha.js +39 -0
- package/dist/runners/src/node-test.d.ts +28 -0
- package/dist/runners/src/node-test.js +44 -0
- package/dist/runners/src/publish.d.ts +30 -0
- package/dist/runners/src/publish.js +68 -0
- package/dist/schema/src/api.d.ts +800 -0
- package/dist/schema/src/api.js +201 -0
- package/dist/schema/src/cli-results.d.ts +210 -0
- package/dist/schema/src/cli-results.js +124 -0
- package/dist/schema/src/common.d.ts +53 -0
- package/dist/schema/src/common.js +42 -0
- package/dist/schema/src/index.d.ts +16 -0
- package/dist/schema/src/index.js +16 -0
- package/dist/schema/src/layer.d.ts +2273 -0
- package/dist/schema/src/layer.js +462 -0
- package/dist/schema/src/live-run.d.ts +123 -0
- package/dist/schema/src/live-run.js +30 -0
- package/dist/schema/src/manifest.d.ts +97 -2
- package/dist/schema/src/manifest.js +84 -0
- package/dist/schema/src/project.d.ts +20 -0
- package/dist/schema/src/project.js +10 -0
- package/dist/schema/src/report-sources.d.ts +29 -0
- package/dist/schema/src/report-sources.js +27 -1
- package/dist/schema/src/runs-index.d.ts +17 -1
- package/dist/schema/src/runs-index.js +15 -0
- package/dist/schema/src/server-config.d.ts +177 -0
- package/dist/schema/src/server-config.js +85 -0
- package/dist/schema/src/spec.d.ts +69 -0
- package/dist/schema/src/spec.js +58 -0
- package/dist/server/src/app.d.ts +72 -0
- package/dist/server/src/app.js +557 -0
- package/dist/server/src/auth.d.ts +41 -0
- package/dist/server/src/auth.js +59 -0
- package/dist/server/src/config.d.ts +83 -0
- package/dist/server/src/config.js +204 -0
- package/dist/server/src/dev.d.ts +12 -0
- package/dist/server/src/dev.js +89 -0
- package/dist/server/src/events.d.ts +29 -0
- package/dist/server/src/events.js +101 -0
- package/dist/server/src/host.d.ts +30 -0
- package/dist/server/src/host.js +115 -0
- package/dist/server/src/index.d.ts +18 -0
- package/dist/server/src/index.js +18 -0
- package/dist/server/src/live-runs.d.ts +52 -0
- package/dist/server/src/live-runs.js +229 -0
- package/dist/server/src/llms.d.ts +19 -0
- package/dist/server/src/llms.js +63 -0
- package/dist/server/src/main.d.ts +30 -0
- package/dist/server/src/main.js +114 -0
- package/dist/server/src/openapi.d.ts +56 -0
- package/dist/server/src/openapi.js +438 -0
- package/dist/server/src/problem.d.ts +8 -0
- package/dist/server/src/problem.js +39 -0
- package/dist/server/src/projects.d.ts +3 -0
- package/dist/server/src/projects.js +154 -0
- package/dist/server/src/settings-page.d.ts +9 -0
- package/dist/server/src/settings-page.js +93 -0
- package/dist/server/src/settings.d.ts +23 -0
- package/dist/server/src/settings.js +72 -0
- package/dist/server/src/site.d.ts +15 -0
- package/dist/server/src/site.js +78 -0
- package/dist/server/src/store-api.d.ts +28 -0
- package/dist/server/src/store-api.js +180 -0
- package/dist/server/src/validate.d.ts +71 -0
- package/dist/server/src/validate.js +197 -0
- package/dist/vitest/src/index.d.ts +27 -5
- package/dist/vitest/src/index.js +18 -5
- package/dist/webdriver/src/driver.d.ts +63 -11
- package/dist/webdriver/src/driver.js +57 -6
- package/dist/webdriver/src/index.d.ts +8 -0
- package/dist/webdriver/src/index.js +1 -0
- package/dist/webdriver/src/protocol.d.ts +75 -6
- package/dist/webdriver/src/protocol.js +79 -6
- package/docs/agents.md +137 -0
- package/docs/deploying.md +345 -0
- package/docs/dogfooding.md +175 -0
- package/docs/extending.md +242 -0
- package/docs/integrating.md +485 -0
- package/docs/reference/bun.md +141 -0
- package/docs/reference/cli.md +148 -0
- package/docs/reference/core.md +2432 -0
- package/docs/reference/detox.md +150 -0
- package/docs/reference/http-api.md +799 -0
- package/docs/reference/playwright.md +303 -0
- package/docs/reference/reports.md +7152 -0
- package/docs/reference/runners.md +169 -0
- package/docs/reference/schema.md +1762 -0
- package/docs/reference/server.md +825 -0
- package/docs/reference/vitest.md +45 -0
- package/docs/reference/webdriver.md +330 -0
- package/docs/report-backends.md +144 -0
- package/docs/report-contract.md +400 -0
- package/docs/reporting-issues.md +160 -0
- package/docs/runners.md +69 -0
- package/docs/server.md +321 -0
- package/docs/statecharts.md +643 -0
- package/llms-full.txt +4114 -0
- package/llms.txt +49 -0
- package/package.json +61 -11
- package/packages/core/README.md +2 -1
- package/packages/reports/README.md +65 -0
- package/packages/reports/STORAGE.md +4 -0
- package/packages/runners/README.md +6 -0
- package/packages/runners/compat/codecept.cjs +2 -0
- package/packages/runners/compat/jest.cjs +2 -0
- package/packages/runners/compat/mocha.cjs +2 -0
- package/packages/schema/README.md +14 -0
- package/packages/schema/schemas/atlas-change.schema.json +4 -0
- package/packages/schema/schemas/check-detail.schema.json +1032 -0
- package/packages/schema/schemas/checks-result.schema.json +26 -0
- package/packages/schema/schemas/command-error.schema.json +35 -0
- package/packages/schema/schemas/doctor-result.schema.json +60 -0
- package/packages/schema/schemas/effective-delta.schema.json +490 -0
- package/packages/schema/schemas/effective-evidence.schema.json +471 -0
- package/packages/schema/schemas/evidence-page.schema.json +271 -0
- package/packages/schema/schemas/hello-event.schema.json +26 -0
- package/packages/schema/schemas/init-plan.schema.json +347 -0
- package/packages/schema/schemas/layer-rows.schema.json +199 -0
- package/packages/schema/schemas/layer.schema.json +2888 -0
- package/packages/schema/schemas/live-run-start.schema.json +42 -0
- package/packages/schema/schemas/live-run-update.schema.json +93 -0
- package/packages/schema/schemas/live-run.schema.json +225 -0
- package/packages/schema/schemas/merge-result.schema.json +40 -0
- package/packages/schema/schemas/outline.schema.json +62 -0
- package/packages/schema/schemas/problem.schema.json +37 -0
- package/packages/schema/schemas/project-slug.schema.json +8 -0
- package/packages/schema/schemas/project.schema.json +22 -0
- package/packages/schema/schemas/projects.schema.json +33 -0
- package/packages/schema/schemas/publish-checks.schema.json +1413 -0
- package/packages/schema/schemas/publish-result.schema.json +168 -0
- package/packages/schema/schemas/ready.schema.json +68 -0
- package/packages/schema/schemas/report-event.schema.json +100 -0
- package/packages/schema/schemas/report-status.schema.json +370 -0
- package/packages/schema/schemas/server-config.schema.json +208 -0
- package/packages/schema/schemas/server-editable-settings.schema.json +132 -0
- package/packages/schema/schemas/server-info.schema.json +114 -0
- package/packages/schema/schemas/server-settings-snapshot.schema.json +285 -0
- package/packages/schema/schemas/spec-set.schema.json +103 -0
- package/packages/schema/schemas/store-listing.schema.json +29 -0
- package/packages/schema/schemas/store-write-result.schema.json +28 -0
- package/packages/schema/schemas/stream-result.schema.json +59 -0
- package/packages/server/README.md +43 -0
- package/packages/server/openapi.json +11021 -0
- package/packages/visualizer/README.md +32 -11
- package/packages/visualizer/bin/atlas-client.mjs +176 -0
- package/packages/visualizer/bin/atlas-visualizer.mjs +2 -175
- package/packages/visualizer/src/app/AppShell.tsx +32 -16
- package/packages/visualizer/src/app/report-sources.tsx +7 -4
- package/packages/visualizer/src/app/router.tsx +3 -3
- package/packages/visualizer/src/components/ActivityIndicator.module.css +4 -0
- package/packages/visualizer/src/components/ActivityIndicator.tsx +6 -0
- package/packages/visualizer/src/components/Header.module.css +101 -192
- package/packages/visualizer/src/components/Header.tsx +88 -13
- package/packages/visualizer/src/components/ProjectPicker.tsx +64 -0
- package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
- package/packages/visualizer/src/data/adapters/index.ts +10 -1
- package/packages/visualizer/src/data/adapters/report-backend.test.ts +3 -2
- package/packages/visualizer/src/data/adapters/report-backend.ts +1 -1
- package/packages/visualizer/src/data/queries.ts +14 -7
- package/packages/visualizer/src/evidence/api.ts +16 -3
- package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
- package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
- package/packages/visualizer/src/evidence/check-detail.tsx +21 -4
- package/packages/visualizer/src/evidence/document-page.tsx +6 -2
- package/packages/visualizer/src/evidence/evidence-context.tsx +79 -12
- package/packages/visualizer/src/evidence/evidence.module.css +11 -1
- package/packages/visualizer/src/evidence/feature-page.tsx +7 -3
- package/packages/visualizer/src/evidence/model-page.tsx +6 -2
- package/packages/visualizer/src/evidence/project-home.tsx +45 -0
- package/packages/visualizer/src/evidence/recording-page.tsx +4 -1
- package/packages/visualizer/src/evidence/spec-index.tsx +2 -1
- package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
- package/packages/visualizer/src/evidence/test-filter.tsx +21 -0
- package/packages/visualizer/src/find/Finder.tsx +1 -7
- package/packages/visualizer/src/find/open.ts +4 -0
- package/packages/visualizer/src/live/live-query.ts +21 -0
- package/packages/visualizer/src/live/live-runs.tsx +69 -0
- package/packages/visualizer/src/live/live-stream.ts +225 -0
- package/packages/visualizer/src/live/report-progress.ts +62 -0
- package/packages/visualizer/src/live/test-progress.tsx +34 -0
- package/packages/visualizer/src/runtime.ts +35 -0
- package/packages/visualizer/src/state/ui-store.ts +3 -2
- package/packages/visualizer/src/views/ChartPage.tsx +2 -0
- package/packages/visualizer/src/views/ChartTests.tsx +43 -0
- package/packages/visualizer/src/views/SidePanel.tsx +2 -0
- package/packages/visualizer/vite/atlas-files.test.ts +4 -4
- package/packages/visualizer/vite/atlas-files.ts +1 -5
- package/packages/visualizer/vite/atlas-server-proxy.ts +37 -0
- package/packages/visualizer/vite.config.ts +11 -3
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cacheable reads of a reports API
|
|
2
|
+
* @module Cacheable reads of a commit-keyed reports API
|
|
3
|
+
*
|
|
4
|
+
* The client side of the older, commit-addressed reports API (`/<sha>/explorer`, `/<sha>/spec`,
|
|
5
|
+
* `/<sha>/sources`, `/<sha>/tests/<source>/<testId>`, ...). Commit pages, sources, stylesheets and
|
|
3
6
|
* test timelines go through TanStack Query. Frame files also land in
|
|
4
7
|
* `ReportCache` (IndexedDB on the wall, a map in tests) so a scrub reads
|
|
5
8
|
* bytes the browser already kept, without putting multi-megabyte HTML into
|
|
6
|
-
* the query document.
|
|
9
|
+
* the query document. A 503 from the API means reports are not available and throws {@link ReportsUnavailable};
|
|
10
|
+
* a 404 for a spec, test or frame is answered as `null` (or an empty stylesheet), not an error.
|
|
7
11
|
*/
|
|
8
12
|
import type { ExplorerData } from './explorer-types.js';
|
|
9
13
|
import type { CommitPage, SourceRecord } from './protocol.js';
|
|
@@ -11,17 +15,24 @@ import type { HtmlReportBlob } from './recording-bundle.js';
|
|
|
11
15
|
import type { RunReport } from './report-types.js';
|
|
12
16
|
import type { HttpClient } from './http.js';
|
|
13
17
|
import { QueryClient } from '@tanstack/solid-query';
|
|
18
|
+
/** Thrown when the reports API answers 503: no reports store is configured or reachable. Its message is `reports_unavailable`. */
|
|
14
19
|
export declare class ReportsUnavailable extends Error {
|
|
15
20
|
constructor();
|
|
16
21
|
}
|
|
17
22
|
/** String store the query persister and frame files share. */
|
|
18
23
|
export type ReportCache = {
|
|
24
|
+
/** The saved value, or null when there is none. */
|
|
19
25
|
get(key: string): Promise<string | null>;
|
|
26
|
+
/** Saves a value, replacing any under the key. */
|
|
20
27
|
set(key: string, value: string): Promise<void>;
|
|
28
|
+
/** Forgets a key; a missing key is not an error. */
|
|
21
29
|
remove(key: string): Promise<void>;
|
|
22
30
|
};
|
|
31
|
+
/** A {@link ReportCache} held in a `Map`, lost on reload. For tests and hosts without persistent storage. */
|
|
23
32
|
export declare function memoryReportCache(): ReportCache;
|
|
33
|
+
/** Creates the TanStack `QueryClient` the fetcher expects: results stay fresh 5 minutes, are kept 1 day, and are neither retried nor refetched on window focus. */
|
|
24
34
|
export declare function reportQueryClient(): QueryClient;
|
|
35
|
+
/** The TanStack query keys of each read, so a host can invalidate or persist them. Every key starts with `reports`. */
|
|
25
36
|
export declare const reportQueryKey: {
|
|
26
37
|
commits: readonly ["reports", "commits"];
|
|
27
38
|
explorer: (sha: string) => readonly ["reports", "explorer", string];
|
|
@@ -31,20 +42,33 @@ export declare const reportQueryKey: {
|
|
|
31
42
|
test: (sha: string, source: string, testId: string) => readonly ["reports", "test", string, string, string];
|
|
32
43
|
blob: (sha: string, source: string, testId: string, file: string) => readonly ["reports", "blob", string, string, string, string];
|
|
33
44
|
};
|
|
45
|
+
/** The reads the explorer makes. Rejections are `ReportsUnavailable` (503) or an `Error` naming the HTTP status. */
|
|
34
46
|
export type ReportFetcher = {
|
|
47
|
+
/** The specs and parts recorded for a commit. Never served from a stale query (always refetched). */
|
|
35
48
|
explorer(sha: string): Promise<ExplorerData>;
|
|
49
|
+
/** The text of one spec of a commit, or null when it is not there. */
|
|
36
50
|
spec(sha: string, path: string): Promise<string | null>;
|
|
51
|
+
/** One page of commits; pass the previous page's `cursor`, or null for the first page. */
|
|
37
52
|
commits(cursor: string | null): Promise<CommitPage>;
|
|
53
|
+
/** The spec-run sources of a commit. */
|
|
38
54
|
sources(sha: string): Promise<readonly SourceRecord[]>;
|
|
55
|
+
/** The stylesheet snapshots of a commit render with; empty when there is none. */
|
|
39
56
|
frameCss(sha: string): Promise<string>;
|
|
57
|
+
/** One recorded test's report, or null when it is not there. */
|
|
40
58
|
test(sha: string, source: string, testId: string): Promise<RunReport | null>;
|
|
59
|
+
/** One captured file of a test, or null when it is missing (a missing file is retried on the next call). Resolved from the `ReportCache` before the network. */
|
|
41
60
|
blob(sha: string, source: string, testId: string, file: string): Promise<HtmlReportBlob | null>;
|
|
42
61
|
};
|
|
62
|
+
/** What {@link createHttpReportFetcher} needs. */
|
|
43
63
|
export type ReportFetcherArgs = {
|
|
64
|
+
/** The HTTP client used for every request. */
|
|
44
65
|
readonly http: HttpClient;
|
|
66
|
+
/** The URL prefix of the reports API, prepended to each path with no added slash (for example `/api/reports`). */
|
|
45
67
|
readonly apiBase: string;
|
|
68
|
+
/** The query cache; see {@link reportQueryClient}. */
|
|
46
69
|
readonly client: QueryClient;
|
|
70
|
+
/** Where captured frame files are kept between visits. */
|
|
47
71
|
readonly cache: ReportCache;
|
|
48
72
|
};
|
|
49
|
-
/**
|
|
73
|
+
/** Creates a {@link ReportFetcher} over HTTP with query-cached reads. Frame files resolve from `cache` before the network. */
|
|
50
74
|
export declare function createHttpReportFetcher(args: ReportFetcherArgs): ReportFetcher;
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cacheable reads of a reports API
|
|
2
|
+
* @module Cacheable reads of a commit-keyed reports API
|
|
3
|
+
*
|
|
4
|
+
* The client side of the older, commit-addressed reports API (`/<sha>/explorer`, `/<sha>/spec`,
|
|
5
|
+
* `/<sha>/sources`, `/<sha>/tests/<source>/<testId>`, ...). Commit pages, sources, stylesheets and
|
|
3
6
|
* test timelines go through TanStack Query. Frame files also land in
|
|
4
7
|
* `ReportCache` (IndexedDB on the wall, a map in tests) so a scrub reads
|
|
5
8
|
* bytes the browser already kept, without putting multi-megabyte HTML into
|
|
6
|
-
* the query document.
|
|
9
|
+
* the query document. A 503 from the API means reports are not available and throws {@link ReportsUnavailable};
|
|
10
|
+
* a 404 for a spec, test or frame is answered as `null` (or an empty stylesheet), not an error.
|
|
7
11
|
*/
|
|
8
12
|
import { QueryClient } from '@tanstack/solid-query';
|
|
9
13
|
const MINUTE = 60_000;
|
|
10
14
|
const DAY = 24 * 60 * MINUTE;
|
|
15
|
+
/** Thrown when the reports API answers 503: no reports store is configured or reachable. Its message is `reports_unavailable`. */
|
|
11
16
|
export class ReportsUnavailable extends Error {
|
|
12
17
|
constructor() {
|
|
13
18
|
super('reports_unavailable');
|
|
14
19
|
this.name = 'ReportsUnavailable';
|
|
15
20
|
}
|
|
16
21
|
}
|
|
22
|
+
/** A {@link ReportCache} held in a `Map`, lost on reload. For tests and hosts without persistent storage. */
|
|
17
23
|
export function memoryReportCache() {
|
|
18
24
|
const map = new Map();
|
|
19
25
|
return {
|
|
@@ -28,6 +34,7 @@ export function memoryReportCache() {
|
|
|
28
34
|
},
|
|
29
35
|
};
|
|
30
36
|
}
|
|
37
|
+
/** Creates the TanStack `QueryClient` the fetcher expects: results stay fresh 5 minutes, are kept 1 day, and are neither retried nor refetched on window focus. */
|
|
31
38
|
export function reportQueryClient() {
|
|
32
39
|
return new QueryClient({
|
|
33
40
|
defaultOptions: {
|
|
@@ -40,6 +47,7 @@ export function reportQueryClient() {
|
|
|
40
47
|
},
|
|
41
48
|
});
|
|
42
49
|
}
|
|
50
|
+
/** The TanStack query keys of each read, so a host can invalidate or persist them. Every key starts with `reports`. */
|
|
43
51
|
export const reportQueryKey = {
|
|
44
52
|
commits: ['reports', 'commits'],
|
|
45
53
|
explorer: (sha) => ['reports', 'explorer', sha],
|
|
@@ -142,7 +150,7 @@ function blobPath(sha, source, testId, file) {
|
|
|
142
150
|
.join('/');
|
|
143
151
|
return `/${sha}/tests/${encodeURIComponent(source)}/${encodeURIComponent(testId)}/files/${encoded}`;
|
|
144
152
|
}
|
|
145
|
-
/**
|
|
153
|
+
/** Creates a {@link ReportFetcher} over HTTP with query-cached reads. Frame files resolve from `cache` before the network. */
|
|
146
154
|
export function createHttpReportFetcher(args) {
|
|
147
155
|
const { http, apiBase, client } = args;
|
|
148
156
|
return {
|
|
@@ -1 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Date formatting
|
|
3
|
+
*
|
|
4
|
+
* Formats epoch-millisecond timestamps for display in the viewer's locale.
|
|
5
|
+
*/
|
|
6
|
+
/** Formats epoch milliseconds as a local date and time (`toLocaleString`); an empty string for 0 or missing, and the number as text if it cannot be formatted. */
|
|
1
7
|
export declare function fmtDate(ms: number): string;
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Date formatting
|
|
3
|
+
*
|
|
4
|
+
* Formats epoch-millisecond timestamps for display in the viewer's locale.
|
|
5
|
+
*/
|
|
6
|
+
/** Formats epoch milliseconds as a local date and time (`toLocaleString`); an empty string for 0 or missing, and the number as text if it cannot be formatted. */
|
|
1
7
|
export function fmtDate(ms) {
|
|
2
8
|
if (!ms)
|
|
3
9
|
return '';
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Display formatting for recorded tests
|
|
3
|
+
*
|
|
4
|
+
* Small pure formatters the viewer uses to show durations, file names, action lines and captured blobs. Nothing
|
|
5
|
+
* here reads a store or the network; missing or unreadable input becomes a placeholder, never an exception.
|
|
6
|
+
*/
|
|
1
7
|
import type { HtmlReportTest } from './recording-bundle.js';
|
|
2
8
|
import type { ReportSnapshotBlob } from './report-types.js';
|
|
9
|
+
/** Formats milliseconds compactly (`850ms`, `1.5s`, `2m 5s`); `—` for null, undefined or a non-finite number. */
|
|
3
10
|
export declare function fmtMs(ms: number | null | undefined): string;
|
|
11
|
+
/** The last `/`-separated segment of a path; empty for no path. */
|
|
4
12
|
export declare function fileName(path: string | undefined): string;
|
|
13
|
+
/** The group label for a test's source file: its base name, or `Other` when it has none. */
|
|
5
14
|
export declare function fileLabel(sourceFile: string | undefined): string;
|
|
6
15
|
/** Compact `arg, arg` for an action line. Empty when the step recorded none. */
|
|
7
16
|
export declare function formatArgs(args: readonly unknown[] | undefined): string;
|
|
@@ -11,6 +20,13 @@ export declare function actionLabel(step: {
|
|
|
11
20
|
readonly args?: readonly unknown[] | undefined;
|
|
12
21
|
readonly title: string;
|
|
13
22
|
}): string;
|
|
23
|
+
/**
|
|
24
|
+
* The text to show for a captured blob: binary blobs become a `(binary name, ~N bytes)` placeholder, JSON is
|
|
25
|
+
* pretty-printed (left as is if it does not parse), anything else is shown as stored, and `(missing)` when the
|
|
26
|
+
* test does not hold the blob's file.
|
|
27
|
+
*/
|
|
14
28
|
export declare function formatBlobBody(test: HtmlReportTest, blob: ReportSnapshotBlob): string;
|
|
29
|
+
/** The heading line of a blob: `name · nodeId · media`, without the node when it has none. */
|
|
15
30
|
export declare function blobHeading(blob: ReportSnapshotBlob): string;
|
|
31
|
+
/** A `data:` URL for a blob stored as base64 bytes, or null when the test holds no such bytes (text blobs have none). */
|
|
16
32
|
export declare function downloadUrl(test: HtmlReportTest, blob: ReportSnapshotBlob): string | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { blobDataUrl, blobText } from './frames.js';
|
|
2
|
+
/** Formats milliseconds compactly (`850ms`, `1.5s`, `2m 5s`); `—` for null, undefined or a non-finite number. */
|
|
2
3
|
export function fmtMs(ms) {
|
|
3
4
|
if (ms === null || ms === undefined || !Number.isFinite(ms))
|
|
4
5
|
return '—';
|
|
@@ -8,9 +9,11 @@ export function fmtMs(ms) {
|
|
|
8
9
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
9
10
|
return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;
|
|
10
11
|
}
|
|
12
|
+
/** The last `/`-separated segment of a path; empty for no path. */
|
|
11
13
|
export function fileName(path) {
|
|
12
14
|
return path ? (path.split('/').pop() ?? '') : '';
|
|
13
15
|
}
|
|
16
|
+
/** The group label for a test's source file: its base name, or `Other` when it has none. */
|
|
14
17
|
export function fileLabel(sourceFile) {
|
|
15
18
|
return sourceFile ? fileName(sourceFile) : 'Other';
|
|
16
19
|
}
|
|
@@ -44,6 +47,11 @@ export function actionLabel(step) {
|
|
|
44
47
|
return step.title;
|
|
45
48
|
return `${step.action}(${formatArgs(step.args)})`;
|
|
46
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The text to show for a captured blob: binary blobs become a `(binary name, ~N bytes)` placeholder, JSON is
|
|
52
|
+
* pretty-printed (left as is if it does not parse), anything else is shown as stored, and `(missing)` when the
|
|
53
|
+
* test does not hold the blob's file.
|
|
54
|
+
*/
|
|
47
55
|
export function formatBlobBody(test, blob) {
|
|
48
56
|
if (blob.media === 'bytes') {
|
|
49
57
|
const bin = test.blobs[blob.file];
|
|
@@ -63,9 +71,11 @@ export function formatBlobBody(test, blob) {
|
|
|
63
71
|
}
|
|
64
72
|
return text;
|
|
65
73
|
}
|
|
74
|
+
/** The heading line of a blob: `name · nodeId · media`, without the node when it has none. */
|
|
66
75
|
export function blobHeading(blob) {
|
|
67
76
|
return `${blob.name}${blob.nodeId ? ` · ${blob.nodeId}` : ''} · ${blob.media}`;
|
|
68
77
|
}
|
|
78
|
+
/** A `data:` URL for a blob stored as base64 bytes, or null when the test holds no such bytes (text blobs have none). */
|
|
69
79
|
export function downloadUrl(test, blob) {
|
|
70
80
|
return blobDataUrl(test, blob);
|
|
71
81
|
}
|
|
@@ -1,12 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Frames of a recorded test
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers over a recorded test's step tree: flattening it into time-ordered frames (captured snapshots),
|
|
5
|
+
* choosing the frame and actor a route selects, stepping to neighbouring frames or failures, and listing the files
|
|
6
|
+
* a report names. Results of `testFrames` and the per-actor orderings are cached per test, keyed by the identity of
|
|
7
|
+
* `test.report.steps`, so treat a loaded report as immutable. Nothing here reads a store.
|
|
8
|
+
*/
|
|
1
9
|
import type { HtmlReportTest } from './recording-bundle.js';
|
|
2
10
|
import type { ReportFrame, ReportSnapshotBlob, ReportStep, RunReport } from './report-types.js';
|
|
3
11
|
import type { Route } from './hash.js';
|
|
12
|
+
/** A frame placed in its step, in the flattened timeline. */
|
|
4
13
|
export type FlatFrame = {
|
|
5
14
|
step: ReportStep;
|
|
6
15
|
frame: ReportFrame;
|
|
16
|
+
/** How deeply the step is nested (0 for a top-level step). */
|
|
7
17
|
depth: number;
|
|
18
|
+
/** Position in the flattened list this frame came from; `testFrames` renumbers it by capture time. Route `frame=N` is `index + 1`. */
|
|
8
19
|
index: number;
|
|
9
20
|
};
|
|
21
|
+
/** A UI snapshot slot for one client (actor) of a frame. */
|
|
10
22
|
export type OrderedUiBlob = {
|
|
11
23
|
readonly nodeId: string;
|
|
12
24
|
/** This frame's snapshot, or the client's latest earlier one when stale. */
|
|
@@ -14,31 +26,52 @@ export type OrderedUiBlob = {
|
|
|
14
26
|
/** True when `blob` is carried forward from an earlier frame. */
|
|
15
27
|
readonly stale: boolean;
|
|
16
28
|
};
|
|
29
|
+
/** A step placed in the flattened (depth-first) step list. */
|
|
17
30
|
export type FlatStep = {
|
|
18
31
|
step: ReportStep;
|
|
19
32
|
depth: number;
|
|
33
|
+
/** The enclosing step, or null for a top-level step. */
|
|
20
34
|
parent: FlatStep | null;
|
|
21
35
|
index: number;
|
|
22
36
|
};
|
|
37
|
+
/** Flattens the frames of a step tree depth first (a step's own frames, then its children's), in step order, not capture-time order. Appends to `out`. */
|
|
23
38
|
export declare function flattenFrames(steps: readonly ReportStep[], out?: FlatFrame[], depth?: number): FlatFrame[];
|
|
39
|
+
/** Flattens a step tree depth first into rows that know their depth and parent. Appends to `out`. */
|
|
24
40
|
export declare function flattenSteps(steps: readonly ReportStep[], out?: FlatStep[], depth?: number, parent?: FlatStep | null): FlatStep[];
|
|
41
|
+
/** The first frame of `step`, or failing that of its first descendant that has one; null when none of them has a frame. */
|
|
25
42
|
export declare function firstFrameFor(step: ReportStep, frames: readonly FlatFrame[]): FlatFrame | null;
|
|
43
|
+
/** The last frame (in list order, which should be capture-time order) captured at or before the step ended; null when none was. */
|
|
26
44
|
export declare function lastFrameBefore(step: ReportStep, frames: readonly FlatFrame[]): FlatFrame | null;
|
|
45
|
+
/** All frames of a test in capture-time order (ties keep step order), each numbered by its position. Cached per report. */
|
|
27
46
|
export declare function testFrames(test: HtmlReportTest): FlatFrame[];
|
|
28
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Returns a frame's UI snapshots (html or png) in one stable actor order, including empty slots, so each client
|
|
49
|
+
* stays in the same column from frame to frame. The order is the report's `meta.actorIds`, then any other client in
|
|
50
|
+
* the order first seen. A client with no snapshot at this frame carries its latest earlier one (`stale: true`), or
|
|
51
|
+
* `null` blob if it has none yet. Snapshots with no `nodeId` follow, named `snapshot-N`.
|
|
52
|
+
*/
|
|
29
53
|
export declare function orderedUiBlobs(frame: FlatFrame | null, test: HtmlReportTest): OrderedUiBlob[];
|
|
54
|
+
/** The frame a route selects: the 1-based `frame` param when in range, otherwise the first frame of a failed step, otherwise the last frame; null with no frames. */
|
|
30
55
|
export declare function selectedFrame(frames: readonly FlatFrame[], route: Route): FlatFrame | null;
|
|
31
|
-
/** Every file a report names: step snapshots, and files attached to the test as a whole. */
|
|
56
|
+
/** Every distinct file a report names, relative to the test's folder: step snapshots, and files attached to the test as a whole (artifacts with their poster and captions). Used to know what to copy or prefetch. */
|
|
32
57
|
export declare function capturedFiles(report: Pick<RunReport, 'steps' | 'artifacts'>): string[];
|
|
58
|
+
/** Whether a snapshot is a rendered user interface (`html` or `png`), as opposed to data such as JSON or text. */
|
|
33
59
|
export declare function isUi(b: ReportSnapshotBlob): boolean;
|
|
60
|
+
/** The UI snapshots (html or png) of a frame; empty for no frame. */
|
|
34
61
|
export declare function uiBlobs(frame: FlatFrame | null): ReportSnapshotBlob[];
|
|
62
|
+
/** All snapshots of a frame of any media; empty for no frame. */
|
|
35
63
|
export declare function stageBlobs(frame: FlatFrame | null): ReportSnapshotBlob[];
|
|
64
|
+
/** Whether a frame has at least one UI snapshot. */
|
|
36
65
|
export declare function frameHasUi(frame: FlatFrame | null): boolean;
|
|
37
|
-
/**
|
|
66
|
+
/** The distinct html, png, json and text files of the frames within `radius` of `index` (the visible one plus neighbors, clamped to the list), to fetch ahead. */
|
|
38
67
|
export declare function frameFilesNear(frames: readonly FlatFrame[], index: number, radius: number): readonly string[];
|
|
68
|
+
/** The clients (actors) to show for a frame: those with a UI snapshot, else those with any snapshot, else the report's declared `meta.actorIds`. */
|
|
39
69
|
export declare function actorIds(frame: FlatFrame | null, test: HtmlReportTest): string[];
|
|
70
|
+
/** The client a route selects: the `node` param when it is one of the frame's actors, else the frame's step actor, else the first actor; null with none. */
|
|
40
71
|
export declare function selectedNode(frame: FlatFrame | null, route: Route, test: HtmlReportTest): string | null;
|
|
72
|
+
/** The text of a blob the test holds as text, or null when it is missing or held as bytes. */
|
|
41
73
|
export declare function blobText(test: HtmlReportTest, blob: ReportSnapshotBlob): string | null;
|
|
74
|
+
/** A `data:` URL for a blob the test holds as base64 bytes, or null when it is missing or held as text. */
|
|
42
75
|
export declare function blobDataUrl(test: HtmlReportTest, blob: ReportSnapshotBlob): string | null;
|
|
43
76
|
/** Whether any frame of `step` (not its children) shows a press target. */
|
|
44
77
|
export declare function stepHasPress(step: ReportStep): boolean;
|
|
@@ -20,6 +20,7 @@ function carriedUiBlobs(test) {
|
|
|
20
20
|
carryCache.set(test.report.steps, out);
|
|
21
21
|
return out;
|
|
22
22
|
}
|
|
23
|
+
/** Flattens the frames of a step tree depth first (a step's own frames, then its children's), in step order, not capture-time order. Appends to `out`. */
|
|
23
24
|
export function flattenFrames(steps, out = [], depth = 0) {
|
|
24
25
|
for (const step of steps) {
|
|
25
26
|
for (const frame of step.frames) {
|
|
@@ -29,6 +30,7 @@ export function flattenFrames(steps, out = [], depth = 0) {
|
|
|
29
30
|
}
|
|
30
31
|
return out;
|
|
31
32
|
}
|
|
33
|
+
/** Flattens a step tree depth first into rows that know their depth and parent. Appends to `out`. */
|
|
32
34
|
export function flattenSteps(steps, out = [], depth = 0, parent = null) {
|
|
33
35
|
for (const step of steps) {
|
|
34
36
|
const row = { step, depth, parent, index: out.length };
|
|
@@ -37,6 +39,7 @@ export function flattenSteps(steps, out = [], depth = 0, parent = null) {
|
|
|
37
39
|
}
|
|
38
40
|
return out;
|
|
39
41
|
}
|
|
42
|
+
/** The first frame of `step`, or failing that of its first descendant that has one; null when none of them has a frame. */
|
|
40
43
|
export function firstFrameFor(step, frames) {
|
|
41
44
|
for (const f of frames) {
|
|
42
45
|
if (f.step === step)
|
|
@@ -49,6 +52,7 @@ export function firstFrameFor(step, frames) {
|
|
|
49
52
|
}
|
|
50
53
|
return null;
|
|
51
54
|
}
|
|
55
|
+
/** The last frame (in list order, which should be capture-time order) captured at or before the step ended; null when none was. */
|
|
52
56
|
export function lastFrameBefore(step, frames) {
|
|
53
57
|
let best = null;
|
|
54
58
|
for (const f of frames) {
|
|
@@ -57,6 +61,7 @@ export function lastFrameBefore(step, frames) {
|
|
|
57
61
|
}
|
|
58
62
|
return best;
|
|
59
63
|
}
|
|
64
|
+
/** All frames of a test in capture-time order (ties keep step order), each numbered by its position. Cached per report. */
|
|
60
65
|
export function testFrames(test) {
|
|
61
66
|
const cached = frameCache.get(test.report.steps);
|
|
62
67
|
if (cached)
|
|
@@ -98,7 +103,12 @@ function allUiActorIds(test) {
|
|
|
98
103
|
actorOrderCache.set(test.report.steps, ordered);
|
|
99
104
|
return ordered;
|
|
100
105
|
}
|
|
101
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* Returns a frame's UI snapshots (html or png) in one stable actor order, including empty slots, so each client
|
|
108
|
+
* stays in the same column from frame to frame. The order is the report's `meta.actorIds`, then any other client in
|
|
109
|
+
* the order first seen. A client with no snapshot at this frame carries its latest earlier one (`stale: true`), or
|
|
110
|
+
* `null` blob if it has none yet. Snapshots with no `nodeId` follow, named `snapshot-N`.
|
|
111
|
+
*/
|
|
102
112
|
export function orderedUiBlobs(frame, test) {
|
|
103
113
|
const blobs = uiBlobs(frame);
|
|
104
114
|
const byNode = new Map();
|
|
@@ -126,6 +136,7 @@ export function orderedUiBlobs(frame, test) {
|
|
|
126
136
|
}
|
|
127
137
|
return ordered;
|
|
128
138
|
}
|
|
139
|
+
/** The frame a route selects: the 1-based `frame` param when in range, otherwise the first frame of a failed step, otherwise the last frame; null with no frames. */
|
|
129
140
|
export function selectedFrame(frames, route) {
|
|
130
141
|
const n = Number.parseInt(route.params['frame'] || '', 10);
|
|
131
142
|
if (Number.isFinite(n) && n >= 1 && n <= frames.length) {
|
|
@@ -137,7 +148,7 @@ export function selectedFrame(frames, route) {
|
|
|
137
148
|
}
|
|
138
149
|
return frames[frames.length - 1] ?? null;
|
|
139
150
|
}
|
|
140
|
-
/** Every file a report names: step snapshots, and files attached to the test as a whole. */
|
|
151
|
+
/** Every distinct file a report names, relative to the test's folder: step snapshots, and files attached to the test as a whole (artifacts with their poster and captions). Used to know what to copy or prefetch. */
|
|
141
152
|
export function capturedFiles(report) {
|
|
142
153
|
const files = new Set();
|
|
143
154
|
const visit = (steps) => {
|
|
@@ -158,15 +169,19 @@ export function capturedFiles(report) {
|
|
|
158
169
|
}
|
|
159
170
|
return [...files];
|
|
160
171
|
}
|
|
172
|
+
/** Whether a snapshot is a rendered user interface (`html` or `png`), as opposed to data such as JSON or text. */
|
|
161
173
|
export function isUi(b) {
|
|
162
174
|
return b.media === 'html' || b.media === 'png';
|
|
163
175
|
}
|
|
176
|
+
/** The UI snapshots (html or png) of a frame; empty for no frame. */
|
|
164
177
|
export function uiBlobs(frame) {
|
|
165
178
|
return frame ? frame.frame.snapshots.filter(isUi) : [];
|
|
166
179
|
}
|
|
180
|
+
/** All snapshots of a frame of any media; empty for no frame. */
|
|
167
181
|
export function stageBlobs(frame) {
|
|
168
182
|
return frame ? [...frame.frame.snapshots] : [];
|
|
169
183
|
}
|
|
184
|
+
/** Whether a frame has at least one UI snapshot. */
|
|
170
185
|
export function frameHasUi(frame) {
|
|
171
186
|
return uiBlobs(frame).length > 0;
|
|
172
187
|
}
|
|
@@ -176,7 +191,7 @@ const PREFETCH_MEDIA = new Set([
|
|
|
176
191
|
'json',
|
|
177
192
|
'text',
|
|
178
193
|
]);
|
|
179
|
-
/**
|
|
194
|
+
/** The distinct html, png, json and text files of the frames within `radius` of `index` (the visible one plus neighbors, clamped to the list), to fetch ahead. */
|
|
180
195
|
export function frameFilesNear(frames, index, radius) {
|
|
181
196
|
const files = [];
|
|
182
197
|
const seen = new Set();
|
|
@@ -192,6 +207,7 @@ export function frameFilesNear(frames, index, radius) {
|
|
|
192
207
|
}
|
|
193
208
|
return files;
|
|
194
209
|
}
|
|
210
|
+
/** The clients (actors) to show for a frame: those with a UI snapshot, else those with any snapshot, else the report's declared `meta.actorIds`. */
|
|
195
211
|
export function actorIds(frame, test) {
|
|
196
212
|
const ids = [];
|
|
197
213
|
const seen = {};
|
|
@@ -213,6 +229,7 @@ export function actorIds(frame, test) {
|
|
|
213
229
|
}
|
|
214
230
|
return ids;
|
|
215
231
|
}
|
|
232
|
+
/** The client a route selects: the `node` param when it is one of the frame's actors, else the frame's step actor, else the first actor; null with none. */
|
|
216
233
|
export function selectedNode(frame, route, test) {
|
|
217
234
|
const ids = actorIds(frame, test);
|
|
218
235
|
const param = route.params['node'];
|
|
@@ -223,10 +240,12 @@ export function selectedNode(frame, route, test) {
|
|
|
223
240
|
}
|
|
224
241
|
return ids[0] ?? null;
|
|
225
242
|
}
|
|
243
|
+
/** The text of a blob the test holds as text, or null when it is missing or held as bytes. */
|
|
226
244
|
export function blobText(test, blob) {
|
|
227
245
|
const b = test.blobs[blob.file];
|
|
228
246
|
return b && typeof b.text === 'string' ? b.text : null;
|
|
229
247
|
}
|
|
248
|
+
/** A `data:` URL for a blob the test holds as base64 bytes, or null when it is missing or held as text. */
|
|
230
249
|
export function blobDataUrl(test, blob) {
|
|
231
250
|
const b = test.blobs[blob.file];
|
|
232
251
|
if (!b?.base64)
|
|
@@ -1,17 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module URL hash routes of the reports viewer
|
|
3
|
+
*
|
|
4
|
+
* The explorer keeps its state in the URL hash (`#/test/<id>?view=single&zoom=fit`) so a link reproduces a view.
|
|
5
|
+
* This module parses a hash into a {@link Route} and builds it back; it is pure and never throws on a missing
|
|
6
|
+
* value, but `parseHash` throws `URIError` on a malformed percent-escape. Unknown params are carried untouched.
|
|
7
|
+
*/
|
|
8
|
+
/** The query part of a hash: decoded keys and values. Empty values are dropped when a hash is built. */
|
|
1
9
|
export type RouteParams = Readonly<Record<string, string>>;
|
|
10
|
+
/** The list of reports or tests (`#/`). */
|
|
2
11
|
export type ListRoute = {
|
|
3
12
|
readonly view: 'list';
|
|
4
13
|
readonly params: RouteParams;
|
|
5
14
|
};
|
|
15
|
+
/** One recorded test (`#/test/<id>`). */
|
|
6
16
|
export type TestRoute = {
|
|
7
17
|
readonly view: 'test';
|
|
18
|
+
/** The test's id, decoded; it may contain slashes (`libs/chess/monkey`). */
|
|
8
19
|
readonly testId: string;
|
|
9
20
|
readonly params: RouteParams;
|
|
10
21
|
};
|
|
22
|
+
/** Where the viewer is: the list, or one test. Anything that is not `#/test/<id>` is the list. */
|
|
11
23
|
export type Route = ListRoute | TestRoute;
|
|
24
|
+
/** Parses a `location.hash` (with or without the leading `#` and `/`) into a {@link Route}; an unrecognized path is the list. */
|
|
12
25
|
export declare function parseHash(hash: string): Route;
|
|
26
|
+
/** Builds the hash for a route (`#/` or `#/test/<encoded id>`) with its non-empty params encoded; the inverse of {@link parseHash}. */
|
|
13
27
|
export declare function buildHash(route: Route): string;
|
|
28
|
+
/** `single` when the `view` param says so, otherwise `multi`. */
|
|
14
29
|
export declare function viewMode(route: Route): 'single' | 'multi';
|
|
30
|
+
/** The `zoom` param, or `fit` when it is absent. */
|
|
15
31
|
export declare function zoomMode(route: Route): string;
|
|
32
|
+
/** Whether the side panel is shown: true unless `aside=0`. */
|
|
16
33
|
export declare function showAside(route: Route): boolean;
|
|
34
|
+
/** Whether press targets are drawn on frames: true unless `press=0`. */
|
|
17
35
|
export declare function showPress(route: Route): boolean;
|
package/dist/reports/src/hash.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module URL hash routes of the reports viewer
|
|
3
|
+
*
|
|
4
|
+
* The explorer keeps its state in the URL hash (`#/test/<id>?view=single&zoom=fit`) so a link reproduces a view.
|
|
5
|
+
* This module parses a hash into a {@link Route} and builds it back; it is pure and never throws on a missing
|
|
6
|
+
* value, but `parseHash` throws `URIError` on a malformed percent-escape. Unknown params are carried untouched.
|
|
7
|
+
*/
|
|
8
|
+
/** Parses a `location.hash` (with or without the leading `#` and `/`) into a {@link Route}; an unrecognized path is the list. */
|
|
1
9
|
export function parseHash(hash) {
|
|
2
10
|
const raw = hash.replace(/^#\/?/, '');
|
|
3
11
|
const q = raw.split('?');
|
|
@@ -18,6 +26,7 @@ export function parseHash(hash) {
|
|
|
18
26
|
}
|
|
19
27
|
return { view: 'list', params };
|
|
20
28
|
}
|
|
29
|
+
/** Builds the hash for a route (`#/` or `#/test/<encoded id>`) with its non-empty params encoded; the inverse of {@link parseHash}. */
|
|
21
30
|
export function buildHash(route) {
|
|
22
31
|
const qs = Object.keys(route.params)
|
|
23
32
|
.filter((k) => {
|
|
@@ -29,15 +38,19 @@ export function buildHash(route) {
|
|
|
29
38
|
const path = route.view === 'test' ? `/test/${encodeURIComponent(route.testId)}` : '/';
|
|
30
39
|
return `#${path}${qs ? `?${qs}` : ''}`;
|
|
31
40
|
}
|
|
41
|
+
/** `single` when the `view` param says so, otherwise `multi`. */
|
|
32
42
|
export function viewMode(route) {
|
|
33
43
|
return route.params['view'] === 'single' ? 'single' : 'multi';
|
|
34
44
|
}
|
|
45
|
+
/** The `zoom` param, or `fit` when it is absent. */
|
|
35
46
|
export function zoomMode(route) {
|
|
36
47
|
return route.params['zoom'] || 'fit';
|
|
37
48
|
}
|
|
49
|
+
/** Whether the side panel is shown: true unless `aside=0`. */
|
|
38
50
|
export function showAside(route) {
|
|
39
51
|
return route.params['aside'] !== '0';
|
|
40
52
|
}
|
|
53
|
+
/** Whether press targets are drawn on frames: true unless `press=0`. */
|
|
41
54
|
export function showPress(route) {
|
|
42
55
|
return route.params['press'] !== '0';
|
|
43
56
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BackendInput } from './compose.js';
|
|
2
|
+
/** Options of {@link reportsHost}. */
|
|
3
|
+
export type ReportsHostOptions = {
|
|
4
|
+
/** The specs directory, relative to the project. Its files are the outline every layer fills. */
|
|
5
|
+
readonly specs: string;
|
|
6
|
+
/**
|
|
7
|
+
* Where layers are: a directory, an `s3://` URL, or any backend. Several stores are one report: layers
|
|
8
|
+
* from all of them are laid over the same specs. Default: `ATLAS_STORE`.
|
|
9
|
+
*/
|
|
10
|
+
readonly stores?: readonly (string | BackendInput)[];
|
|
11
|
+
/** Groups the specs in the viewer. Default: the repository folder's name, or `product`. */
|
|
12
|
+
readonly domain?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The reports half of the viewer, for a project's own stores. Export it from the module that
|
|
16
|
+
* `ATLAS_REPORTS_MODULE` names:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { reportsHost } from '@crvouga/atlas/reports/host';
|
|
20
|
+
* export const reportsDevFetch = reportsHost({ specs: './specs', stores: ['./atlas-reports'] });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The returned function takes the incoming `Request` and the repository root (relative store paths and `specs`
|
|
24
|
+
* resolve against it); a path starting `/api/reports` has that prefix removed before routing. The app is built
|
|
25
|
+
* on first use and rebuilt if the root changes. A store given as a string is opened with `openStore` and gets an
|
|
26
|
+
* id derived from its location; a failure while answering (for example a wrong `specs` path) is a 500 whose JSON
|
|
27
|
+
* body names the `specs` directory and the store ids.
|
|
28
|
+
*/
|
|
29
|
+
export declare function reportsHost(options: ReportsHostOptions): (request: Request, repoRoot: string) => Promise<Response>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Reports host for a project's own stores
|
|
3
|
+
*
|
|
4
|
+
* Mounts the reports API (outline of a specs directory, layers from one or more stores) as a plain
|
|
5
|
+
* `(request, repoRoot) => Response` function, for a dev server or a project's own server to call at
|
|
6
|
+
* `/api/reports`. It owns opening the stores and lazily building the app once per repository root; the report
|
|
7
|
+
* logic itself is in `reports-route.ts` and `compose.ts`. Node/Bun only. It forwards only the method, URL and
|
|
8
|
+
* headers of a request (not the body), so it serves reads.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/dogfooding.md
|
|
11
|
+
*/
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { openStore, storeId, storeSpecs } from './store-url.js';
|
|
14
|
+
import { loadDirectoryOutline } from './directory-outline.js';
|
|
15
|
+
import { createReportsApp } from './reports-route.js';
|
|
16
|
+
const PREFIX = '/api/reports';
|
|
17
|
+
/**
|
|
18
|
+
* The reports half of the viewer, for a project's own stores. Export it from the module that
|
|
19
|
+
* `ATLAS_REPORTS_MODULE` names:
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { reportsHost } from '@crvouga/atlas/reports/host';
|
|
23
|
+
* export const reportsDevFetch = reportsHost({ specs: './specs', stores: ['./atlas-reports'] });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The returned function takes the incoming `Request` and the repository root (relative store paths and `specs`
|
|
27
|
+
* resolve against it); a path starting `/api/reports` has that prefix removed before routing. The app is built
|
|
28
|
+
* on first use and rebuilt if the root changes. A store given as a string is opened with `openStore` and gets an
|
|
29
|
+
* id derived from its location; a failure while answering (for example a wrong `specs` path) is a 500 whose JSON
|
|
30
|
+
* body names the `specs` directory and the store ids.
|
|
31
|
+
*/
|
|
32
|
+
export function reportsHost(options) {
|
|
33
|
+
let app;
|
|
34
|
+
let root = '';
|
|
35
|
+
return async (request, repoRoot) => {
|
|
36
|
+
if (!app || root !== repoRoot) {
|
|
37
|
+
root = repoRoot;
|
|
38
|
+
// Ids given by the caller are kept; the ones derived from the store step around them.
|
|
39
|
+
const given = options.stores ?? storeSpecs([], process.env);
|
|
40
|
+
const used = new Set(given.flatMap((store) => (typeof store === 'string' ? [] : [store.id])));
|
|
41
|
+
const backends = given.map((store) => {
|
|
42
|
+
if (typeof store !== 'string')
|
|
43
|
+
return store;
|
|
44
|
+
const opened = openStore(store, { base: repoRoot, env: process.env });
|
|
45
|
+
const id = storeId(opened.location, used);
|
|
46
|
+
used.add(id);
|
|
47
|
+
return { id, label: opened.location.kind === 'directory' ? path.basename(opened.location.directory) : opened.label, store: opened.store };
|
|
48
|
+
});
|
|
49
|
+
const specs = path.resolve(repoRoot, options.specs);
|
|
50
|
+
app = createReportsApp({
|
|
51
|
+
store: null,
|
|
52
|
+
backends: backends.map(backend => ({ ...backend, label: backend.label ?? backend.id })),
|
|
53
|
+
clock: { now: Date.now },
|
|
54
|
+
timers: { setTimeout: (fn, ms) => setTimeout(fn, ms), clearTimeout: handle => clearTimeout(handle), setInterval: (fn, ms) => setInterval(fn, ms), clearInterval: handle => clearInterval(handle) },
|
|
55
|
+
outline: () => loadDirectoryOutline(specs, { repoRoot, domain: options.domain ?? (path.basename(repoRoot) || 'product') })
|
|
56
|
+
});
|
|
57
|
+
// A wrong path says which one, instead of a bare "Internal Server Error".
|
|
58
|
+
app.onError((error, c) => c.json({ error: error instanceof Error ? error.message : String(error), specs, stores: backends.map(backend => backend.id) }, 500));
|
|
59
|
+
}
|
|
60
|
+
const url = new URL(request.url);
|
|
61
|
+
const pathname = url.pathname === PREFIX || url.pathname.startsWith(`${PREFIX}/`) ? url.pathname.slice(PREFIX.length) || '/' : url.pathname;
|
|
62
|
+
return app.request(new Request(`http://reports${pathname}${url.search}`, { method: request.method, headers: request.headers }));
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { FlatFrame } from './frames.js';
|
|
2
|
-
/**
|
|
3
|
-
* A self-contained HTML document for a frame that has no UI snapshot: a
|
|
4
|
-
* Quint step's model state, or any spec step that only recorded text.
|
|
5
|
-
* Painted in the same phone surface as a real snapshot.
|
|
6
|
-
*/
|
|
7
2
|
type Section = {
|
|
8
3
|
readonly heading: string;
|
|
9
4
|
readonly body: string;
|
|
10
5
|
};
|
|
11
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* The HTML document of one frame's model or recorded state: the step's kind and timings, its title, its error,
|
|
8
|
+
* its `detail` (shown as a name/value table when every line reads `name = value`, otherwise as preformatted text)
|
|
9
|
+
* and then one block per extra `sections` entry. Everything from the recording is escaped.
|
|
10
|
+
*/
|
|
12
11
|
export declare function stateFrameHtml(frame: FlatFrame, sections: readonly Section[]): string;
|
|
13
12
|
export {};
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module HTML cards for frames without a UI snapshot
|
|
3
|
+
*
|
|
4
|
+
* Builds a self-contained HTML document for a frame that has no UI snapshot: a Quint step's model state, or any
|
|
5
|
+
* spec step that only recorded text. It is painted in the same phone surface as a real snapshot. All text taken
|
|
6
|
+
* from the recording is HTML-escaped and the document carries only its own inline style, no script.
|
|
7
|
+
*/
|
|
1
8
|
import { fmtMs } from './format.js';
|
|
2
9
|
const CARD_CSS = [
|
|
3
10
|
'html,body{margin:0;background:#f4f1ea;color:#1c1915;',
|
|
@@ -53,7 +60,11 @@ function sectionBlocks(sections) {
|
|
|
53
60
|
.map((section) => `<h2>${escapeHtml(section.heading)}</h2><pre>${escapeHtml(section.body)}</pre>`)
|
|
54
61
|
.join('');
|
|
55
62
|
}
|
|
56
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* The HTML document of one frame's model or recorded state: the step's kind and timings, its title, its error,
|
|
65
|
+
* its `detail` (shown as a name/value table when every line reads `name = value`, otherwise as preformatted text)
|
|
66
|
+
* and then one block per extra `sections` entry. Everything from the recording is escaped.
|
|
67
|
+
*/
|
|
57
68
|
export function stateFrameHtml(frame, sections) {
|
|
58
69
|
const step = frame.step;
|
|
59
70
|
const error = step.error === undefined
|