@scenetest/dashboard 0.11.0 → 0.12.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/README.md +87 -35
- package/dist/app.d.ts +10 -7
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +12 -130
- package/dist/app.js.map +1 -1
- package/dist/collections/index.d.ts +19 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +18 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/collections/options.d.ts +29 -0
- package/dist/collections/options.d.ts.map +1 -0
- package/dist/collections/options.js +68 -0
- package/dist/collections/options.js.map +1 -0
- package/dist/collections/projections.d.ts +117 -0
- package/dist/collections/projections.d.ts.map +1 -0
- package/dist/collections/projections.js +228 -0
- package/dist/collections/projections.js.map +1 -0
- package/dist/collections/source.d.ts +32 -0
- package/dist/collections/source.d.ts.map +1 -0
- package/dist/collections/source.js +84 -0
- package/dist/collections/source.js.map +1 -0
- package/dist/collections/types.d.ts +87 -0
- package/dist/collections/types.d.ts.map +1 -0
- package/dist/collections/types.js +2 -0
- package/dist/collections/types.js.map +1 -0
- package/dist/dashboard.d.ts +20 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +114 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/dev-transport.d.ts +3 -3
- package/dist/dev-transport.d.ts.map +1 -1
- package/dist/dev-transport.js +3 -7
- package/dist/dev-transport.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts +8 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +249 -0
- package/dist/runner.js.map +1 -0
- package/dist/select-helpers.d.ts +50 -0
- package/dist/select-helpers.d.ts.map +1 -0
- package/dist/select-helpers.js +48 -0
- package/dist/select-helpers.js.map +1 -0
- package/dist/select-runner.d.ts +55 -0
- package/dist/select-runner.d.ts.map +1 -0
- package/dist/select-runner.js +108 -0
- package/dist/select-runner.js.map +1 -0
- package/dist/select-waterfall.d.ts +12 -0
- package/dist/select-waterfall.d.ts.map +1 -0
- package/dist/select-waterfall.js +79 -0
- package/dist/select-waterfall.js.map +1 -0
- package/dist/style.css +878 -0
- package/dist/types.d.ts +6 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/use-live-query.d.ts +28 -0
- package/dist/use-live-query.d.ts.map +1 -0
- package/dist/use-live-query.js +25 -0
- package/dist/use-live-query.js.map +1 -0
- package/dist/use-run-slice.d.ts +25 -0
- package/dist/use-run-slice.d.ts.map +1 -0
- package/dist/use-run-slice.js +48 -0
- package/dist/use-run-slice.js.map +1 -0
- package/package.json +10 -5
- package/dist/mount.d.ts +0 -14
- package/dist/mount.d.ts.map +0 -1
- package/dist/mount.js +0 -50
- package/dist/mount.js.map +0 -1
- package/dist/store.d.ts +0 -19
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -164
- package/dist/store.js.map +0 -1
- package/dist/styles.d.ts +0 -10
- package/dist/styles.d.ts.map +0 -1
- package/dist/styles.js +0 -152
- package/dist/styles.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,64 +1,116 @@
|
|
|
1
1
|
# @scenetest/dashboard
|
|
2
2
|
|
|
3
|
-
The scenetest dashboard as a
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
transport adapter, nothing else.
|
|
3
|
+
The scenetest dashboard as a **Preact component**. The same UI renders the live
|
|
4
|
+
run in dev (inside the Vite plugin's `/__scenetest` page) and in scenetest-cloud
|
|
5
|
+
(a Worker-served page) — the host supplies a transport adapter, nothing else.
|
|
7
6
|
|
|
8
|
-
```
|
|
9
|
-
import {
|
|
7
|
+
```tsx
|
|
8
|
+
import { render } from 'preact'
|
|
9
|
+
import { Dashboard, createDevTransport } from '@scenetest/dashboard'
|
|
10
|
+
import '@scenetest/dashboard/style.css'
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
transport: createDevTransport(),
|
|
13
|
-
})
|
|
14
|
-
// later: handle.unmount()
|
|
12
|
+
render(<Dashboard transport={createDevTransport()} />, document.getElementById('root'))
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
It's a plain light-DOM Preact component — no shadow root. Styles ship as a
|
|
16
|
+
stylesheet the host imports (`@scenetest/dashboard/style.css`), scoped under the
|
|
17
|
+
`.scenetest-dashboard` root so they don't leak. Both hosts are Preact, so they
|
|
18
|
+
render `<Dashboard>` directly; there's no imperative mount wrapper.
|
|
20
19
|
|
|
21
20
|
## Transport adapter
|
|
22
21
|
|
|
23
|
-
The only thing that differs between dev and cloud. The
|
|
24
|
-
|
|
25
|
-
actions back as protocol commands:
|
|
22
|
+
The only thing that differs between dev and cloud. The dashboard subscribes to
|
|
23
|
+
the run stream and pushes user actions back as protocol commands:
|
|
26
24
|
|
|
27
25
|
```ts
|
|
28
26
|
interface Transport {
|
|
29
|
-
fetchState(): Promise<RunEvent[]>
|
|
30
27
|
subscribe(onEvent: (e: RunEvent) => void, onStatus?: (s: ConnectionStatus) => void): () => void
|
|
31
28
|
sendCommand(command: Command): Promise<void>
|
|
32
29
|
}
|
|
33
30
|
```
|
|
34
31
|
|
|
35
32
|
Events and commands are the `@scenetest/protocol` vocabulary. `createDevTransport()`
|
|
36
|
-
speaks to the Vite middleware (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
speaks to the Vite middleware (SSE); a cloud adapter speaks to the worker
|
|
34
|
+
(WebSocket). History and live events both flow through `subscribe`: the
|
|
35
|
+
transport replays the run so far on connect (SSE replays its buffer; the cloud
|
|
36
|
+
WebSocket replays from a `sinceSeq`) and then streams live ones — the read model
|
|
37
|
+
folds both the same way, so there's no separate snapshot fetch.
|
|
41
38
|
|
|
42
39
|
## Theming
|
|
43
40
|
|
|
44
|
-
The
|
|
45
|
-
|
|
41
|
+
The only theming surface is a small set of CSS custom properties, passed as
|
|
42
|
+
`theme` and applied as inline `--st-*` variables on the `.scenetest-dashboard`
|
|
43
|
+
root:
|
|
46
44
|
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
transport
|
|
50
|
-
theme
|
|
51
|
-
|
|
45
|
+
```tsx
|
|
46
|
+
<Dashboard
|
|
47
|
+
transport={transport}
|
|
48
|
+
theme={{ bg: '#0b0d12', accent: '#7c93ff', font: 'IBM Plex Mono, monospace', fontSize: '12px' }}
|
|
49
|
+
/>
|
|
52
50
|
```
|
|
53
51
|
|
|
54
52
|
These map to `--st-bg`, `--st-accent`, `--st-font`, `--st-font-size`. Nothing
|
|
55
|
-
else is reachable; they are versioned with the
|
|
53
|
+
else is reachable; they are versioned with the component, like the wire protocol.
|
|
56
54
|
|
|
57
|
-
##
|
|
55
|
+
## Selectors
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
separately and is DOM-free — useful
|
|
57
|
+
`<Dashboard>` reads the run stream through the collections read model (below),
|
|
58
|
+
but the view-projection logic is exported separately and is DOM-free — useful
|
|
59
|
+
for tests, SSR, or computing a rollup:
|
|
61
60
|
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
61
|
+
- `selectWaterfall(slice)` / `selectSnapshot(slice)` — project a latest-run `RunSlice` into the Waterfall / Runner view shapes
|
|
62
|
+
- `mapReportToSnapshot(report)` — adapt a past-run JSON report into the Runner shape
|
|
63
|
+
- `completedSceneCount(state)` — count of finished scenes
|
|
64
64
|
- `sceneSummary(scene)` — the plain-text "copy failures" summary
|
|
65
|
+
|
|
66
|
+
## Collections (`@scenetest/dashboard/collections`)
|
|
67
|
+
|
|
68
|
+
A read-only [TanStack DB](https://tanstack.com/db) read model over the run
|
|
69
|
+
stream — **the store `<Dashboard>` itself reads from** (dev and cloud), with
|
|
70
|
+
**live queries** (filter / aggregate / sort, recomputed incrementally). It's
|
|
71
|
+
also a subpath export so a cloud consumer can build the same collections with
|
|
72
|
+
**its own** `@tanstack/db` instance (e.g. fed by a Durable Object's WebSocket).
|
|
73
|
+
|
|
74
|
+
`createRunSource(transport)` wraps the transport as one shared, fan-out stream;
|
|
75
|
+
`runCollectionOptions({ source, projection })` returns a `CollectionConfig` you
|
|
76
|
+
pass to **your own** `createCollection` — so the collection is built by the same
|
|
77
|
+
`@tanstack/db` instance your `useLiveQuery` uses. Several collections ride
|
|
78
|
+
**one** connection ("subscribe to the stream, attach the tables"), and each is
|
|
79
|
+
a server-owned replica: the projection is the sole writer, so client
|
|
80
|
+
`.insert()`/`.update()` throws.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { createCollection, count } from '@tanstack/db'
|
|
84
|
+
import { useLiveQuery } from '@tanstack/react-db'
|
|
85
|
+
import { createDevTransport } from '@scenetest/dashboard'
|
|
86
|
+
import { createRunSource, runCollectionOptions, scenesProjection, assertionsProjection } from '@scenetest/dashboard/collections'
|
|
87
|
+
|
|
88
|
+
const source = createRunSource(createDevTransport()) // one connection…
|
|
89
|
+
const scenes = createCollection(runCollectionOptions({ source, projection: scenesProjection() }))
|
|
90
|
+
const assertions = createCollection(runCollectionOptions({ source, projection: assertionsProjection() })) // …two tables
|
|
91
|
+
|
|
92
|
+
const { data } = useLiveQuery((q) =>
|
|
93
|
+
q.from({ s: scenes }).groupBy(({ s }) => s.status)
|
|
94
|
+
.select(({ s }) => ({ status: s.status, n: count(s.id) }))
|
|
95
|
+
)
|
|
96
|
+
// on teardown: source.close()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
A projection speaks a tiny `RowOp` vocabulary (`insert`/`update`/`delete`/`reset`),
|
|
100
|
+
so `scenesProjection()` / `assertionsProjection()` / `runsProjection()` are
|
|
101
|
+
testable without TanStack DB at all. `@tanstack/db` is a **runtime dependency**
|
|
102
|
+
of the package (the dashboard calls `createCollection` to build its store), but
|
|
103
|
+
this `./collections` subpath itself only `import type`s `CollectionConfig` — so
|
|
104
|
+
a cloud consumer can build the collections with its own `@tanstack/db` instance
|
|
105
|
+
(one instance, so its `useLiveQuery` can join across them).
|
|
106
|
+
|
|
107
|
+
**Multi-run.** Rows are partitioned by `runId` (the `run:start` timestamp), so
|
|
108
|
+
one collection holds a whole PR's history — a new `run:start` opens a new
|
|
109
|
+
partition rather than truncating. `runsProjection()` gives one row per run
|
|
110
|
+
(status, counts, duration), which is the run picker / "most recent" rollup /
|
|
111
|
+
flaky surface as plain live queries; the live timeline is `where runId = <latest>`.
|
|
112
|
+
See `docs/public/design/unified-console.md`.
|
|
113
|
+
|
|
114
|
+
History/ordering/de-duplication remain the transport's contract (SSE replay in
|
|
115
|
+
dev, WebSocket `sinceSeq` replay in cloud); the source just consumes `onEvent`
|
|
116
|
+
and resets its replay buffer on `run:start`.
|
package/dist/app.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Command } from '@scenetest/protocol';
|
|
2
|
+
import type { DashboardState, Scene } from './types.js';
|
|
2
3
|
/**
|
|
3
|
-
* The
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The Waterfall view — the live timeline of actors and inline assertions. It's
|
|
5
|
+
* a pure view: the Dashboard root computes `state` from the shared read model
|
|
6
|
+
* (`selectWaterfall` over the collections) and passes it in, along with `send`
|
|
7
|
+
* for header controls. Same component renders in dev and cloud.
|
|
6
8
|
*/
|
|
7
|
-
export declare function
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
export declare function Waterfall({ state, send }: {
|
|
10
|
+
state: DashboardState;
|
|
11
|
+
send: (c: Command) => void;
|
|
12
|
+
}): import("preact").JSX.Element;
|
|
10
13
|
/** Build the plain-text "copy failures" summary, matching the original dashboard. */
|
|
11
14
|
export declare function sceneSummary(scene: Scene): string;
|
|
12
15
|
//# sourceMappingURL=app.d.ts.map
|
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAEvD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,gCAkB/F;AAgJD,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CA0BjD"}
|
package/dist/app.js
CHANGED
|
@@ -1,50 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useEffect,
|
|
3
|
-
import
|
|
4
|
-
import { applyEvent, completedSceneCount, initialState, withConnection } from './store.js';
|
|
5
|
-
const html = htm.bind(h);
|
|
6
|
-
function reducer(state, action) {
|
|
7
|
-
return action.kind === 'event'
|
|
8
|
-
? applyEvent(state, action.event)
|
|
9
|
-
: withConnection(state, action.status);
|
|
10
|
-
}
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'preact/hooks';
|
|
3
|
+
import { completedSceneCount } from './select-waterfall.js';
|
|
11
4
|
/**
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
14
|
-
*
|
|
5
|
+
* The Waterfall view — the live timeline of actors and inline assertions. It's
|
|
6
|
+
* a pure view: the Dashboard root computes `state` from the shared read model
|
|
7
|
+
* (`selectWaterfall` over the collections) and passes it in, along with `send`
|
|
8
|
+
* for header controls. Same component renders in dev and cloud.
|
|
15
9
|
*/
|
|
16
|
-
export function
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
let alive = true;
|
|
20
|
-
transport.fetchState().then((events) => {
|
|
21
|
-
if (!alive)
|
|
22
|
-
return;
|
|
23
|
-
for (const event of events)
|
|
24
|
-
dispatch({ kind: 'event', event });
|
|
25
|
-
});
|
|
26
|
-
const unsubscribe = transport.subscribe((event) => dispatch({ kind: 'event', event }), (status) => dispatch({ kind: 'status', status }));
|
|
27
|
-
return () => {
|
|
28
|
-
alive = false;
|
|
29
|
-
unsubscribe();
|
|
30
|
-
};
|
|
31
|
-
}, [transport]);
|
|
32
|
-
const send = (command) => {
|
|
33
|
-
void transport.sendCommand(command);
|
|
34
|
-
};
|
|
35
|
-
return html `
|
|
36
|
-
<div class="root">
|
|
37
|
-
${Header({ state, send })}
|
|
38
|
-
<main>
|
|
39
|
-
${state.scenes.length === 0
|
|
40
|
-
? html `<div class="waiting">
|
|
41
|
-
<h2>Waiting for scene run…</h2>
|
|
42
|
-
<p>Run <code>scenetest</code> to see the live timeline here.</p>
|
|
43
|
-
</div>`
|
|
44
|
-
: state.scenes.map((scene, i) => SceneCard({ scene, index: i, send }))}
|
|
45
|
-
</main>
|
|
46
|
-
</div>
|
|
47
|
-
`;
|
|
10
|
+
export function Waterfall({ state, send }) {
|
|
11
|
+
return (_jsxs("div", { class: "root", children: [_jsx(Header, { state: state, send: send }), _jsx("main", { children: state.scenes.length === 0 ? (_jsxs("div", { class: "waiting", children: [_jsx("h2", { children: "Waiting for scene run\u2026" }), _jsxs("p", { children: ["Run ", _jsx("code", { children: "scenetest" }), " to see the live timeline here."] })] })) : (state.scenes.map((scene) => _jsx(SceneCard, { scene: scene, send: send }, scene.name))) })] }));
|
|
48
12
|
}
|
|
49
13
|
function Header({ state, send }) {
|
|
50
14
|
const [team, setTeam] = useState('');
|
|
@@ -70,40 +34,9 @@ function Header({ state, send }) {
|
|
|
70
34
|
? 'progress done'
|
|
71
35
|
: 'progress';
|
|
72
36
|
const replay = () => send({ type: 'run:replay', ...(team ? { team } : {}) });
|
|
73
|
-
return
|
|
74
|
-
<header class=${running ? 'running' : ''}>
|
|
75
|
-
<h1><span class="logo">S</span> Scenetest Dashboard</h1>
|
|
76
|
-
<button class="replay-all-btn" disabled=${running} onClick=${replay}>▶ Replay All</button>
|
|
77
|
-
<label class="team-select-wrap">
|
|
78
|
-
Team:
|
|
79
|
-
<select
|
|
80
|
-
value=${team}
|
|
81
|
-
onChange=${(e) => setTeam(e.target.value)}
|
|
82
|
-
>
|
|
83
|
-
<option value="">all teams</option>
|
|
84
|
-
${state.teams.map((t) => html `<option value=${t}>${t}</option>`)}
|
|
85
|
-
</select>
|
|
86
|
-
</label>
|
|
87
|
-
<button onClick=${() => send({ type: 'run:pause' })}>❚❚ Pause</button>
|
|
88
|
-
<button class="stop-btn" onClick=${() => send({ type: 'run:stop' })}>■ Stop</button>
|
|
89
|
-
<div class="spacer"></div>
|
|
90
|
-
<div class="stats">
|
|
91
|
-
<div class="stat"><span class="label">Scenes:</span><span class="value">${completed}/${state.sceneCount}</span></div>
|
|
92
|
-
<div class="stat pass"><span class="label">Pass:</span><span class="value">${state.passCount}</span></div>
|
|
93
|
-
<div class="stat fail"><span class="label">Fail:</span><span class="value">${state.failCount}</span></div>
|
|
94
|
-
<div class="stat"><span class="label">Time:</span><span class="value">${elapsed}</span></div>
|
|
95
|
-
<div
|
|
96
|
-
class=${'conn ' + state.connection}
|
|
97
|
-
title=${'SSE ' + state.connection}
|
|
98
|
-
></div>
|
|
99
|
-
</div>
|
|
100
|
-
${state.sceneCount > 0
|
|
101
|
-
? html `<div class=${progressClass}><div class="progress-fill" style=${`width:${pct}%`}></div></div>`
|
|
102
|
-
: null}
|
|
103
|
-
</header>
|
|
104
|
-
`;
|
|
37
|
+
return (_jsxs("header", { class: running ? 'running' : '', children: [_jsxs("h1", { children: [_jsx("span", { class: "logo", children: "S" }), " Scenetest Dashboard"] }), _jsx("button", { class: "replay-all-btn", disabled: running, onClick: replay, children: "\u25B6 Replay All" }), _jsxs("label", { class: "team-select-wrap", children: ["Team:", _jsxs("select", { value: team, onChange: (e) => setTeam(e.target.value), children: [_jsx("option", { value: "", children: "all teams" }), state.teams.map((t) => (_jsx("option", { value: t, children: t }, t)))] })] }), _jsx("button", { onClick: () => send({ type: 'run:pause' }), children: "\u275A\u275A Pause" }), _jsx("button", { class: "stop-btn", onClick: () => send({ type: 'run:stop' }), children: "\u25A0 Stop" }), _jsx("div", { class: "spacer" }), _jsxs("div", { class: "stats", children: [_jsxs("div", { class: "stat", children: [_jsx("span", { class: "label", children: "Scenes:" }), _jsxs("span", { class: "value", children: [completed, "/", state.sceneCount] })] }), _jsxs("div", { class: "stat pass", children: [_jsx("span", { class: "label", children: "Pass:" }), _jsx("span", { class: "value", children: state.passCount })] }), _jsxs("div", { class: "stat fail", children: [_jsx("span", { class: "label", children: "Fail:" }), _jsx("span", { class: "value", children: state.failCount })] }), _jsxs("div", { class: "stat", children: [_jsx("span", { class: "label", children: "Time:" }), _jsx("span", { class: "value", children: elapsed })] }), _jsx("div", { class: 'conn ' + state.connection, title: 'SSE ' + state.connection })] }), state.sceneCount > 0 ? (_jsx("div", { class: progressClass, children: _jsx("div", { class: "progress-fill", style: `width:${pct}%` }) })) : null] }));
|
|
105
38
|
}
|
|
106
|
-
function SceneCard({ scene,
|
|
39
|
+
function SceneCard({ scene, send }) {
|
|
107
40
|
const [copied, setCopied] = useState(false);
|
|
108
41
|
const statusMark = scene.status === 'completed' ? '✓' : scene.status === 'running' ? '◷' : '✗';
|
|
109
42
|
const copy = () => {
|
|
@@ -111,58 +44,7 @@ function SceneCard({ scene, index, send, }) {
|
|
|
111
44
|
setCopied(true);
|
|
112
45
|
setTimeout(() => setCopied(false), 1200);
|
|
113
46
|
};
|
|
114
|
-
return
|
|
115
|
-
<div class=${'scene ' + (scene.status === 'failed' || scene.status === 'timeout' ? 'failed' : '')}>
|
|
116
|
-
<div class="scene-head">
|
|
117
|
-
<span class=${'scene-status ' + scene.status}>${statusMark}</span>
|
|
118
|
-
<span class="scene-name">${scene.name}</span>
|
|
119
|
-
${scene.file ? html `<span class="scene-file">${scene.file}</span>` : null}
|
|
120
|
-
${scene.team?.name ? html `<span class="scene-team">${scene.team.name}</span>` : null}
|
|
121
|
-
${scene.duration != null ? html `<span class="scene-dur">${scene.duration}ms</span>` : null}
|
|
122
|
-
<button
|
|
123
|
-
class=${'copy-btn' + (copied ? ' copied' : '')}
|
|
124
|
-
title="Copy scene summary"
|
|
125
|
-
onClick=${copy}
|
|
126
|
-
>
|
|
127
|
-
${copied ? '✓ Copied' : '⧉ Copy'}
|
|
128
|
-
</button>
|
|
129
|
-
${scene.file
|
|
130
|
-
? html `<button
|
|
131
|
-
class="copy-btn"
|
|
132
|
-
onClick=${() => send({ type: 'run:replay', file: scene.file })}
|
|
133
|
-
>▶ Replay</button>`
|
|
134
|
-
: null}
|
|
135
|
-
</div>
|
|
136
|
-
<div class="lanes">
|
|
137
|
-
${scene.lanes.map((lane) => html `
|
|
138
|
-
<div class="lane">
|
|
139
|
-
<span class="lane-actor">${lane.actor}</span>
|
|
140
|
-
<div class="lane-items">
|
|
141
|
-
${lane.items.map((item) => html `
|
|
142
|
-
<span class=${'pill ' + item.status} title=${item.error ?? ''}>
|
|
143
|
-
${item.action}${item.target
|
|
144
|
-
? html `<span class="tgt"> ${item.target}</span>`
|
|
145
|
-
: null}
|
|
146
|
-
</span>
|
|
147
|
-
`)}
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
`)}
|
|
151
|
-
</div>
|
|
152
|
-
${scene.assertions.length > 0
|
|
153
|
-
? html `<div class="assertions">
|
|
154
|
-
${scene.assertions.map((a) => html `
|
|
155
|
-
<div class=${'assert ' + (a.result ? 'ok' : 'bad')}>
|
|
156
|
-
<span class="mark">${a.result ? '✓' : '✗'}</span>
|
|
157
|
-
${a.actor ? html `<span class="who">[${a.actor}]</span>` : null}
|
|
158
|
-
<span>${a.description}</span>
|
|
159
|
-
</div>
|
|
160
|
-
`)}
|
|
161
|
-
</div>`
|
|
162
|
-
: null}
|
|
163
|
-
${scene.error ? html `<div class="scene-error">${scene.error}</div>` : null}
|
|
164
|
-
</div>
|
|
165
|
-
`;
|
|
47
|
+
return (_jsxs("div", { class: 'scene ' + (scene.status === 'failed' || scene.status === 'timeout' ? 'failed' : ''), children: [_jsxs("div", { class: "scene-head", children: [_jsx("span", { class: 'scene-status ' + scene.status, children: statusMark }), _jsx("span", { class: "scene-name", children: scene.name }), scene.file ? _jsx("span", { class: "scene-file", children: scene.file }) : null, scene.team?.name ? _jsx("span", { class: "scene-team", children: scene.team.name }) : null, scene.duration != null ? _jsxs("span", { class: "scene-dur", children: [scene.duration, "ms"] }) : null, _jsx("button", { class: 'copy-btn' + (copied ? ' copied' : ''), title: "Copy scene summary", onClick: copy, children: copied ? '✓ Copied' : '⧉ Copy' }), scene.file ? (_jsx("button", { class: "copy-btn", onClick: () => send({ type: 'run:replay', file: scene.file }), children: "\u25B6 Replay" })) : null] }), _jsx("div", { class: "lanes", children: scene.lanes.map((lane) => (_jsxs("div", { class: "lane", children: [_jsx("span", { class: "lane-actor", children: lane.actor }), _jsx("div", { class: "lane-items", children: lane.items.map((item, i) => (_jsxs("span", { class: 'pill ' + item.status, title: item.error ?? '', children: [item.action, item.target ? _jsxs("span", { class: "tgt", children: [" ", item.target] }) : null] }, i))) })] }, lane.actor))) }), scene.assertions.length > 0 ? (_jsx("div", { class: "assertions", children: scene.assertions.map((a, i) => (_jsxs("div", { class: 'assert ' + (a.result ? 'ok' : 'bad'), children: [_jsx("span", { class: "mark", children: a.result ? '✓' : '✗' }), a.actor ? _jsxs("span", { class: "who", children: ["[", a.actor, "]"] }) : null, _jsx("span", { children: a.description })] }, i))) })) : null, scene.error ? _jsx("div", { class: "scene-error", children: scene.error }) : null] }));
|
|
166
48
|
}
|
|
167
49
|
/** Build the plain-text "copy failures" summary, matching the original dashboard. */
|
|
168
50
|
export function sceneSummary(scene) {
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A read-only TanStack DB read model over the run event stream.
|
|
3
|
+
*
|
|
4
|
+
* `createRunSource(transport)` wraps the widget's existing transport (SSE in
|
|
5
|
+
* dev, WebSocket in cloud) as a shared, fan-out stream; `runCollectionOptions`
|
|
6
|
+
* turns a projection of that stream into a `CollectionConfig` you pass to your
|
|
7
|
+
* own `createCollection` (so the collection is built by the same `@tanstack/db`
|
|
8
|
+
* your `useLiveQuery` uses). Several collections share one source — one
|
|
9
|
+
* connection, many tables — and each is a server-owned replica with no client
|
|
10
|
+
* writes.
|
|
11
|
+
*
|
|
12
|
+
* This is the *read* half of the pipeline (a client of the broadcast layer),
|
|
13
|
+
* distinct from `@scenetest/receiver`, which is the *ingest* half.
|
|
14
|
+
*/
|
|
15
|
+
export { createRunSource } from './source.js';
|
|
16
|
+
export { runCollectionOptions } from './options.js';
|
|
17
|
+
export { scenesProjection, assertionsProjection, actionsProjection, runsProjection, attributeToScene, type SceneRow, type AssertionRecord, type ActionRecord, type RunRow, } from './projections.js';
|
|
18
|
+
export type { RowOp, RunProjection, RunSource, RunCollectionOptions, } from './types.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,MAAM,GACZ,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,KAAK,EACL,aAAa,EACb,SAAS,EACT,oBAAoB,GACrB,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A read-only TanStack DB read model over the run event stream.
|
|
3
|
+
*
|
|
4
|
+
* `createRunSource(transport)` wraps the widget's existing transport (SSE in
|
|
5
|
+
* dev, WebSocket in cloud) as a shared, fan-out stream; `runCollectionOptions`
|
|
6
|
+
* turns a projection of that stream into a `CollectionConfig` you pass to your
|
|
7
|
+
* own `createCollection` (so the collection is built by the same `@tanstack/db`
|
|
8
|
+
* your `useLiveQuery` uses). Several collections share one source — one
|
|
9
|
+
* connection, many tables — and each is a server-owned replica with no client
|
|
10
|
+
* writes.
|
|
11
|
+
*
|
|
12
|
+
* This is the *read* half of the pipeline (a client of the broadcast layer),
|
|
13
|
+
* distinct from `@scenetest/receiver`, which is the *ingest* half.
|
|
14
|
+
*/
|
|
15
|
+
export { createRunSource } from './source.js';
|
|
16
|
+
export { runCollectionOptions } from './options.js';
|
|
17
|
+
export { scenesProjection, assertionsProjection, actionsProjection, runsProjection, attributeToScene, } from './projections.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,GAKjB,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CollectionConfig } from '@tanstack/db';
|
|
2
|
+
import type { RunCollectionOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build TanStack DB collection options that turn the read-only run stream
|
|
5
|
+
* into a reactive table — pass the result to `createCollection`.
|
|
6
|
+
*
|
|
7
|
+
* The projection is the sole writer: a sync transaction is opened per event,
|
|
8
|
+
* the projection's row ops are applied, and the transaction commits. There
|
|
9
|
+
* are no mutation handlers (`onInsert`/`onUpdate`/`onDelete`), so a stray
|
|
10
|
+
* client `.insert()`/`.update()` throws — this is a replica of the run, not
|
|
11
|
+
* a writable store. Validation, if any, is a read-time concern; the sync
|
|
12
|
+
* path lands rows raw, which is exactly right for a server-owned read model.
|
|
13
|
+
*
|
|
14
|
+
* Many collections can share one {@link RunCollectionOptions.source}, each
|
|
15
|
+
* with its own projection, so several tables ride one transport connection.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const source = createRunSource(transport)
|
|
20
|
+
* const scenes = createCollection(
|
|
21
|
+
* runCollectionOptions({ source, projection: scenesProjection() })
|
|
22
|
+
* )
|
|
23
|
+
* const assertions = createCollection(
|
|
24
|
+
* runCollectionOptions({ source, projection: assertionsProjection() })
|
|
25
|
+
* )
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function runCollectionOptions<T extends object, TKey extends string | number>(options: RunCollectionOptions<T, TKey>): CollectionConfig<T, TKey>;
|
|
29
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/collections/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,GAAG,MAAM,EACjF,OAAO,EAAE,oBAAoB,CAAC,CAAC,EAAE,IAAI,CAAC,GACrC,gBAAgB,CAAC,CAAC,EAAE,IAAI,CAAC,CA0C3B"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build TanStack DB collection options that turn the read-only run stream
|
|
3
|
+
* into a reactive table — pass the result to `createCollection`.
|
|
4
|
+
*
|
|
5
|
+
* The projection is the sole writer: a sync transaction is opened per event,
|
|
6
|
+
* the projection's row ops are applied, and the transaction commits. There
|
|
7
|
+
* are no mutation handlers (`onInsert`/`onUpdate`/`onDelete`), so a stray
|
|
8
|
+
* client `.insert()`/`.update()` throws — this is a replica of the run, not
|
|
9
|
+
* a writable store. Validation, if any, is a read-time concern; the sync
|
|
10
|
+
* path lands rows raw, which is exactly right for a server-owned read model.
|
|
11
|
+
*
|
|
12
|
+
* Many collections can share one {@link RunCollectionOptions.source}, each
|
|
13
|
+
* with its own projection, so several tables ride one transport connection.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const source = createRunSource(transport)
|
|
18
|
+
* const scenes = createCollection(
|
|
19
|
+
* runCollectionOptions({ source, projection: scenesProjection() })
|
|
20
|
+
* )
|
|
21
|
+
* const assertions = createCollection(
|
|
22
|
+
* runCollectionOptions({ source, projection: assertionsProjection() })
|
|
23
|
+
* )
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function runCollectionOptions(options) {
|
|
27
|
+
const { source, projection } = options;
|
|
28
|
+
return {
|
|
29
|
+
id: projection.id,
|
|
30
|
+
getKey: projection.getKey,
|
|
31
|
+
startSync: options.startSync ?? true,
|
|
32
|
+
sync: {
|
|
33
|
+
sync: ({ begin, write, commit, truncate, markReady }) => {
|
|
34
|
+
// A shadow map of what this projection has written, so the projection
|
|
35
|
+
// can read prior rows (`get`) without reaching into TanStack DB
|
|
36
|
+
// internals — and so a `reset` clears it in lockstep with `truncate`.
|
|
37
|
+
const rows = new Map();
|
|
38
|
+
const handle = (event) => {
|
|
39
|
+
const ops = projection.project(event, (key) => rows.get(key));
|
|
40
|
+
if (ops.length === 0)
|
|
41
|
+
return;
|
|
42
|
+
begin();
|
|
43
|
+
for (const op of ops) {
|
|
44
|
+
if (op.type === 'reset') {
|
|
45
|
+
rows.clear();
|
|
46
|
+
truncate();
|
|
47
|
+
}
|
|
48
|
+
else if (op.type === 'delete') {
|
|
49
|
+
rows.delete(op.key);
|
|
50
|
+
write({ type: 'delete', key: op.key });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
rows.set(projection.getKey(op.value), op.value);
|
|
54
|
+
write({ type: op.type, value: op.value });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
commit();
|
|
58
|
+
};
|
|
59
|
+
const unsubscribe = source.subscribe(handle);
|
|
60
|
+
// The read model is ready immediately; rows stream in as events arrive
|
|
61
|
+
// (the source replays the current run synchronously on subscribe).
|
|
62
|
+
markReady();
|
|
63
|
+
return unsubscribe;
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/collections/options.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAsC;IAEtC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAA;IAEtC,OAAO;QACL,EAAE,EAAE,UAAU,CAAC,EAAE;QACjB,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;QACpC,IAAI,EAAE;YACJ,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;gBACtD,sEAAsE;gBACtE,gEAAgE;gBAChE,sEAAsE;gBACtE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAW,CAAA;gBAE/B,MAAM,MAAM,GAAG,CAAC,KAA+C,EAAE,EAAE;oBACjE,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;oBAC7D,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAM;oBAE5B,KAAK,EAAE,CAAA;oBACP,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;wBACrB,IAAI,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;4BACxB,IAAI,CAAC,KAAK,EAAE,CAAA;4BACZ,QAAQ,EAAE,CAAA;wBACZ,CAAC;6BAAM,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;4BAChC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;4BACnB,KAAK,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;wBACxC,CAAC;6BAAM,CAAC;4BACN,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAA;4BAC/C,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,CAAA;wBAC3C,CAAC;oBACH,CAAC;oBACD,MAAM,EAAE,CAAA;gBACV,CAAC,CAAA;gBAED,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;gBAC5C,uEAAuE;gBACvE,mEAAmE;gBACnE,SAAS,EAAE,CAAA;gBACX,OAAO,WAAW,CAAA;YACpB,CAAC;SACF;KACF,CAAA;AACH,CAAC"}
|