@picked/svelte 0.0.1-dev.1793772853474 → 0.0.1-dev.1793835766402

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 (62) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/plugins/devtools/DevToolsExperience.svelte +169 -84
  3. package/dist/plugins/devtools/components/AdaptedToolHost.svelte +9 -3
  4. package/dist/plugins/devtools/customTools.d.ts +16 -7
  5. package/dist/plugins/devtools/customTools.js +1 -1
  6. package/dist/plugins/devtools/derive/answer-display/answer-display.d.ts +1 -1
  7. package/dist/plugins/devtools/derive/answer-display/answer-display.js +1 -1
  8. package/dist/plugins/devtools/derive/guide-sections/guide-sections.js +1 -1
  9. package/dist/plugins/devtools/derive/index.d.ts +3 -3
  10. package/dist/plugins/devtools/derive/index.js +1 -1
  11. package/dist/plugins/devtools/derive/journey-outline/journey-outline.js +1 -1
  12. package/dist/plugins/devtools/derive/journey-tree/journey-tree.js +1 -1
  13. package/dist/plugins/devtools/derive/persona-builder/persona-builder.js +1 -1
  14. package/dist/plugins/devtools/derive/personas/index.d.ts +2 -2
  15. package/dist/plugins/devtools/derive/personas/index.js +1 -1
  16. package/dist/plugins/devtools/derive/personas/personas.d.ts +89 -8
  17. package/dist/plugins/devtools/derive/personas/personas.js +1 -1
  18. package/dist/plugins/devtools/derive/plain-language/index.d.ts +1 -1
  19. package/dist/plugins/devtools/derive/plain-language/index.js +1 -1
  20. package/dist/plugins/devtools/derive/plain-language/plain-language.d.ts +20 -25
  21. package/dist/plugins/devtools/derive/plain-language/plain-language.js +1 -1
  22. package/dist/plugins/devtools/derive/plain-logic/plain-logic.d.ts +1 -1
  23. package/dist/plugins/devtools/derive/plain-logic/plain-logic.js +1 -1
  24. package/dist/plugins/devtools/derive/safety-posture/safety-posture.js +1 -1
  25. package/dist/plugins/devtools/index.d.ts +1 -1
  26. package/dist/plugins/devtools/launcher/Launcher.svelte +13 -4
  27. package/dist/plugins/devtools/options.d.ts +27 -24
  28. package/dist/plugins/devtools/options.js +1 -1
  29. package/dist/plugins/devtools/review/answers/AnswersPanel.svelte +14 -14
  30. package/dist/plugins/devtools/review/guide/GuidePanel.svelte +1 -1
  31. package/dist/plugins/devtools/review/index.d.ts +2 -2
  32. package/dist/plugins/devtools/review/journey/ExperienceDetails.svelte +129 -0
  33. package/dist/plugins/devtools/review/journey/JourneyMap.svelte +16 -14
  34. package/dist/plugins/devtools/review/journey/JourneyPanel.svelte +70 -24
  35. package/dist/plugins/devtools/review/journey/JourneyTimeline.svelte +22 -21
  36. package/dist/plugins/devtools/review/personas/PersonaBuilder.svelte +15 -46
  37. package/dist/plugins/devtools/review/personas/PersonaCard.svelte +227 -44
  38. package/dist/plugins/devtools/review/personas/PersonasPanel.svelte +191 -78
  39. package/dist/plugins/devtools/review/personas/load-persona.d.ts +43 -22
  40. package/dist/plugins/devtools/review/personas/load-persona.js +1 -1
  41. package/dist/plugins/devtools/review/personas/persona-route.d.ts +87 -0
  42. package/dist/plugins/devtools/review/personas/persona-route.js +1 -0
  43. package/dist/plugins/devtools/review/personas/persona-session.d.ts +94 -0
  44. package/dist/plugins/devtools/review/personas/persona-session.js +1 -0
  45. package/dist/plugins/devtools/review/session/SessionPanel.svelte +11 -11
  46. package/dist/plugins/devtools/review/variations/VariationsPanel.svelte +12 -13
  47. package/dist/plugins/devtools/screens/custom-tools/CustomToolsScreen.svelte +11 -25
  48. package/dist/plugins/devtools/sidebar/Sidebar.svelte +2 -33
  49. package/dist/plugins/devtools/sidebar/SidebarRow.svelte +5 -42
  50. package/dist/plugins/devtools/sidebar/nav.d.ts +26 -28
  51. package/dist/plugins/devtools/sidebar/nav.js +1 -1
  52. package/package.json +2 -2
  53. package/dist/plugins/devtools/screens/experience-instances/ExperienceInstancesListScreen.svelte +0 -10
  54. package/dist/plugins/devtools/screens/experiences/ExperienceDetailsScreen.svelte +0 -44
  55. package/dist/plugins/devtools/screens/experiences/ExperienceExperienceInstancesListScreen.svelte +0 -13
  56. package/dist/plugins/devtools/screens/experiences/ExperienceScreen.svelte +0 -32
  57. package/dist/plugins/devtools/screens/experiences/ExperienceTreeScreen.svelte +0 -500
  58. package/dist/plugins/devtools/screens/experiences/ExperiencesListScreen.svelte +0 -22
  59. package/dist/plugins/devtools/screens/features/FeaturesListScreen.svelte +0 -239
  60. package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +0 -286
  61. package/dist/plugins/devtools/screens/responses/ResponsesListScreen.svelte +0 -12
  62. package/dist/plugins/devtools/screens/responses/ResponsesTable.svelte +0 -88
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * @file The Answers tab: every question a journey can ask, and the answer given — the Developer
4
- * group's Responses screen in a reviewer's vocabulary.
3
+ * @file The Responses tab: every field an experience can ask for, and the response given — the
4
+ * Developer group's Responses screen, laid out for a reviewer.
5
5
  *
