@picked/svelte 0.0.1-dev.1789541668638 → 0.0.1-dev.1793753513002

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.
Files changed (118) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +51 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/_virtual/_vite/preload-helper.js +1 -1
  5. package/dist/components/index.js +1 -1
  6. package/dist/experience/ExperienceHost.svelte +29 -13
  7. package/dist/experience/ExperienceSlot.svelte +21 -3
  8. package/dist/experience/ExperienceWrapper.svelte +23 -0
  9. package/dist/form/index.js +1 -1
  10. package/dist/hooks/action.d.ts +47 -18
  11. package/dist/hooks/action.js +1 -1
  12. package/dist/hooks/index.d.ts +2 -2
  13. package/dist/hooks/index.js +1 -1
  14. package/dist/hooks/step.d.ts +14 -0
  15. package/dist/hooks/step.js +1 -1
  16. package/dist/plugins/devtools/DevToolsExperience.svelte +545 -203
  17. package/dist/plugins/devtools/components/AdaptedToolHost.svelte +2 -2
  18. package/dist/plugins/devtools/constants.d.ts +5 -0
  19. package/dist/plugins/devtools/constants.js +1 -0
  20. package/dist/plugins/devtools/customTools.d.ts +42 -6
  21. package/dist/plugins/devtools/customTools.js +1 -1
  22. package/dist/plugins/devtools/derive/answer-display/answer-display.d.ts +112 -0
  23. package/dist/plugins/devtools/derive/answer-display/answer-display.js +1 -0
  24. package/dist/plugins/devtools/derive/answer-display/index.d.ts +3 -0
  25. package/dist/plugins/devtools/derive/answer-display/index.js +1 -0
  26. package/dist/plugins/devtools/derive/answer-history/answer-history.d.ts +76 -0
  27. package/dist/plugins/devtools/derive/answer-history/answer-history.js +1 -0
  28. package/dist/plugins/devtools/derive/answer-history/index.d.ts +3 -0
  29. package/dist/plugins/devtools/derive/answer-history/index.js +1 -0
  30. package/dist/plugins/devtools/derive/guide-sections/guide-sections.d.ts +67 -0
  31. package/dist/plugins/devtools/derive/guide-sections/guide-sections.js +1 -0
  32. package/dist/plugins/devtools/derive/guide-sections/index.d.ts +3 -0
  33. package/dist/plugins/devtools/derive/guide-sections/index.js +1 -0
  34. package/dist/plugins/devtools/derive/index.d.ts +28 -0
  35. package/dist/plugins/devtools/derive/index.js +1 -0
  36. package/dist/plugins/devtools/derive/journey-outline/index.d.ts +3 -0
  37. package/dist/plugins/devtools/derive/journey-outline/index.js +1 -0
  38. package/dist/plugins/devtools/derive/journey-outline/journey-outline.d.ts +86 -0
  39. package/dist/plugins/devtools/derive/journey-outline/journey-outline.js +1 -0
  40. package/dist/plugins/devtools/derive/journey-pick/index.d.ts +3 -0
  41. package/dist/plugins/devtools/derive/journey-pick/index.js +1 -0
  42. package/dist/plugins/devtools/derive/journey-pick/journey-pick.d.ts +73 -0
  43. package/dist/plugins/devtools/derive/journey-pick/journey-pick.js +1 -0
  44. package/dist/plugins/devtools/derive/journey-timeline/index.d.ts +3 -0
  45. package/dist/plugins/devtools/derive/journey-timeline/index.js +1 -0
  46. package/dist/plugins/devtools/derive/journey-timeline/journey-timeline.d.ts +70 -0
  47. package/dist/plugins/devtools/derive/journey-timeline/journey-timeline.js +1 -0
  48. package/dist/plugins/devtools/derive/journey-tree/index.d.ts +3 -0
  49. package/dist/plugins/devtools/derive/journey-tree/index.js +1 -0
  50. package/dist/plugins/devtools/derive/journey-tree/journey-tree.d.ts +191 -0
  51. package/dist/plugins/devtools/derive/journey-tree/journey-tree.js +1 -0
  52. package/dist/plugins/devtools/derive/plain-language/index.d.ts +3 -0
  53. package/dist/plugins/devtools/derive/plain-language/index.js +1 -0
  54. package/dist/plugins/devtools/derive/plain-language/plain-language.d.ts +73 -0
  55. package/dist/plugins/devtools/derive/plain-language/plain-language.js +1 -0
  56. package/dist/plugins/devtools/derive/plain-logic/index.d.ts +3 -0
  57. package/dist/plugins/devtools/derive/plain-logic/index.js +1 -0
  58. package/dist/plugins/devtools/derive/plain-logic/plain-logic.d.ts +48 -0
  59. package/dist/plugins/devtools/derive/plain-logic/plain-logic.js +1 -0
  60. package/dist/plugins/devtools/derive/safety-posture/index.d.ts +3 -0
  61. package/dist/plugins/devtools/derive/safety-posture/index.js +1 -0
  62. package/dist/plugins/devtools/derive/safety-posture/safety-posture.d.ts +43 -0
  63. package/dist/plugins/devtools/derive/safety-posture/safety-posture.js +1 -0
  64. package/dist/plugins/devtools/derive/visited-steps/index.d.ts +3 -0
  65. package/dist/plugins/devtools/derive/visited-steps/index.js +1 -0
  66. package/dist/plugins/devtools/derive/visited-steps/visited-steps.d.ts +35 -0
  67. package/dist/plugins/devtools/derive/visited-steps/visited-steps.js +1 -0
  68. package/dist/plugins/devtools/derive/walk-route/index.d.ts +3 -0
  69. package/dist/plugins/devtools/derive/walk-route/index.js +1 -0
  70. package/dist/plugins/devtools/derive/walk-route/walk-route.d.ts +22 -0
  71. package/dist/plugins/devtools/derive/walk-route/walk-route.js +1 -0
  72. package/dist/plugins/devtools/index.d.ts +5 -2
  73. package/dist/plugins/devtools/index.js +1 -1
  74. package/dist/plugins/devtools/launcher/Launcher.svelte +150 -0
  75. package/dist/plugins/devtools/launcher/launcher.d.ts +36 -0
  76. package/dist/plugins/devtools/launcher/launcher.js +1 -0
  77. package/dist/plugins/devtools/options.d.ts +106 -0
  78. package/dist/plugins/devtools/options.js +1 -0
  79. package/dist/plugins/devtools/plugin.d.ts +8 -8
  80. package/dist/plugins/devtools/plugin.js +1 -1
  81. package/dist/plugins/devtools/posture-context.d.ts +15 -0
  82. package/dist/plugins/devtools/posture-context.js +1 -0
  83. package/dist/plugins/devtools/review/answers/AnswersPanel.svelte +526 -0
  84. package/dist/plugins/devtools/review/guide/GuideLine.svelte +20 -0
  85. package/dist/plugins/devtools/review/guide/GuidePanel.svelte +133 -0
  86. package/dist/plugins/devtools/review/index.d.ts +6 -0
  87. package/dist/plugins/devtools/review/index.js +1 -0
  88. package/dist/plugins/devtools/review/journey/JourneyMap.svelte +1076 -0
  89. package/dist/plugins/devtools/review/journey/JourneyPanel.svelte +584 -0
  90. package/dist/plugins/devtools/review/journey/JourneyTimeline.svelte +750 -0
  91. package/dist/plugins/devtools/review/journey/TimelineMarker.svelte +59 -0
  92. package/dist/plugins/devtools/review/session/SessionPanel.svelte +214 -0
  93. package/dist/plugins/devtools/review/variations/VariationsPanel.svelte +279 -0
  94. package/dist/plugins/devtools/screens/config/ConfigScreen.svelte +6 -0
  95. package/dist/plugins/devtools/screens/custom-tools/CustomToolsScreen.svelte +3 -3
  96. package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +6 -0
  97. package/dist/plugins/devtools/session/index.d.ts +3 -0
  98. package/dist/plugins/devtools/session/index.js +1 -0
  99. package/dist/plugins/devtools/session/session.d.ts +90 -0
  100. package/dist/plugins/devtools/session/session.js +1 -0
  101. package/dist/plugins/devtools/session-access.d.ts +32 -0
  102. package/dist/plugins/devtools/session-access.js +1 -0
  103. package/dist/plugins/devtools/sidebar/Sidebar.svelte +105 -0
  104. package/dist/plugins/devtools/sidebar/SidebarRow.svelte +117 -0
  105. package/dist/plugins/devtools/sidebar/nav.d.ts +105 -0
  106. package/dist/plugins/devtools/sidebar/nav.js +1 -0
  107. package/dist/plugins/devtools/ui/actions/Button.svelte +10 -1
  108. package/dist/plugins/devtools/ui/actions/CopyButton.svelte +13 -1
  109. package/dist/plugins/devtools/ui/data/Badge.svelte +3 -2
  110. package/dist/plugins/devtools/ui/data/EmptyState.svelte +16 -1
  111. package/dist/plugins/devtools/ui/feedback/Banner.svelte +3 -2
  112. package/dist/plugins/framework/index.js +1 -1
  113. package/dist/render/browser.js +1 -1
  114. package/package.json +23 -12
  115. package/dist/form/__tests__/CreateFormHarness.svelte +0 -30
  116. package/dist/hooks/events.d.ts +0 -2
  117. package/dist/hooks/events.js +0 -1
  118. package/dist/vite-env.d.js +0 -0
