@glyphlab/scenario-react 0.0.0-stage → 0.1.1
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 +21 -0
- package/README.md +27 -2
- package/index.d.ts +80 -0
- package/index.js +417 -0
- package/index.js.map +1 -0
- package/package.json +46 -4
- package/scenario.css +487 -0
- package/theme.css +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Subtubes
|
|
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
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @glyphlab/scenario-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The scenario player for React: the state of a run, playback controls, the trace and state panels, and a player that puts them together. No comments and nothing of the app. Peer dependencies: `react`, `zustand`, `@glyphlab/scenario-core`.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { createSimulationStore, ScenarioPlayer, SimulationContext } from '@glyphlab/scenario-react'
|
|
7
|
+
import '@glyphlab/scenario-react/theme.css' // default colours; skip it if the host defines the variables itself
|
|
8
|
+
import '@glyphlab/scenario-react/scenario.css'
|
|
9
|
+
import { highlightsAt } from '@glyphlab/scenario-core'
|
|
10
|
+
|
|
11
|
+
const store = createSimulationStore(() => ({ nodes, edges })) // where the diagram comes from, asked at every run
|
|
12
|
+
store.getState().registerScenarios(scenarios)
|
|
13
|
+
store.getState().openPanel()
|
|
14
|
+
|
|
15
|
+
<SimulationContext.Provider value={store}>
|
|
16
|
+
<ScenarioPlayer />
|
|
17
|
+
</SimulationContext.Provider>
|
|
18
|
+
|
|
19
|
+
// Light the path up on a diagram (for example a DiagramViewer's highlights) as the run plays:
|
|
20
|
+
store.subscribe((s) => s.trace && setHighlights(highlightsAt(s.trace, s.cursor)))
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **One store per player.** Two players on a page do not share a diagram, scenarios or timer. A host with a single player can call `setDefaultSimulationStore(store)` once instead of using a provider.
|
|
24
|
+
- **Slots** on `ScenarioPlayer`'s `config`: `trace` (your own list of steps), `footer`, `onDelete(scenario)` (shows a delete button; ask for confirmation and call `removeScenario` yourself), `height` and `resizer`. `TraceList` takes `stepActions`, `stepExtras` and `emptyHint`, which is how the Glyph app adds comments to each step.
|
|
25
|
+
- **Pieces on their own**: `PlaybackControls`, `StatePanel` and `TraceList` can be placed anywhere inside a provider.
|
|
26
|
+
- **Theme**: CSS variables only (`theme.css` lists them). The few shared controls it uses (`.icon-btn`, `.chip`) are declared at no specificity, so a host's own styles win.
|
|
27
|
+
|
|
28
|
+
In the Glyph app, `src/simulation/store.ts` makes the open design's store and `components/ScenarioPlayer.tsx` / `TraceList.tsx` add the app's comments, review bar and confirmed delete.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as zustand from 'zustand';
|
|
2
|
+
import { Scenario, Values, Trace, ScenarioIssue, GraphModel, TraceStep } from '@glyphlab/scenario-core';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
type PlayStatus = 'idle' | 'playing' | 'paused' | 'finished';
|
|
7
|
+
interface RunOptions {
|
|
8
|
+
autoplay?: boolean;
|
|
9
|
+
seek?: number;
|
|
10
|
+
report?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface SimulationState {
|
|
13
|
+
scenarios: Scenario[];
|
|
14
|
+
activeId: string | null;
|
|
15
|
+
input: Values;
|
|
16
|
+
trace: Trace | null;
|
|
17
|
+
cursor: number;
|
|
18
|
+
status: PlayStatus;
|
|
19
|
+
stepMs: number;
|
|
20
|
+
panelOpen: boolean;
|
|
21
|
+
runId: number;
|
|
22
|
+
reportable: boolean;
|
|
23
|
+
focusMode: boolean;
|
|
24
|
+
issues: ScenarioIssue[];
|
|
25
|
+
issuesHidden: boolean;
|
|
26
|
+
setIssuesHidden: (hidden: boolean) => void;
|
|
27
|
+
refreshIssues: () => void;
|
|
28
|
+
registerScenarios: (scenarios: Scenario[]) => void;
|
|
29
|
+
removeScenario: (id: string) => void;
|
|
30
|
+
addScenario: (scenario: Scenario) => void;
|
|
31
|
+
replaceScenario: (scenario: Scenario) => void;
|
|
32
|
+
openPanel: () => void;
|
|
33
|
+
closePanel: () => void;
|
|
34
|
+
setFocusMode: (on: boolean) => void;
|
|
35
|
+
selectScenario: (id: string) => void;
|
|
36
|
+
run: (options?: RunOptions) => void;
|
|
37
|
+
runWith: (scenarioId: string, input: Values, options?: RunOptions) => void;
|
|
38
|
+
play: () => void;
|
|
39
|
+
pause: () => void;
|
|
40
|
+
stepForward: () => void;
|
|
41
|
+
stepBack: () => void;
|
|
42
|
+
seek: (index: number) => void;
|
|
43
|
+
reset: () => void;
|
|
44
|
+
setStepMs: (ms: number) => void;
|
|
45
|
+
}
|
|
46
|
+
declare function findScenario(scenarios: Scenario[], id: string | null): Scenario | undefined;
|
|
47
|
+
declare function createSimulationStore(graph: () => GraphModel): zustand.UseBoundStore<zustand.StoreApi<SimulationState>>;
|
|
48
|
+
type SimulationStore = ReturnType<typeof createSimulationStore>;
|
|
49
|
+
|
|
50
|
+
declare const SimulationContext: react.Context<zustand.UseBoundStore<zustand.StoreApi<SimulationState>> | null>;
|
|
51
|
+
declare function setDefaultSimulationStore(store: SimulationStore | null): void;
|
|
52
|
+
declare function useSimStore(): SimulationStore;
|
|
53
|
+
declare function useSim<T>(selector: (state: ReturnType<SimulationStore['getState']>) => T): T;
|
|
54
|
+
|
|
55
|
+
declare function PlaybackControls(): react.JSX.Element;
|
|
56
|
+
|
|
57
|
+
declare function StatePanel(): react.JSX.Element;
|
|
58
|
+
|
|
59
|
+
interface TraceListProps {
|
|
60
|
+
stepActions?: (step: TraceStep, trace: Trace) => ReactNode;
|
|
61
|
+
stepExtras?: (step: TraceStep, trace: Trace) => ReactNode;
|
|
62
|
+
emptyHint?: ReactNode;
|
|
63
|
+
}
|
|
64
|
+
declare function TraceList({ stepActions, stepExtras, emptyHint }?: TraceListProps): react.JSX.Element;
|
|
65
|
+
|
|
66
|
+
interface ScenarioPlayerConfig {
|
|
67
|
+
height?: number;
|
|
68
|
+
resizer?: ReactNode;
|
|
69
|
+
trace?: ReactNode;
|
|
70
|
+
footer?: ReactNode;
|
|
71
|
+
onDelete?: (scenario: Scenario) => void;
|
|
72
|
+
closable?: boolean;
|
|
73
|
+
statePanel?: boolean;
|
|
74
|
+
}
|
|
75
|
+
declare function ScenarioPlayer({ config }: {
|
|
76
|
+
config?: ScenarioPlayerConfig;
|
|
77
|
+
}): react.JSX.Element | null;
|
|
78
|
+
|
|
79
|
+
export { PlaybackControls, ScenarioPlayer, SimulationContext, StatePanel, TraceList, createSimulationStore, findScenario, setDefaultSimulationStore, useSim, useSimStore };
|
|
80
|
+
export type { PlayStatus, RunOptions, ScenarioPlayerConfig, SimulationState, SimulationStore, TraceListProps };
|
package/index.js
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// packages/scenario-react/src/store.ts
|
|
4
|
+
import { create } from "zustand";
|
|
5
|
+
import { defaultInput, runScenario, validateScenario } from "@glyphlab/scenario-core";
|
|
6
|
+
function findScenario(scenarios, id) {
|
|
7
|
+
return scenarios.find((s) => s.id === id);
|
|
8
|
+
}
|
|
9
|
+
function createSimulationStore(graph) {
|
|
10
|
+
return create()((set, get) => {
|
|
11
|
+
let timer;
|
|
12
|
+
const stopTimer = () => {
|
|
13
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
14
|
+
timer = void 0;
|
|
15
|
+
};
|
|
16
|
+
const schedule = () => {
|
|
17
|
+
stopTimer();
|
|
18
|
+
timer = setTimeout(() => {
|
|
19
|
+
const { trace, cursor, status } = get();
|
|
20
|
+
if (!trace || status !== "playing") return;
|
|
21
|
+
if (cursor >= trace.steps.length - 1) {
|
|
22
|
+
set({ status: "finished" });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const next = cursor + 1;
|
|
26
|
+
set({ cursor: next, status: next >= trace.steps.length - 1 ? "finished" : "playing" });
|
|
27
|
+
if (get().status === "playing") schedule();
|
|
28
|
+
}, get().stepMs);
|
|
29
|
+
};
|
|
30
|
+
const clearRun = () => {
|
|
31
|
+
stopTimer();
|
|
32
|
+
return { trace: null, cursor: -1, status: "idle" };
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
scenarios: [],
|
|
36
|
+
activeId: null,
|
|
37
|
+
input: {},
|
|
38
|
+
trace: null,
|
|
39
|
+
cursor: -1,
|
|
40
|
+
status: "idle",
|
|
41
|
+
stepMs: 1100,
|
|
42
|
+
panelOpen: false,
|
|
43
|
+
runId: 0,
|
|
44
|
+
reportable: false,
|
|
45
|
+
focusMode: true,
|
|
46
|
+
issues: [],
|
|
47
|
+
issuesHidden: false,
|
|
48
|
+
setIssuesHidden: (issuesHidden) => set({ issuesHidden }),
|
|
49
|
+
refreshIssues: () => {
|
|
50
|
+
const scenario = findScenario(get().scenarios, get().activeId);
|
|
51
|
+
if (!scenario) return;
|
|
52
|
+
const issues = validateScenario(scenario, graph());
|
|
53
|
+
if (JSON.stringify(issues) !== JSON.stringify(get().issues)) set({ issues, issuesHidden: issues.length === 0 ? get().issuesHidden : false });
|
|
54
|
+
},
|
|
55
|
+
registerScenarios: (scenarios) => {
|
|
56
|
+
set({ scenarios });
|
|
57
|
+
const { activeId } = get();
|
|
58
|
+
if (activeId && !findScenario(scenarios, activeId)) set({ activeId: null, input: {}, issues: [], ...clearRun() });
|
|
59
|
+
if (!get().activeId && scenarios.length > 0) get().selectScenario(scenarios[0].id);
|
|
60
|
+
},
|
|
61
|
+
addScenario: (scenario) => {
|
|
62
|
+
set((s) => ({ scenarios: [...s.scenarios, scenario] }));
|
|
63
|
+
if (!get().activeId) get().selectScenario(scenario.id);
|
|
64
|
+
},
|
|
65
|
+
replaceScenario: (scenario) => {
|
|
66
|
+
const { scenarios, activeId } = get();
|
|
67
|
+
if (!scenarios.some((s) => s.id === scenario.id)) return;
|
|
68
|
+
set({ scenarios: scenarios.map((s) => s.id === scenario.id ? scenario : s) });
|
|
69
|
+
if (activeId === scenario.id) get().selectScenario(scenario.id);
|
|
70
|
+
},
|
|
71
|
+
removeScenario: (id) => {
|
|
72
|
+
const { scenarios, activeId } = get();
|
|
73
|
+
const index = scenarios.findIndex((s) => s.id === id);
|
|
74
|
+
if (index < 0) return;
|
|
75
|
+
const rest = scenarios.filter((s) => s.id !== id);
|
|
76
|
+
set({ scenarios: rest });
|
|
77
|
+
if (activeId !== id) return;
|
|
78
|
+
const next = rest[Math.min(index, rest.length - 1)];
|
|
79
|
+
if (next) get().selectScenario(next.id);
|
|
80
|
+
else set({ activeId: null, input: {}, issues: [], ...clearRun() });
|
|
81
|
+
},
|
|
82
|
+
openPanel: () => set({ panelOpen: true }),
|
|
83
|
+
setFocusMode: (focusMode) => set({ focusMode }),
|
|
84
|
+
closePanel: () => {
|
|
85
|
+
stopTimer();
|
|
86
|
+
set((s) => ({ panelOpen: false, status: s.status === "playing" ? "paused" : s.status }));
|
|
87
|
+
},
|
|
88
|
+
selectScenario: (id) => {
|
|
89
|
+
const scenario = findScenario(get().scenarios, id);
|
|
90
|
+
if (!scenario) return;
|
|
91
|
+
set({
|
|
92
|
+
activeId: id,
|
|
93
|
+
input: defaultInput(scenario),
|
|
94
|
+
issues: validateScenario(scenario, graph()),
|
|
95
|
+
issuesHidden: false,
|
|
96
|
+
...clearRun()
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
run: ({ autoplay = true, seek = 0, report = true } = {}) => {
|
|
100
|
+
const { scenarios, activeId, input } = get();
|
|
101
|
+
const scenario = findScenario(scenarios, activeId);
|
|
102
|
+
if (!scenario) return;
|
|
103
|
+
const trace = runScenario(graph(), scenario, input);
|
|
104
|
+
const cursor = Math.min(Math.max(seek, 0), trace.steps.length - 1);
|
|
105
|
+
const atEnd = cursor >= trace.steps.length - 1;
|
|
106
|
+
stopTimer();
|
|
107
|
+
set((s) => ({
|
|
108
|
+
trace,
|
|
109
|
+
cursor,
|
|
110
|
+
status: atEnd ? "finished" : autoplay ? "playing" : "paused",
|
|
111
|
+
runId: s.runId + 1,
|
|
112
|
+
reportable: autoplay && seek === 0 && report,
|
|
113
|
+
issues: validateScenario(scenario, graph()),
|
|
114
|
+
panelOpen: true
|
|
115
|
+
}));
|
|
116
|
+
if (get().status === "playing") schedule();
|
|
117
|
+
},
|
|
118
|
+
runWith: (scenarioId, input, options) => {
|
|
119
|
+
const scenario = findScenario(get().scenarios, scenarioId);
|
|
120
|
+
if (!scenario) return;
|
|
121
|
+
stopTimer();
|
|
122
|
+
set({ activeId: scenarioId, input: { ...defaultInput(scenario), ...input }, panelOpen: true });
|
|
123
|
+
get().run(options);
|
|
124
|
+
},
|
|
125
|
+
play: () => {
|
|
126
|
+
const { trace, cursor } = get();
|
|
127
|
+
if (!trace) return get().run();
|
|
128
|
+
if (cursor >= trace.steps.length - 1) set({ cursor: 0 });
|
|
129
|
+
set({ status: "playing" });
|
|
130
|
+
schedule();
|
|
131
|
+
},
|
|
132
|
+
pause: () => {
|
|
133
|
+
stopTimer();
|
|
134
|
+
set((s) => ({ status: s.status === "playing" ? "paused" : s.status }));
|
|
135
|
+
},
|
|
136
|
+
stepForward: () => {
|
|
137
|
+
const { trace, cursor } = get();
|
|
138
|
+
if (!trace) return get().run({ autoplay: false });
|
|
139
|
+
stopTimer();
|
|
140
|
+
const next = Math.min(cursor + 1, trace.steps.length - 1);
|
|
141
|
+
set({ cursor: next, status: next >= trace.steps.length - 1 ? "finished" : "paused" });
|
|
142
|
+
},
|
|
143
|
+
stepBack: () => {
|
|
144
|
+
const { trace, cursor } = get();
|
|
145
|
+
if (!trace) return;
|
|
146
|
+
stopTimer();
|
|
147
|
+
set({ cursor: Math.max(cursor - 1, 0), status: "paused" });
|
|
148
|
+
},
|
|
149
|
+
seek: (index) => {
|
|
150
|
+
const { trace } = get();
|
|
151
|
+
if (!trace) return;
|
|
152
|
+
stopTimer();
|
|
153
|
+
const cursor = Math.min(Math.max(index, 0), trace.steps.length - 1);
|
|
154
|
+
set({ cursor, status: cursor >= trace.steps.length - 1 ? "finished" : "paused" });
|
|
155
|
+
},
|
|
156
|
+
reset: () => {
|
|
157
|
+
const { trace } = get();
|
|
158
|
+
stopTimer();
|
|
159
|
+
if (trace) set({ cursor: 0, status: "paused" });
|
|
160
|
+
},
|
|
161
|
+
setStepMs: (ms) => {
|
|
162
|
+
set({ stepMs: ms });
|
|
163
|
+
if (get().status === "playing") schedule();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// packages/scenario-react/src/context.ts
|
|
170
|
+
import { createContext, useContext } from "react";
|
|
171
|
+
import { useStore } from "zustand";
|
|
172
|
+
var SimulationContext = createContext(null);
|
|
173
|
+
var fallback = null;
|
|
174
|
+
function setDefaultSimulationStore(store) {
|
|
175
|
+
fallback = store;
|
|
176
|
+
}
|
|
177
|
+
function useSimStore() {
|
|
178
|
+
const store = useContext(SimulationContext) ?? fallback;
|
|
179
|
+
if (!store) throw new Error("No simulation store: wrap the player in <SimulationContext.Provider value={createSimulationStore(...)}> or call setDefaultSimulationStore.");
|
|
180
|
+
return store;
|
|
181
|
+
}
|
|
182
|
+
function useSim(selector) {
|
|
183
|
+
return useStore(useSimStore(), selector);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// packages/scenario-react/src/PlaybackControls.tsx
|
|
187
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
188
|
+
var SPEEDS = [
|
|
189
|
+
{ label: "0.5\xD7", ms: 2200 },
|
|
190
|
+
{ label: "1\xD7", ms: 1100 },
|
|
191
|
+
{ label: "2\xD7", ms: 550 },
|
|
192
|
+
{ label: "4\xD7", ms: 275 }
|
|
193
|
+
];
|
|
194
|
+
function Icon({ d }) {
|
|
195
|
+
return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d }) });
|
|
196
|
+
}
|
|
197
|
+
function PlaybackControls() {
|
|
198
|
+
const trace = useSim((s) => s.trace);
|
|
199
|
+
const cursor = useSim((s) => s.cursor);
|
|
200
|
+
const status = useSim((s) => s.status);
|
|
201
|
+
const stepMs = useSim((s) => s.stepMs);
|
|
202
|
+
const { play, pause, stepForward, stepBack, seek, reset, setStepMs } = useSimStore().getState();
|
|
203
|
+
const total = trace?.steps.length ?? 0;
|
|
204
|
+
const playing = status === "playing";
|
|
205
|
+
return /* @__PURE__ */ jsxs("div", { className: "sim-controls", children: [
|
|
206
|
+
/* @__PURE__ */ jsxs("div", { className: "sim-buttons", role: "group", "aria-label": "Playback", children: [
|
|
207
|
+
/* @__PURE__ */ jsx("button", { className: "icon-btn", type: "button", onClick: reset, disabled: !trace, "aria-label": "Back to the start", title: "Back to the start", children: /* @__PURE__ */ jsx(Icon, { d: "M6 5v14M19 5 9 12l10 7z" }) }),
|
|
208
|
+
/* @__PURE__ */ jsx("button", { className: "icon-btn", type: "button", onClick: stepBack, disabled: !trace || cursor <= 0, "aria-label": "Previous step", title: "Previous step", children: /* @__PURE__ */ jsx(Icon, { d: "m15 18-6-6 6-6" }) }),
|
|
209
|
+
/* @__PURE__ */ jsx(
|
|
210
|
+
"button",
|
|
211
|
+
{
|
|
212
|
+
className: "icon-btn sim-play",
|
|
213
|
+
type: "button",
|
|
214
|
+
onClick: playing ? pause : play,
|
|
215
|
+
"aria-label": playing ? "Pause" : "Play",
|
|
216
|
+
title: playing ? "Pause" : "Play",
|
|
217
|
+
children: playing ? /* @__PURE__ */ jsx(Icon, { d: "M8 5v14M16 5v14" }) : /* @__PURE__ */ jsx(Icon, { d: "M7 4v16l13-8z" })
|
|
218
|
+
}
|
|
219
|
+
),
|
|
220
|
+
/* @__PURE__ */ jsx("button", { className: "icon-btn", type: "button", onClick: stepForward, disabled: trace !== null && cursor >= total - 1, "aria-label": "Next step", title: "Next step", children: /* @__PURE__ */ jsx(Icon, { d: "m9 18 6-6-6-6" }) })
|
|
221
|
+
] }),
|
|
222
|
+
/* @__PURE__ */ jsx(
|
|
223
|
+
"input",
|
|
224
|
+
{
|
|
225
|
+
className: "sim-progress",
|
|
226
|
+
type: "range",
|
|
227
|
+
min: 0,
|
|
228
|
+
max: Math.max(total - 1, 0),
|
|
229
|
+
value: Math.max(cursor, 0),
|
|
230
|
+
disabled: !trace,
|
|
231
|
+
onChange: (e) => seek(Number(e.target.value)),
|
|
232
|
+
"aria-label": "Progress"
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
/* @__PURE__ */ jsx("span", { className: "sim-count", children: trace ? `Step ${cursor + 1} of ${total}` : "Not started" }),
|
|
236
|
+
/* @__PURE__ */ jsx(
|
|
237
|
+
"select",
|
|
238
|
+
{
|
|
239
|
+
className: "sim-speed",
|
|
240
|
+
value: stepMs,
|
|
241
|
+
onChange: (e) => setStepMs(Number(e.target.value)),
|
|
242
|
+
"aria-label": "Speed",
|
|
243
|
+
children: SPEEDS.map((s) => /* @__PURE__ */ jsx("option", { value: s.ms, children: s.label }, s.ms))
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
] });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// packages/scenario-react/src/StatePanel.tsx
|
|
250
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
251
|
+
var OUTCOME_LABEL = {
|
|
252
|
+
success: "Succeeded",
|
|
253
|
+
failure: "Failed",
|
|
254
|
+
blocked: "Blocked",
|
|
255
|
+
stopped: "Stopped"
|
|
256
|
+
};
|
|
257
|
+
function show(value) {
|
|
258
|
+
return typeof value === "boolean" ? value ? "on" : "off" : String(value);
|
|
259
|
+
}
|
|
260
|
+
function StatePanel() {
|
|
261
|
+
const trace = useSim((s) => s.trace);
|
|
262
|
+
const cursor = useSim((s) => s.cursor);
|
|
263
|
+
const scenario = useSim((s) => s.scenarios.find((x) => x.id === s.activeId));
|
|
264
|
+
const pendingInput = useSim((s) => s.input);
|
|
265
|
+
const input = trace?.input ?? pendingInput;
|
|
266
|
+
const step = trace?.steps[cursor];
|
|
267
|
+
const labels = new Map(scenario?.inputs.map((f) => [f.name, f.label]));
|
|
268
|
+
const changed = new Set(step?.changes.map((c) => c.key));
|
|
269
|
+
const finished = step?.kind === "end" && step.outcome;
|
|
270
|
+
return /* @__PURE__ */ jsxs2("div", { className: "sim-state", children: [
|
|
271
|
+
finished && step.outcome && /* @__PURE__ */ jsxs2("div", { className: `sim-outcome outcome-${step.outcome}`, role: "status", children: [
|
|
272
|
+
/* @__PURE__ */ jsx2("strong", { children: OUTCOME_LABEL[step.outcome] }),
|
|
273
|
+
/* @__PURE__ */ jsx2("p", { children: step.title })
|
|
274
|
+
] }),
|
|
275
|
+
/* @__PURE__ */ jsx2("h3", { children: "Input" }),
|
|
276
|
+
Object.keys(input).length > 0 ? /* @__PURE__ */ jsx2("dl", { children: Object.entries(input).map(([key, value]) => /* @__PURE__ */ jsxs2("div", { children: [
|
|
277
|
+
/* @__PURE__ */ jsx2("dt", { children: labels.get(key) ?? key }),
|
|
278
|
+
/* @__PURE__ */ jsx2("dd", { children: show(value) })
|
|
279
|
+
] }, key)) }) : /* @__PURE__ */ jsx2("p", { className: "sim-muted", children: "Nothing yet." }),
|
|
280
|
+
/* @__PURE__ */ jsx2("h3", { children: "State" }),
|
|
281
|
+
step && Object.keys(step.state).length > 0 ? /* @__PURE__ */ jsx2("dl", { children: Object.entries(step.state).map(([key, value]) => /* @__PURE__ */ jsxs2("div", { className: changed.has(key) ? "changed" : "", children: [
|
|
282
|
+
/* @__PURE__ */ jsx2("dt", { children: /* @__PURE__ */ jsx2("code", { children: key }) }),
|
|
283
|
+
/* @__PURE__ */ jsx2("dd", { children: show(value) })
|
|
284
|
+
] }, key)) }) : /* @__PURE__ */ jsx2("p", { className: "sim-muted", children: "Empty." })
|
|
285
|
+
] });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// packages/scenario-react/src/TraceList.tsx
|
|
289
|
+
import { useEffect, useRef } from "react";
|
|
290
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
291
|
+
var KIND_LABEL = { enter: "Step", call: "Call", end: "Result" };
|
|
292
|
+
function show2(value) {
|
|
293
|
+
return value === void 0 ? "\u2205" : String(value);
|
|
294
|
+
}
|
|
295
|
+
function StepBody({ step }) {
|
|
296
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
297
|
+
/* @__PURE__ */ jsxs3("div", { className: "sim-step-head", children: [
|
|
298
|
+
/* @__PURE__ */ jsx3("span", { className: `sim-kind kind-${step.kind}`, children: KIND_LABEL[step.kind] }),
|
|
299
|
+
/* @__PURE__ */ jsx3("strong", { children: step.nodeTitle })
|
|
300
|
+
] }),
|
|
301
|
+
/* @__PURE__ */ jsx3("p", { className: "sim-step-title", children: step.title }),
|
|
302
|
+
step.detail && /* @__PURE__ */ jsx3("p", { className: "sim-step-detail", children: step.detail }),
|
|
303
|
+
step.changes.length > 0 && /* @__PURE__ */ jsx3("ul", { className: "sim-changes", "aria-label": "State changes", children: step.changes.map((c) => /* @__PURE__ */ jsxs3("li", { children: [
|
|
304
|
+
/* @__PURE__ */ jsx3("code", { children: c.key }),
|
|
305
|
+
/* @__PURE__ */ jsxs3("span", { children: [
|
|
306
|
+
c.from === void 0 ? "set to" : `${show2(c.from)} \u2192`,
|
|
307
|
+
" ",
|
|
308
|
+
/* @__PURE__ */ jsx3("b", { children: show2(c.to) })
|
|
309
|
+
] }),
|
|
310
|
+
c.ai && /* @__PURE__ */ jsx3("span", { className: "chip ai", title: "Interpreted by the AI", children: "AI" })
|
|
311
|
+
] }, c.key)) }),
|
|
312
|
+
step.decision && /* @__PURE__ */ jsx3("ul", { className: "sim-decision", "aria-label": "Branches", children: step.decision.options.map((o) => /* @__PURE__ */ jsxs3("li", { className: o.taken ? "taken" : "", children: [
|
|
313
|
+
o.taken ? "\u2713" : "\xB7",
|
|
314
|
+
" ",
|
|
315
|
+
o.label
|
|
316
|
+
] }, o.label)) }),
|
|
317
|
+
step.warnings.map((w) => /* @__PURE__ */ jsx3("p", { className: "sim-warning", children: w }, w))
|
|
318
|
+
] });
|
|
319
|
+
}
|
|
320
|
+
function TraceList({ stepActions, stepExtras, emptyHint } = {}) {
|
|
321
|
+
const trace = useSim((s) => s.trace);
|
|
322
|
+
const cursor = useSim((s) => s.cursor);
|
|
323
|
+
const seek = useSim((s) => s.seek);
|
|
324
|
+
const listRef = useRef(null);
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
const list = listRef.current;
|
|
327
|
+
if (list) list.scrollTop = list.scrollHeight;
|
|
328
|
+
}, [cursor, trace]);
|
|
329
|
+
if (!trace) {
|
|
330
|
+
return /* @__PURE__ */ jsx3("p", { className: "sim-empty", children: emptyHint ?? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
331
|
+
"Press ",
|
|
332
|
+
/* @__PURE__ */ jsx3("strong", { children: "Play" }),
|
|
333
|
+
" to run this scenario with its default input. The path lights up on the diagram as it plays, and every step is listed here."
|
|
334
|
+
] }) });
|
|
335
|
+
}
|
|
336
|
+
return /* @__PURE__ */ jsx3("ol", { className: "sim-steps", ref: listRef, "aria-label": "Trace", children: trace.steps.slice(0, cursor + 1).map((step) => {
|
|
337
|
+
const active = step.index === cursor;
|
|
338
|
+
const extras = stepExtras?.(step, trace) ?? null;
|
|
339
|
+
return /* @__PURE__ */ jsxs3("li", { className: `sim-step${active ? " active" : ""}${step.outcome ? ` outcome-${step.outcome}` : ""}`, children: [
|
|
340
|
+
/* @__PURE__ */ jsxs3("button", { className: "sim-step-main", type: "button", onClick: () => seek(step.index), "aria-current": active, children: [
|
|
341
|
+
/* @__PURE__ */ jsx3("span", { className: "sim-index", children: step.index + 1 }),
|
|
342
|
+
/* @__PURE__ */ jsx3("span", { className: "sim-step-content", children: /* @__PURE__ */ jsx3(StepBody, { step }) })
|
|
343
|
+
] }),
|
|
344
|
+
stepActions?.(step, trace),
|
|
345
|
+
extras && /* @__PURE__ */ jsx3("div", { className: "sim-inline", children: extras })
|
|
346
|
+
] }, step.index);
|
|
347
|
+
}) });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// packages/scenario-react/src/ScenarioPlayer.tsx
|
|
351
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
352
|
+
var Close = ({ size }) => /* @__PURE__ */ jsx4("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M6 6l12 12M18 6 6 18" }) });
|
|
353
|
+
function ScenarioPlayer({ config = {} }) {
|
|
354
|
+
const { height, resizer, trace, footer, onDelete, closable = true, statePanel = false } = config;
|
|
355
|
+
const store = useSimStore();
|
|
356
|
+
const open = useSim((s) => s.panelOpen);
|
|
357
|
+
const scenarios = useSim((s) => s.scenarios);
|
|
358
|
+
const activeId = useSim((s) => s.activeId);
|
|
359
|
+
const issues = useSim((s) => s.issues);
|
|
360
|
+
const focusMode = useSim((s) => s.focusMode);
|
|
361
|
+
const issuesHidden = useSim((s) => s.issuesHidden);
|
|
362
|
+
const { setFocusMode, selectScenario, closePanel, setIssuesHidden } = store.getState();
|
|
363
|
+
if (!open) return null;
|
|
364
|
+
const scenario = scenarios.find((s) => s.id === activeId);
|
|
365
|
+
return /* @__PURE__ */ jsxs4("section", { className: "sim-player", "aria-label": "Scenario player", style: height === void 0 ? void 0 : { height }, children: [
|
|
366
|
+
resizer,
|
|
367
|
+
/* @__PURE__ */ jsxs4("header", { className: "sim-head", children: [
|
|
368
|
+
/* @__PURE__ */ jsx4("h2", { children: "Scenario player" }),
|
|
369
|
+
scenarios.length > 0 ? /* @__PURE__ */ jsx4("select", { value: activeId ?? "", onChange: (e) => selectScenario(e.target.value), "aria-label": "Scenario", children: scenarios.map((s) => /* @__PURE__ */ jsx4("option", { value: s.id, children: s.title }, s.id)) }) : null,
|
|
370
|
+
scenario && onDelete && /* @__PURE__ */ jsx4("button", { className: "icon-btn small", type: "button", onClick: () => onDelete(scenario), "aria-label": "Delete scenario", title: "Delete this scenario from the design", children: /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6" }) }) }),
|
|
371
|
+
/* @__PURE__ */ jsx4("p", { className: "sim-desc", children: scenarios.length === 0 ? "No scenarios." : scenario?.description }),
|
|
372
|
+
issues.length > 0 && issuesHidden && /* @__PURE__ */ jsxs4("button", { className: "sim-problems-chip", type: "button", onClick: () => setIssuesHidden(false), title: "Show the problems with this scenario", children: [
|
|
373
|
+
issues.length,
|
|
374
|
+
" problem",
|
|
375
|
+
issues.length === 1 ? "" : "s"
|
|
376
|
+
] }),
|
|
377
|
+
/* @__PURE__ */ jsxs4("label", { className: "sim-focus", title: "Fade the parts of the diagram this step does not involve", children: [
|
|
378
|
+
/* @__PURE__ */ jsx4("input", { type: "checkbox", checked: focusMode, onChange: (e) => setFocusMode(e.target.checked) }),
|
|
379
|
+
"Focus"
|
|
380
|
+
] }),
|
|
381
|
+
closable && /* @__PURE__ */ jsx4("button", { className: "icon-btn", type: "button", onClick: closePanel, "aria-label": "Close scenario player", children: /* @__PURE__ */ jsx4(Close, { size: 16 }) })
|
|
382
|
+
] }),
|
|
383
|
+
issues.length > 0 && !issuesHidden && /* @__PURE__ */ jsxs4("div", { className: "sim-problems", children: [
|
|
384
|
+
/* @__PURE__ */ jsxs4("header", { children: [
|
|
385
|
+
/* @__PURE__ */ jsxs4("strong", { children: [
|
|
386
|
+
issues.length,
|
|
387
|
+
" problem",
|
|
388
|
+
issues.length === 1 ? "" : "s",
|
|
389
|
+
" with this scenario"
|
|
390
|
+
] }),
|
|
391
|
+
/* @__PURE__ */ jsx4("button", { className: "icon-btn small", type: "button", onClick: () => setIssuesHidden(true), "aria-label": "Close problems", children: /* @__PURE__ */ jsx4(Close, { size: 14 }) })
|
|
392
|
+
] }),
|
|
393
|
+
/* @__PURE__ */ jsx4("ul", { className: "sim-issues", "aria-label": "Scenario problems", children: issues.map((issue, i) => /* @__PURE__ */ jsx4("li", { className: issue.severity, children: issue.message }, i)) })
|
|
394
|
+
] }),
|
|
395
|
+
scenarios.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "sim-empty", role: "status", children: "Nothing to play." }) : /* @__PURE__ */ jsxs4("div", { className: statePanel ? "sim-grid has-side" : "sim-grid", children: [
|
|
396
|
+
/* @__PURE__ */ jsxs4("div", { className: "sim-trace", children: [
|
|
397
|
+
/* @__PURE__ */ jsx4(PlaybackControls, {}),
|
|
398
|
+
trace ?? /* @__PURE__ */ jsx4(TraceList, {}),
|
|
399
|
+
footer
|
|
400
|
+
] }),
|
|
401
|
+
statePanel && /* @__PURE__ */ jsx4("aside", { className: "sim-side", "aria-label": "State", children: /* @__PURE__ */ jsx4(StatePanel, {}) })
|
|
402
|
+
] })
|
|
403
|
+
] });
|
|
404
|
+
}
|
|
405
|
+
export {
|
|
406
|
+
PlaybackControls,
|
|
407
|
+
ScenarioPlayer,
|
|
408
|
+
SimulationContext,
|
|
409
|
+
StatePanel,
|
|
410
|
+
TraceList,
|
|
411
|
+
createSimulationStore,
|
|
412
|
+
findScenario,
|
|
413
|
+
setDefaultSimulationStore,
|
|
414
|
+
useSim,
|
|
415
|
+
useSimStore
|
|
416
|
+
};
|
|
417
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/context.ts","../src/PlaybackControls.tsx","../src/StatePanel.tsx","../src/TraceList.tsx","../src/ScenarioPlayer.tsx"],"sourcesContent":["import { create } from 'zustand'\nimport { defaultInput, runScenario, validateScenario } from '@glyphlab/scenario-core'\nimport type { GraphModel, Scenario, ScenarioIssue, Trace, Values } from '@glyphlab/scenario-core'\n\nexport type PlayStatus = 'idle' | 'playing' | 'paused' | 'finished'\n\nexport interface RunOptions {\n autoplay?: boolean\n // Step to show first; later steps stay hidden until played or sought.\n seek?: number\n // Whether a finished run is reported to the host (the app posts it to the chat). Off when\n // the run was started by something that already has the result, like an assistant's tool call.\n report?: boolean\n}\n\nexport interface SimulationState {\n scenarios: Scenario[]\n activeId: string | null\n input: Values\n trace: Trace | null\n // Index of the step currently shown; steps after it are not revealed yet.\n cursor: number\n status: PlayStatus\n // Milliseconds each step stays on screen while playing.\n stepMs: number\n panelOpen: boolean\n // Increments for every new run, so listeners can tell a new run from a replay.\n runId: number\n // True for a run that was played from the start, as opposed to one reopened at a given\n // step; only played runs are worth reporting.\n reportable: boolean\n // Fade the rest of the diagram while a run is on screen. A display preference the host\n // acts on; the simulation itself does not care.\n focusMode: boolean\n issues: ScenarioIssue[]\n // The problems list is closed (it comes back with the next scenario, or when reopened from the header).\n issuesHidden: boolean\n setIssuesHidden: (hidden: boolean) => void\n // Checks the selected scenario against the diagram as it is now. For when the diagram changed under a scenario that is\n // already selected; nothing happens when the problems are the same.\n refreshIssues: () => void\n\n registerScenarios: (scenarios: Scenario[]) => void\n // Takes a scenario out of this design. If it was the one selected, the next one is, or none when it was the last.\n removeScenario: (id: string) => void\n // Adds a scenario to this design, and selects it when none is selected. Does not check it; see check.ts.\n addScenario: (scenario: Scenario) => void\n // Swaps a scenario for a new definition with the same id, keeping its place in the list. If it is the selected one,\n // the input resets to the new defaults and any run is cleared, since the run was of the old steps.\n replaceScenario: (scenario: Scenario) => void\n openPanel: () => void\n closePanel: () => void\n setFocusMode: (on: boolean) => void\n selectScenario: (id: string) => void\n run: (options?: RunOptions) => void\n // Selects a scenario, sets its input and runs it: the entry point for callers like the chat.\n runWith: (scenarioId: string, input: Values, options?: RunOptions) => void\n play: () => void\n pause: () => void\n stepForward: () => void\n stepBack: () => void\n seek: (index: number) => void\n reset: () => void\n setStepMs: (ms: number) => void\n}\n\nexport function findScenario(scenarios: Scenario[], id: string | null) {\n return scenarios.find((s) => s.id === id)\n}\n\n// A player's state. A host makes one per diagram it plays scenarios on (the Glyph app has one for the open design, and\n// a report makes its own over a saved snapshot), so each has its own diagram, scenarios and timer. `graph` says where\n// to get the diagram when a scenario is run or checked: the player never reads a diagram itself.\nexport function createSimulationStore(graph: () => GraphModel) {\n return create<SimulationState>()((set, get) => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const stopTimer = () => {\n if (timer !== undefined) clearTimeout(timer)\n timer = undefined\n }\n\n // Advances one step on a timer until the trace ends.\n const schedule = () => {\n stopTimer()\n timer = setTimeout(() => {\n const { trace, cursor, status } = get()\n if (!trace || status !== 'playing') return\n if (cursor >= trace.steps.length - 1) {\n set({ status: 'finished' })\n return\n }\n const next = cursor + 1\n set({ cursor: next, status: next >= trace.steps.length - 1 ? 'finished' : 'playing' })\n if (get().status === 'playing') schedule()\n }, get().stepMs)\n }\n\n const clearRun = () => {\n stopTimer()\n return { trace: null, cursor: -1, status: 'idle' as PlayStatus }\n }\n\n return {\n scenarios: [],\n activeId: null,\n input: {},\n trace: null,\n cursor: -1,\n status: 'idle',\n stepMs: 1100,\n panelOpen: false,\n runId: 0,\n reportable: false,\n focusMode: true,\n issues: [],\n issuesHidden: false,\n setIssuesHidden: (issuesHidden) => set({ issuesHidden }),\n\n refreshIssues: () => {\n const scenario = findScenario(get().scenarios, get().activeId)\n if (!scenario) return\n const issues = validateScenario(scenario, graph())\n if (JSON.stringify(issues) !== JSON.stringify(get().issues)) set({ issues, issuesHidden: issues.length === 0 ? get().issuesHidden : false })\n },\n\n registerScenarios: (scenarios) => {\n set({ scenarios })\n const { activeId } = get()\n if (activeId && !findScenario(scenarios, activeId)) set({ activeId: null, input: {}, issues: [], ...clearRun() })\n if (!get().activeId && scenarios.length > 0) get().selectScenario(scenarios[0].id)\n },\n\n addScenario: (scenario) => {\n set((s) => ({ scenarios: [...s.scenarios, scenario] }))\n if (!get().activeId) get().selectScenario(scenario.id)\n },\n\n replaceScenario: (scenario) => {\n const { scenarios, activeId } = get()\n if (!scenarios.some((s) => s.id === scenario.id)) return\n set({ scenarios: scenarios.map((s) => (s.id === scenario.id ? scenario : s)) })\n if (activeId === scenario.id) get().selectScenario(scenario.id)\n },\n\n removeScenario: (id) => {\n const { scenarios, activeId } = get()\n const index = scenarios.findIndex((s) => s.id === id)\n if (index < 0) return\n const rest = scenarios.filter((s) => s.id !== id)\n set({ scenarios: rest })\n if (activeId !== id) return\n const next = rest[Math.min(index, rest.length - 1)]\n if (next) get().selectScenario(next.id)\n else set({ activeId: null, input: {}, issues: [], ...clearRun() })\n },\n\n openPanel: () => set({ panelOpen: true }),\n\n setFocusMode: (focusMode) => set({ focusMode }),\n\n closePanel: () => {\n stopTimer()\n set((s) => ({ panelOpen: false, status: s.status === 'playing' ? 'paused' : s.status }))\n },\n\n selectScenario: (id) => {\n const scenario = findScenario(get().scenarios, id)\n if (!scenario) return\n set({\n activeId: id,\n input: defaultInput(scenario),\n issues: validateScenario(scenario, graph()),\n issuesHidden: false,\n ...clearRun(),\n })\n },\n\n run: ({ autoplay = true, seek = 0, report = true } = {}) => {\n const { scenarios, activeId, input } = get()\n const scenario = findScenario(scenarios, activeId)\n if (!scenario) return\n const trace = runScenario(graph(), scenario, input)\n const cursor = Math.min(Math.max(seek, 0), trace.steps.length - 1)\n const atEnd = cursor >= trace.steps.length - 1\n stopTimer()\n set((s) => ({\n trace,\n cursor,\n status: atEnd ? 'finished' : autoplay ? 'playing' : 'paused',\n runId: s.runId + 1,\n reportable: autoplay && seek === 0 && report,\n issues: validateScenario(scenario, graph()),\n panelOpen: true,\n }))\n if (get().status === 'playing') schedule()\n },\n\n runWith: (scenarioId, input, options) => {\n const scenario = findScenario(get().scenarios, scenarioId)\n if (!scenario) return\n stopTimer()\n set({ activeId: scenarioId, input: { ...defaultInput(scenario), ...input }, panelOpen: true })\n get().run(options)\n },\n\n play: () => {\n const { trace, cursor } = get()\n if (!trace) return get().run()\n if (cursor >= trace.steps.length - 1) set({ cursor: 0 })\n set({ status: 'playing' })\n schedule()\n },\n\n pause: () => {\n stopTimer()\n set((s) => ({ status: s.status === 'playing' ? 'paused' : s.status }))\n },\n\n stepForward: () => {\n const { trace, cursor } = get()\n if (!trace) return get().run({ autoplay: false })\n stopTimer()\n const next = Math.min(cursor + 1, trace.steps.length - 1)\n set({ cursor: next, status: next >= trace.steps.length - 1 ? 'finished' : 'paused' })\n },\n\n stepBack: () => {\n const { trace, cursor } = get()\n if (!trace) return\n stopTimer()\n set({ cursor: Math.max(cursor - 1, 0), status: 'paused' })\n },\n\n seek: (index) => {\n const { trace } = get()\n if (!trace) return\n stopTimer()\n const cursor = Math.min(Math.max(index, 0), trace.steps.length - 1)\n set({ cursor, status: cursor >= trace.steps.length - 1 ? 'finished' : 'paused' })\n },\n\n reset: () => {\n const { trace } = get()\n stopTimer()\n if (trace) set({ cursor: 0, status: 'paused' })\n },\n\n setStepMs: (ms) => {\n set({ stepMs: ms })\n if (get().status === 'playing') schedule()\n },\n }\n })\n}\n\nexport type SimulationStore = ReturnType<typeof createSimulationStore>\n","import { createContext, useContext } from 'react'\nimport { useStore } from 'zustand'\nimport type { SimulationStore } from './store'\n\n// Which simulation the player components show. A host wraps them in `SimulationContext.Provider` with a store it made\n// (`createSimulationStore`); a host with one player for the whole app can instead name it once with\n// `setDefaultSimulationStore`, and the components use it wherever there is no provider.\nexport const SimulationContext = createContext<SimulationStore | null>(null)\n\nlet fallback: SimulationStore | null = null\nexport function setDefaultSimulationStore(store: SimulationStore | null) {\n fallback = store\n}\n\nexport function useSimStore(): SimulationStore {\n const store = useContext(SimulationContext) ?? fallback\n if (!store) throw new Error('No simulation store: wrap the player in <SimulationContext.Provider value={createSimulationStore(...)}> or call setDefaultSimulationStore.')\n return store\n}\n\nexport function useSim<T>(selector: (state: ReturnType<SimulationStore['getState']>) => T): T {\n return useStore(useSimStore(), selector)\n}\n","import { useSim, useSimStore } from './context'\n\nconst SPEEDS = [\n { label: '0.5×', ms: 2200 },\n { label: '1×', ms: 1100 },\n { label: '2×', ms: 550 },\n { label: '4×', ms: 275 },\n]\n\nfunction Icon({ d }: { d: string }) {\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d={d} />\n </svg>\n )\n}\n\nexport function PlaybackControls() {\n const trace = useSim((s) => s.trace)\n const cursor = useSim((s) => s.cursor)\n const status = useSim((s) => s.status)\n const stepMs = useSim((s) => s.stepMs)\n const { play, pause, stepForward, stepBack, seek, reset, setStepMs } = useSimStore().getState()\n\n const total = trace?.steps.length ?? 0\n const playing = status === 'playing'\n\n return (\n <div className=\"sim-controls\">\n <div className=\"sim-buttons\" role=\"group\" aria-label=\"Playback\">\n <button className=\"icon-btn\" type=\"button\" onClick={reset} disabled={!trace} aria-label=\"Back to the start\" title=\"Back to the start\">\n <Icon d=\"M6 5v14M19 5 9 12l10 7z\" />\n </button>\n <button className=\"icon-btn\" type=\"button\" onClick={stepBack} disabled={!trace || cursor <= 0} aria-label=\"Previous step\" title=\"Previous step\">\n <Icon d=\"m15 18-6-6 6-6\" />\n </button>\n <button\n className=\"icon-btn sim-play\"\n type=\"button\"\n onClick={playing ? pause : play}\n aria-label={playing ? 'Pause' : 'Play'}\n title={playing ? 'Pause' : 'Play'}\n >\n {playing ? <Icon d=\"M8 5v14M16 5v14\" /> : <Icon d=\"M7 4v16l13-8z\" />}\n </button>\n <button className=\"icon-btn\" type=\"button\" onClick={stepForward} disabled={trace !== null && cursor >= total - 1} aria-label=\"Next step\" title=\"Next step\">\n <Icon d=\"m9 18 6-6-6-6\" />\n </button>\n </div>\n\n <input\n className=\"sim-progress\"\n type=\"range\"\n min={0}\n max={Math.max(total - 1, 0)}\n value={Math.max(cursor, 0)}\n disabled={!trace}\n onChange={(e) => seek(Number(e.target.value))}\n aria-label=\"Progress\"\n />\n <span className=\"sim-count\">{trace ? `Step ${cursor + 1} of ${total}` : 'Not started'}</span>\n\n <select\n className=\"sim-speed\"\n value={stepMs}\n onChange={(e) => setStepMs(Number(e.target.value))}\n aria-label=\"Speed\"\n >\n {SPEEDS.map((s) => (\n <option key={s.ms} value={s.ms}>\n {s.label}\n </option>\n ))}\n </select>\n </div>\n )\n}\n","import { useSim } from './context'\nimport type { Outcome } from '@glyphlab/scenario-core'\n\nconst OUTCOME_LABEL: Record<Outcome, string> = {\n success: 'Succeeded',\n failure: 'Failed',\n blocked: 'Blocked',\n stopped: 'Stopped',\n}\n\nfunction show(value: string | number | boolean) {\n return typeof value === 'boolean' ? (value ? 'on' : 'off') : String(value)\n}\n\n// What the run knows right now: its input, the state it has built up, and, at the end,\n// how it turned out.\nexport function StatePanel() {\n const trace = useSim((s) => s.trace)\n const cursor = useSim((s) => s.cursor)\n const scenario = useSim((s) => s.scenarios.find((x) => x.id === s.activeId))\n // Before a run starts, show the input it will use.\n const pendingInput = useSim((s) => s.input)\n const input = trace?.input ?? pendingInput\n\n const step = trace?.steps[cursor]\n const labels = new Map(scenario?.inputs.map((f) => [f.name, f.label]))\n const changed = new Set(step?.changes.map((c) => c.key))\n const finished = step?.kind === 'end' && step.outcome\n\n return (\n <div className=\"sim-state\">\n {finished && step.outcome && (\n <div className={`sim-outcome outcome-${step.outcome}`} role=\"status\">\n <strong>{OUTCOME_LABEL[step.outcome]}</strong>\n <p>{step.title}</p>\n </div>\n )}\n\n <h3>Input</h3>\n {Object.keys(input).length > 0 ? (\n <dl>\n {Object.entries(input).map(([key, value]) => (\n <div key={key}>\n <dt>{labels.get(key) ?? key}</dt>\n <dd>{show(value)}</dd>\n </div>\n ))}\n </dl>\n ) : (\n <p className=\"sim-muted\">Nothing yet.</p>\n )}\n\n <h3>State</h3>\n {step && Object.keys(step.state).length > 0 ? (\n <dl>\n {Object.entries(step.state).map(([key, value]) => (\n <div key={key} className={changed.has(key) ? 'changed' : ''}>\n <dt>\n <code>{key}</code>\n </dt>\n <dd>{show(value)}</dd>\n </div>\n ))}\n </dl>\n ) : (\n <p className=\"sim-muted\">Empty.</p>\n )}\n </div>\n )\n}\n","import { useEffect, useRef } from 'react'\nimport type { ReactNode } from 'react'\nimport type { Trace, TraceStep } from '@glyphlab/scenario-core'\nimport { useSim } from './context'\n\nconst KIND_LABEL = { enter: 'Step', call: 'Call', end: 'Result' } as const\n\nfunction show(value: string | number | boolean | undefined) {\n return value === undefined ? '∅' : String(value)\n}\n\nfunction StepBody({ step }: { step: TraceStep }) {\n return (\n <>\n <div className=\"sim-step-head\">\n <span className={`sim-kind kind-${step.kind}`}>{KIND_LABEL[step.kind]}</span>\n <strong>{step.nodeTitle}</strong>\n </div>\n <p className=\"sim-step-title\">{step.title}</p>\n {step.detail && <p className=\"sim-step-detail\">{step.detail}</p>}\n\n {step.changes.length > 0 && (\n <ul className=\"sim-changes\" aria-label=\"State changes\">\n {step.changes.map((c) => (\n <li key={c.key}>\n <code>{c.key}</code>\n <span>\n {c.from === undefined ? 'set to' : `${show(c.from)} →`} <b>{show(c.to)}</b>\n </span>\n {c.ai && <span className=\"chip ai\" title=\"Interpreted by the AI\">AI</span>}\n </li>\n ))}\n </ul>\n )}\n\n {step.decision && (\n <ul className=\"sim-decision\" aria-label=\"Branches\">\n {step.decision.options.map((o) => (\n <li key={o.label} className={o.taken ? 'taken' : ''}>\n {o.taken ? '✓' : '·'} {o.label}\n </li>\n ))}\n </ul>\n )}\n\n {step.warnings.map((w) => (\n <p key={w} className=\"sim-warning\">\n {w}\n </p>\n ))}\n </>\n )\n}\n\nexport interface TraceListProps {\n // Something beside each step, such as a button to comment on it.\n stepActions?: (step: TraceStep, trace: Trace) => ReactNode\n // Something under each step, such as the comments on it. Nothing is drawn when it returns null.\n stepExtras?: (step: TraceStep, trace: Trace) => ReactNode\n // What is said before a run starts.\n emptyHint?: ReactNode\n}\n\n// The steps played so far, newest at the bottom. Clicking a step goes back to it.\nexport function TraceList({ stepActions, stepExtras, emptyHint }: TraceListProps = {}) {\n const trace = useSim((s) => s.trace)\n const cursor = useSim((s) => s.cursor)\n const seek = useSim((s) => s.seek)\n const listRef = useRef<HTMLOListElement>(null)\n\n // Keep the newest step in view while playing.\n useEffect(() => {\n const list = listRef.current\n if (list) list.scrollTop = list.scrollHeight\n }, [cursor, trace])\n\n if (!trace) {\n return (\n <p className=\"sim-empty\">\n {emptyHint ?? (\n <>\n Press <strong>Play</strong> to run this scenario with its default input. The path lights up on the diagram as it plays, and every step is listed here.\n </>\n )}\n </p>\n )\n }\n\n return (\n <ol className=\"sim-steps\" ref={listRef} aria-label=\"Trace\">\n {trace.steps.slice(0, cursor + 1).map((step) => {\n const active = step.index === cursor\n const extras = stepExtras?.(step, trace) ?? null\n return (\n <li key={step.index} className={`sim-step${active ? ' active' : ''}${step.outcome ? ` outcome-${step.outcome}` : ''}`}>\n <button className=\"sim-step-main\" type=\"button\" onClick={() => seek(step.index)} aria-current={active}>\n <span className=\"sim-index\">{step.index + 1}</span>\n <span className=\"sim-step-content\">\n <StepBody step={step} />\n </span>\n </button>\n {stepActions?.(step, trace)}\n {extras && <div className=\"sim-inline\">{extras}</div>}\n </li>\n )\n })}\n </ol>\n )\n}\n","import type { ReactNode } from 'react'\nimport type { Scenario } from '@glyphlab/scenario-core'\nimport { StatePanel } from './StatePanel'\nimport { PlaybackControls } from './PlaybackControls'\nimport { TraceList } from './TraceList'\nimport { useSim, useSimStore } from './context'\n\nexport interface ScenarioPlayerConfig {\n // How tall the player is, and the handle that resizes it (the player does not know about layout).\n height?: number\n resizer?: ReactNode\n // The list of steps; the plain one when not given.\n trace?: ReactNode\n // Under the steps, for example a bar of review comments.\n footer?: ReactNode\n // Shows a delete button for the open scenario and calls this when it is pressed. The host asks for confirmation and\n // removes the scenario (`removeScenario` on the store); without it there is no button.\n onDelete?: (scenario: Scenario) => void\n // Whether the header has a button that closes the player. On unless false; a host that always shows the player turns it off.\n closable?: boolean\n // Shows the state panel beside the trace. Off unless true; a host that places StatePanel itself leaves it off.\n statePanel?: boolean\n}\n\nconst Close = ({ size }: { size: number }) => (\n <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <path d=\"M6 6l12 12M18 6 6 18\" />\n </svg>\n)\n\n// The scenario player: pick a scenario and watch it play out. It reads and writes only the simulation store; what lights\n// up on the diagram, and anything about comments or deleting, is up to the host.\nexport function ScenarioPlayer({ config = {} }: { config?: ScenarioPlayerConfig }) {\n const { height, resizer, trace, footer, onDelete, closable = true, statePanel = false } = config\n const store = useSimStore()\n const open = useSim((s) => s.panelOpen)\n const scenarios = useSim((s) => s.scenarios)\n const activeId = useSim((s) => s.activeId)\n const issues = useSim((s) => s.issues)\n const focusMode = useSim((s) => s.focusMode)\n const issuesHidden = useSim((s) => s.issuesHidden)\n const { setFocusMode, selectScenario, closePanel, setIssuesHidden } = store.getState()\n\n if (!open) return null\n\n const scenario = scenarios.find((s) => s.id === activeId)\n\n return (\n <section className=\"sim-player\" aria-label=\"Scenario player\" style={height === undefined ? undefined : { height }}>\n {resizer}\n <header className=\"sim-head\">\n <h2>Scenario player</h2>\n {scenarios.length > 0 ? (\n <select value={activeId ?? ''} onChange={(e) => selectScenario(e.target.value)} aria-label=\"Scenario\">\n {scenarios.map((s) => (\n <option key={s.id} value={s.id}>\n {s.title}\n </option>\n ))}\n </select>\n ) : null}\n {scenario && onDelete && (\n <button className=\"icon-btn small\" type=\"button\" onClick={() => onDelete(scenario)} aria-label=\"Delete scenario\" title=\"Delete this scenario from the design\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6\" />\n </svg>\n </button>\n )}\n <p className=\"sim-desc\">{scenarios.length === 0 ? 'No scenarios.' : scenario?.description}</p>\n {issues.length > 0 && issuesHidden && (\n <button className=\"sim-problems-chip\" type=\"button\" onClick={() => setIssuesHidden(false)} title=\"Show the problems with this scenario\">\n {issues.length} problem{issues.length === 1 ? '' : 's'}\n </button>\n )}\n <label className=\"sim-focus\" title=\"Fade the parts of the diagram this step does not involve\">\n <input type=\"checkbox\" checked={focusMode} onChange={(e) => setFocusMode(e.target.checked)} />\n Focus\n </label>\n {closable && (\n <button className=\"icon-btn\" type=\"button\" onClick={closePanel} aria-label=\"Close scenario player\">\n <Close size={16} />\n </button>\n )}\n </header>\n\n {issues.length > 0 && !issuesHidden && (\n <div className=\"sim-problems\">\n <header>\n <strong>\n {issues.length} problem{issues.length === 1 ? '' : 's'} with this scenario\n </strong>\n <button className=\"icon-btn small\" type=\"button\" onClick={() => setIssuesHidden(true)} aria-label=\"Close problems\">\n <Close size={14} />\n </button>\n </header>\n <ul className=\"sim-issues\" aria-label=\"Scenario problems\">\n {issues.map((issue, i) => (\n <li key={i} className={issue.severity}>\n {issue.message}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {scenarios.length === 0 ? (\n <p className=\"sim-empty\" role=\"status\">\n Nothing to play.\n </p>\n ) : (\n <div className={statePanel ? 'sim-grid has-side' : 'sim-grid'}>\n <div className=\"sim-trace\">\n <PlaybackControls />\n {trace ?? <TraceList />}\n {footer}\n </div>\n {statePanel && (\n <aside className=\"sim-side\" aria-label=\"State\">\n <StatePanel />\n </aside>\n )}\n </div>\n )}\n </section>\n )\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","show","jsx","jsxs"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glyphlab/scenario-react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The scenario player for React: the state of a run (play, pause, step), playback controls, the trace and state panels, and a player that puts them together. No comments, no app.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./index.js",
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"default": "./index.js"
|
|
13
|
+
},
|
|
14
|
+
"./scenario.css": "./scenario.css",
|
|
15
|
+
"./theme.css": "./theme.css"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"*.js",
|
|
19
|
+
"*.js.map",
|
|
20
|
+
"*.d.ts",
|
|
21
|
+
"*.css",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": [
|
|
26
|
+
"./scenario.css",
|
|
27
|
+
"./theme.css"
|
|
28
|
+
],
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@glyphlab/scenario-core": "^0.1.1",
|
|
31
|
+
"react": "^19",
|
|
32
|
+
"zustand": "^5"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/subtubes-io/subtubes-editor.git",
|
|
37
|
+
"directory": "packages/scenario-react"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"diagram",
|
|
44
|
+
"react",
|
|
45
|
+
"system-design",
|
|
46
|
+
"glyph"
|
|
47
|
+
]
|
|
48
|
+
}
|
package/scenario.css
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/* Scenario player. Everything is scoped to .sim-* and uses theme variables (see theme.css for defaults). */
|
|
2
|
+
|
|
3
|
+
/* The few shared controls the player uses, at no specificity, so a host that styles .icon-btn or .chip itself wins. */
|
|
4
|
+
:where(.sim-player, .sim-controls) .icon-btn {
|
|
5
|
+
display: inline-grid;
|
|
6
|
+
place-items: center;
|
|
7
|
+
width: 36px;
|
|
8
|
+
height: 36px;
|
|
9
|
+
border: 1px solid var(--border);
|
|
10
|
+
background: var(--surface);
|
|
11
|
+
color: inherit;
|
|
12
|
+
border-radius: 8px;
|
|
13
|
+
font-size: 18px;
|
|
14
|
+
line-height: 1;
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
transition: background 0.12s, border-color 0.12s;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:where(.sim-player, .sim-controls) .icon-btn:hover:not(:disabled) {
|
|
20
|
+
background: var(--surface-2);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
:where(.sim-player, .sim-controls) .icon-btn:disabled {
|
|
24
|
+
cursor: not-allowed;
|
|
25
|
+
opacity: 0.55;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
:where(.sim-player, .sim-controls) .chip {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 5px;
|
|
32
|
+
padding: 1px 8px;
|
|
33
|
+
border-radius: 999px;
|
|
34
|
+
font-size: 12px;
|
|
35
|
+
border: 1px solid var(--border);
|
|
36
|
+
background: var(--surface);
|
|
37
|
+
white-space: nowrap;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sim-player {
|
|
41
|
+
--sim-active: var(--tools);
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
position: relative;
|
|
45
|
+
height: min(400px, 48vh);
|
|
46
|
+
min-height: 160px;
|
|
47
|
+
max-height: 80%;
|
|
48
|
+
flex: none;
|
|
49
|
+
border-top: 1px solid var(--border);
|
|
50
|
+
background: var(--surface);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sim-head {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 12px;
|
|
57
|
+
padding: 8px 16px;
|
|
58
|
+
border-bottom: 1px solid var(--border);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.sim-head h2 {
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
white-space: nowrap;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.sim-head select,
|
|
67
|
+
.sim-speed {
|
|
68
|
+
padding: 5px 8px;
|
|
69
|
+
border: 1px solid var(--border);
|
|
70
|
+
border-radius: 8px;
|
|
71
|
+
background: var(--bg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.sim-desc {
|
|
75
|
+
flex: 1;
|
|
76
|
+
min-width: 0;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
font-size: 12.5px;
|
|
79
|
+
color: var(--muted);
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
text-overflow: ellipsis;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.sim-focus {
|
|
85
|
+
display: inline-flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 6px;
|
|
88
|
+
font-size: 12.5px;
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
white-space: nowrap;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.sim-focus input {
|
|
95
|
+
accent-color: var(--accent);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.sim-grid {
|
|
99
|
+
display: grid;
|
|
100
|
+
grid-template-columns: minmax(0, 1fr);
|
|
101
|
+
flex: 1;
|
|
102
|
+
min-height: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.sim-grid.has-side {
|
|
106
|
+
grid-template-columns: minmax(0, 1fr) 260px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.sim-side {
|
|
110
|
+
padding: 12px 16px;
|
|
111
|
+
overflow-y: auto;
|
|
112
|
+
border-left: 1px solid var(--border);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.sim-trace {
|
|
116
|
+
display: flex;
|
|
117
|
+
flex-direction: column;
|
|
118
|
+
min-width: 0;
|
|
119
|
+
min-height: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Problems found when checking the scenario against the diagram */
|
|
123
|
+
.sim-problems {
|
|
124
|
+
flex: none;
|
|
125
|
+
max-height: 35%;
|
|
126
|
+
overflow: auto;
|
|
127
|
+
border-bottom: 1px solid var(--border);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.sim-problems > header {
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: space-between;
|
|
134
|
+
padding: 4px 8px 0 16px;
|
|
135
|
+
font-size: 12px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.icon-btn.small {
|
|
139
|
+
display: grid;
|
|
140
|
+
place-items: center;
|
|
141
|
+
width: 24px;
|
|
142
|
+
height: 24px;
|
|
143
|
+
padding: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.sim-problems-chip {
|
|
147
|
+
flex: none;
|
|
148
|
+
padding: 3px 9px;
|
|
149
|
+
border: 1px solid color-mix(in srgb, var(--danger) 50%, var(--border));
|
|
150
|
+
border-radius: 999px;
|
|
151
|
+
background: color-mix(in srgb, var(--danger) 10%, var(--surface));
|
|
152
|
+
color: var(--danger);
|
|
153
|
+
font: inherit;
|
|
154
|
+
font-size: 12px;
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.sim-issues {
|
|
159
|
+
margin: 0;
|
|
160
|
+
padding: 4px 16px 6px 32px;
|
|
161
|
+
font-size: 12px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.sim-issues .error {
|
|
165
|
+
color: var(--danger);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.sim-issues .warning {
|
|
169
|
+
color: var(--tools);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Controls */
|
|
173
|
+
.sim-controls {
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
gap: 12px;
|
|
177
|
+
padding: 8px 16px;
|
|
178
|
+
border-bottom: 1px solid var(--border);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.sim-buttons {
|
|
182
|
+
display: flex;
|
|
183
|
+
gap: 4px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.sim-buttons .icon-btn {
|
|
187
|
+
width: 32px;
|
|
188
|
+
height: 32px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.sim-buttons .sim-play {
|
|
192
|
+
background: var(--accent);
|
|
193
|
+
border-color: var(--accent);
|
|
194
|
+
color: var(--accent-text);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.sim-buttons .sim-play:hover:not(:disabled) {
|
|
198
|
+
background: var(--accent);
|
|
199
|
+
filter: brightness(1.1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.sim-progress {
|
|
203
|
+
flex: 1;
|
|
204
|
+
min-width: 80px;
|
|
205
|
+
accent-color: var(--accent);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.sim-count {
|
|
209
|
+
min-width: 92px;
|
|
210
|
+
font-size: 12px;
|
|
211
|
+
color: var(--muted);
|
|
212
|
+
font-variant-numeric: tabular-nums;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Trace */
|
|
216
|
+
.sim-empty {
|
|
217
|
+
padding: 24px 20px;
|
|
218
|
+
font-size: 13px;
|
|
219
|
+
color: var(--muted);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.sim-steps {
|
|
223
|
+
flex: 1;
|
|
224
|
+
min-height: 0;
|
|
225
|
+
margin: 0;
|
|
226
|
+
padding: 8px 12px;
|
|
227
|
+
overflow-y: auto;
|
|
228
|
+
list-style: none;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.sim-step {
|
|
232
|
+
position: relative;
|
|
233
|
+
margin-bottom: 6px;
|
|
234
|
+
border: 1px solid var(--border);
|
|
235
|
+
border-radius: 10px;
|
|
236
|
+
background: var(--bg);
|
|
237
|
+
opacity: 0.78;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.sim-step.active {
|
|
241
|
+
opacity: 1;
|
|
242
|
+
border-color: var(--sim-active);
|
|
243
|
+
box-shadow: 0 0 0 1px var(--sim-active);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.sim-step.outcome-success.active {
|
|
247
|
+
border-color: var(--ok);
|
|
248
|
+
box-shadow: 0 0 0 1px var(--ok);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.sim-step.outcome-failure.active {
|
|
252
|
+
border-color: var(--danger);
|
|
253
|
+
box-shadow: 0 0 0 1px var(--danger);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.sim-step-main {
|
|
257
|
+
display: flex;
|
|
258
|
+
gap: 10px;
|
|
259
|
+
width: 100%;
|
|
260
|
+
padding: 8px 40px 8px 10px;
|
|
261
|
+
border: none;
|
|
262
|
+
background: none;
|
|
263
|
+
text-align: left;
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.sim-index {
|
|
268
|
+
flex: none;
|
|
269
|
+
display: grid;
|
|
270
|
+
place-items: center;
|
|
271
|
+
width: 22px;
|
|
272
|
+
height: 22px;
|
|
273
|
+
border-radius: 50%;
|
|
274
|
+
background: var(--surface-2);
|
|
275
|
+
font-size: 11px;
|
|
276
|
+
font-weight: 700;
|
|
277
|
+
color: var(--muted);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.sim-step.active .sim-index {
|
|
281
|
+
background: var(--sim-active);
|
|
282
|
+
color: #1b1405;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.sim-step-content {
|
|
286
|
+
min-width: 0;
|
|
287
|
+
flex: 1;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.sim-step-head {
|
|
291
|
+
display: flex;
|
|
292
|
+
align-items: center;
|
|
293
|
+
gap: 8px;
|
|
294
|
+
font-size: 13px;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.sim-kind {
|
|
298
|
+
padding: 0 6px;
|
|
299
|
+
border-radius: 4px;
|
|
300
|
+
background: var(--surface-2);
|
|
301
|
+
font-size: 10px;
|
|
302
|
+
font-weight: 700;
|
|
303
|
+
letter-spacing: 0.05em;
|
|
304
|
+
text-transform: uppercase;
|
|
305
|
+
color: var(--muted);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.sim-kind.kind-call {
|
|
309
|
+
color: var(--runtime);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.sim-kind.kind-end {
|
|
313
|
+
color: var(--ok);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.sim-step-title {
|
|
317
|
+
margin: 2px 0 0;
|
|
318
|
+
font-size: 13px;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.sim-step-detail {
|
|
322
|
+
margin: 2px 0 0;
|
|
323
|
+
font-size: 12px;
|
|
324
|
+
color: var(--muted);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.sim-changes,
|
|
328
|
+
.sim-decision {
|
|
329
|
+
display: flex;
|
|
330
|
+
flex-wrap: wrap;
|
|
331
|
+
gap: 4px 10px;
|
|
332
|
+
margin: 6px 0 0;
|
|
333
|
+
padding: 0;
|
|
334
|
+
list-style: none;
|
|
335
|
+
font-size: 12px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.sim-changes li {
|
|
339
|
+
display: inline-flex;
|
|
340
|
+
align-items: center;
|
|
341
|
+
gap: 6px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.sim-changes code {
|
|
345
|
+
padding: 0 5px;
|
|
346
|
+
border-radius: 4px;
|
|
347
|
+
background: var(--surface-2);
|
|
348
|
+
font-family: var(--mono);
|
|
349
|
+
font-size: 11.5px;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.sim-decision li {
|
|
353
|
+
color: var(--muted);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.sim-decision li.taken {
|
|
357
|
+
color: var(--text);
|
|
358
|
+
font-weight: 600;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.chip.ai {
|
|
362
|
+
color: var(--runtime);
|
|
363
|
+
border-color: color-mix(in srgb, var(--runtime) 45%, transparent);
|
|
364
|
+
background: color-mix(in srgb, var(--runtime) 12%, transparent);
|
|
365
|
+
font-size: 10.5px;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.sim-warning {
|
|
369
|
+
margin: 4px 0 0;
|
|
370
|
+
font-size: 12px;
|
|
371
|
+
color: var(--tools);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.sim-comment-btn {
|
|
375
|
+
position: absolute;
|
|
376
|
+
top: 6px;
|
|
377
|
+
right: 8px;
|
|
378
|
+
display: grid;
|
|
379
|
+
place-items: center;
|
|
380
|
+
width: 26px;
|
|
381
|
+
height: 26px;
|
|
382
|
+
border: 1px solid transparent;
|
|
383
|
+
border-radius: 6px;
|
|
384
|
+
background: none;
|
|
385
|
+
color: var(--muted);
|
|
386
|
+
opacity: 0;
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.sim-step:hover .sim-comment-btn,
|
|
391
|
+
.sim-comment-btn:focus-visible {
|
|
392
|
+
opacity: 1;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.sim-comment-btn:hover {
|
|
396
|
+
border-color: var(--border);
|
|
397
|
+
background: var(--surface-2);
|
|
398
|
+
color: var(--accent);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.sim-inline {
|
|
402
|
+
display: flex;
|
|
403
|
+
flex-direction: column;
|
|
404
|
+
gap: 8px;
|
|
405
|
+
padding: 0 10px 10px 42px;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* State and outcome */
|
|
409
|
+
.sim-state h3 {
|
|
410
|
+
margin: 0 0 6px;
|
|
411
|
+
font-size: 11px;
|
|
412
|
+
letter-spacing: 0.06em;
|
|
413
|
+
text-transform: uppercase;
|
|
414
|
+
color: var(--muted);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.sim-state h3:not(:first-child) {
|
|
418
|
+
margin-top: 14px;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.sim-state dl {
|
|
422
|
+
margin: 0;
|
|
423
|
+
font-size: 12.5px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.sim-state dl > div {
|
|
427
|
+
display: flex;
|
|
428
|
+
justify-content: space-between;
|
|
429
|
+
gap: 10px;
|
|
430
|
+
padding: 3px 6px;
|
|
431
|
+
border-radius: 6px;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.sim-state dl > div.changed {
|
|
435
|
+
background: color-mix(in srgb, var(--sim-active) 18%, transparent);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.sim-state dt {
|
|
439
|
+
flex: 1 1 0;
|
|
440
|
+
min-width: 0;
|
|
441
|
+
color: var(--muted);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.sim-state dd {
|
|
445
|
+
flex: 0 0 auto;
|
|
446
|
+
min-width: 4ch;
|
|
447
|
+
max-width: 60%;
|
|
448
|
+
margin: 0;
|
|
449
|
+
overflow-wrap: break-word;
|
|
450
|
+
text-align: right;
|
|
451
|
+
font-weight: 600;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.sim-state code {
|
|
455
|
+
font-family: var(--mono);
|
|
456
|
+
font-size: 12px;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.sim-muted {
|
|
460
|
+
font-size: 12.5px;
|
|
461
|
+
color: var(--muted);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.sim-outcome {
|
|
465
|
+
margin-bottom: 12px;
|
|
466
|
+
padding: 10px 12px;
|
|
467
|
+
border: 1px solid var(--border);
|
|
468
|
+
border-left-width: 4px;
|
|
469
|
+
border-radius: 8px;
|
|
470
|
+
font-size: 12.5px;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.sim-outcome p {
|
|
474
|
+
margin: 4px 0 0;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.sim-outcome.outcome-success {
|
|
478
|
+
border-left-color: var(--ok);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.sim-outcome.outcome-failure {
|
|
482
|
+
border-left-color: var(--danger);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.sim-outcome.outcome-blocked {
|
|
486
|
+
border-left-color: var(--tools);
|
|
487
|
+
}
|
package/theme.css
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* Default colours for the scenario player, for a host that does not define these variables itself. They are declared
|
|
2
|
+
with :where(), so any value the host sets on :root wins whatever the order of the style sheets. */
|
|
3
|
+
:where(:root) {
|
|
4
|
+
--mono: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
5
|
+
--bg: #f4f5f7;
|
|
6
|
+
--surface: #ffffff;
|
|
7
|
+
--surface-2: #f0f2f5;
|
|
8
|
+
--border: #dfe3e8;
|
|
9
|
+
--text: #14171a;
|
|
10
|
+
--muted: #667085;
|
|
11
|
+
--accent: #2563eb;
|
|
12
|
+
--accent-text: #ffffff;
|
|
13
|
+
--ok: #059669;
|
|
14
|
+
--danger: #dc2626;
|
|
15
|
+
--runtime: #7c3aed;
|
|
16
|
+
--tools: #d97706;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@media (prefers-color-scheme: dark) {
|
|
20
|
+
:where(:root) {
|
|
21
|
+
--bg: #161718;
|
|
22
|
+
--surface: #1f2022;
|
|
23
|
+
--surface-2: #2a2b2e;
|
|
24
|
+
--border: #35373b;
|
|
25
|
+
--text: #e8eaed;
|
|
26
|
+
--muted: #9aa0a8;
|
|
27
|
+
--accent: #3b82f6;
|
|
28
|
+
--ok: #34d399;
|
|
29
|
+
--danger: #f87171;
|
|
30
|
+
--runtime: #a78bfa;
|
|
31
|
+
--tools: #fbbf24;
|
|
32
|
+
}
|
|
33
|
+
}
|