@scenetest/dashboard 0.11.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Michael Snook
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @scenetest/dashboard
2
+
3
+ The scenetest dashboard as a mountable widget. The same Preact UI renders the
4
+ live run in dev (inside the Vite plugin's `/__scenetest` page) and in
5
+ scenetest-cloud (a Worker-served page) — the host supplies a DOM element and a
6
+ transport adapter, nothing else.
7
+
8
+ ```ts
9
+ import { mountDashboard, createDevTransport } from '@scenetest/dashboard'
10
+
11
+ const handle = mountDashboard(document.getElementById('root'), {
12
+ transport: createDevTransport(),
13
+ })
14
+ // later: handle.unmount()
15
+ ```
16
+
17
+ The widget renders into a **shadow root** with its own styles and fonts, so it
18
+ drops into any host without leaking styles in either direction and without the
19
+ host using Preact.
20
+
21
+ ## Transport adapter
22
+
23
+ The only thing that differs between dev and cloud. The widget calls the
24
+ adapter to fetch a snapshot and subscribe to live events, and pushes user
25
+ actions back as protocol commands:
26
+
27
+ ```ts
28
+ interface Transport {
29
+ fetchState(): Promise<RunEvent[]>
30
+ subscribe(onEvent: (e: RunEvent) => void, onStatus?: (s: ConnectionStatus) => void): () => void
31
+ sendCommand(command: Command): Promise<void>
32
+ }
33
+ ```
34
+
35
+ Events and commands are the `@scenetest/protocol` vocabulary. `createDevTransport()`
36
+ speaks to the Vite middleware (fetch + SSE); a cloud adapter speaks to the
37
+ worker (fetch + WebSocket). History may arrive through either `fetchState`
38
+ (snapshot) or the initial `subscribe` burst — the store folds both the same
39
+ way, so a transport picks whichever its backend makes natural (SSE replays the
40
+ buffer through `subscribe`, so the dev adapter's `fetchState` returns empty).
41
+
42
+ ## Theming
43
+
44
+ The widget's only theming surface is a small set of CSS custom properties,
45
+ passed as `theme` and applied to the shadow host:
46
+
47
+ ```ts
48
+ mountDashboard(el, {
49
+ transport,
50
+ theme: { bg: '#0b0d12', accent: '#7c93ff', font: 'IBM Plex Mono, monospace', fontSize: '12px' },
51
+ })
52
+ ```
53
+
54
+ These map to `--st-bg`, `--st-accent`, `--st-font`, `--st-font-size`. Nothing
55
+ else is reachable; they are versioned with the widget, like the wire protocol.
56
+
57
+ ## Store
58
+
59
+ `mountDashboard` is the entry point, but the event-folding logic is exported
60
+ separately and is DOM-free — useful for tests, SSR, or computing a rollup:
61
+
62
+ - `foldEvents(events)` / `applyEvent(state, event)` — reduce protocol events into `DashboardState`
63
+ - `initialState()`, `completedSceneCount(state)`, `withConnection(state, status)`
64
+ - `sceneSummary(scene)` — the plain-text "copy failures" summary
package/dist/app.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { Scene, Transport } from './types.js';
2
+ /**
3
+ * The dashboard root. Owns the folded state, drives it from the transport,
4
+ * and turns header controls into protocol commands. The same component
5
+ * renders in dev and cloud — only the injected `transport` differs.
6
+ */
7
+ export declare function Dashboard({ transport }: {
8
+ transport: Transport;
9
+ }): import("preact").VNode<import("preact").Attributes> | import("preact").VNode<import("preact").Attributes>[];
10
+ /** Build the plain-text "copy failures" summary, matching the original dashboard. */
11
+ export declare function sceneSummary(scene: Scene): string;
12
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAoC,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAYpF;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,SAAS,CAAA;CAAE,+GAoChE;AAiJD,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CA0BjD"}
package/dist/app.js ADDED
@@ -0,0 +1,221 @@
1
+ import { h } from 'preact';
2
+ import { useEffect, useReducer, useState } from 'preact/hooks';
3
+ import htm from 'htm';
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
+ }
11
+ /**
12
+ * The dashboard root. Owns the folded state, drives it from the transport,
13
+ * and turns header controls into protocol commands. The same component
14
+ * renders in dev and cloud — only the injected `transport` differs.
15
+ */
16
+ export function Dashboard({ transport }) {
17
+ const [state, dispatch] = useReducer(reducer, undefined, initialState);
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
+ `;
48
+ }
49
+ function Header({ state, send }) {
50
+ const [team, setTeam] = useState('');
51
+ const [, force] = useState(0);
52
+ const running = state.running;
53
+ // Tick the elapsed clock while a run is in progress.
54
+ useEffect(() => {
55
+ if (!running)
56
+ return;
57
+ const id = setInterval(() => force((n) => n + 1), 200);
58
+ return () => clearInterval(id);
59
+ }, [running]);
60
+ const completed = completedSceneCount(state);
61
+ const elapsed = state.endDurationMs != null
62
+ ? `${state.endDurationMs}ms`
63
+ : state.runStartTime
64
+ ? `${Date.now() - state.runStartTime}ms`
65
+ : '—';
66
+ const pct = state.sceneCount > 0 ? Math.round((completed / state.sceneCount) * 100) : 0;
67
+ const progressClass = state.failCount > 0
68
+ ? 'progress has-failures'
69
+ : completed === state.sceneCount && state.sceneCount > 0
70
+ ? 'progress done'
71
+ : 'progress';
72
+ const replay = () => send({ type: 'run:replay', ...(team ? { team } : {}) });
73
+ return html `
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
+ `;
105
+ }
106
+ function SceneCard({ scene, index, send, }) {
107
+ const [copied, setCopied] = useState(false);
108
+ const statusMark = scene.status === 'completed' ? '✓' : scene.status === 'running' ? '◷' : '✗';
109
+ const copy = () => {
110
+ copyToClipboard(sceneSummary(scene));
111
+ setCopied(true);
112
+ setTimeout(() => setCopied(false), 1200);
113
+ };
114
+ return html `
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
+ `;
166
+ }
167
+ /** Build the plain-text "copy failures" summary, matching the original dashboard. */
168
+ export function sceneSummary(scene) {
169
+ const lines = [`Scene: ${scene.name}`];
170
+ if (scene.file)
171
+ lines.push(`File: ${scene.file}`);
172
+ if (scene.status)
173
+ lines.push(`Status: ${scene.status}`);
174
+ if (scene.duration != null)
175
+ lines.push(`Duration: ${scene.duration}ms`);
176
+ const errs = [];
177
+ for (const lane of scene.lanes) {
178
+ for (const item of lane.items) {
179
+ if (item.error) {
180
+ errs.push(` ✗ ${item.action}${item.target ? `(${item.target})` : ''} — ${item.error}`);
181
+ }
182
+ }
183
+ }
184
+ if (scene.error && !errs.some((l) => l.includes(scene.error))) {
185
+ errs.push(` ✗ ${scene.error}`);
186
+ }
187
+ if (errs.length > 0) {
188
+ lines.push('', 'Errors:', ...errs);
189
+ }
190
+ const failed = scene.assertions.filter((a) => !a.result);
191
+ if (failed.length > 0) {
192
+ lines.push('', 'Failed assertions:', ...failed.map((a) => ` ✗ [${a.actor ?? ''}] ${a.description}`));
193
+ }
194
+ return lines.join('\n');
195
+ }
196
+ function copyToClipboard(text) {
197
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
198
+ navigator.clipboard.writeText(text).catch(() => fallbackCopy(text));
199
+ }
200
+ else {
201
+ fallbackCopy(text);
202
+ }
203
+ }
204
+ function fallbackCopy(text) {
205
+ if (typeof document === 'undefined')
206
+ return;
207
+ const ta = document.createElement('textarea');
208
+ ta.value = text;
209
+ ta.style.position = 'fixed';
210
+ ta.style.opacity = '0';
211
+ document.body.appendChild(ta);
212
+ ta.select();
213
+ try {
214
+ document.execCommand('copy');
215
+ }
216
+ catch {
217
+ /* ignore */
218
+ }
219
+ document.body.removeChild(ta);
220
+ }
221
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { Transport } from './types.js';
2
+ export interface DevTransportOptions {
3
+ /**
4
+ * Base path the Vite middleware is mounted at. Defaults to `/__scenetest`.
5
+ * The transport talks to `<base>/events` (SSE) and the command endpoints
6
+ * under `<base>`.
7
+ */
8
+ base?: string;
9
+ }
10
+ /**
11
+ * Transport adapter for dev mode: the Vite middleware, same-origin.
12
+ *
13
+ * - Live events arrive over the SSE stream at `<base>/events`, which also
14
+ * replays the buffered events for the current run on connect — so history
15
+ * comes through `subscribe`, and `fetchState` returns empty.
16
+ * - Commands map onto the middleware's existing endpoints:
17
+ * `run:replay` → POST `<base>/replay` (`{ file?, team? }`),
18
+ * `run:stop` → POST `<base>/stop`,
19
+ * `run:pause`/`run:resume` → POST `<base>/pause` (a toggle the server owns).
20
+ */
21
+ export declare function createDevTransport(options?: DevTransportOptions): Transport;
22
+ //# sourceMappingURL=dev-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-transport.d.ts","sourceRoot":"","sources":["../src/dev-transport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAoB,SAAS,EAAE,MAAM,YAAY,CAAA;AAE7D,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,SAAS,CA0D/E"}
@@ -0,0 +1,69 @@
1
+ import { isEventShaped } from '@scenetest/protocol';
2
+ /**
3
+ * Transport adapter for dev mode: the Vite middleware, same-origin.
4
+ *
5
+ * - Live events arrive over the SSE stream at `<base>/events`, which also
6
+ * replays the buffered events for the current run on connect — so history
7
+ * comes through `subscribe`, and `fetchState` returns empty.
8
+ * - Commands map onto the middleware's existing endpoints:
9
+ * `run:replay` → POST `<base>/replay` (`{ file?, team? }`),
10
+ * `run:stop` → POST `<base>/stop`,
11
+ * `run:pause`/`run:resume` → POST `<base>/pause` (a toggle the server owns).
12
+ */
13
+ export function createDevTransport(options = {}) {
14
+ const base = (options.base ?? '/__scenetest').replace(/\/+$/, '');
15
+ return {
16
+ // SSE replays the run buffer on connect, so there is no separate snapshot.
17
+ async fetchState() {
18
+ return [];
19
+ },
20
+ subscribe(onEvent, onStatus) {
21
+ const source = new EventSource(`${base}/events`);
22
+ const status = (s) => onStatus?.(s);
23
+ status('connecting');
24
+ source.onopen = () => status('connected');
25
+ source.onerror = () => status('disconnected');
26
+ source.onmessage = (e) => {
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(e.data);
30
+ }
31
+ catch {
32
+ return;
33
+ }
34
+ // Envelope check only — render event types newer than this widget
35
+ // the producer might send, rather than dropping them.
36
+ if (isEventShaped(parsed))
37
+ onEvent(parsed);
38
+ };
39
+ return () => source.close();
40
+ },
41
+ async sendCommand(command) {
42
+ const post = (path, body) => fetch(`${base}${path}`, {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: body === undefined ? undefined : JSON.stringify(body),
46
+ });
47
+ switch (command.type) {
48
+ case 'run:replay': {
49
+ const body = {};
50
+ if (command.file)
51
+ body.file = command.file;
52
+ if (command.team)
53
+ body.team = command.team;
54
+ await post('/replay', body);
55
+ return;
56
+ }
57
+ case 'run:stop':
58
+ await post('/stop');
59
+ return;
60
+ // The dev server exposes pause as a single toggle; both map to it.
61
+ case 'run:pause':
62
+ case 'run:resume':
63
+ await post('/pause');
64
+ return;
65
+ }
66
+ },
67
+ };
68
+ }
69
+ //# sourceMappingURL=dev-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-transport.js","sourceRoot":"","sources":["../src/dev-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAanD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAA+B,EAAE;IAClE,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,cAAc,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAEjE,OAAO;QACL,2EAA2E;QAC3E,KAAK,CAAC,UAAU;YACd,OAAO,EAAE,CAAA;QACX,CAAC;QAED,SAAS,CAAC,OAAO,EAAE,QAAQ;YACzB,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,GAAG,IAAI,SAAS,CAAC,CAAA;YAChD,MAAM,MAAM,GAAG,CAAC,CAAmB,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;YACrD,MAAM,CAAC,YAAY,CAAC,CAAA;YAEpB,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;YACzC,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;YAC7C,MAAM,CAAC,SAAS,GAAG,CAAC,CAAe,EAAE,EAAE;gBACrC,IAAI,MAAe,CAAA;gBACnB,IAAI,CAAC;oBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;gBAC7B,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAM;gBACR,CAAC;gBACD,kEAAkE;gBAClE,sDAAsD;gBACtD,IAAI,aAAa,CAAC,MAAM,CAAC;oBAAE,OAAO,CAAC,MAAkB,CAAC,CAAA;YACxD,CAAC,CAAA;YAED,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;QAC7B,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,OAAgB;YAChC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,IAAc,EAAE,EAAE,CAC5C,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE;gBACtB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC5D,CAAC,CAAA;YAEJ,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;gBACrB,KAAK,YAAY,CAAC,CAAC,CAAC;oBAClB,MAAM,IAAI,GAAqC,EAAE,CAAA;oBACjD,IAAI,OAAO,CAAC,IAAI;wBAAE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;oBAC1C,IAAI,OAAO,CAAC,IAAI;wBAAE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;oBAC1C,MAAM,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;oBAC3B,OAAM;gBACR,CAAC;gBACD,KAAK,UAAU;oBACb,MAAM,IAAI,CAAC,OAAO,CAAC,CAAA;oBACnB,OAAM;gBACR,mEAAmE;gBACnE,KAAK,WAAW,CAAC;gBACjB,KAAK,YAAY;oBACf,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAA;oBACpB,OAAM;YACV,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { mountDashboard } from './mount.js';
2
+ export { createDevTransport, type DevTransportOptions } from './dev-transport.js';
3
+ export { applyEvent, foldEvents, initialState, withConnection, completedSceneCount, } from './store.js';
4
+ export { sceneSummary } from './app.js';
5
+ export type { Transport, ConnectionStatus, DashboardState, DashboardTheme, DashboardHandle, MountOptions, Scene, Lane, ActionItem, AssertionRow, } from './types.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACjF,OAAO,EACL,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,mBAAmB,GACpB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,eAAe,EACf,YAAY,EACZ,KAAK,EACL,IAAI,EACJ,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { mountDashboard } from './mount.js';
2
+ export { createDevTransport } from './dev-transport.js';
3
+ export { applyEvent, foldEvents, initialState, withConnection, completedSceneCount, } from './store.js';
4
+ export { sceneSummary } from './app.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAA4B,MAAM,oBAAoB,CAAA;AACjF,OAAO,EACL,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,mBAAmB,GACpB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA"}
@@ -0,0 +1,14 @@
1
+ import type { DashboardHandle, MountOptions } from './types.js';
2
+ /**
3
+ * Mount the dashboard widget into `element`. The widget renders into a shadow
4
+ * root with its own styles and fonts, so it can drop into any host — the
5
+ * `/__scenetest` page, a worker-served page, a docs island — without leaking
6
+ * styles in either direction and without the host needing to use Preact.
7
+ *
8
+ * The host supplies only a transport adapter (the dev/cloud seam) and an
9
+ * optional theme; everything else is internal. Returns a handle whose
10
+ * `unmount()` tears down Preact, the transport subscription, and the shadow
11
+ * content.
12
+ */
13
+ export declare function mountDashboard(element: HTMLElement, options: MountOptions): DashboardHandle;
14
+ //# sourceMappingURL=mount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAkB,YAAY,EAAE,MAAM,YAAY,CAAA;AAE/E;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,eAAe,CAqB3F"}
package/dist/mount.js ADDED
@@ -0,0 +1,50 @@
1
+ import { h, render } from 'preact';
2
+ import { Dashboard } from './app.js';
3
+ import { STYLES } from './styles.js';
4
+ /**
5
+ * Mount the dashboard widget into `element`. The widget renders into a shadow
6
+ * root with its own styles and fonts, so it can drop into any host — the
7
+ * `/__scenetest` page, a worker-served page, a docs island — without leaking
8
+ * styles in either direction and without the host needing to use Preact.
9
+ *
10
+ * The host supplies only a transport adapter (the dev/cloud seam) and an
11
+ * optional theme; everything else is internal. Returns a handle whose
12
+ * `unmount()` tears down Preact, the transport subscription, and the shadow
13
+ * content.
14
+ */
15
+ export function mountDashboard(element, options) {
16
+ const root = element.shadowRoot ?? element.attachShadow({ mode: 'open' });
17
+ root.innerHTML = '';
18
+ const style = document.createElement('style');
19
+ style.textContent = STYLES;
20
+ root.appendChild(style);
21
+ if (options.theme)
22
+ applyTheme(root, options.theme);
23
+ const container = document.createElement('div');
24
+ root.appendChild(container);
25
+ render(h(Dashboard, { transport: options.transport }), container);
26
+ return {
27
+ unmount() {
28
+ render(null, container);
29
+ root.innerHTML = '';
30
+ },
31
+ };
32
+ }
33
+ /**
34
+ * Apply the small `--st-*` theming surface to the shadow host. A
35
+ * `:host { ... }` block can't be set imperatively, so the custom properties
36
+ * are written onto the host element's inline style, where `:host` rules in the
37
+ * stylesheet pick them up as overrides.
38
+ */
39
+ function applyTheme(root, theme) {
40
+ const host = root.host;
41
+ if (theme.bg)
42
+ host.style.setProperty('--st-bg', theme.bg);
43
+ if (theme.accent)
44
+ host.style.setProperty('--st-accent', theme.accent);
45
+ if (theme.font)
46
+ host.style.setProperty('--st-font', theme.font);
47
+ if (theme.fontSize)
48
+ host.style.setProperty('--st-font-size', theme.fontSize);
49
+ }
50
+ //# sourceMappingURL=mount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount.js","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,OAAoB,EAAE,OAAqB;IACxE,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IACzE,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;IAEnB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;IAC7C,KAAK,CAAC,WAAW,GAAG,MAAM,CAAA;IAC1B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAEvB,IAAI,OAAO,CAAC,KAAK;QAAE,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;IAElD,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC/C,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAA;IAE3B,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,SAAS,CAAC,CAAA;IAEjE,OAAO;QACL,OAAO;YACL,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;YACvB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;QACrB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,IAAgB,EAAE,KAAqB;IACzD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAmB,CAAA;IACrC,IAAI,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;IACzD,IAAI,KAAK,CAAC,MAAM;QAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACrE,IAAI,KAAK,CAAC,IAAI;QAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;IAC/D,IAAI,KAAK,CAAC,QAAQ;QAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AAC9E,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { RunEvent } from '@scenetest/protocol';
2
+ import type { ConnectionStatus, DashboardState } from './types.js';
3
+ /** The empty state, before any events. */
4
+ export declare function initialState(): DashboardState;
5
+ /**
6
+ * Fold one protocol event into the state, returning a new state object (and
7
+ * new objects for the scenes/scene that changed) so Preact re-renders. This
8
+ * is the same reduction the original inline dashboard did imperatively, made
9
+ * pure. Unknown event types are ignored — a newer producer can add events
10
+ * without breaking an older widget.
11
+ */
12
+ export declare function applyEvent(state: DashboardState, event: RunEvent): DashboardState;
13
+ /** Fold a snapshot of events into a fresh state, in order. */
14
+ export declare function foldEvents(events: RunEvent[]): DashboardState;
15
+ /** Return a new state with the connection liveness updated. */
16
+ export declare function withConnection(state: DashboardState, connection: ConnectionStatus): DashboardState;
17
+ /** Number of scenes that have finished (not currently running). */
18
+ export declare function completedSceneCount(state: DashboardState): number;
19
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAe,MAAM,YAAY,CAAA;AAE/E,0CAA0C;AAC1C,wBAAgB,YAAY,IAAI,cAAc,CAa7C;AAqBD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,GAAG,cAAc,CA2GjF;AAYD,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAE7D;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB,GAAG,cAAc,CAElG;AAED,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAEjE"}
package/dist/store.js ADDED
@@ -0,0 +1,164 @@
1
+ /** The empty state, before any events. */
2
+ export function initialState() {
3
+ return {
4
+ scenes: [],
5
+ currentSceneIndex: null,
6
+ runStartTime: null,
7
+ passCount: 0,
8
+ failCount: 0,
9
+ sceneCount: 0,
10
+ teams: [],
11
+ running: false,
12
+ endDurationMs: null,
13
+ connection: 'connecting',
14
+ };
15
+ }
16
+ function cloneScene(scene) {
17
+ return {
18
+ ...scene,
19
+ actors: scene.actors.slice(),
20
+ lanes: scene.lanes.map((lane) => ({ actor: lane.actor, items: lane.items.slice() })),
21
+ assertions: scene.assertions.slice(),
22
+ };
23
+ }
24
+ function laneFor(scene, actor) {
25
+ let lane = scene.lanes.find((l) => l.actor === actor);
26
+ if (!lane) {
27
+ lane = { actor, items: [] };
28
+ scene.lanes.push(lane);
29
+ if (!scene.actors.includes(actor))
30
+ scene.actors.push(actor);
31
+ }
32
+ return lane;
33
+ }
34
+ /**
35
+ * Fold one protocol event into the state, returning a new state object (and
36
+ * new objects for the scenes/scene that changed) so Preact re-renders. This
37
+ * is the same reduction the original inline dashboard did imperatively, made
38
+ * pure. Unknown event types are ignored — a newer producer can add events
39
+ * without breaking an older widget.
40
+ */
41
+ export function applyEvent(state, event) {
42
+ switch (event.type) {
43
+ case 'run:start':
44
+ return {
45
+ ...initialState(),
46
+ connection: state.connection,
47
+ runStartTime: event.timestamp,
48
+ sceneCount: event.sceneCount,
49
+ running: true,
50
+ };
51
+ case 'scene:start': {
52
+ const scene = {
53
+ name: event.name,
54
+ file: event.file,
55
+ actors: (event.actors ?? []).slice(),
56
+ lanes: (event.actors ?? []).map((actor) => ({ actor, items: [] })),
57
+ assertions: [],
58
+ startTime: event.timestamp,
59
+ endTime: null,
60
+ status: 'running',
61
+ team: event.team ?? {},
62
+ teamIndex: event.teamIndex ?? 0,
63
+ };
64
+ const scenes = state.scenes.concat(scene);
65
+ const teamName = scene.team?.name;
66
+ const teams = teamName && !state.teams.includes(teamName) ? state.teams.concat(teamName) : state.teams;
67
+ return { ...state, scenes, currentSceneIndex: scenes.length - 1, teams };
68
+ }
69
+ case 'action:start': {
70
+ return updateCurrentScene(state, (scene) => {
71
+ const lane = laneFor(scene, event.actor);
72
+ lane.items.push({
73
+ action: event.action,
74
+ target: event.target,
75
+ startTime: event.timestamp,
76
+ endTime: null,
77
+ duration: null,
78
+ error: null,
79
+ status: 'running',
80
+ });
81
+ });
82
+ }
83
+ case 'action:end': {
84
+ return updateCurrentScene(state, (scene) => {
85
+ const lane = scene.lanes.find((l) => l.actor === event.actor);
86
+ if (!lane)
87
+ return;
88
+ for (let i = lane.items.length - 1; i >= 0; i--) {
89
+ const item = lane.items[i];
90
+ if (item.status === 'running' && item.action === event.action) {
91
+ item.endTime = event.timestamp;
92
+ item.duration = event.duration;
93
+ item.error = event.error ?? null;
94
+ item.status = event.error ? 'error' : event.duration > 500 ? 'slow' : 'success';
95
+ break;
96
+ }
97
+ }
98
+ });
99
+ }
100
+ case 'assertion': {
101
+ return updateCurrentScene(state, (scene) => {
102
+ scene.assertions.push({
103
+ actor: event.actor,
104
+ description: event.description,
105
+ result: event.result,
106
+ timestamp: event.timestamp,
107
+ });
108
+ });
109
+ }
110
+ case 'scene:end': {
111
+ if (state.currentSceneIndex == null)
112
+ return state;
113
+ const next = updateCurrentScene(state, (scene) => {
114
+ scene.endTime = event.timestamp;
115
+ scene.status = event.status;
116
+ scene.duration = event.duration;
117
+ scene.error = event.error;
118
+ });
119
+ const passed = event.status === 'completed';
120
+ return {
121
+ ...next,
122
+ currentSceneIndex: null,
123
+ passCount: next.passCount + (passed ? 1 : 0),
124
+ failCount: next.failCount + (passed ? 0 : 1),
125
+ };
126
+ }
127
+ case 'run:progress':
128
+ return { ...state, progress: { pct: event.pct, failing: event.failing, flaky: event.flaky } };
129
+ case 'run:end':
130
+ return {
131
+ ...state,
132
+ running: false,
133
+ sceneCount: event.summary?.scenes ?? state.sceneCount,
134
+ passCount: event.summary?.completed ?? state.passCount,
135
+ failCount: event.summary?.failed ?? state.failCount,
136
+ endDurationMs: event.duration,
137
+ };
138
+ default:
139
+ return state;
140
+ }
141
+ }
142
+ function updateCurrentScene(state, mutate) {
143
+ const idx = state.currentSceneIndex;
144
+ if (idx == null || idx < 0 || idx >= state.scenes.length)
145
+ return state;
146
+ const scene = cloneScene(state.scenes[idx]);
147
+ mutate(scene);
148
+ const scenes = state.scenes.slice();
149
+ scenes[idx] = scene;
150
+ return { ...state, scenes };
151
+ }
152
+ /** Fold a snapshot of events into a fresh state, in order. */
153
+ export function foldEvents(events) {
154
+ return events.reduce(applyEvent, initialState());
155
+ }
156
+ /** Return a new state with the connection liveness updated. */
157
+ export function withConnection(state, connection) {
158
+ return { ...state, connection };
159
+ }
160
+ /** Number of scenes that have finished (not currently running). */
161
+ export function completedSceneCount(state) {
162
+ return state.scenes.filter((s) => s.status !== 'running').length;
163
+ }
164
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The widget's stylesheet, injected into its shadow root. The only theming
3
+ * surface is the small set of `--st-*` custom properties on `:host`; every
4
+ * internal color derives from them or from a fixed terminal palette. A host
5
+ * may set `--st-bg`, `--st-accent`, `--st-font`, `--st-font-size` (and nothing
6
+ * else) to retheme the pane. These are versioned with the widget, like the
7
+ * wire protocol.
8
+ */
9
+ export declare const STYLES = "\n:host {\n /* \u2500\u2500 Theming surface (host may override these four) \u2500\u2500 */\n --st-bg: #0f1117;\n --st-accent: #3b82f6;\n --st-font: 'SF Mono', 'Cascadia Code', 'Fira Code', ui-monospace, monospace;\n --st-font-size: 13px;\n\n /* \u2500\u2500 Internal palette (derived; not a public surface) \u2500\u2500 */\n --bg: var(--st-bg);\n --bg2: #1a1d27;\n --bg3: #252833;\n --border: #2e3140;\n --text: #e1e4ed;\n --text2: #8b8fa3;\n --green: #22c55e;\n --red: #ef4444;\n --amber: #f59e0b;\n --blue: var(--st-accent);\n\n display: block;\n font-family: var(--st-font);\n font-size: var(--st-font-size);\n color: var(--text);\n background: var(--bg);\n}\n\n* { margin: 0; padding: 0; box-sizing: border-box; }\n\n.root { min-height: 100%; background: var(--bg); }\n\nheader {\n position: sticky;\n top: 0;\n z-index: 10;\n padding: 12px 20px;\n border-bottom: 1px solid var(--border);\n background: var(--bg2);\n display: flex;\n align-items: center;\n gap: 14px;\n flex-wrap: wrap;\n}\nheader.running .logo { animation: pulse 1.2s ease-in-out infinite; }\n\nh1 { font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 8px; }\n.logo {\n display: inline-flex; align-items: center; justify-content: center;\n width: 22px; height: 22px; border-radius: 5px;\n background: var(--blue); color: #fff; font-weight: 700;\n}\n\nbutton {\n font-family: inherit; font-size: 12px; cursor: pointer;\n border: 1px solid var(--border); background: var(--bg3); color: var(--text);\n padding: 5px 10px; border-radius: 5px; display: inline-flex; align-items: center; gap: 6px;\n}\nbutton:hover:not(:disabled) { border-color: var(--blue); }\nbutton:disabled { opacity: 0.5; cursor: default; }\n.replay-all-btn { color: var(--green); }\n.stop-btn { color: var(--red); }\n\n.team-select-wrap { font-size: 12px; color: var(--text2); display: flex; align-items: center; gap: 6px; }\nselect {\n font-family: inherit; font-size: 12px; background: var(--bg3); color: var(--text);\n border: 1px solid var(--border); border-radius: 5px; padding: 4px 6px;\n}\n\n.spacer { flex: 1; }\n\n.stats { display: flex; align-items: center; gap: 14px; font-size: 12px; }\n.stat { display: flex; align-items: center; gap: 5px; }\n.stat .label { color: var(--text2); }\n.stat .value { font-weight: 600; }\n.stat.pass .value { color: var(--green); }\n.stat.fail .value { color: var(--red); }\n\n.conn { width: 9px; height: 9px; border-radius: 50%; background: var(--text2); }\n.conn.connected { background: var(--green); }\n.conn.disconnected { background: var(--red); }\n\n.progress { flex-basis: 100%; height: 3px; background: var(--bg3); border-radius: 2px; overflow: hidden; }\n.progress-fill { height: 100%; width: 0; background: var(--blue); transition: width 0.2s ease; }\n.progress.done .progress-fill { background: var(--green); }\n.progress.has-failures .progress-fill { background: var(--red); }\n\nmain { padding: 16px 20px; }\n.waiting { text-align: center; color: var(--text2); padding: 60px 20px; }\n.waiting h2 { font-size: 16px; font-weight: 500; margin-bottom: 8px; color: var(--text); }\n.waiting code { background: var(--bg3); padding: 2px 6px; border-radius: 4px; }\n\n.scene {\n border: 1px solid var(--border); border-radius: 8px; background: var(--bg2);\n margin-bottom: 14px; overflow: hidden;\n}\n.scene.failed { border-color: var(--red); }\n.scene-head {\n display: flex; align-items: center; gap: 10px; padding: 10px 14px;\n border-bottom: 1px solid var(--border); background: var(--bg3);\n}\n.scene-status { font-weight: 700; }\n.scene-status.completed { color: var(--green); }\n.scene-status.failed, .scene-status.timeout { color: var(--red); }\n.scene-status.running { color: var(--amber); }\n.scene-name { font-weight: 600; }\n.scene-file { color: var(--text2); font-size: 11px; }\n.scene-team {\n font-size: 11px; color: var(--blue); border: 1px solid var(--border);\n padding: 1px 6px; border-radius: 10px;\n}\n.scene-dur { color: var(--text2); font-size: 11px; margin-left: auto; }\n.copy-btn { padding: 3px 7px; font-size: 11px; }\n.copy-btn.copied { color: var(--green); border-color: var(--green); }\n\n.lanes { padding: 8px 14px; display: flex; flex-direction: column; gap: 6px; }\n.lane { display: flex; align-items: flex-start; gap: 8px; }\n.lane-actor { color: var(--text2); min-width: 90px; font-size: 11px; padding-top: 3px; }\n.lane-items { display: flex; flex-wrap: wrap; gap: 4px; }\n.pill {\n font-size: 11px; padding: 2px 7px; border-radius: 4px;\n border: 1px solid var(--border); background: var(--bg3); color: var(--text);\n}\n.pill.running { border-color: var(--amber); color: var(--amber); }\n.pill.success { border-color: var(--green); }\n.pill.slow { border-color: var(--amber); }\n.pill.error { border-color: var(--red); color: var(--red); }\n.pill .tgt { color: var(--text2); }\n\n.assertions { padding: 0 14px 10px; display: flex; flex-direction: column; gap: 3px; }\n.assert { font-size: 12px; display: flex; gap: 6px; align-items: baseline; }\n.assert .mark { font-weight: 700; }\n.assert.ok .mark { color: var(--green); }\n.assert.bad .mark { color: var(--red); }\n.assert .who { color: var(--text2); }\n\n.scene-error {\n margin: 0 14px 12px; padding: 8px 10px; border-radius: 6px;\n background: rgba(239, 68, 68, 0.1); border: 1px solid var(--red);\n color: var(--red); font-size: 12px; white-space: pre-wrap; cursor: pointer;\n}\n\n@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }\n";
10
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,o4KA8IlB,CAAA"}
package/dist/styles.js ADDED
@@ -0,0 +1,152 @@
1
+ /**
2
+ * The widget's stylesheet, injected into its shadow root. The only theming
3
+ * surface is the small set of `--st-*` custom properties on `:host`; every
4
+ * internal color derives from them or from a fixed terminal palette. A host
5
+ * may set `--st-bg`, `--st-accent`, `--st-font`, `--st-font-size` (and nothing
6
+ * else) to retheme the pane. These are versioned with the widget, like the
7
+ * wire protocol.
8
+ */
9
+ export const STYLES = `
10
+ :host {
11
+ /* ── Theming surface (host may override these four) ── */
12
+ --st-bg: #0f1117;
13
+ --st-accent: #3b82f6;
14
+ --st-font: 'SF Mono', 'Cascadia Code', 'Fira Code', ui-monospace, monospace;
15
+ --st-font-size: 13px;
16
+
17
+ /* ── Internal palette (derived; not a public surface) ── */
18
+ --bg: var(--st-bg);
19
+ --bg2: #1a1d27;
20
+ --bg3: #252833;
21
+ --border: #2e3140;
22
+ --text: #e1e4ed;
23
+ --text2: #8b8fa3;
24
+ --green: #22c55e;
25
+ --red: #ef4444;
26
+ --amber: #f59e0b;
27
+ --blue: var(--st-accent);
28
+
29
+ display: block;
30
+ font-family: var(--st-font);
31
+ font-size: var(--st-font-size);
32
+ color: var(--text);
33
+ background: var(--bg);
34
+ }
35
+
36
+ * { margin: 0; padding: 0; box-sizing: border-box; }
37
+
38
+ .root { min-height: 100%; background: var(--bg); }
39
+
40
+ header {
41
+ position: sticky;
42
+ top: 0;
43
+ z-index: 10;
44
+ padding: 12px 20px;
45
+ border-bottom: 1px solid var(--border);
46
+ background: var(--bg2);
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 14px;
50
+ flex-wrap: wrap;
51
+ }
52
+ header.running .logo { animation: pulse 1.2s ease-in-out infinite; }
53
+
54
+ h1 { font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
55
+ .logo {
56
+ display: inline-flex; align-items: center; justify-content: center;
57
+ width: 22px; height: 22px; border-radius: 5px;
58
+ background: var(--blue); color: #fff; font-weight: 700;
59
+ }
60
+
61
+ button {
62
+ font-family: inherit; font-size: 12px; cursor: pointer;
63
+ border: 1px solid var(--border); background: var(--bg3); color: var(--text);
64
+ padding: 5px 10px; border-radius: 5px; display: inline-flex; align-items: center; gap: 6px;
65
+ }
66
+ button:hover:not(:disabled) { border-color: var(--blue); }
67
+ button:disabled { opacity: 0.5; cursor: default; }
68
+ .replay-all-btn { color: var(--green); }
69
+ .stop-btn { color: var(--red); }
70
+
71
+ .team-select-wrap { font-size: 12px; color: var(--text2); display: flex; align-items: center; gap: 6px; }
72
+ select {
73
+ font-family: inherit; font-size: 12px; background: var(--bg3); color: var(--text);
74
+ border: 1px solid var(--border); border-radius: 5px; padding: 4px 6px;
75
+ }
76
+
77
+ .spacer { flex: 1; }
78
+
79
+ .stats { display: flex; align-items: center; gap: 14px; font-size: 12px; }
80
+ .stat { display: flex; align-items: center; gap: 5px; }
81
+ .stat .label { color: var(--text2); }
82
+ .stat .value { font-weight: 600; }
83
+ .stat.pass .value { color: var(--green); }
84
+ .stat.fail .value { color: var(--red); }
85
+
86
+ .conn { width: 9px; height: 9px; border-radius: 50%; background: var(--text2); }
87
+ .conn.connected { background: var(--green); }
88
+ .conn.disconnected { background: var(--red); }
89
+
90
+ .progress { flex-basis: 100%; height: 3px; background: var(--bg3); border-radius: 2px; overflow: hidden; }
91
+ .progress-fill { height: 100%; width: 0; background: var(--blue); transition: width 0.2s ease; }
92
+ .progress.done .progress-fill { background: var(--green); }
93
+ .progress.has-failures .progress-fill { background: var(--red); }
94
+
95
+ main { padding: 16px 20px; }
96
+ .waiting { text-align: center; color: var(--text2); padding: 60px 20px; }
97
+ .waiting h2 { font-size: 16px; font-weight: 500; margin-bottom: 8px; color: var(--text); }
98
+ .waiting code { background: var(--bg3); padding: 2px 6px; border-radius: 4px; }
99
+
100
+ .scene {
101
+ border: 1px solid var(--border); border-radius: 8px; background: var(--bg2);
102
+ margin-bottom: 14px; overflow: hidden;
103
+ }
104
+ .scene.failed { border-color: var(--red); }
105
+ .scene-head {
106
+ display: flex; align-items: center; gap: 10px; padding: 10px 14px;
107
+ border-bottom: 1px solid var(--border); background: var(--bg3);
108
+ }
109
+ .scene-status { font-weight: 700; }
110
+ .scene-status.completed { color: var(--green); }
111
+ .scene-status.failed, .scene-status.timeout { color: var(--red); }
112
+ .scene-status.running { color: var(--amber); }
113
+ .scene-name { font-weight: 600; }
114
+ .scene-file { color: var(--text2); font-size: 11px; }
115
+ .scene-team {
116
+ font-size: 11px; color: var(--blue); border: 1px solid var(--border);
117
+ padding: 1px 6px; border-radius: 10px;
118
+ }
119
+ .scene-dur { color: var(--text2); font-size: 11px; margin-left: auto; }
120
+ .copy-btn { padding: 3px 7px; font-size: 11px; }
121
+ .copy-btn.copied { color: var(--green); border-color: var(--green); }
122
+
123
+ .lanes { padding: 8px 14px; display: flex; flex-direction: column; gap: 6px; }
124
+ .lane { display: flex; align-items: flex-start; gap: 8px; }
125
+ .lane-actor { color: var(--text2); min-width: 90px; font-size: 11px; padding-top: 3px; }
126
+ .lane-items { display: flex; flex-wrap: wrap; gap: 4px; }
127
+ .pill {
128
+ font-size: 11px; padding: 2px 7px; border-radius: 4px;
129
+ border: 1px solid var(--border); background: var(--bg3); color: var(--text);
130
+ }
131
+ .pill.running { border-color: var(--amber); color: var(--amber); }
132
+ .pill.success { border-color: var(--green); }
133
+ .pill.slow { border-color: var(--amber); }
134
+ .pill.error { border-color: var(--red); color: var(--red); }
135
+ .pill .tgt { color: var(--text2); }
136
+
137
+ .assertions { padding: 0 14px 10px; display: flex; flex-direction: column; gap: 3px; }
138
+ .assert { font-size: 12px; display: flex; gap: 6px; align-items: baseline; }
139
+ .assert .mark { font-weight: 700; }
140
+ .assert.ok .mark { color: var(--green); }
141
+ .assert.bad .mark { color: var(--red); }
142
+ .assert .who { color: var(--text2); }
143
+
144
+ .scene-error {
145
+ margin: 0 14px 12px; padding: 8px 10px; border-radius: 6px;
146
+ background: rgba(239, 68, 68, 0.1); border: 1px solid var(--red);
147
+ color: var(--red); font-size: 12px; white-space: pre-wrap; cursor: pointer;
148
+ }
149
+
150
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
151
+ `;
152
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8IrB,CAAA"}
@@ -0,0 +1,112 @@
1
+ import type { Command, RunEvent, TeamMeta } from '@scenetest/protocol';
2
+ /**
3
+ * Liveness of the transport's event subscription, surfaced in the header's
4
+ * connection indicator. Transports report this through `subscribe`'s second
5
+ * argument; transports that can't tell may stay `'connected'`.
6
+ */
7
+ export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected';
8
+ /**
9
+ * The injection point where dev and cloud differ. The widget calls the
10
+ * adapter to fetch a state snapshot and subscribe to live events, and pushes
11
+ * user actions back as protocol commands. The adapter speaks to whatever
12
+ * backend is present — the Vite middleware (fetch + SSE) in dev, the worker
13
+ * API (fetch + WebSocket) in cloud — so the dashboard behaves the same in
14
+ * both by construction.
15
+ */
16
+ export interface Transport {
17
+ /**
18
+ * A snapshot of the run so far, as an ordered list of protocol events the
19
+ * widget folds into its initial state. Transports that deliver history
20
+ * through the initial `subscribe` burst instead (e.g. SSE replay) may
21
+ * return an empty array — the store folds both the same way.
22
+ */
23
+ fetchState(): Promise<RunEvent[]>;
24
+ /**
25
+ * Subscribe to live events. `onEvent` receives every event after the
26
+ * snapshot; `onStatus`, if given, receives connection-liveness changes.
27
+ * Returns an unsubscribe function that tears down the underlying stream.
28
+ */
29
+ subscribe(onEvent: (event: RunEvent) => void, onStatus?: (status: ConnectionStatus) => void): () => void;
30
+ /** Send a command toward the runner (replay, stop, pause, resume). */
31
+ sendCommand(command: Command): Promise<void>;
32
+ }
33
+ /** A single DSL action on one actor's lane. */
34
+ export interface ActionItem {
35
+ action: string;
36
+ target?: string;
37
+ startTime: number;
38
+ endTime: number | null;
39
+ duration: number | null;
40
+ error: string | null;
41
+ status: 'running' | 'success' | 'slow' | 'error';
42
+ }
43
+ /** One actor's ordered actions within a scene. */
44
+ export interface Lane {
45
+ actor: string;
46
+ items: ActionItem[];
47
+ }
48
+ /** An inline assertion result observed during a scene. */
49
+ export interface AssertionRow {
50
+ actor?: string;
51
+ description: string;
52
+ result: boolean;
53
+ timestamp: number;
54
+ }
55
+ /** A scene folded from its lifecycle events. */
56
+ export interface Scene {
57
+ name: string;
58
+ file: string;
59
+ actors: string[];
60
+ lanes: Lane[];
61
+ assertions: AssertionRow[];
62
+ startTime: number;
63
+ endTime: number | null;
64
+ status: 'running' | 'completed' | 'failed' | 'timeout' | string;
65
+ duration?: number;
66
+ error?: string;
67
+ team: TeamMeta;
68
+ teamIndex: number;
69
+ }
70
+ /** Everything the widget renders, folded from the protocol event stream. */
71
+ export interface DashboardState {
72
+ scenes: Scene[];
73
+ currentSceneIndex: number | null;
74
+ runStartTime: number | null;
75
+ passCount: number;
76
+ failCount: number;
77
+ sceneCount: number;
78
+ /** Distinct team names seen in `scene:start`, for the replay filter. */
79
+ teams: string[];
80
+ running: boolean;
81
+ /** Final run duration once `run:end` arrives, else null. */
82
+ endDurationMs: number | null;
83
+ /** Latest `run:progress` rollup, when the producer emits one. */
84
+ progress?: {
85
+ pct: number;
86
+ failing: number;
87
+ flaky: number;
88
+ };
89
+ connection: ConnectionStatus;
90
+ }
91
+ /** Theming surface — the only knobs a host may set. Versioned with the widget. */
92
+ export interface DashboardTheme {
93
+ /** Background of the terminal pane. */
94
+ bg?: string;
95
+ /** Accent / primary action color. */
96
+ accent?: string;
97
+ /** Font family for the pane. */
98
+ font?: string;
99
+ /** Base font size (e.g. `'13px'`). */
100
+ fontSize?: string;
101
+ }
102
+ export interface MountOptions {
103
+ transport: Transport;
104
+ /** Optional theme overrides applied as `--st-*` custom properties. */
105
+ theme?: DashboardTheme;
106
+ }
107
+ /** Handle returned by `mountDashboard`. */
108
+ export interface DashboardHandle {
109
+ /** Tear down the widget, its subscription, and its shadow root contents. */
110
+ unmount(): void;
111
+ }
112
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@scenetest/dashboard",
3
+ "version": "0.11.0",
4
+ "description": "Mountable Preact dashboard widget for scenetest — renders a live run into a shadow root, fed by a pluggable transport adapter",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/scenetest/scenetest-js",
10
+ "directory": "packages/dashboard"
11
+ },
12
+ "keywords": [
13
+ "testing",
14
+ "e2e",
15
+ "playwright",
16
+ "dashboard",
17
+ "preact",
18
+ "widget"
19
+ ],
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ },
27
+ "./package.json": "./package.json"
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "htm": "^3.1.1",
37
+ "preact": "^10.22.0",
38
+ "@scenetest/protocol": "0.11.0"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.3.3",
42
+ "vite": "^6.4.2",
43
+ "vitest": "^4.1.4"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "typecheck": "tsc --noEmit",
48
+ "test": "vitest run",
49
+ "test:watch": "vitest"
50
+ }
51
+ }