@crvouga/atlas 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/README.md +21 -2
- package/dist/reports/src/evidence-live.d.ts +6 -0
- package/dist/reports/src/evidence-live.js +35 -3
- package/dist/reports/src/evidence-types.d.ts +2 -0
- package/dist/reports/src/evidence-view.d.ts +15 -2
- package/dist/reports/src/evidence-view.js +31 -3
- package/dist/reports/src/export-evidence.js +2 -10
- package/dist/reports/src/frames.d.ts +3 -1
- package/dist/reports/src/frames.js +21 -0
- package/dist/reports/src/layer-axes.d.ts +18 -0
- package/dist/reports/src/layer-axes.js +51 -0
- package/dist/reports/src/layer-catalog.js +1 -1
- package/dist/reports/src/layer-merge.d.ts +1 -1
- package/dist/reports/src/layer-merge.js +9 -4
- package/dist/reports/src/layer-source.js +3 -0
- package/dist/reports/src/layer-types.d.ts +2 -0
- package/dist/reports/src/layers-route.js +2 -5
- package/dist/reports/src/protocol.d.ts +4 -0
- package/dist/reports/src/publish.d.ts +2 -0
- package/dist/reports/src/publish.js +6 -11
- package/dist/reports/src/recording-keys.js +4 -0
- package/dist/reports/src/report-parts.js +3 -0
- package/dist/reports/src/report-types.d.ts +19 -2
- package/dist/reports/src/source-entries.js +2 -12
- package/package.json +5 -4
- package/packages/reports/README.md +4 -2
- package/packages/reports/STORAGE.md +1 -1
- package/packages/visualizer/README.md +9 -6
- package/packages/visualizer/example/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/example/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/example/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/example/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/example/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/example/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/scripts/make-fixtures.ts +2 -0
- package/packages/visualizer/scripts/publish-data.ts +1 -2
- package/packages/visualizer/src/app/AppShell.tsx +11 -12
- package/packages/visualizer/src/app/gazetteer.module.css +12 -2
- package/packages/visualizer/src/app/gazetteer.tsx +38 -26
- package/packages/visualizer/src/components/Header.module.css +20 -4
- package/packages/visualizer/src/components/Header.tsx +7 -4
- package/packages/visualizer/src/data/model/build.ts +17 -5
- package/packages/visualizer/src/data/model/types.ts +10 -1
- package/packages/visualizer/src/evidence/api.ts +6 -1
- package/packages/visualizer/src/evidence/check-detail.tsx +35 -3
- package/packages/visualizer/src/evidence/evidence.module.css +20 -0
- package/packages/visualizer/src/evidence/marks.tsx +2 -0
- package/packages/visualizer/src/evidence/overview.tsx +3 -3
- package/packages/visualizer/src/evidence/recording-page.tsx +19 -2
- package/packages/visualizer/src/evidence/recording.module.css +4 -8
- package/packages/visualizer/src/evidence/recording.tsx +74 -83
- package/packages/visualizer/src/evidence/snapshot.tsx +5 -4
- package/packages/visualizer/src/evidence/sources-panel.tsx +88 -34
- package/packages/visualizer/src/evidence/spec-index.tsx +3 -11
- package/packages/visualizer/src/find/Finder.module.css +168 -0
- package/packages/visualizer/src/find/Finder.tsx +303 -0
- package/packages/visualizer/src/find/corpus.test.ts +77 -0
- package/packages/visualizer/src/find/corpus.ts +368 -0
- package/packages/visualizer/src/find/open.ts +51 -0
- package/packages/visualizer/src/find/rank.test.ts +237 -0
- package/packages/visualizer/src/find/rank.ts +405 -0
- package/packages/visualizer/src/find/semantic.test.ts +62 -0
- package/packages/visualizer/src/find/semantic.ts +93 -0
- package/packages/visualizer/src/find/semantic.worker.ts +40 -0
- package/packages/visualizer/src/looks/Stage.tsx +322 -0
- package/packages/visualizer/src/looks/collect.test.ts +158 -0
- package/packages/visualizer/src/looks/collect.ts +245 -0
- package/packages/visualizer/src/looks/stage.module.css +300 -0
- package/packages/visualizer/src/reachable.test.ts +1 -1
- package/packages/visualizer/src/state/ui-store.ts +6 -2
- package/packages/visualizer/src/views/SidePanel.tsx +39 -49
- package/packages/visualizer/vite/example-reports.ts +475 -0
- package/packages/visualizer/vite.config.ts +9 -1
- package/packages/visualizer/src/app/search-query.tsx +0 -14
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Feature: Order a drink
|
|
2
|
+
A customer picks a drink, pays, and picks it up.
|
|
3
|
+
|
|
4
|
+
Rule: Paying for the order
|
|
5
|
+
|
|
6
|
+
Scenario: Pays by card
|
|
7
|
+
Given a drink in the cart
|
|
8
|
+
When the customer pays by card
|
|
9
|
+
Then the barista can start the drink
|
|
10
|
+
|
|
11
|
+
Scenario: Pays with a wallet
|
|
12
|
+
Given a drink in the cart
|
|
13
|
+
When the customer pays with a wallet
|
|
14
|
+
Then the order is paid
|
|
15
|
+
|
|
16
|
+
Scenario: Card declined, then a wallet works
|
|
17
|
+
Given a drink in the cart
|
|
18
|
+
When the card is declined
|
|
19
|
+
And the customer pays with a wallet
|
|
20
|
+
Then the order is paid
|
|
21
|
+
|
|
22
|
+
Scenario: Slow payment
|
|
23
|
+
Given a drink in the cart
|
|
24
|
+
When the payment takes longer than a minute
|
|
25
|
+
Then the customer can check the payment again
|
|
26
|
+
|
|
27
|
+
Scenario: Leaves checkout and comes back
|
|
28
|
+
Given a drink in the cart
|
|
29
|
+
When the customer closes checkout
|
|
30
|
+
And the customer starts checkout again
|
|
31
|
+
Then checkout is open
|
|
32
|
+
|
|
33
|
+
Scenario: Drink left too long
|
|
34
|
+
Given a finished drink
|
|
35
|
+
When nobody picks it up
|
|
36
|
+
Then the drink is thrown away and refunded
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/">
|
|
3
|
+
<decision id="drink-price" name="Drink price">
|
|
4
|
+
<decisionTable id="Drink price" hitPolicy="UNIQUE">
|
|
5
|
+
<input label="Size">
|
|
6
|
+
<inputExpression typeRef="string"><text>size</text></inputExpression>
|
|
7
|
+
</input>
|
|
8
|
+
<input label="Milk">
|
|
9
|
+
<inputExpression typeRef="string"><text>milk</text></inputExpression>
|
|
10
|
+
</input>
|
|
11
|
+
<output label="Price" name="price" />
|
|
12
|
+
<rule>
|
|
13
|
+
<inputEntry><text>"Small"</text></inputEntry>
|
|
14
|
+
<inputEntry><text>"None"</text></inputEntry>
|
|
15
|
+
<outputEntry><text>3</text></outputEntry>
|
|
16
|
+
</rule>
|
|
17
|
+
<rule>
|
|
18
|
+
<inputEntry><text>"Large"</text></inputEntry>
|
|
19
|
+
<inputEntry><text>"Oat"</text></inputEntry>
|
|
20
|
+
<outputEntry><text>5</text></outputEntry>
|
|
21
|
+
</rule>
|
|
22
|
+
</decisionTable>
|
|
23
|
+
</decision>
|
|
24
|
+
</definitions>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Feature: Order a drink
|
|
2
|
+
A customer picks a drink, pays, and picks it up.
|
|
3
|
+
|
|
4
|
+
Rule: Paying for the order
|
|
5
|
+
|
|
6
|
+
Scenario: Pays by card
|
|
7
|
+
Given a drink in the cart
|
|
8
|
+
When the customer pays by card
|
|
9
|
+
Then the barista can start the drink
|
|
10
|
+
|
|
11
|
+
Scenario: Pays with a wallet
|
|
12
|
+
Given a drink in the cart
|
|
13
|
+
When the customer pays with a wallet
|
|
14
|
+
Then the order is paid
|
|
15
|
+
|
|
16
|
+
Scenario: Card declined, then a wallet works
|
|
17
|
+
Given a drink in the cart
|
|
18
|
+
When the card is declined
|
|
19
|
+
And the customer pays with a wallet
|
|
20
|
+
Then the order is paid
|
|
21
|
+
|
|
22
|
+
Scenario: Slow payment
|
|
23
|
+
Given a drink in the cart
|
|
24
|
+
When the payment takes longer than a minute
|
|
25
|
+
Then the customer can check the payment again
|
|
26
|
+
|
|
27
|
+
Scenario: Leaves checkout and comes back
|
|
28
|
+
Given a drink in the cart
|
|
29
|
+
When the customer closes checkout
|
|
30
|
+
And the customer starts checkout again
|
|
31
|
+
Then checkout is open
|
|
32
|
+
|
|
33
|
+
Scenario: Drink left too long
|
|
34
|
+
Given a finished drink
|
|
35
|
+
When nobody picks it up
|
|
36
|
+
Then the drink is thrown away and refunded
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/">
|
|
3
|
+
<decision id="drink-price" name="Drink price">
|
|
4
|
+
<decisionTable id="Drink price" hitPolicy="UNIQUE">
|
|
5
|
+
<input label="Size">
|
|
6
|
+
<inputExpression typeRef="string"><text>size</text></inputExpression>
|
|
7
|
+
</input>
|
|
8
|
+
<input label="Milk">
|
|
9
|
+
<inputExpression typeRef="string"><text>milk</text></inputExpression>
|
|
10
|
+
</input>
|
|
11
|
+
<output label="Price" name="price" />
|
|
12
|
+
<rule>
|
|
13
|
+
<inputEntry><text>"Small"</text></inputEntry>
|
|
14
|
+
<inputEntry><text>"None"</text></inputEntry>
|
|
15
|
+
<outputEntry><text>3</text></outputEntry>
|
|
16
|
+
</rule>
|
|
17
|
+
<rule>
|
|
18
|
+
<inputEntry><text>"Large"</text></inputEntry>
|
|
19
|
+
<inputEntry><text>"Oat"</text></inputEntry>
|
|
20
|
+
<outputEntry><text>5</text></outputEntry>
|
|
21
|
+
</rule>
|
|
22
|
+
</decisionTable>
|
|
23
|
+
</decision>
|
|
24
|
+
</definitions>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Feature: Order a drink
|
|
2
|
+
A customer picks a drink, pays, and picks it up.
|
|
3
|
+
|
|
4
|
+
Rule: Paying for the order
|
|
5
|
+
|
|
6
|
+
Scenario: Pays by card
|
|
7
|
+
Given a drink in the cart
|
|
8
|
+
When the customer pays by card
|
|
9
|
+
Then the barista can start the drink
|
|
10
|
+
|
|
11
|
+
Scenario: Pays with a wallet
|
|
12
|
+
Given a drink in the cart
|
|
13
|
+
When the customer pays with a wallet
|
|
14
|
+
Then the order is paid
|
|
15
|
+
|
|
16
|
+
Scenario: Card declined, then a wallet works
|
|
17
|
+
Given a drink in the cart
|
|
18
|
+
When the card is declined
|
|
19
|
+
And the customer pays with a wallet
|
|
20
|
+
Then the order is paid
|
|
21
|
+
|
|
22
|
+
Scenario: Slow payment
|
|
23
|
+
Given a drink in the cart
|
|
24
|
+
When the payment takes longer than a minute
|
|
25
|
+
Then the customer can check the payment again
|
|
26
|
+
|
|
27
|
+
Scenario: Leaves checkout and comes back
|
|
28
|
+
Given a drink in the cart
|
|
29
|
+
When the customer closes checkout
|
|
30
|
+
And the customer starts checkout again
|
|
31
|
+
Then checkout is open
|
|
32
|
+
|
|
33
|
+
Scenario: Drink left too long
|
|
34
|
+
Given a finished drink
|
|
35
|
+
When nobody picks it up
|
|
36
|
+
Then the drink is thrown away and refunded
|
|
@@ -30,6 +30,8 @@ import { APP_ROOT, FIXTURES_ROOT, listSpecFiles } from '../vite/atlas-files';
|
|
|
30
30
|
* Regenerates every fixture from the example spec in `example/specs` (a coffee-ordering app: an
|
|
31
31
|
* XState root chart with a parallel region that invokes an SCXML checkout chart) and a generated
|
|
32
32
|
* large atlas. All media is synthetic: ImageMagick draws the screens and ffmpeg the clips.
|
|
33
|
+
* Gherkin, Quint, DMN, Alloy and OpenSLO documents in that spec are copied with the charts.
|
|
34
|
+
* The visualizer dev server serves the layered report for them; this script does not rebuild it.
|
|
33
35
|
*/
|
|
34
36
|
|
|
35
37
|
const EXAMPLE_SPECS = path.join(APP_ROOT, 'example/specs');
|
|
@@ -5,8 +5,7 @@ import { publishData } from '../vite/publish';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* `pnpm publish-data`: the spec and the newest runs into `public/data` (or `ATLAS_OUT`), where
|
|
8
|
-
* `pnpm build` picks them up. Roots come from `ATLAS_SPECS_ROOT` / `ATLAS_RUNS_ROOT
|
|
9
|
-
* `ATLAS_FIXTURE`, as for the dev server.
|
|
8
|
+
* `pnpm build` picks them up. Roots come from `ATLAS_SPECS_ROOT` / `ATLAS_RUNS_ROOT`, as for the dev server.
|
|
10
9
|
*/
|
|
11
10
|
try {
|
|
12
11
|
const roots = resolveRoots();
|
|
@@ -6,9 +6,9 @@ import { SourcesPanel } from '../evidence/sources-panel';
|
|
|
6
6
|
import { SpecIndex } from '../evidence/spec-index';
|
|
7
7
|
import { kindOfAddress } from '../spec-kind';
|
|
8
8
|
import styles from '../views/views.module.css';
|
|
9
|
+
import { Finder } from '../find/Finder';
|
|
9
10
|
import { AtlasProvider, useAtlas } from './atlas-context';
|
|
10
11
|
import { Gazetteer } from './gazetteer';
|
|
11
|
-
import { SearchQueryProvider } from './search-query';
|
|
12
12
|
|
|
13
13
|
function Index() {
|
|
14
14
|
const { pathname } = useLocation();
|
|
@@ -38,18 +38,17 @@ export function AppShell() {
|
|
|
38
38
|
return (
|
|
39
39
|
<AtlasProvider run={run}>
|
|
40
40
|
<EvidenceProvider>
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
</main>
|
|
49
|
-
</div>
|
|
50
|
-
<SourcesPanel />
|
|
41
|
+
<div className={styles.app}>
|
|
42
|
+
<Header />
|
|
43
|
+
<div className={styles.frame}>
|
|
44
|
+
<Index />
|
|
45
|
+
<main className={styles.main}>
|
|
46
|
+
<Body />
|
|
47
|
+
</main>
|
|
51
48
|
</div>
|
|
52
|
-
|
|
49
|
+
<SourcesPanel />
|
|
50
|
+
<Finder />
|
|
51
|
+
</div>
|
|
53
52
|
</EvidenceProvider>
|
|
54
53
|
</AtlasProvider>
|
|
55
54
|
);
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
.row {
|
|
30
30
|
display: flex;
|
|
31
31
|
justify-content: space-between;
|
|
32
|
-
align-items:
|
|
33
|
-
gap:
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: 10px;
|
|
34
34
|
padding: 6px 10px;
|
|
35
35
|
border-radius: 8px;
|
|
36
36
|
color: inherit;
|
|
@@ -38,6 +38,16 @@
|
|
|
38
38
|
text-decoration: none;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
.thumb {
|
|
42
|
+
flex: none;
|
|
43
|
+
width: 28px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.name {
|
|
47
|
+
flex: 1;
|
|
48
|
+
min-width: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
.chart {
|
|
42
52
|
color: var(--ink-2);
|
|
43
53
|
font-weight: 600;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { Link, useLocation, useParams, useSearch } from '@tanstack/react-router';
|
|
2
2
|
|
|
3
|
+
import { ScreenImage } from '../components/Screen';
|
|
3
4
|
import { STATUS_LABEL, type AtlasView, type StateView } from '../data/model';
|
|
4
5
|
import { useAtlas } from './atlas-context';
|
|
5
|
-
import { useSearchQuery } from './search-query';
|
|
6
6
|
import styles from './gazetteer.module.css';
|
|
7
7
|
|
|
8
|
-
function
|
|
9
|
-
const
|
|
10
|
-
|
|
8
|
+
function openingScreen(view: AtlasView, steps: readonly { transitionIds: readonly string[] }[]) {
|
|
9
|
+
for (const step of steps) {
|
|
10
|
+
const transition = view.transitions.get(step.transitionIds[0] ?? '');
|
|
11
|
+
const landing = transition?.target ? view.states.get(transition.target) : undefined;
|
|
12
|
+
if (landing && (landing.kind === 'screen' || landing.kind === 'final')) return landing;
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
function screensOf(view: AtlasView, chartId: string) {
|
|
@@ -19,7 +23,6 @@ function screensOf(view: AtlasView, chartId: string) {
|
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export function Gazetteer() {
|
|
22
|
-
const { query } = useSearchQuery();
|
|
23
26
|
const { view } = useAtlas();
|
|
24
27
|
const params = useParams({ strict: false });
|
|
25
28
|
const search = useSearch({ strict: false });
|
|
@@ -27,7 +30,7 @@ export function Gazetteer() {
|
|
|
27
30
|
const screen = typeof search.screen === 'string' ? search.screen : undefined;
|
|
28
31
|
const journeyId = typeof search.journey === 'string' ? search.journey : undefined;
|
|
29
32
|
const group = typeof search.group === 'string' ? search.group : undefined;
|
|
30
|
-
const journeys = (view?.journeys ?? []).filter((journey) => journey.chartIds.length > 0
|
|
33
|
+
const journeys = (view?.journeys ?? []).filter((journey) => journey.chartIds.length > 0);
|
|
31
34
|
return (
|
|
32
35
|
<nav className={styles.index} aria-label="Places">
|
|
33
36
|
<h2>Places</h2>
|
|
@@ -38,9 +41,7 @@ export function Gazetteer() {
|
|
|
38
41
|
{context.chartIds.map((chartId) => {
|
|
39
42
|
const chart = view.charts.get(chartId);
|
|
40
43
|
if (!chart) return null;
|
|
41
|
-
const screens = screensOf(view, chartId)
|
|
42
|
-
const chartHit = hit(query, `${chart.name} ${context.name}`);
|
|
43
|
-
if (query.trim() && screens.length === 0 && !chartHit) return null;
|
|
44
|
+
const screens = screensOf(view, chartId);
|
|
44
45
|
return (
|
|
45
46
|
<div key={chartId}>
|
|
46
47
|
<Link className={styles.chart} to="/chart/$chartId" params={{ chartId }} search={(prev) => ({ run: prev.run, screen: undefined, journey: undefined, group: undefined })} activeOptions={{ includeSearch: true, explicitUndefined: true }} aria-current={params.chartId === chartId && !screen && !journeyId && !group ? 'page' : undefined}>
|
|
@@ -57,7 +58,10 @@ export function Gazetteer() {
|
|
|
57
58
|
search={(prev) => ({ run: prev.run, screen: state.name, journey: prev.journey })}
|
|
58
59
|
aria-current={params.chartId === chartId && screen === state.name ? 'page' : undefined}
|
|
59
60
|
>
|
|
60
|
-
<span
|
|
61
|
+
<span className={styles.thumb} aria-hidden="true">
|
|
62
|
+
<ScreenImage state={state} size="thumb" description={false} lines={false} caption={false} />
|
|
63
|
+
</span>
|
|
64
|
+
<span className={styles.name}>{state.name}</span>
|
|
61
65
|
<span className={styles.status}>{STATUS_LABEL[state.status]}</span>
|
|
62
66
|
</Link>
|
|
63
67
|
</li>
|
|
@@ -72,21 +76,29 @@ export function Gazetteer() {
|
|
|
72
76
|
<section>
|
|
73
77
|
<h3>Journeys</h3>
|
|
74
78
|
<ul>
|
|
75
|
-
{journeys.map((journey) =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
{journeys.map((journey) => {
|
|
80
|
+
const opening = view ? openingScreen(view, journey.steps) : undefined;
|
|
81
|
+
return (
|
|
82
|
+
<li key={journey.id}>
|
|
83
|
+
<Link
|
|
84
|
+
className={styles.row}
|
|
85
|
+
data-status={journey.status}
|
|
86
|
+
to="/chart/$chartId"
|
|
87
|
+
params={{ chartId: journey.chartIds[0] ?? '' }}
|
|
88
|
+
search={(prev) => ({ run: prev.run, journey: journey.id })}
|
|
89
|
+
aria-current={journeyId === journey.id && pathname.startsWith('/chart') ? 'page' : undefined}
|
|
90
|
+
>
|
|
91
|
+
{opening && (
|
|
92
|
+
<span className={styles.thumb} aria-hidden="true">
|
|
93
|
+
<ScreenImage state={opening} size="thumb" description={false} lines={false} caption={false} />
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
<span className={styles.name}>{journey.name}</span>
|
|
97
|
+
<span className={styles.status}>{STATUS_LABEL[journey.status]}</span>
|
|
98
|
+
</Link>
|
|
99
|
+
</li>
|
|
100
|
+
);
|
|
101
|
+
})}
|
|
90
102
|
</ul>
|
|
91
103
|
</section>
|
|
92
104
|
)}
|
|
@@ -94,7 +106,7 @@ export function Gazetteer() {
|
|
|
94
106
|
<section>
|
|
95
107
|
<h2>Not in the spec yet</h2>
|
|
96
108
|
<ul>
|
|
97
|
-
{view.excluded.
|
|
109
|
+
{view.excluded.map((item) => (
|
|
98
110
|
<li key={item} className={styles.excluded}>{item}</li>
|
|
99
111
|
))}
|
|
100
112
|
</ul>
|
|
@@ -137,23 +137,39 @@
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
.search {
|
|
140
|
+
display: inline-flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: 10px;
|
|
140
143
|
width: min(15rem, 28vw);
|
|
141
144
|
height: 2.125rem;
|
|
142
|
-
padding: 0 14px;
|
|
145
|
+
padding: 0 8px 0 14px;
|
|
143
146
|
border: 1px solid transparent;
|
|
144
147
|
border-radius: var(--pill);
|
|
145
148
|
background: var(--bg-2);
|
|
149
|
+
color: var(--ink-3);
|
|
146
150
|
font-size: 0.875rem;
|
|
151
|
+
font-weight: 500;
|
|
152
|
+
text-align: left;
|
|
153
|
+
cursor: pointer;
|
|
147
154
|
}
|
|
148
155
|
|
|
149
|
-
.search
|
|
156
|
+
.search kbd {
|
|
157
|
+
margin-left: auto;
|
|
158
|
+
padding: 0 0.35rem;
|
|
159
|
+
border: 1px solid var(--line-2);
|
|
160
|
+
border-radius: 0.35rem;
|
|
150
161
|
color: var(--ink-3);
|
|
162
|
+
font-family: var(--font);
|
|
163
|
+
font-size: 0.75rem;
|
|
164
|
+
font-weight: 600;
|
|
165
|
+
line-height: 1.35rem;
|
|
151
166
|
}
|
|
152
167
|
|
|
153
|
-
.search:
|
|
168
|
+
.search:hover,
|
|
169
|
+
.search:focus-visible {
|
|
154
170
|
border-color: var(--place);
|
|
155
171
|
background: var(--bg);
|
|
156
|
-
|
|
172
|
+
color: var(--ink);
|
|
157
173
|
}
|
|
158
174
|
|
|
159
175
|
@media (max-width: 760px) {
|
|
@@ -2,7 +2,8 @@ import { Link, useLocation, useNavigate, useSearch } from '@tanstack/react-route
|
|
|
2
2
|
import { useEffect, useMemo } from 'react';
|
|
3
3
|
|
|
4
4
|
import { useAtlas } from '../app/atlas-context';
|
|
5
|
-
import {
|
|
5
|
+
import { commandKey } from '../find/Finder';
|
|
6
|
+
import { setFinderOpen } from '../find/open';
|
|
6
7
|
import type { AtlasView } from '../data/model';
|
|
7
8
|
import { LATEST } from '../data/queries';
|
|
8
9
|
import { useEvidence } from '../evidence/evidence-context';
|
|
@@ -76,7 +77,7 @@ function SourcesButton() {
|
|
|
76
77
|
return (
|
|
77
78
|
<button type="button" className={styles.sources} data-filtered={off > 0 || undefined} onClick={() => setOpen(true)}>
|
|
78
79
|
Sources
|
|
79
|
-
<span>{unavailable ? 'unavailable' : off > 0 ? `${off} off` : plural(layers.length, '
|
|
80
|
+
<span>{unavailable ? 'unavailable' : off > 0 ? `${off} off` : plural(layers.length, 'layer', 'layers')}</span>
|
|
80
81
|
</button>
|
|
81
82
|
);
|
|
82
83
|
}
|
|
@@ -85,7 +86,6 @@ export function Header() {
|
|
|
85
86
|
const { view } = useAtlas();
|
|
86
87
|
const evidence = useEvidence();
|
|
87
88
|
const { pathname } = useLocation();
|
|
88
|
-
const { query, setQuery } = useSearchQuery();
|
|
89
89
|
const kind = kindOfAddress(pathname);
|
|
90
90
|
const product = productName(view?.title);
|
|
91
91
|
useEffect(() => {
|
|
@@ -120,7 +120,10 @@ export function Header() {
|
|
|
120
120
|
{!kind && view && <RunPicker view={view} />}
|
|
121
121
|
{!kind && view && <DataIssues issues={view.issues} />}
|
|
122
122
|
<SourcesButton />
|
|
123
|
-
<
|
|
123
|
+
<button type="button" className={styles.search} aria-keyshortcuts="Meta+K Control+K" onClick={() => setFinderOpen(true)}>
|
|
124
|
+
Find a flow
|
|
125
|
+
<kbd>{commandKey()}</kbd>
|
|
126
|
+
</button>
|
|
124
127
|
</div>
|
|
125
128
|
</header>
|
|
126
129
|
);
|
|
@@ -108,12 +108,24 @@ export function buildAtlasView(input: BuildInput): AtlasView {
|
|
|
108
108
|
|
|
109
109
|
const processing = input.runProgress === 'running' || run?.info.finishedAt === null;
|
|
110
110
|
const itemStatus = (status: ItemStatus | undefined) => processing && status === 'not-reached' ? 'not-yet-run' : status ?? unrun;
|
|
111
|
+
const shotOf = (img: { png: string; webp?: string | undefined; client?: string | undefined }) => {
|
|
112
|
+
const full = input.resolveMedia(run!.id, img.png) || null;
|
|
113
|
+
const thumb = img.webp ? input.resolveMedia(run!.id, img.webp) || full : full;
|
|
114
|
+
if (!full && !thumb) return null;
|
|
115
|
+
return { client: img.client ?? null, thumb, full };
|
|
116
|
+
};
|
|
111
117
|
const image = (img: Image | null | undefined, marked: string | undefined): ImageMedia => {
|
|
112
|
-
|
|
113
|
-
if (marked === 'processing'
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
118
|
+
const empty = (state: ImageMedia['state']): ImageMedia => ({ state, thumb: null, full: null, client: img?.client ?? null, also: [] });
|
|
119
|
+
if (!run || !img) return empty(marked === 'processing' || processing ? 'processing' : 'missing');
|
|
120
|
+
if (marked === 'processing') return empty('processing');
|
|
121
|
+
const main = shotOf(img);
|
|
122
|
+
const also = (img.also ?? []).flatMap((extra) => {
|
|
123
|
+
if (!extra.png) return [];
|
|
124
|
+
const shot = shotOf({ png: extra.png, webp: extra.webp, client: extra.client });
|
|
125
|
+
return shot ? [shot] : [];
|
|
126
|
+
});
|
|
127
|
+
if (!main) return { state: 'missing', thumb: null, full: null, client: img.client ?? null, also };
|
|
128
|
+
return { state: 'available', thumb: main.thumb, full: main.full, client: main.client, also };
|
|
117
129
|
};
|
|
118
130
|
const pathRef = (id: string): PathRef => ({ id, name: run?.paths.find((p) => p.id === id)?.name ?? id });
|
|
119
131
|
|
|
@@ -25,7 +25,16 @@ export type SpecChange = 'same' | 'added' | 'removed' | 'changed';
|
|
|
25
25
|
|
|
26
26
|
export type MediaState = 'available' | 'missing' | 'processing';
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
/** One client's still of a screen. `also` on ImageMedia holds the other clients at the same moment. */
|
|
29
|
+
export type ImageShot = { client: string | null; thumb: string | null; full: string | null };
|
|
30
|
+
|
|
31
|
+
export type ImageMedia = {
|
|
32
|
+
state: MediaState;
|
|
33
|
+
thumb: string | null;
|
|
34
|
+
full: string | null;
|
|
35
|
+
client: string | null;
|
|
36
|
+
also: ImageShot[];
|
|
37
|
+
};
|
|
29
38
|
|
|
30
39
|
export type VideoMedia = {
|
|
31
40
|
state: MediaState;
|
|
@@ -19,8 +19,13 @@ function explain(status: number) {
|
|
|
19
19
|
return `Evidence could not be read (${status}).`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** A captured file's address in the browser. Pictures and video use it directly; fetches use the same address. */
|
|
23
|
+
export function reportAssetUrl(path: string): string {
|
|
24
|
+
return staticEvidence ? `${reportsBase}/${evidenceFile(path).split('/').map(encodeURIComponent).join('/')}` : `${reportsBase}${path}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
export async function reportResponse(path: string, signal?: AbortSignal) {
|
|
23
|
-
const url =
|
|
28
|
+
const url = reportAssetUrl(path);
|
|
24
29
|
let response: Response;
|
|
25
30
|
try {
|
|
26
31
|
response = await fetch(url, { signal });
|