6
6
  * Four differences from that screen, all deliberate:
7
7
  * 1. Rows are titled by the QUESTION the visitor saw (`field.label`), not `field.key`.
@@ -76,8 +76,8 @@
76
76
  )
77
77
 
78
78
  const filters = $derived<{ value: Filter; label: string }[]>([
79
- { value: 'answered', label: `Answered (${answeredCount})` },
80
- { value: 'unanswered', label: `Not answered (${rows.length - answeredCount})` },
79
+ { value: 'answered', label: `With a response (${answeredCount})` },
80
+ { value: 'unanswered', label: `No response (${rows.length - answeredCount})` },
81
81
  { value: 'all', label: 'All' },
82
82
  ])
83
83
 
@@ -254,15 +254,15 @@
254
254
  </div>
255
255
 
256
256
  {#if confirmingReset}
257
- <Banner status="warning" title="Clear every answer?">
257
+ <Banner status="warning" title="Clear every response?">
258
258
  <Stack direction="vertical" gap="small">
259
259
  <Text size="small">
260
- This empties every answer in this session. Nothing about a real customer changes.
260
+ This empties every response in this session. Nothing about a real visitor changes.
261
261
  </Text>
262
262
  <Stack direction="horizontal" gap="small">
263
263
  <Button variant="destructive" size="small" onclick={reset}>Clear everything</Button>
264
264
  <Button variant="secondary" size="small" onclick={() => (confirmingReset = false)}>
265
- Keep my answers
265
+ Keep my responses
266
266
  </Button>
267
267
  </Stack>
268
268
  </Stack>
@@ -274,7 +274,7 @@
274
274
  again. Saying so beats a reviewer reporting the stale screen as a bug. -->
275
275
  <Banner status="info" testId="devtools-answers-follow-up">
276
276
  <Stack direction="horizontal" gap="small" justify="center" align="space-between">
277
- <Text size="small">What is on screen was worked out before this change.</Text>
277
+ <Text size="small">What the page shows was worked out before this change.</Text>
278
278
  <Button
279
279
  variant="secondary"
280
280
  size="small"
@@ -292,8 +292,8 @@
292
292
  <EmptyState
293
293
  message="Nothing to show here"
294
294
  hint={filter === 'answered'
295
- ? 'No questions have been answered yet. Start the journey on the page.'
296
- : 'Every question this journey asks already has an answer.'}
295
+ ? 'No field has a response yet. Start the experience on the page.'
296
+ : 'Every field this experience asks for already has a response.'}
297
297
  />