@@ -0,0 +1,584 @@
1
+ <script lang="ts">
2
+ /**
3
+ * @file The Journey map tab: any journey's flow in plain English — the Developer group's
4
+ * experience tree without the node ids or the raw JSON Logic. It opens on the journey in front
5
+ * of the reviewer, and the picker reaches every other experience in the workspace.
6
+ *
7
+ * Navigation is BACKWARDS ONLY, and only in the journey that is OPEN. A screen already visited is
8
+ * a plain `goToStepByKey`: the flow has been through every action in between, so returning to it
9
+ * is safe. A screen AHEAD is rendered but not clickable — jumping forward skips the actions that
10
+ * make it renderable, and the screen it lands on can render an error state no visitor could
11
+ * reach. Forward motion belongs to the journey itself, or to a custom tool that replays one.
12
+ *
13
+ * THREE VIEWS, ONE GRAPH. The outline answers "where am I, and why did it go there"; the map
14
+ * answers "what shape is this, and where does each branch end"; the timeline answers "in what
15
+ * order, and where do the branches meet again". All three read the same `treeStore` through the
16
+ * same vocabulary modules, so a screen is named identically in each and they cannot drift.
17
+ */
18
+ import type { Picked } from '@picked/client'
19
+ import { Badge, Banner, Button, Card, EmptyState, Stack } from '../../ui'
20
+ import { buildOutline, buildTimeline, buildTree } from '../../derive'
21
+ import type { FieldLookup, OutlineRow, SafetyPosture, Trails } from '../../derive'
22
+ import type { JourneyView } from '../../session'
23
+ import JourneyMap from './JourneyMap.svelte'
24
+ import JourneyTimeline from './JourneyTimeline.svelte'
25
+
26
+ interface JourneyPanelProps {
27
+ client: Picked
28
+ posture: SafetyPosture
29
+ journey: JourneyView | null
30
+ /** Step keys entered this session, per experience, from the session record. */
31
+ trails: Trails
32
+ /** Experience keys the picker never lists and the tab never follows. */
33
+ hiddenKeys: ReadonlySet<string>
34
+ }
35
+
36
+ const { client, posture, journey, trails, hiddenKeys }: JourneyPanelProps = $props()
37
+
38
+ /** The slice of an experience's flow graph the three views read. */
39
+ interface FlowGraph {
40
+ nodes: { id: string; type: string; name?: string }[]
41
+ edges: { id: string; sourceNodeId: string; destinationNodeId: string; sourceHandle?: string }[]
42
+ conditions: { id: string; name: string; logic: unknown }[]
43
+ }
44
+
45
+ interface JourneyChoice {
46
+ key: string
47
+ name: string
48
+ /** Nodes in its flow — read once, to pick a sensible default when no journey is open. */
49
+ size: number
50
+ }
51
+
52
+ const EMPTY_GRAPH: FlowGraph = { nodes: [], edges: [], conditions: [] }
53
+ const NOTHING_VISITED: ReadonlySet<string> = new Set()
54
+
55
+ // SDK stores read straight off the prop: effector stores meet Svelte's store contract, so `$`
56
+ // subscribes, and `$derived` keeps it on whichever client the panel is given.
57
+ const experiences = $derived(client.experiences.listStore)
58
+ // `null` FOLLOWS the open journey, so the tab keeps tracking the visitor until the reviewer picks
59
+ // something else on purpose.
60
+ let picked = $state<string | null>(null)
61
+ let expandedRule = $state<string | null>(null)
62
+ // The union goes on `$state`, not on the `let`. With the annotation alone TypeScript narrows the
63
+ // variable to its initial literal and the `view === 'map'` below reads as a comparison that can
64
+ // never hold — `$derived` is an expression in this same scope, so nothing widens it back.
65
+ let view = $state<'outline' | 'map' | 'timeline'>('map')
66
+
67
+ /** The open journey, unless it is one the tab never follows. */
68
+ const followed = $derived(
69
+ journey !== null && !hiddenKeys.has(journey.experienceKey) ? journey : null,
70
+ )
71
+
72
+ const choices = $derived<JourneyChoice[]>(
73
+ $experiences
74
+ .filter((experience) => !hiddenKeys.has(experience.key))
75
+ .map((experience) => ({
76
+ key: experience.key,
77
+ name: experience.name,
78
+ size: experience.treeStore.getState().nodes.length,
79
+ }))
80
+ .sort((a, b) => a.name.localeCompare(b.name)),
81
+ )
82
+
83
+ /** With nothing open and nothing picked, the largest flow — the main journey — is the one to show. */
84
+ const largest = $derived(
85
+ choices.reduce<JourneyChoice | null>(
86
+ (best, choice) => (best === null || choice.size > best.size ? choice : best),
87
+ null,
88
+ ),
89
+ )
90
+
91
+ const selectedKey = $derived(picked ?? followed?.experienceKey ?? largest?.key ?? null)
92
+ /** The trail, "you are here" and every jump belong to the journey the visitor is in, and only it. */
93
+ const isOpen = $derived(followed !== null && followed.experienceKey === selectedKey)
94
+
95
+ // The graph lives on the EXPERIENCE, not the instance, as its own store. Read through a `$derived`
96
+ // store variable, so Svelte re-subscribes when the selection points at a different journey and
97
+ // drops the old one — no hand-kept nested subscription.
98
+ //
99
+ // It reads the shared `listStore` rather than `experiences.getByKey`, which mints a NEW derived
100
+ // store per call and never releases it.
101
+ const treeStore = $derived(
102
+ selectedKey === null
103
+ ? undefined
104
+ : $experiences.find((experience) => experience.key === selectedKey)?.treeStore,
105
+ )
106
+ const graph: FlowGraph = $derived($treeStore ?? EMPTY_GRAPH)
107
+
108
+ // Fields arrive asynchronously and AFTER the graph does. Read reactively, so the views re-render
109
+ // into plain English once they land — read once instead, and a decision authored on a field that
110
+ // had not loaded yet would stay labelled by its raw key forever.
111
+ const fields = $derived(client.fields.listStore)
112
+
113
+ const fieldsByKey = $derived(new Map($fields.map((field) => [field.key, field])))
114
+
115
+ const fieldLookup = $derived<FieldLookup>((key) => {
116
+ const field = fieldsByKey.get(key)
117
+ if (field === undefined) return undefined
118
+
119
+ return { name: field.name, label: field.label, options: field.options ?? null }
120
+ })
121
+
122
+ const source = $derived({
123
+ nodes: graph.nodes,
124
+ edges: graph.edges,
125
+ conditions: graph.conditions,
126
+ fieldLookup,
127
+ visited: (selectedKey === null ? undefined : trails.get(selectedKey)) ?? NOTHING_VISITED,
128
+ currentStepKey: isOpen ? (followed?.stepKey ?? null) : null,
129
+ })
130
+
131
+ const rows = $derived(buildOutline(source))
132
+
133
+ // Laid out only while its view is on screen — the map is the default, so in practice nearly
134
+ // always, but a reviewer reading the outline pays nothing for it.
135
+ const layout = $derived(view === 'map' ? buildTree(source) : null)
136
+ const timeline = $derived(view === 'timeline' ? buildTimeline(source) : null)
137
+
138
+ const canNavigate = $derived(posture.canNavigate && isOpen)
139
+
140
+ /**
141
+ * Whether an outline row can take the visitor back to its screen.
142
+ *
143
+ * @param {OutlineRow} row - The row to test.
144
+ * @returns {boolean} True for a visited screen in the open journey, when the posture allows it.
145
+ * @example
146
+ * canJump(row) // true for a screen on the route taken
147
+ */
148
+ function canJump(row: OutlineRow): boolean {
149
+ return row.kind === 'screen' && row.state === 'visited' && row.stepKey !== null && canNavigate
150
+ }
151
+
152
+ /**
153
+ * Why a screen row cannot be jumped to, as its tooltip.
154
+ *
155
+ * @param {OutlineRow} row - The row to explain.
156
+ * @returns {string | undefined} The reason, or undefined when there is nothing to explain.
157
+ * @example
158
+ * jumpReason(aheadRow) // "You haven't answered enough to get here yet."
159
+ */
160
+ function jumpReason(row: OutlineRow): string | undefined {
161
+ if (row.kind !== 'screen') return undefined
162
+ if (posture.reason !== null) return posture.reason
163
+ if (!isOpen) return "This journey isn't open, so there is nowhere to go back to."
164
+ if (row.state === 'current') return "You're on this screen."
165
+ if (row.state === 'ahead') return "You haven't answered enough to get here yet."
166
+ return undefined
167
+ }
168
+
169
+ /**
170
+ * Moves the open journey back to a screen.
171
+ *
172
+ * @param {string} stepKey - The screen's step key.
173
+ * @example
174
+ * jumpTo('contact-details')
175
+ */
176
+ function jumpTo(stepKey: string): void {
177
+ if (followed === null || !isOpen) return
178
+ followed.goToStepByKey(stepKey)
179
+ }
180
+
181
+ /**
182
+ * Moves the open journey back to an outline row's screen.
183
+ *
184
+ * @param {OutlineRow} row - The row the reviewer chose.
185
+ * @example
186
+ * jump(row)
187
+ */
188
+ function jump(row: OutlineRow): void {
189
+ if (row.kind !== 'screen' || row.stepKey === null) return
190
+ jumpTo(row.stepKey)
191
+ }
192
+
193
+ /**
194
+ * Shows the journey chosen in the picker. Picking the open journey goes back to FOLLOWING it,
195
+ * rather than pinning it.
196
+ *
197
+ * @param {Event} event - The picker's change event.
198
+ * @example
199
+ * pick(event)
200
+ */
201
+ function pick(event: Event): void {
202
+ if (!(event.currentTarget instanceof HTMLSelectElement)) return
203
+ const key = event.currentTarget.value
204
+ picked = key === followed?.experienceKey ? null : key
205
+ }
206
+
207
+ /**
208
+ * Shows a decision's verbatim rule, or hides it again.
209
+ *
210
+ * @param {string} id - The decision row's id.
211
+ * @example
212
+ * toggleRule(row.id)
213
+ */
214
+ function toggleRule(id: string): void {
215
+ expandedRule = expandedRule === id ? null : id
216
+ }
217
+ </script>
218
+
219
+ <Stack direction="vertical" gap="medium">
220
+ {#if selectedKey === null}
221
+ <EmptyState
222
+ message="No journeys yet"
223
+ hint="The workspace has not loaded its experiences. Reload the page if this does not change."
224
+ />
225
+ {:else}
226
+ <!-- One row: how to look on the left, what to look at on the right. -->
227
+ <div class="controls">
228
+ <div class="views" role="group" aria-label="Journey view">
229
+ <button
230
+ type="button"
231
+ class="view"
232
+ class:selected={view === 'outline'}
233
+ aria-pressed={view === 'outline'}
234
+ data-testid="devtools-journey-view-outline"
235
+ onclick={() => (view = 'outline')}
236
+ >
237
+ Outline
238
+ </button>
239
+ <button
240
+ type="button"
241
+ class="view"
242
+ class:selected={view === 'map'}
243
+ aria-pressed={view === 'map'}
244
+ data-testid="devtools-journey-view-map"
245
+ onclick={() => (view = 'map')}
246
+ >
247
+ Map
248
+ </button>
249
+ <button
250
+ type="button"
251
+ class="view"
252
+ class:selected={view === 'timeline'}
253
+ aria-pressed={view === 'timeline'}
254
+ data-testid="devtools-journey-view-timeline"
255
+ onclick={() => (view = 'timeline')}
256
+ >
257
+ Timeline
258
+ </button>
259
+ </div>
260
+
261
+ <div class="picker">
262
+ {#if followed !== null && !isOpen}
263
+ <Button variant="ghost" size="small" onclick={() => (picked = null)}>
264
+ Back to the open journey
265
+ </Button>
266
+ {/if}
267
+ <select
268
+ class="picker-select"
269
+ aria-label="Journey"
270
+ value={selectedKey}
271
+ onchange={pick}
272
+ data-testid="devtools-journey-picker"
273
+ >
274
+ {#each choices as choice (choice.key)}
275
+ <option value={choice.key}>
276
+ {choice.name}{choice.key === followed?.experienceKey ? ' — open now' : ''}
277
+ </option>
278
+ {/each}
279
+ </select>
280
+ </div>
281
+ </div>
282
+
283
+ {#if posture.readOnly && posture.reason !== null}
284
+ <Banner status="warning" testId="devtools-journey-readonly">{posture.reason}</Banner>
285
+ {/if}
286
+
287
+ <Card sectioned>
288
+ {#if rows.length === 0}
289
+ <EmptyState
290
+ message="This journey has no screens yet"
291
+ hint="Its flow has not loaded, or it is built entirely from background steps."
292
+ />
293
+ {:else if layout !== null}
294
+ <!-- Keyed on the experience, so a switch re-frames the map instead of keeping the last
295
+ journey's pan and zoom over a different tree. -->
296
+ {#key selectedKey}
297
+ <JourneyMap {layout} {canNavigate} onJump={jumpTo} />
298
+ {/key}
299
+ {:else if timeline !== null}
300
+ {#key selectedKey}
301
+ <JourneyTimeline layout={timeline} {canNavigate} onJump={jumpTo} />
302
+ {/key}
303
+ {:else}
304
+ <ul class="outline" data-testid="devtools-journey">
305
+ {#each rows as row (row.id)}
306
+ <li class="row kind-{row.kind}" style:--depth={row.depth}>
307
+ {#if row.kind === 'screen'}
308
+ <button
309
+ type="button"
310
+ class="screen state-{row.state}"
311
+ class:jumpable={canJump(row)}
312
+ disabled={!canJump(row)}
313
+ title={jumpReason(row)}
314
+ data-testid="devtools-journey-screen"
315
+ onclick={() => jump(row)}
316
+ >
317
+ <span class="marker" aria-hidden="true"></span>
318
+ <span class="screen-label">{row.label}</span>
319
+ {#if row.state === 'current'}
320
+ <Badge status="info" size="small">You are here</Badge>
321
+ {:else if row.state === 'visited' && canNavigate}
322
+ <span class="jump-hint">Go back</span>
323
+ {/if}
324
+ </button>
325
+ {:else if row.kind === 'decision'}
326
+ <div class="decision">
327
+ <span class="decision-label">If {row.label}</span>
328
+ <button type="button" class="disclosure" onclick={() => toggleRule(row.id)}>
329
+ {expandedRule === row.id ? 'Hide rule' : 'Show rule'}
330
+ </button>
331
+ </div>
332
+ {#if expandedRule === row.id}
333
+ <!-- The sentence above is lossy by design; this is what makes that safe. -->
334
+ <pre class="rule">{row.rule}</pre>
335
+ {/if}
336
+ {:else if row.kind === 'arm'}
337
+ <span class="arm">{row.label}</span>
338
+ {:else if row.kind === 'end'}
339
+ <span class="end-row">⇥ Ends: {row.label} (behind the scenes)</span>
340
+ {:else if row.kind === 'journey'}
341
+ <span class="journey-row">
342
+ <span class="marker muted" aria-hidden="true"></span>
343
+ Opens {row.label}
344
+ </span>
345
+ {:else if row.kind === 'backref'}
346
+ <span class="backref">↩ back to {row.label}</span>
347
+ {:else}
348
+ <span class="start">{row.label}</span>
349
+ {/if}
350
+ </li>
351
+ {/each}
352
+ </ul>
353
+ {/if}
354
+ </Card>
355
+ {/if}
356
+ </Stack>
357
+
358
+ <style>
359
+ .controls {
360
+ display: flex;
361
+ align-items: center;
362
+ justify-content: space-between;
363
+ gap: var(--space-2);
364
+ flex-wrap: wrap;
365
+ }
366
+
367
+ .picker {
368
+ display: flex;
369
+ align-items: center;
370
+ gap: var(--space-2);
371
+ }
372
+
373
+ .picker-select {
374
+ min-width: 260px;
375
+ max-width: 100%;
376
+ padding: var(--space-1) var(--space-2);
377
+ font-family: inherit;
378
+ font-size: var(--text-sm);
379
+ color: var(--color-gray-900);
380
+ background-color: var(--color-white);
381
+ border: 1px solid var(--color-gray-300);
382
+ border-radius: var(--radius-md);
383
+ }
384
+
385
+ .picker-select:focus-visible {
386
+ outline: var(--ring-width) solid var(--ring-color);
387
+ outline-offset: var(--ring-offset);
388
+ }
389
+
390
+ .views {
391
+ display: inline-flex;
392
+ padding: 2px;
393
+ gap: 2px;
394
+ background-color: var(--color-gray-100);
395
+ border-radius: var(--radius-md);
396
+ }
397
+
398
+ .view {
399
+ padding: var(--space-0-5) var(--space-3);
400
+ font-family: inherit;
401
+ font-size: var(--text-xs);
402
+ font-weight: var(--font-medium);
403
+ color: var(--color-gray-500);
404
+ background: none;
405
+ border: none;
406
+ border-radius: var(--radius-sm);
407
+ cursor: pointer;
408
+ }
409
+
410
+ .view.selected {
411
+ color: var(--color-gray-900);
412
+ background-color: var(--color-white);
413
+ box-shadow: var(--shadow-card);
414
+ }
415
+
416
+ .view:focus-visible {
417
+ outline: var(--ring-width) solid var(--ring-color);
418
+ outline-offset: -1px;
419
+ }
420
+
421
+ .outline {
422
+ list-style: none;
423
+ margin: 0;
424
+ padding: var(--space-2) var(--space-1) var(--space-3);
425
+ }
426
+
427
+ .row {
428
+ padding-left: calc(var(--depth) * var(--space-3));
429
+ }
430
+
431
+ .screen {
432
+ display: flex;
433
+ align-items: center;
434
+ gap: var(--space-2);
435
+ width: 100%;
436
+ text-align: left;
437
+ background: none;
438
+ border: none;
439
+ font-family: inherit;
440
+ font-size: var(--text-sm);
441
+ color: var(--color-gray-900);
442
+ padding: var(--space-1-5) var(--space-2);
443
+ border-radius: var(--radius-md);
444
+ }
445
+
446
+ .screen:disabled {
447
+ cursor: default;
448
+ }
449
+
450
+ .screen.jumpable {
451
+ cursor: pointer;
452
+ }
453
+
454
+ .screen.jumpable:hover {
455
+ background-color: var(--color-gray-50);
456
+ }
457
+
458
+ .screen:focus-visible,
459
+ .disclosure:focus-visible {
460
+ outline: var(--ring-width) solid var(--ring-color);
461
+ outline-offset: calc(-1 * var(--ring-width));
462
+ }
463
+
464
+ .screen-label {
465
+ flex: 1;
466
+ min-width: 0;
467
+ overflow-wrap: anywhere;
468
+ }
469
+
470
+ .state-ahead .screen-label {
471
+ color: var(--color-gray-500);
472
+ }
473
+
474
+ .state-current .screen-label {
475
+ font-weight: var(--font-semibold);
476
+ }
477
+
478
+ .marker {
479
+ width: 8px;
480
+ height: 8px;
481
+ border-radius: var(--radius-full);
482
+ background-color: var(--color-gray-300);
483
+ flex-shrink: 0;
484
+ }
485
+
486
+ .state-visited .marker {
487
+ background-color: var(--color-blue-600);
488
+ }
489
+
490
+ .state-current .marker {
491
+ background-color: var(--color-blue-600);
492
+ box-shadow: 0 0 0 3px #dbeafe;
493
+ }
494
+
495
+ .marker.muted {
496
+ width: 6px;
497
+ height: 6px;
498
+ background-color: var(--color-gray-200);
499
+ }
500
+
501
+ .jump-hint {
502
+ font-size: var(--text-xs);
503
+ color: var(--color-blue-600);
504
+ opacity: 0;
505
+ }
506
+
507
+ .screen.jumpable:hover .jump-hint,
508
+ .screen.jumpable:focus-visible .jump-hint {
509
+ opacity: 1;
510
+ }
511
+
512
+ .decision {
513
+ display: flex;
514
+ align-items: baseline;
515
+ gap: var(--space-2);
516
+ padding: var(--space-2) var(--space-2) var(--space-1);
517
+ }
518
+
519
+ .decision-label {
520
+ flex: 1;
521
+ min-width: 0;
522
+ font-size: var(--text-sm);
523
+ font-weight: var(--font-medium);
524
+ color: var(--color-gray-700);
525
+ overflow-wrap: anywhere;
526
+ }
527
+
528
+ .disclosure {
529
+ background: none;
530
+ border: none;
531
+ font-family: inherit;
532
+ font-size: var(--text-xs);
533
+ color: var(--color-blue-600);
534
+ cursor: pointer;
535
+ padding: 0;
536
+ flex-shrink: 0;
537
+ }
538
+
539
+ .rule {
540
+ margin: 0 var(--space-2) var(--space-2);
541
+ padding: var(--space-2);
542
+ background-color: var(--color-gray-50);
543
+ border: 1px solid var(--color-gray-200);
544
+ border-radius: var(--radius-md);
545
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
546
+ font-size: 10px;
547
+ white-space: pre-wrap;
548
+ overflow-x: auto;
549
+ }
550
+
551
+ .arm {
552
+ display: inline-block;
553
+ font-size: var(--text-xs);
554
+ font-weight: var(--font-medium);
555
+ color: var(--color-gray-500);
556
+ padding: var(--space-1) var(--space-2);
557
+ text-transform: uppercase;
558
+ letter-spacing: 0.04em;
559
+ }
560
+
561
+ .end-row {
562
+ display: block;
563
+ padding: var(--space-1) var(--space-2);
564
+ font-size: var(--text-xs);
565
+ color: var(--color-gray-500);
566
+ }
567
+
568
+ .journey-row,
569
+ .backref,
570
+ .start {
571
+ display: flex;
572
+ align-items: center;
573
+ gap: var(--space-2);
574
+ padding: var(--space-1-5) var(--space-2);
575
+ font-size: var(--text-xs);
576
+ color: var(--color-gray-500);
577
+ }
578
+
579
+ .start {
580
+ font-size: var(--text-sm);
581
+ font-weight: var(--font-medium);
582
+ color: var(--color-gray-700);
583
+ }
584
+ </style>