@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/plugins/devtools/DevToolsExperience.svelte +169 -84
- package/dist/plugins/devtools/components/AdaptedToolHost.svelte +9 -3
- package/dist/plugins/devtools/customTools.d.ts +16 -7
- package/dist/plugins/devtools/customTools.js +1 -1
- package/dist/plugins/devtools/derive/answer-display/answer-display.d.ts +1 -1
- package/dist/plugins/devtools/derive/answer-display/answer-display.js +1 -1
- package/dist/plugins/devtools/derive/guide-sections/guide-sections.js +1 -1
- package/dist/plugins/devtools/derive/index.d.ts +3 -3
- package/dist/plugins/devtools/derive/index.js +1 -1
- package/dist/plugins/devtools/derive/journey-outline/journey-outline.js +1 -1
- package/dist/plugins/devtools/derive/journey-tree/journey-tree.js +1 -1
- package/dist/plugins/devtools/derive/persona-builder/persona-builder.js +1 -1
- package/dist/plugins/devtools/derive/personas/index.d.ts +2 -2
- package/dist/plugins/devtools/derive/personas/index.js +1 -1
- package/dist/plugins/devtools/derive/personas/personas.d.ts +89 -8
- package/dist/plugins/devtools/derive/personas/personas.js +1 -1
- package/dist/plugins/devtools/derive/plain-language/index.d.ts +1 -1
- package/dist/plugins/devtools/derive/plain-language/index.js +1 -1
- package/dist/plugins/devtools/derive/plain-language/plain-language.d.ts +20 -25
- package/dist/plugins/devtools/derive/plain-language/plain-language.js +1 -1
- package/dist/plugins/devtools/derive/plain-logic/plain-logic.d.ts +1 -1
- package/dist/plugins/devtools/derive/plain-logic/plain-logic.js +1 -1
- package/dist/plugins/devtools/derive/safety-posture/safety-posture.js +1 -1
- package/dist/plugins/devtools/index.d.ts +1 -1
- package/dist/plugins/devtools/launcher/Launcher.svelte +13 -4
- package/dist/plugins/devtools/options.d.ts +27 -24
- package/dist/plugins/devtools/options.js +1 -1
- package/dist/plugins/devtools/review/answers/AnswersPanel.svelte +14 -14
- package/dist/plugins/devtools/review/guide/GuidePanel.svelte +1 -1
- package/dist/plugins/devtools/review/index.d.ts +2 -2
- package/dist/plugins/devtools/review/journey/ExperienceDetails.svelte +129 -0
- package/dist/plugins/devtools/review/journey/JourneyMap.svelte +16 -14
- package/dist/plugins/devtools/review/journey/JourneyPanel.svelte +70 -24
- package/dist/plugins/devtools/review/journey/JourneyTimeline.svelte +22 -21
- package/dist/plugins/devtools/review/personas/PersonaBuilder.svelte +15 -46
- package/dist/plugins/devtools/review/personas/PersonaCard.svelte +227 -44
- package/dist/plugins/devtools/review/personas/PersonasPanel.svelte +191 -78
- package/dist/plugins/devtools/review/personas/load-persona.d.ts +43 -22
- package/dist/plugins/devtools/review/personas/load-persona.js +1 -1
- package/dist/plugins/devtools/review/personas/persona-route.d.ts +87 -0
- package/dist/plugins/devtools/review/personas/persona-route.js +1 -0
- package/dist/plugins/devtools/review/personas/persona-session.d.ts +94 -0
- package/dist/plugins/devtools/review/personas/persona-session.js +1 -0
- package/dist/plugins/devtools/review/session/SessionPanel.svelte +11 -11
- package/dist/plugins/devtools/review/variations/VariationsPanel.svelte +12 -13
- package/dist/plugins/devtools/screens/custom-tools/CustomToolsScreen.svelte +11 -25
- package/dist/plugins/devtools/sidebar/Sidebar.svelte +2 -33
- package/dist/plugins/devtools/sidebar/SidebarRow.svelte +5 -42
- package/dist/plugins/devtools/sidebar/nav.d.ts +26 -28
- package/dist/plugins/devtools/sidebar/nav.js +1 -1
- package/package.json +2 -2
- package/dist/plugins/devtools/screens/experience-instances/ExperienceInstancesListScreen.svelte +0 -10
- package/dist/plugins/devtools/screens/experiences/ExperienceDetailsScreen.svelte +0 -44
- package/dist/plugins/devtools/screens/experiences/ExperienceExperienceInstancesListScreen.svelte +0 -13
- package/dist/plugins/devtools/screens/experiences/ExperienceScreen.svelte +0 -32
- package/dist/plugins/devtools/screens/experiences/ExperienceTreeScreen.svelte +0 -500
- package/dist/plugins/devtools/screens/experiences/ExperiencesListScreen.svelte +0 -22
- package/dist/plugins/devtools/screens/features/FeaturesListScreen.svelte +0 -239
- package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +0 -286
- package/dist/plugins/devtools/screens/responses/ResponsesListScreen.svelte +0 -12
- package/dist/plugins/devtools/screens/responses/ResponsesTable.svelte +0 -88
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* @file The
|
|
4
|
-
* group's Responses screen
|
|
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: `
|
|
80
|
-
{ value: 'unanswered', label: `
|
|
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
|
|
257
|
+
<Banner status="warning" title="Clear every response?">
|
|
258
258
|
<Stack direction="vertical" gap="small">
|
|
259
259
|
<Text size="small">
|
|
260
|
-
This empties every
|
|
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
|
|
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
|
|
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
|
|
296
|
-
: 'Every
|
|
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' : '
|
|
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
|
|
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
|
|
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">
|
|
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: '
|
|
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,
|
|
3
|
-
*
|
|
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
|
|
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
|
|
8
|
-
*
|
|
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:
|
|
54
|
-
screen:
|
|
55
|
-
decision:
|
|
56
|
-
system:
|
|
57
|
-
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} (
|
|
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
|
|
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>
|
|
594
|
-
<span class="key kind-screen"></span>
|
|
595
|
-
<span class="key kind-decision"></span>
|
|
596
|
-
<span class="key kind-journey"></span>
|
|
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
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
8
|
-
* a plain `goToStepByKey`: the flow has been through every action in between, so returning to
|
|
9
|
-
* is safe. A
|
|
10
|
-
* make it renderable, and the
|
|
11
|
-
*
|
|
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
|
|
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
|
|
103
|
+
const selectedExperience = $derived(
|
|
102
104
|
selectedKey === null
|
|
103
105
|
? undefined
|
|
104
|
-
: $experiences.find((experience) => experience.key === selectedKey)
|
|
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) // "
|
|
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
|
|
164
|
-
if (row.state === 'current') return "You're on this
|
|
165
|
-
if (row.state === 'ahead') return "
|
|
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
|
|
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="
|
|
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
|
|
301
|
+
Back to the open experience
|
|
265
302
|
</Button>
|
|
266
303
|
{/if}
|
|
267
304
|
<select
|
|
268
305
|
class="picker-select"
|
|
269
|
-
aria-label="
|
|
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
|
|
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
|
|
291
|
-
hint="Its flow has not loaded, or it is built entirely from
|
|
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} (
|
|
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
|
|
4
|
-
* bottom; branches run in lanes beside the rows and MERGE back into the node they
|
|
5
|
-
*
|
|
6
|
-
* jumps. `derive/journey-timeline` owns the layout; this file owns selection and scrolling
|
|
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
|
|
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:
|
|
36
|
-
screen:
|
|
37
|
-
decision:
|
|
38
|
-
system:
|
|
39
|
-
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}
|
|
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} (
|
|
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
|
|
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
|
|
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>
|
|
359
|
-
<span class="key-swatch kind-screen"></span>
|
|
360
|
-
<span class="key-swatch kind-decision"></span>
|
|
361
|
-
<span class="key-swatch kind-journey"></span>
|
|
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
|
|
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>
|