298
298
  {:else}
299
299
  <ul class="rows" data-testid="devtools-answers">
@@ -330,7 +330,7 @@
330
330
  title={withheld}
331
331
  onclick={() => startEdit(row)}
332
332
  >
333
- {row.view.answered ? 'Change' : 'Answer'}
333
+ {row.view.answered ? 'Change' : 'Add'}
334
334
  </Button>
335
335
  {#if row.view.answered}
336
336
  <Button
@@ -346,7 +346,7 @@
346
346
  </span>
347
347
  {/if}
348
348
  {:else}
349
- <Badge status="default" size="small">Change it on its screen</Badge>
349
+ <Badge status="default" size="small">Change it on its step</Badge>
350
350
  {/if}
351
351
  </div>
352
352
 
@@ -388,7 +388,7 @@
388
388
  class="inline-input"
389
389
  type={row.view.kind === 'number' ? 'number' : 'text'}
390
390
  bind:value={draft}
391
- placeholder={row.field.placeholder ?? 'Type an answer'}
391
+ placeholder={row.field.placeholder ?? 'Type a response'}
392
392
  aria-label={row.view.title}
393
393
  />
394
394
  {/if}
@@ -405,7 +405,7 @@
405
405
  {:else if row.view.answered && row.view.display !== null}
406
406
  <Text size="small" color="subdued">{row.view.display}</Text>
407
407
  {:else}
408
- <Text size="small" color="subdued">Not answered yet</Text>
408
+ <Text size="small" color="subdued">No response yet</Text>
409
409
  {/if}
410
410
  </li>
411
411
  {/each}
@@ -24,7 +24,7 @@
24
24
  * @param {GuideSection} section - The section to read.
25
25
  * @returns {{ id: string; label: string } | null} The tab to open and its name.
26
26
  * @example
27
- * openTarget({ ...section, tab: 'answers' }) // { id: 'answers', label: 'Answers' }
27
+ * openTarget({ ...section, tab: 'answers' }) // { id: 'answers', label: 'Responses' }
28
28
  */
29
29
  function openTarget(section: GuideSection): { id: string; label: string } | null {
30
30
  const id = section.tab
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @file The review tabs' panels: How to use, This session, Answers, Journey map, Personas &
3
- * Scenarios and Variations.
2
+ * @file The review tabs' panels: How to use, This session, Responses, Flow, Personas & Scenarios
3
+ * and Feature flags.
4
4
  */
5
5
  export { default as GuidePanel } from './guide/GuidePanel.svelte';
6
6
  export { default as SessionPanel } from './session/SessionPanel.svelte';
@@ -0,0 +1,129 @@
1
+ <script lang="ts">
2
+ /**
3
+ * @file The Experiences tab's Details view: what one experience is, where on the page it shows,
4
+ * when it starts, and its instances on this page.
5
+ */
6
+ import type { ExperienceStore, Picked } from '@picked/client'
7
+ import { Card, CodeBlock, Text } from '../../ui'
8
+ import { describeRule, type FieldLookup } from '../../derive'
9
+ import ExperienceInstancesTable from '../../components/ExperienceInstancesTable.svelte'
10
+
11
+ interface ExperienceDetailsProps {
12
+ client: Picked
13
+ experience: ExperienceStore
14
+ /** Names a field the condition reads. */
15
+ fieldLookup: FieldLookup
16
+ }
17
+
18
+ const { client, experience, fieldLookup }: ExperienceDetailsProps = $props()
19
+
20
+ const config = $derived(experience.configStore)
21
+ const component = $derived(experience.componentStore)
22
+ const instancesStore = $derived(client.experienceInstances.listStore)
23
+ const instances = $derived(
24
+ $instancesStore.filter((instance) => instance.experienceKey === experience.key),
25
+ )
26
+
27
+ /** How a placement puts the experience on the page, by placement. */
28
+ const PLACEMENTS = {
29
+ replace: 'In place of',
30
+ append: 'At the end of',
31
+ prepend: 'At the start of',
32
+ before: 'Before',
33
+ after: 'After',
34
+ } as const
35
+
36
+ /** The condition's verbatim rule is shown while this is `true`. */
37
+ let showRule = $state(false)
38
+ </script>
39
+
40
+ <div class="details" data-testid="devtools-experience-details">
41
+ <Card sectioned>
42
+ <dl class="facts">
43
+ <dt>Name</dt>
44
+ <dd>{experience.name}</dd>
45
+ <dt>Key</dt>
46
+ <dd><code>{experience.key}</code></dd>
47
+ <dt>Component</dt>
48
+ <dd>{$component?.name ?? 'None'}</dd>
49
+ <dt>Shows on the page</dt>
50
+ <dd>
51
+ {#if $config.targetSelectors.length === 0}
52
+ Nowhere by itself. Code, or another experience, opens it.
53
+ {:else}
54
+ <ul class="targets">
55
+ {#each $config.targetSelectors as target (`${target.selector}/${target.placement}`)}
56
+ <li>{PLACEMENTS[target.placement]} <code>{target.selector}</code></li>
57
+ {/each}
58
+ </ul>
59
+ {/if}
60
+ </dd>
61
+ <dt>Starts when</dt>
62
+ <dd>
63
+ <span data-testid="devtools-experience-condition">
64
+ {describeRule($config.decisionLogic, fieldLookup)}
65
+ </span>
66
+ <button type="button" class="disclosure" onclick={() => (showRule = !showRule)}>
67
+ {showRule ? 'Hide rule' : 'Show rule'}
68
+ </button>
69
+ {#if showRule}
70
+ <CodeBlock code={JSON.stringify($config.decisionLogic ?? null, null, 2)} />
71
+ {/if}
72
+ </dd>
73
+ </dl>
74
+ </Card>
75
+
76
+ <section class="instances" aria-label="On this page">
77
+ <Text weight="semibold">On this page</Text>
78
+ <ExperienceInstancesTable experienceInstances={instances} />
79
+ </section>
80
+ </div>
81
+
82
+ <style>
83
+ .details {
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: var(--space-4);
87
+ }
88
+
89
+ .facts {
90
+ display: grid;
91
+ grid-template-columns: max-content 1fr;
92
+ gap: var(--space-2) var(--space-4);
93
+ margin: 0;
94
+ padding: var(--space-4);
95
+ font-size: var(--text-sm);
96
+ }
97
+
98
+ .facts dt {
99
+ color: var(--color-gray-500);
100
+ }
101
+
102
+ .facts dd {
103
+ margin: 0;
104
+ min-width: 0;
105
+ color: var(--color-gray-900);
106
+ }
107
+
108
+ .targets {
109
+ margin: 0;
110
+ padding-left: var(--space-4);
111
+ }
112
+
113
+ .disclosure {
114
+ margin-left: var(--space-2);
115
+ padding: 0;
116
+ border: 0;
117
+ background: none;
118
+ color: var(--color-blue-600);
119
+ font: inherit;
120
+ font-size: var(--text-xs);
121
+ cursor: pointer;
122
+ }
123
+
124
+ .instances {
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: var(--space-2);
128
+ }
129
+ </style>
@@ -1,11 +1,12 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * @file The journey drawn as a tree — the Developer group's experience tree, in plain English.
3
+ * @file An experience's flow drawn as a tree — the Developer group's experience tree, in plain
4
+ * English.
4
5
  *
5
6
  * `derive/journey-tree` does the thinking: the graph-to-tree walk and the tidy layout. This file
6
7
  * owns nothing but pan, zoom and selection, which is why the layout is testable and this is not.
7
- * The look follows the experience tree: left to right, a coloured dot per node (grey start, blue
8
- * screen, amber decision, sky-blue for a branch that rejoins), curved links, a decision's arms
8
+ * The look follows the experience tree: left to right, a coloured dot per node (grey origin, blue
9
+ * step, amber condition, sky-blue for a branch that rejoins), curved links, a condition's arms
9
10
  * green and red, and each label above its dot — below it at the end of a branch.
10
11
  *
11
12
  * NODES ARE HTML, LINKS ARE SVG. A node is a real `<button>` holding its dot and label,
@@ -23,6 +24,7 @@
23
24
  * ACTIONS ARE NOT DRAWN. A route runs straight across them, and where a branch hands off to one
24
25
  * and stops, the pop-out names it on the node before.
25
26
  */
27
+ import { NODE_LABELS } from '../../derive'
26
28
  import type { TreeLayout, TreeNode, TreeNodeKind } from '../../derive'
27
29
 
28
30
  interface JourneyMapProps {
@@ -50,11 +52,11 @@
50
52
  const POP_EDGE = 8
51
53
 
52
54
  const KIND_LABELS: Record<TreeNodeKind, string> = {
53
- start: 'Start',
54
- screen: 'Screen',
55
- decision: 'Decision',
56
- system: 'Behind the scenes',
57
- journey: 'Opens another journey',
55
+ start: NODE_LABELS.origin,
56
+ screen: NODE_LABELS.step,
57
+ decision: NODE_LABELS.condition,
58
+ system: NODE_LABELS.action,
59
+ journey: NODE_LABELS.experience,
58
60
  }
59
61
 
60
62
  let viewport: HTMLDivElement | undefined = $state()
@@ -554,7 +556,7 @@
554
556
  {#if end.armLabel !== null}
555
557
  <span class="arm-name arm-{end.arm ?? 'none'}">{end.armLabel}</span>
556
558
  {/if}
557
- <span>Ends: {end.label} (behind the scenes)</span>
559
+ <span>Ends: {end.label} (action)</span>
558
560
  </li>
559
561
  {/each}
560
562
  </ul>
@@ -582,7 +584,7 @@
582
584
  </button>
583
585
  {:else if canJump(selectedNode)}
584
586
  <button type="button" class="pop-action" onclick={() => goBack(selectedNode)}>
585
- Go back to this screen
587
+ Go back to this step
586
588
  </button>
587
589
  {/if}
588
590
  </div>
@@ -590,10 +592,10 @@
590
592
  </div>
591
593
 
592
594
  <p class="legend">
593
- <span class="key kind-start"></span> Start
594
- <span class="key kind-screen"></span> Screen
595
- <span class="key kind-decision"></span> Decision
596
- <span class="key kind-journey"></span> Opens another journey
595
+ <span class="key kind-start"></span> {KIND_LABELS.start}
596
+ <span class="key kind-screen"></span> {KIND_LABELS.screen}
597
+ <span class="key kind-decision"></span> {KIND_LABELS.decision}
598
+ <span class="key kind-journey"></span> {KIND_LABELS.journey}
597
599
  <span class="key reference"></span> Rejoins an earlier branch (dashed)
598
600
  <span class="key seen"></span> Seen
599
601
  <span class="key current"></span> You are here
@@ -1,25 +1,27 @@
1
1
  <script lang="ts">
2
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.
3
+ * @file The Experiences tab: any experience's flow in plain English, without node ids or raw
4
+ * JSON Logic, and its details. It opens on the experience in front of the reviewer, the picker
5
+ * reaches every other experience in the workspace, and Launch experience opens the one picked.
6
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.
7
+ * Navigation is BACKWARDS ONLY, and only in the experience that is OPEN. A step already visited
8
+ * is a plain `goToStepByKey`: the flow has been through every action in between, so returning to
9
+ * it is safe. A step AHEAD is rendered but not clickable — jumping forward skips the actions that
10
+ * make it renderable, and the step it lands on can render an error state no visitor could reach.
11
+ * Forward motion belongs to the experience itself, or to a custom tool that replays one.
12
12
  *
13
13
  * THREE VIEWS, ONE GRAPH. The outline answers "where am I, and why did it go there"; the map
14
14
  * answers "what shape is this, and where does each branch end"; the timeline answers "in what
15
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.
16
+ * same vocabulary modules, so a step is named identically in each and they cannot drift. The
17
+ * fourth view, Details, says where the experience shows, when it starts, and its instances.
17
18
  */
18
19
  import type { Picked } from '@picked/client'
19
20
  import { Badge, Banner, Button, Card, EmptyState } from '../../ui'
20
21
  import { buildOutline, buildTimeline, buildTree } from '../../derive'
21
22
  import type { FieldLookup, OutlineRow, SafetyPosture, Trails } from '../../derive'
22
23
  import type { JourneyView } from '../../session'
24
+ import ExperienceDetails from './ExperienceDetails.svelte'
23
25
  import JourneyMap from './JourneyMap.svelte'
24
26
  import JourneyTimeline from './JourneyTimeline.svelte'
25
27
 
@@ -62,7 +64,7 @@
62
64
  // The union goes on `$state`, not on the `let`. With the annotation alone TypeScript narrows the
63
65
  // variable to its initial literal and the `view === 'map'` below reads as a comparison that can
64
66
  // never hold — `$derived` is an expression in this same scope, so nothing widens it back.
65
- let view = $state<'outline' | 'map' | 'timeline'>('map')
67
+ let view = $state<'outline' | 'map' | 'timeline' | 'details'>('map')
66
68
 
67
69
  /** The open journey, unless it is one the tab never follows. */
68
70
  const followed = $derived(
@@ -98,11 +100,12 @@
98
100
  //
99
101
  // It reads the shared `listStore` rather than `experiences.getByKey`, which mints a NEW derived
100
102
  // store per call and never releases it.
101
- const treeStore = $derived(
103
+ const selectedExperience = $derived(
102
104
  selectedKey === null
103
105
  ? undefined
104
- : $experiences.find((experience) => experience.key === selectedKey)?.treeStore,
106
+ : $experiences.find((experience) => experience.key === selectedKey),
105
107
  )
108
+ const treeStore = $derived(selectedExperience?.treeStore)
106
109
  const graph: FlowGraph = $derived($treeStore ?? EMPTY_GRAPH)
107
110
 
108
111
  // Fields arrive asynchronously and AFTER the graph does. Read reactively, so the views re-render
@@ -155,14 +158,14 @@
155
158
  * @param {OutlineRow} row - The row to explain.
156
159
  * @returns {string | undefined} The reason, or undefined when there is nothing to explain.
157
160
  * @example
158
- * jumpReason(aheadRow) // "You haven't answered enough to get here yet."
161
+ * jumpReason(aheadRow) // "Your responses don't reach this step yet."
159
162
  */
160
163
  function jumpReason(row: OutlineRow): string | undefined {
161
164
  if (row.kind !== 'screen') return undefined
162
165
  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
+ if (!isOpen) return "This experience isn't open, so there is nowhere to go back to."
167
+ if (row.state === 'current') return "You're on this step."
168
+ if (row.state === 'ahead') return "Your responses don't reach this step yet."
166
169
  return undefined
167
170
  }
168
171
 
@@ -204,6 +207,17 @@
204
207
  picked = key === followed?.experienceKey ? null : key
205
208
  }
206
209
 
210
+ /**
211
+ * Opens the experience picked in the picker, past its own condition.
212
+ *
213
+ * @example
214
+ * launch()
215
+ */
216
+ function launch(): void {
217
+ if (selectedKey === null) return
218
+ void client.experiences.open({ key: selectedKey, options: { bypassCondition: true } })
219
+ }
220
+
207
221
  /**
208
222
  * Shows a decision's verbatim rule, or hides it again.
209
223
  *
@@ -219,13 +233,26 @@
219
233
  <div class="journey">
220
234
  {#if selectedKey === null}
221
235
  <EmptyState
222
- message="No journeys yet"
236
+ message="No experiences yet"
223
237
  hint="The workspace has not loaded its experiences. Reload the page if this does not change."
224
238
  />
225
239
  {:else}
240
+ <div class="launch">
241
+ <Button
242
+ variant="primary"
243
+ size="small"
244
+ disabled={!posture.canNavigate}
245
+ title={posture.canNavigate ? undefined : (posture.reason ?? undefined)}
246
+ testId="devtools-journey-launch"
247
+ onclick={launch}
248
+ >
249
+ Launch experience
250
+ </Button>
251
+ </div>
252
+
226
253
  <!-- One row: how to look on the left, what to look at on the right. -->
227
254
  <div class="controls">
228
- <div class="views" role="group" aria-label="Journey view">
255
+ <div class="views" role="group" aria-label="Flow view">
229
256
  <button
230
257
  type="button"
231
258
  class="view"
@@ -256,17 +283,27 @@
256
283
  >
257
284
  Timeline
258
285
  </button>
286
+ <button
287
+ type="button"
288
+ class="view"
289
+ class:selected={view === 'details'}
290
+ aria-pressed={view === 'details'}
291
+ data-testid="devtools-journey-view-details"
292
+ onclick={() => (view = 'details')}
293
+ >
294
+ Details
295
+ </button>
259
296
  </div>
260
297
 
261
298
  <div class="picker">
262
299
  {#if followed !== null && !isOpen}
263
300
  <Button variant="ghost" size="small" onclick={() => (picked = null)}>
264
- Back to the open journey
301
+ Back to the open experience
265
302
  </Button>
266
303
  {/if}
267
304
  <select
268
305
  class="picker-select"
269
- aria-label="Journey"
306
+ aria-label="Experience"
270
307
  value={selectedKey}
271
308
  onchange={pick}
272
309
  data-testid="devtools-journey-picker"
@@ -284,11 +321,15 @@
284
321
  <Banner status="warning" testId="devtools-journey-readonly">{posture.reason}</Banner>
285
322
  {/if}
286
323
 
287
- {#if rows.length === 0}
324
+ {#if view === 'details'}
325
+ {#if selectedExperience !== undefined}
326
+ <ExperienceDetails {client} experience={selectedExperience} {fieldLookup} />
327
+ {/if}
328
+ {:else if rows.length === 0}
288
329
  <Card sectioned>
289
330
  <EmptyState
290
- message="This journey has no screens yet"
291
- hint="Its flow has not loaded, or it is built entirely from background steps."
331
+ message="This experience has no steps yet"
332
+ hint="Its flow has not loaded, or it is built entirely from actions."
292
333
  />
293
334
  </Card>
294
335
  {:else if layout !== null}
@@ -343,7 +384,7 @@
343
384
  {:else if row.kind === 'arm'}
344
385
  <span class="arm">{row.label}</span>
345
386
  {:else if row.kind === 'end'}
346
- <span class="end-row">⇥ Ends: {row.label} (behind the scenes)</span>
387
+ <span class="end-row">⇥ Ends: {row.label} (action)</span>
347
388
  {:else if row.kind === 'journey'}
348
389
  <span class="journey-row">
349
390
  <span class="marker muted" aria-hidden="true"></span>
@@ -380,6 +421,11 @@
380
421
  flex-direction: column;
381
422
  }
382
423
 
424
+ .launch {
425
+ display: flex;
426
+ justify-content: flex-end;
427
+ }
428
+
383
429
  .controls {
384
430
  display: flex;
385
431
  align-items: center;
@@ -1,24 +1,25 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * @file The journey drawn as a TIMELINE — `git log --graph` for a flow. One row per node, top to
4
- * bottom; branches run in lanes beside the rows and MERGE back into the node they meet, so a
5
- * screen many branches reach is one row with lines converging on it, not a tree full of dashed
6
- * jumps. `derive/journey-timeline` owns the layout; this file owns selection and scrolling only.
3
+ * @file An experience's flow drawn as a TIMELINE — `git log --graph` for a flow. One row per
4
+ * node, top to bottom; branches run in lanes beside the rows and MERGE back into the node they
5
+ * meet, so a step many branches reach is one row with lines converging on it, not a tree full of
6
+ * dashed jumps. `derive/journey-timeline` owns the layout; this file owns selection and scrolling
7
+ * only.
7
8
  *
8
9
  * A LOOP is the one thing a git graph never has. It stays off the drawing and rides on its row as
9
10
  * a "↩ back to …" note, and the details pane can take the reviewer to its target.
10
11
  *
11
12
  * COLOUR IS THE BRANCH, as in a git tool: every line and dot takes its branch's colour, so the
12
13
  * eye can follow one route down the lanes. That frees the node KIND to be a SHAPE — a circle for
13
- * a screen, a diamond for a decision, a square for another journey. Actions are not drawn at all;
14
- * where a branch ends on one, the row names it.
14
+ * a step, a diamond for a condition, a square for another experience. Actions are not drawn at
15
+ * all; where a branch ends on one, the row names it.
15
16
  *
16
17
  * ROWS ARE HTML, LINES ARE SVG, the same split as the map: each row is a real `<button>` the full
17
18
  * width of the timeline, and the graph column is painted over its left edge with
18
19
  * `pointer-events: none`, so clicking a dot or its label selects the same row.
19
20
  */
20
21
  import { tick } from 'svelte'
21
- import { COLOURS, ROW_HEIGHT } from '../../derive'
22
+ import { COLOURS, NODE_LABELS, ROW_HEIGHT } from '../../derive'
22
23
  import type { TimelineLayout, TimelineNode, TreeNodeKind } from '../../derive'
23
24
  import TimelineMarker from './TimelineMarker.svelte'
24
25
 
@@ -32,11 +33,11 @@
32
33
  const { layout, canNavigate, onJump }: JourneyTimelineProps = $props()
33
34
 
34
35
  const KIND_LABELS: Record<TreeNodeKind, string> = {
35
- start: 'Start',
36
- screen: 'Screen',
37
- decision: 'Decision',
38
- system: 'Behind the scenes',
39
- journey: 'Opens another journey',
36
+ start: NODE_LABELS.origin,
37
+ screen: NODE_LABELS.step,
38
+ decision: NODE_LABELS.condition,
39
+ system: NODE_LABELS.action,
40
+ journey: NODE_LABELS.experience,
40
41
  }
41
42
 
42
43
  /** Marker size per kind — the start a little larger, as on the map. */
@@ -242,7 +243,7 @@
242
243
  >
243
244
  {/each}
244
245
  {#each node.ends as end (end.key)}
245
- <span class="end" title="Hands off to {end.label}, behind the scenes"
246
+ <span class="end" title="Hands off to the {end.label} action"
246
247
  >⇥ {end.armLabel === null ? '' : `${end.armLabel}: `}{end.label}</span
247
248
  >
248
249
  {/each}
@@ -331,13 +332,13 @@
331
332
  {#if end.armLabel !== null}
332
333
  <span class="arm arm-{end.arm ?? 'none'}">{end.armLabel}</span>
333
334
  {/if}
334
- <span class="end-text">Ends: {end.label} (behind the scenes)</span>
335
+ <span class="end-text">Ends: {end.label} (action)</span>
335
336
  </li>
336
337
  {/each}
337
338
  </ul>
338
339
  </div>
339
340
  {:else}
340
- <p class="text">The journey ends here.</p>
341
+ <p class="text">The experience ends here.</p>
341
342
  {/if}
342
343
 
343
344
  {#if selectedNode.rule !== null}
@@ -347,7 +348,7 @@
347
348
 
348
349
  {#if canJump(selectedNode)}
349
350
  <button type="button" class="action" onclick={() => goBack(selectedNode)}>
350
- Go back to this screen
351
+ Go back to this step
351
352
  </button>
352
353
  {/if}
353
354
  {/if}
@@ -355,14 +356,14 @@
355
356
  </div>
356
357
 
357
358
  <p class="legend">
358
- <span class="key-swatch kind-start"></span> Start
359
- <span class="key-swatch kind-screen"></span> Screen
360
- <span class="key-swatch kind-decision"></span> Decision
361
- <span class="key-swatch kind-journey"></span> Opens another journey
359
+ <span class="key-swatch kind-start"></span> {KIND_LABELS.start}
360
+ <span class="key-swatch kind-screen"></span> {KIND_LABELS.screen}
361
+ <span class="key-swatch kind-decision"></span> {KIND_LABELS.decision}
362
+ <span class="key-swatch kind-journey"></span> {KIND_LABELS.journey}
362
363
  <span class="key-swatch seen"></span> Seen
363
364
  <span class="key-swatch current"></span> You are here
364
365
  <span
365
- >· each colour is one branch · a decision's Yes runs straight on · ↩ loops back · ⇥ hands off</span
366
+ >· each colour is one branch · a condition's Yes runs straight on · ↩ loops back · ⇥ hands off</span
366
367
  >
367
368
  </p>
368
369
  </div>