@picked/svelte 0.0.1-dev.1793776638554 → 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 (44) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/plugins/devtools/DevToolsExperience.svelte +145 -93
  3. package/dist/plugins/devtools/customTools.d.ts +2 -2
  4. package/dist/plugins/devtools/derive/guide-sections/guide-sections.js +1 -1
  5. package/dist/plugins/devtools/derive/index.d.ts +3 -3
  6. package/dist/plugins/devtools/derive/index.js +1 -1
  7. package/dist/plugins/devtools/derive/personas/index.d.ts +2 -2
  8. package/dist/plugins/devtools/derive/personas/index.js +1 -1
  9. package/dist/plugins/devtools/derive/personas/personas.d.ts +88 -7
  10. package/dist/plugins/devtools/derive/personas/personas.js +1 -1
  11. package/dist/plugins/devtools/derive/plain-language/index.d.ts +1 -1
  12. package/dist/plugins/devtools/derive/plain-language/index.js +1 -1
  13. package/dist/plugins/devtools/derive/plain-language/plain-language.d.ts +3 -5
  14. package/dist/plugins/devtools/derive/plain-language/plain-language.js +1 -1
  15. package/dist/plugins/devtools/index.d.ts +1 -1
  16. package/dist/plugins/devtools/launcher/Launcher.svelte +13 -4
  17. package/dist/plugins/devtools/options.d.ts +6 -4
  18. package/dist/plugins/devtools/options.js +1 -1
  19. package/dist/plugins/devtools/review/journey/ExperienceDetails.svelte +129 -0
  20. package/dist/plugins/devtools/review/journey/JourneyPanel.svelte +54 -8
  21. package/dist/plugins/devtools/review/personas/PersonaBuilder.svelte +3 -34
  22. package/dist/plugins/devtools/review/personas/PersonaCard.svelte +226 -43
  23. package/dist/plugins/devtools/review/personas/PersonasPanel.svelte +171 -74
  24. package/dist/plugins/devtools/review/personas/load-persona.d.ts +41 -20
  25. package/dist/plugins/devtools/review/personas/load-persona.js +1 -1
  26. package/dist/plugins/devtools/review/personas/persona-route.d.ts +87 -0
  27. package/dist/plugins/devtools/review/personas/persona-route.js +1 -0
  28. package/dist/plugins/devtools/review/personas/persona-session.d.ts +94 -0
  29. package/dist/plugins/devtools/review/personas/persona-session.js +1 -0
  30. package/dist/plugins/devtools/sidebar/Sidebar.svelte +3 -68
  31. package/dist/plugins/devtools/sidebar/SidebarRow.svelte +5 -42
  32. package/dist/plugins/devtools/sidebar/nav.d.ts +18 -44
  33. package/dist/plugins/devtools/sidebar/nav.js +1 -1
  34. package/package.json +2 -2
  35. package/dist/plugins/devtools/screens/experience-instances/ExperienceInstancesListScreen.svelte +0 -10
  36. package/dist/plugins/devtools/screens/experiences/ExperienceDetailsScreen.svelte +0 -44
  37. package/dist/plugins/devtools/screens/experiences/ExperienceExperienceInstancesListScreen.svelte +0 -13
  38. package/dist/plugins/devtools/screens/experiences/ExperienceScreen.svelte +0 -32
  39. package/dist/plugins/devtools/screens/experiences/ExperienceTreeScreen.svelte +0 -500
  40. package/dist/plugins/devtools/screens/experiences/ExperiencesListScreen.svelte +0 -22
  41. package/dist/plugins/devtools/screens/features/FeaturesListScreen.svelte +0 -239
  42. package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +0 -286
  43. package/dist/plugins/devtools/screens/responses/ResponsesListScreen.svelte +0 -12
  44. package/dist/plugins/devtools/screens/responses/ResponsesTable.svelte +0 -88
@@ -18,16 +18,12 @@
18
18
  import Sidebar from './sidebar/Sidebar.svelte'
19
19
  import SidebarFooter from './sidebar/SidebarFooter.svelte'
20
20
  import {
21
- CUSTOM_TOOLS_OPEN_STORAGE_KEY,
22
21
  CUSTOM_TOOLS_TAB,
23
- DEVELOPER_OPEN_STORAGE_KEY,
24
22
  TAB_STORAGE_KEY,
25
23
  buildNav,
26
- developerTabId,
27
- isInCustomTools,
28
- isInDeveloper,
29
24
  readStored,
30
25
  resolveTab,
26
+ screenTabId,
31
27
  toolTabId,
32
28
  writeStored,
33
29
  } from './sidebar/nav'
@@ -46,6 +42,7 @@
46
42
  TAB_INTROS,
47
43
  configSourceLabel,
48
44
  derivePosture,
45
+ applyEdits,
49
46
  guideFixes,
50
47
  guideSections,
51
48
  readSavedPersonas,
@@ -62,26 +59,31 @@
62
59
  VariationsPanel,
63
60
  } from './review'
64
61
  import {
62
+ openPersonaJourney,
65
63
  screenQuestions,
66
64
  takePendingLoad,
67
65
  walkNotice,
68
66
  walkPersona,
67
+ type PendingLoad,
69
68
  type WalkSettings,
70
69
  } from './review/personas/load-persona'
70
+ import {
71
+ prefillPersona,
72
+ readActivePersona,
73
+ takePersonaLink,
74
+ writeActivePersona,
75
+ type Prefill,
76
+ } from './review/personas/persona-session'
71
77
  import ProjectScreen from './screens/project/ProjectScreen.svelte'
72
78
  import ComponentsListScreen from './screens/components/ComponentsListScreen.svelte'
73
79
  import ConfigScreen from './screens/config/ConfigScreen.svelte'
74
80
  import ContentListScreen from './screens/content/ContentListScreen.svelte'
75
81
  import EventsListScreen from './screens/events/EventsListScreen.svelte'
76
- import ExperienceInstancesListScreen from './screens/experience-instances/ExperienceInstancesListScreen.svelte'
77
- import ExperiencesListScreen from './screens/experiences/ExperiencesListScreen.svelte'
78
- import FeaturesListScreen from './screens/features/FeaturesListScreen.svelte'
79
82
  import FieldsListScreen from './screens/fields/FieldsListScreen.svelte'
80
83
  import FrameworksListScreen from './screens/frameworks/FrameworksListScreen.svelte'
81
84
  import HistoryEntriesListScreen from './screens/history/HistoryEntriesListScreen.svelte'
82
85
  import IdentityScreen from './screens/identity/IdentityScreen.svelte'
83
86
  import PredicatesListScreen from './screens/predicates/PredicatesListScreen.svelte'
84
- import ResponsesListScreen from './screens/responses/ResponsesListScreen.svelte'
85
87
  import CustomToolsScreen from './screens/custom-tools/CustomToolsScreen.svelte'
86
88
 
87
89
  const experience = getExperience()
@@ -118,22 +120,18 @@
118
120
  const sessionStorageOf = (): Storage => window.sessionStorage
119
121
  const localStorageOf = (): Storage => window.localStorage
120
122
 
121
- /** The developer screens, keyed by their sidebar id. */
122
- const DEVELOPER_COMPONENTS: Record<string, Component> = {
123
- [developerTabId('project')]: ProjectScreen,
124
- [developerTabId('components')]: ComponentsListScreen,
125
- [developerTabId('config')]: ConfigScreen,
126
- [developerTabId('content')]: ContentListScreen,
127
- [developerTabId('events')]: EventsListScreen,
128
- [developerTabId('experience-instances')]: ExperienceInstancesListScreen,
129
- [developerTabId('experiences')]: ExperiencesListScreen,
130
- [developerTabId('features')]: FeaturesListScreen,
131
- [developerTabId('fields')]: FieldsListScreen,
132
- [developerTabId('frameworks')]: FrameworksListScreen,
133
- [developerTabId('history')]: HistoryEntriesListScreen,
134
- [developerTabId('identity')]: IdentityScreen,
135
- [developerTabId('predicates')]: PredicatesListScreen,
136
- [developerTabId('responses')]: ResponsesListScreen,
123
+ /** The SDK's own screens, keyed by their sidebar id. */
124
+ const SCREEN_COMPONENTS: Record<string, Component> = {
125
+ [screenTabId('project')]: ProjectScreen,
126
+ [screenTabId('components')]: ComponentsListScreen,
127
+ [screenTabId('config')]: ConfigScreen,
128
+ [screenTabId('content')]: ContentListScreen,
129
+ [screenTabId('events')]: EventsListScreen,
130
+ [screenTabId('fields')]: FieldsListScreen,
131
+ [screenTabId('frameworks')]: FrameworksListScreen,
132
+ [screenTabId('history')]: HistoryEntriesListScreen,
133
+ [screenTabId('identity')]: IdentityScreen,
134
+ [screenTabId('predicates')]: PredicatesListScreen,
137
135
  }
138
136
 
139
137
  // ── The session record ───────────────────────────────────────────────────────────────────────
@@ -169,26 +167,19 @@
169
167
  )
170
168
  const storedTab = readStored(sessionStorageOf, TAB_STORAGE_KEY)
171
169
  let tab = $state(storedTab ?? 'guide')
172
- let developerOpen = $state(readStored(sessionStorageOf, DEVELOPER_OPEN_STORAGE_KEY) === 'true')
173
- let customOpen = $state(readStored(sessionStorageOf, CUSTOM_TOOLS_OPEN_STORAGE_KEY) !== 'false')
174
170
  let drawerOpen = $state(false)
175
171
 
176
- // A tab can disappear under the viewer (Feature flags once the feature list is empty), and a
177
- // stored developer screen opens its group.
172
+ // A tab can disappear under the viewer: Feature flags once the feature list is empty.
178
173
  $effect(() => {
179
174
  const resolved = resolveTab(tab, nav)
180
175
  if (resolved !== tab) tab = resolved
181
- if (isInDeveloper(tab, nav) && !developerOpen) developerOpen = true
182
- if (isInCustomTools(tab, nav) && !customOpen) customOpen = true
183
176
  })
184
177
 
185
- const selectedItem = $derived(
186
- [...nav.primary, ...nav.custom, ...nav.developer, ...nav.footer].find((item) => item.id === tab),
187
- )
178
+ const selectedItem = $derived([...nav.primary, ...nav.footer].find((item) => item.id === tab))
188
179
  const selectedTool = $derived(
189
180
  [...tools, ...customTools, ...footer].find((tool) => toolTabId(tool.id) === tab),
190
181
  )
191
- const developerScreen = $derived(DEVELOPER_COMPONENTS[tab])
182
+ const sdkScreen = $derived(SCREEN_COMPONENTS[tab])
192
183
 
193
184
  /**
194
185
  * Narrows a tab id to one of the review tabs.
@@ -213,26 +204,6 @@
213
204
  writeStored(sessionStorageOf, TAB_STORAGE_KEY, id)
214
205
  }
215
206
 
216
- /**
217
- * Opens or folds the Custom Tools group, and remembers it.
218
- *
219
- * @example toggleCustom()
220
- */
221
- function toggleCustom(): void {
222
- customOpen = !customOpen
223
- writeStored(sessionStorageOf, CUSTOM_TOOLS_OPEN_STORAGE_KEY, String(customOpen))
224
- }
225
-
226
- /**
227
- * Opens or folds the Developer group, and remembers it.
228
- *
229
- * @example toggleDeveloper()
230
- */
231
- function toggleDeveloper(): void {
232
- developerOpen = !developerOpen
233
- writeStored(sessionStorageOf, DEVELOPER_OPEN_STORAGE_KEY, String(developerOpen))
234
- }
235
-
236
207
  // ── How to use ───────────────────────────────────────────────────────────────────────────────
237
208
  const guide = $derived.by(() => {
238
209
  const available = { variations: $features.length > 0 }
@@ -262,7 +233,7 @@
262
233
  sections: guideSections(available, { lead, tools: toolSections }),
263
234
  fixes: guideFixes(available, options.guide.fixes),
264
235
  tabLabels: Object.fromEntries(
265
- [...nav.primary, ...nav.custom, ...nav.footer].map((item) => [item.id, item.label]),
236
+ [...nav.primary, ...nav.footer].map((item) => [item.id, item.label]),
266
237
  ),
267
238
  }
268
239
  })
@@ -285,14 +256,22 @@
285
256
  writeAutoHide(localStorageOf, value)
286
257
  }
287
258
 
288
- // ── A persona load, carried across its reload ────────────────────────────────────────────────
289
- // Load reloads the page clean, so the walk runs here, as the devtools mount on the new page. The
290
- // panel stays shut while it walks, so the reviewer watches the journey move, and opens on the
291
- // Personas tab when the walk stops short.
259
+ // ── A persona session, carried across reloads ──────────────────────────────────────────────
260
+ // Walk through and Jump to step reload the page clean, so the session starts here, as the
261
+ // devtools mount on the new page, and again on every later page of the tab until Stop. Each
262
+ // start runs the persona's setup and pre-fills every step with its responses. A jump walks to
263
+ // its step first, with the panel shut so the reviewer watches the journey move, and opens on the
264
+ // Personas tab when the walk stops short. A shared link starts a walk-through on the page it
265
+ // opens.
292
266
  let personaWalkNotice = $state<{
293
267
  status: 'info' | 'success' | 'warning' | 'error'
294
268
  text: string
295
269
  } | null>(null)
270
+ /** The site's personas, once the option has loaded them. */
271
+ let sitePersonaList = $state.raw<Persona[]>([])
272
+ /** The persona this tab walks through as. */
273
+ let activePersona = $state.raw<Persona | null>(null)
274
+ let prefill: Prefill | null = null
296
275
 
297
276
  const walkSettings: WalkSettings = {
298
277
  exclude: options.hiddenExperienceKeys,
@@ -304,30 +283,108 @@
304
283
  }
305
284
 
306
285
  /**
307
- * The persona a reload was for, among the site's and this browser's.
286
+ * A persona by id, among the site's as this browser edited them and this browser's own.
287
+ *
288
+ * @param {string} id - The persona's id.
289
+ * @param {readonly Persona[]} site - The site's personas.
290
+ * @returns {Persona | undefined} The persona, or `undefined` when none has the id.
291
+ * @example findPersona('mover', sitePersonaList)
292
+ */
293
+ function findPersona(id: string, site: readonly Persona[]): Persona | undefined {
294
+ const listed = applyEdits(site, readSavedPersonas(localStorageOf, client.projectId))
295
+ return [...listed.site, ...listed.own].find((persona) => persona.id === id)
296
+ }
297
+
298
+ /**
299
+ * Walks a jump to its step, then says how it went, opening the panel when it stopped short.
308
300
  *
309
- * @returns {Persona | undefined} The persona, or `undefined` when no load is waiting.
310
- * @example pendingPersona()
301
+ * @param {Persona} persona - The persona.
302
+ * @param {PendingLoad} pending - The jump.
303
+ * @returns {Promise<void>} Settles once the walk ends.
304
+ * @example await jump(persona, pending)
311
305
  */
312
- function pendingPersona(): Persona | undefined {
313
- const id = takePendingLoad(sessionStorageOf())
314
- if (id === null || !posture.canEditAnswers) return undefined
315
- const saved = readSavedPersonas(localStorageOf, client.projectId)
316
- return [...options.personas, ...saved].find((persona) => persona.id === id)
317
- }
318
-
319
- const walking = pendingPersona()
320
- if (walking !== undefined) {
321
- personaWalkNotice = { status: 'info', text: `Loading ${walking.name}…` }
322
- void walkPersona(client, walking, walkSettings).then((outcome) => {
323
- const notice = walkNotice(walking, outcome)
324
- personaWalkNotice = notice
325
- if (notice.status === 'success') return
326
- selectTab('personas')
327
- experience.open()
306
+ async function jump(persona: Persona, pending: PendingLoad): Promise<void> {
307
+ const stepName = pending.stop?.stepName ?? pending.stop?.stepKey ?? ''
308
+ personaWalkNotice = { status: 'info', text: `Jumping to “${stepName}” as ${persona.name}…` }
309
+ const outcome = await walkPersona(
310
+ client,
311
+ { ...persona, landing: pending.stop, passed: pending.passed },
312
+ walkSettings,
313
+ )
314
+ const notice = walkNotice(persona, outcome)
315
+ personaWalkNotice = notice
316
+ if (notice.status === 'success') return
317
+ selectTab('personas')
318
+ experience.open()
319
+ }
320
+
321
+ /**
322
+ * Starts or resumes this tab's persona session: a reload's start, a shared link, or the session
323
+ * a reload carried on.
324
+ *
325
+ * @returns {Promise<void>} Settles once the persona is set up and any jump has walked.
326
+ * @example await startPersonaSession()
327
+ */
328
+ async function startPersonaSession(): Promise<void> {
329
+ const storage = sessionStorageOf()
330
+ const pending = takePendingLoad(storage)
331
+ const linked = takePersonaLink(window.location, (url) => {
332
+ window.history.replaceState(window.history.state, '', url)
328
333
  })
334
+ const site = await options.personas()
335
+ sitePersonaList = site
336
+ const id = pending?.id ?? linked ?? readActivePersona(storage)
337
+ if (id === null || !posture.canEditAnswers) return
338
+ const persona = findPersona(id, site)
339
+ if (persona === undefined || (pending === null && linked !== null && persona.source !== 'site')) {
340
+ writeActivePersona(storage, null)
341
+ return
342
+ }
343
+
344
+ writeActivePersona(storage, persona.id)
345
+ activePersona = persona
346
+ try {
347
+ await persona.setup?.(client)
348
+ } catch (error) {
349
+ const detail = error instanceof Error ? ` ${error.message}` : ''
350
+ personaWalkNotice = { status: 'error', text: `${persona.name}’s setup failed.${detail}` }
351
+ }
352
+
353
+ prefill = prefillPersona(
354
+ client,
355
+ { ...persona.responses, ...options.personaWalk.responses },
356
+ session.askedStore,
357
+ )
358
+ if (pending?.stop != null) await jump(persona, pending)
359
+ else if (pending !== null || linked !== null) {
360
+ await openPersonaJourney(client, persona, walkSettings)
361
+ }
329
362
  }
330
363
 
364
+ /**
365
+ * Ends this tab's persona session: every field gets its own default back.
366
+ *
367
+ * @example stopPersonaSession()
368
+ */
369
+ function stopPersonaSession(): void {
370
+ prefill?.stop()
371
+ prefill = null
372
+ writeActivePersona(sessionStorageOf(), null)
373
+ if (activePersona !== null) {
374
+ personaWalkNotice = {
375
+ status: 'success',
376
+ text: `Stopped walking through as ${activePersona.name}. Fields already filled in keep their values.`,
377
+ }
378
+ }
379
+
380
+ activePersona = null
381
+ }
382
+
383
+ void startPersonaSession()
384
+ $effect(() => () => {
385
+ prefill?.stop()
386
+ })
387
+
331
388
  // ── Dialog behaviour ─────────────────────────────────────────────────────────────────────────
332
389
  /** The open panel; Svelte sets it back to `null` when the panel unmounts. */
333
390
  let panel: HTMLDivElement | null = $state(null)
@@ -453,15 +510,7 @@
453
510
  <div class="drawer-scrim" aria-hidden="true" onclick={() => (drawerOpen = false)}></div>
454
511
  {/if}
455
512
  <div class="nav-column" class:drawer-open={drawerOpen} id="devtools-nav">
456
- <Sidebar
457
- {nav}
458
- selected={tab}
459
- {developerOpen}
460
- {customOpen}
461
- onSelect={selectTab}
462
- onToggleDeveloper={toggleDeveloper}
463
- onToggleCustom={toggleCustom}
464
- />
513
+ <Sidebar {nav} selected={tab} onSelect={selectTab} />
465
514
  <SidebarFooter
466
515
  tools={nav.footer}
467
516
  selected={tab}
@@ -472,9 +521,9 @@
472
521
  </div>
473
522
 
474
523
  <section class="pane" aria-label={selectedItem?.label} data-testid="devtools-pane">
475
- {#if developerScreen}
476
- {@const Screen = developerScreen}
477
- <div class="developer-body">
524
+ {#if sdkScreen}
525
+ {@const Screen = sdkScreen}
526
+ <div class="screen-body">
478
527
  <Screen />
479
528
  </div>
480
529
  {:else if tab === CUSTOM_TOOLS_TAB}
@@ -533,13 +582,15 @@
533
582
  {client}
534
583
  {posture}
535
584
  journey={$journey}
536
- personas={options.personas}
585
+ personas={sitePersonaList}
537
586
  asked={$asked}
538
587
  route={$route}
539
588
  hiddenKeys={options.hiddenExperienceKeys}
540
589
  codeOwned={options.codeOwnedFields}
541
590
  personaWalk={options.personaWalk}
542
591
  tools={personaTools}
592
+ {activePersona}
593
+ onStop={stopPersonaSession}
543
594
  walkNotice={personaWalkNotice}
544
595
  onWalkNoticeDismiss={() => (personaWalkNotice = null)}
545
596
  />
@@ -557,6 +608,7 @@
557
608
  position={options.launcher.position}
558
609
  {autoHide}
559
610
  {preview}
611
+ walkingAs={activePersona?.name ?? null}
560
612
  onOpen={() => experience.open()}
561
613
  />
562
614
  {/if}
@@ -824,7 +876,7 @@
824
876
  display: grid;
825
877
  }
826
878
 
827
- .developer-body {
879
+ .screen-body {
828
880
  flex: 1 1 auto;
829
881
  display: grid;
830
882
  min-height: 0;
@@ -28,7 +28,7 @@ export interface CustomTool {
28
28
  * `'review'` lists the tool as a tab of its own, beside the review tabs. `'personas'` shows it
29
29
  * as a section of the Personas & Scenarios tab, under its label. `'footer'` shows it as an icon
30
30
  * at the foot of the sidebar, beside the auto-hide switch, with its label as a tooltip.
31
- * `'developer'` lists it as a row of its own in the sidebar's Custom Tools group.
31
+ * `'developer'` lists it as a top-level row of its own, after the review-placed tools.
32
32
  *
33
33
  * @default 'developer'
34
34
  */
@@ -82,7 +82,7 @@ export declare function personasTools(tools: readonly CustomTool[]): CustomTool[
82
82
  */
83
83
  export declare function footerTools(tools: readonly CustomTool[]): CustomTool[];
84
84
  /**
85
- * The tools listed in the sidebar's Custom Tools group.
85
+ * The tools listed as top-level rows after the review-placed tools.
86
86
  *
87
87
  * @param {readonly CustomTool[]} tools - The registered tools.
88
88
  * @returns {CustomTool[]} The unplaced tools and those placed `'developer'`, in registration order.
@@ -1 +1 @@
1
- var e=[{id:`session`,title:`See where you are`,steps:[],notes:[`To see the step you are on and every response so far, open **This session**.`],tab:`session`},{id:`answers`,title:`Change a response`,steps:[`Open **Responses**.`,`Find the field. Use **With a response**, **No response** or **All** to narrow the list.`,`Click **Change**, or **Add** if it has no response yet.`,`Pick or type the new response, then click **Save**.`],notes:[`To remove one response, click **Clear** beside it.`,`To start the whole experience again, click **Start over**, then **Clear everything**.`,`If a field says **Change it on its step**, go back to that step and change it there.`],tab:`answers`},{id:`journey`,title:`Go back to an earlier step`,steps:[`Open **Flow**.`,`Find a step on your route. Your route is blue, and your step says **You are here**.`,`Click the step, then click **Go back to this step**.`],notes:[`You can only go back to steps you have already visited.`,`To change how the flow is drawn, pick **Map**, **Timeline** or **Outline** at the top.`],tab:`journey`},{id:`personas`,title:`Try the experience as a visitor`,steps:[`Open **Personas & Scenarios**.`,`Click **Load** beside the visitor you want. The experience opens where they would be.`],notes:[`To keep where you are for later, click **Create**, give it a name, then click **Save**.`,`What you save stays in this browser only.`],tab:`personas`},{id:`variations`,title:`View a feature flag variant`,steps:[`Open **Feature flags**.`,`Find the feature flag and pick the variant you want.`,`Click **Show me this variant**. The page reloads showing it.`,`To share exactly what you saw, click **Copy link** and send it.`],notes:[`To go back to normal for that feature flag, click **Leave this one alone**.`],tab:`variations`}],t=[{tab:`journey`,entry:{problem:`I can't click a step in the Flow tab`,fix:`Go back only to steps you have visited.`}}];function n(e,t){return e!==`variations`||t.variations}function r(t,r){return[...r?.lead??[],...e.filter(e=>n(e.tab,t)),...r?.tools??[]]}function i(e,r){return[...t.filter(t=>n(t.tab,e)).map(e=>e.entry),...r??[]]}function a(e){let t=e.split(`**`);return t.length%2==0?[{text:e,bold:!1}]:t.map((e,t)=>({text:e,bold:t%2==1})).filter(e=>e.text.length>0)}export{a as emphasis,i as guideFixes,r as guideSections};
1
+ var e=[{id:`session`,title:`See where you are`,steps:[],notes:[`To see the step you are on and every response so far, open **This session**.`],tab:`session`},{id:`answers`,title:`Change a response`,steps:[`Open **Responses**.`,`Find the field. Use **With a response**, **No response** or **All** to narrow the list.`,`Click **Change**, or **Add** if it has no response yet.`,`Pick or type the new response, then click **Save**.`],notes:[`To remove one response, click **Clear** beside it.`,`To start the whole experience again, click **Start over**, then **Clear everything**.`,`If a field says **Change it on its step**, go back to that step and change it there.`],tab:`answers`},{id:`journey`,title:`Go back to an earlier step`,steps:[`Open **Experiences**.`,`Find a step on your route. Your route is blue, and your step says **You are here**.`,`Click the step, then click **Go back to this step**.`],notes:[`You can only go back to steps you have already visited.`,`To change how the flow is drawn, pick **Map**, **Timeline** or **Outline** at the top.`,`To see where an experience shows and when it starts, pick **Details**.`,`To open an experience that is not on the page, pick it, then click **Launch experience**.`],tab:`journey`},{id:`personas`,title:`Try the experience as a visitor`,steps:[`Open **Personas & Scenarios**.`,`Click **Walk through** beside the visitor you want. The page starts again.`,`Go through the experience. Each step shows their responses. Click **Continue**, or change a response first.`,`When you are done, click **Stop** at the top of the tab.`],notes:[`To start further in, click **Jump to step**, then pick the step.`,`To change a persona, click **Edit**. Your change stays in this browser only.`,`To keep where you are for later, click **Create**, give it a name, then click **Save**.`,`To send a site persona to someone else, click its copy icon and share the link.`],tab:`personas`},{id:`variations`,title:`View a feature flag variant`,steps:[`Open **Feature flags**.`,`Find the feature flag and pick the variant you want.`,`Click **Show me this variant**. The page reloads showing it.`,`To share exactly what you saw, click **Copy link** and send it.`],notes:[`To go back to normal for that feature flag, click **Leave this one alone**.`],tab:`variations`}],t=[{tab:`journey`,entry:{problem:`I can't click a step in the Experiences tab`,fix:`Go back only to steps you have visited.`}}];function n(e,t){return e!==`variations`||t.variations}function r(t,r){return[...r?.lead??[],...e.filter(e=>n(e.tab,t)),...r?.tools??[]]}function i(e,r){return[...t.filter(t=>n(t.tab,e)).map(e=>e.entry),...r??[]]}function a(e){let t=e.split(`**`);return t.length%2==0?[{text:e,bold:!1}]:t.map((e,t)=>({text:e,bold:t%2==1})).filter(e=>e.text.length>0)}export{a as emphasis,i as guideFixes,r as guideSections};
@@ -16,12 +16,12 @@ export { COLOURS, LANE_WIDTH, ROW_HEIGHT, buildTimeline } from './journey-timeli
16
16
  export type { TimelineEdge, TimelineLayout, TimelineNext, TimelineNode } from './journey-timeline';
17
17
  export { BREADTH, DEPTH, buildTree } from './journey-tree';
18
18
  export type { FlowEnd, TreeInput, TreeJump, TreeLayout, TreeLink, TreeNode, TreeNodeKind, } from './journey-tree';
19
- export { DEVELOPER_LABEL, NODE_LABELS, TAB_INTROS, TAB_LABELS, configSourceLabel, deslug, screenName, } from './plain-language';
19
+ export { NODE_LABELS, TAB_INTROS, TAB_LABELS, configSourceLabel, deslug, screenName, } from './plain-language';
20
20
  export type { ReviewTabId } from './plain-language';
21
21
  export { builderGraph, responseKeysOf, walkDraft } from './persona-builder';
22
22
  export type { BuilderCondition, BuilderEdge, BuilderField, BuilderInput, BuilderJourney, BuilderNode, BuilderRow, BuilderStep, BuilderWalk, GraphSource, } from './persona-builder';
23
- export { PERSONA_STORAGE_PREFIX, buildPersona, customerAnswers, describeLanding, entryUrlOf, passedScreens, personaStorageKey, readAnswers, readEntryUrl, readSavedPersonas, sitePersonas, writeSavedPersonas, } from './personas';
24
- export type { BuildPersonaResult, Persona, PersonaCapture, PersonaDefinition, PersonaDraft, PersonaLanding, PersonaLoader, PersonaScreen, PersonaSource, PersonaStorageGetter, } from './personas';
23
+ export { PERSONA_STORAGE_PREFIX, applyEdits, buildPersona, customerAnswers, describeLanding, entryUrlOf, groupPersonas, matchesSearch, passedScreens, personaStorageKey, readAnswers, readEntryUrl, readLanding, readPassed, readSavedPersonas, sitePersonas, startOf, writeSavedPersonas, } from './personas';
24
+ export type { BuildPersonaResult, EditedPersonas, Persona, PersonaCapture, PersonaDefinition, PersonaDraft, PersonaGroup, PersonaLanding, PersonaScreen, PersonaSetup, PersonaSource, PersonaStorageGetter, } from './personas';
25
25
  export { UNDESCRIBABLE, describeRule, varKeyOf } from './plain-logic';
26
26
  export type { FieldLookup, FieldSummary } from './plain-logic';
27
27
  export { derivePosture } from './safety-posture';
@@ -1 +1 @@
1
- import{isBlank as e,nextHistory as t,submittedAnswers as n}from"./answer-history/answer-history.js";import{DEVELOPER_LABEL as r,NODE_LABELS as i,TAB_INTROS as a,TAB_LABELS as o,configSourceLabel as s,deslug as c,screenName as l}from"./plain-language/plain-language.js";import{PERSONA_STORAGE_PREFIX as u,buildPersona as d,customerAnswers as f,describeLanding as p,entryUrlOf as m,passedScreens as h,personaStorageKey as g,readAnswers as _,readEntryUrl as v,readSavedPersonas as y,sitePersonas as b,writeSavedPersonas as x}from"./personas/personas.js";import{askedFieldKeys as S,describeAnswer as C,kindOf as w,nounOf as T,questionOf as E,renderAnswer as D,systemPayload as O}from"./answer-display/answer-display.js";import{emphasis as k,guideFixes as A,guideSections as j}from"./guide-sections/guide-sections.js";import{UNDESCRIBABLE as M,describeRule as N,varKeyOf as P}from"./plain-logic/plain-logic.js";import{buildOutline as F}from"./journey-outline/journey-outline.js";import{nextOpenOrder as I,pickJourney as L}from"./journey-pick/journey-pick.js";import{BREADTH as R,DEPTH as z,buildTree as B}from"./journey-tree/journey-tree.js";import"./journey-tree/index.js";import{COLOURS as V,LANE_WIDTH as H,ROW_HEIGHT as U,buildTimeline as W}from"./journey-timeline/journey-timeline.js";import"./journey-timeline/index.js";import{builderGraph as G,responseKeysOf as K,walkDraft as q}from"./persona-builder/persona-builder.js";import{derivePosture as J}from"./safety-posture/safety-posture.js";import{nextTrails as Y}from"./visited-steps/visited-steps.js";import{nextRoute as X}from"./walk-route/walk-route.js";export{R as BREADTH,V as COLOURS,z as DEPTH,r as DEVELOPER_LABEL,H as LANE_WIDTH,i as NODE_LABELS,u as PERSONA_STORAGE_PREFIX,U as ROW_HEIGHT,a as TAB_INTROS,o as TAB_LABELS,M as UNDESCRIBABLE,S as askedFieldKeys,F as buildOutline,d as buildPersona,W as buildTimeline,B as buildTree,G as builderGraph,s as configSourceLabel,f as customerAnswers,J as derivePosture,C as describeAnswer,p as describeLanding,N as describeRule,c as deslug,k as emphasis,m as entryUrlOf,A as guideFixes,j as guideSections,e as isBlank,w as kindOf,t as nextHistory,I as nextOpenOrder,X as nextRoute,Y as nextTrails,T as nounOf,h as passedScreens,g as personaStorageKey,L as pickJourney,E as questionOf,_ as readAnswers,v as readEntryUrl,y as readSavedPersonas,D as renderAnswer,K as responseKeysOf,l as screenName,b as sitePersonas,n as submittedAnswers,O as systemPayload,P as varKeyOf,q as walkDraft,x as writeSavedPersonas};
1
+ import{isBlank as e,nextHistory as t,submittedAnswers as n}from"./answer-history/answer-history.js";import{NODE_LABELS as r,TAB_INTROS as i,TAB_LABELS as a,configSourceLabel as o,deslug as s,screenName as c}from"./plain-language/plain-language.js";import{PERSONA_STORAGE_PREFIX as l,applyEdits as u,buildPersona as d,customerAnswers as f,describeLanding as p,entryUrlOf as m,groupPersonas as h,matchesSearch as g,passedScreens as _,personaStorageKey as v,readAnswers as y,readEntryUrl as b,readLanding as x,readPassed as S,readSavedPersonas as C,sitePersonas as w,startOf as T,writeSavedPersonas as E}from"./personas/personas.js";import{askedFieldKeys as D,describeAnswer as O,kindOf as k,nounOf as A,questionOf as j,renderAnswer as M,systemPayload as N}from"./answer-display/answer-display.js";import{emphasis as P,guideFixes as F,guideSections as I}from"./guide-sections/guide-sections.js";import{UNDESCRIBABLE as L,describeRule as R,varKeyOf as z}from"./plain-logic/plain-logic.js";import{buildOutline as B}from"./journey-outline/journey-outline.js";import{nextOpenOrder as V,pickJourney as H}from"./journey-pick/journey-pick.js";import{BREADTH as U,DEPTH as W,buildTree as G}from"./journey-tree/journey-tree.js";import"./journey-tree/index.js";import{COLOURS as K,LANE_WIDTH as q,ROW_HEIGHT as J,buildTimeline as Y}from"./journey-timeline/journey-timeline.js";import"./journey-timeline/index.js";import{builderGraph as X,responseKeysOf as Z,walkDraft as Q}from"./persona-builder/persona-builder.js";import{derivePosture as $}from"./safety-posture/safety-posture.js";import{nextTrails as ee}from"./visited-steps/visited-steps.js";import{nextRoute as te}from"./walk-route/walk-route.js";export{U as BREADTH,K as COLOURS,W as DEPTH,q as LANE_WIDTH,r as NODE_LABELS,l as PERSONA_STORAGE_PREFIX,J as ROW_HEIGHT,i as TAB_INTROS,a as TAB_LABELS,L as UNDESCRIBABLE,u as applyEdits,D as askedFieldKeys,B as buildOutline,d as buildPersona,Y as buildTimeline,G as buildTree,X as builderGraph,o as configSourceLabel,f as customerAnswers,$ as derivePosture,O as describeAnswer,p as describeLanding,R as describeRule,s as deslug,P as emphasis,m as entryUrlOf,h as groupPersonas,F as guideFixes,I as guideSections,e as isBlank,k as kindOf,g as matchesSearch,t as nextHistory,V as nextOpenOrder,te as nextRoute,ee as nextTrails,A as nounOf,_ as passedScreens,v as personaStorageKey,H as pickJourney,j as questionOf,y as readAnswers,b as readEntryUrl,x as readLanding,S as readPassed,C as readSavedPersonas,M as renderAnswer,Z as responseKeysOf,c as screenName,w as sitePersonas,T as startOf,n as submittedAnswers,N as systemPayload,z as varKeyOf,Q as walkDraft,E as writeSavedPersonas};
@@ -1,3 +1,3 @@
1
1
  /** @file Public entrypoint for personas: building, saving, reading and describing them. */
2
- export { PERSONA_STORAGE_PREFIX, buildPersona, customerAnswers, describeLanding, entryUrlOf, passedScreens, personaStorageKey, readAnswers, readEntryUrl, readSavedPersonas, sitePersonas, writeSavedPersonas, } from './personas';
3
- export type { BuildPersonaResult, Persona, PersonaCapture, PersonaDefinition, PersonaDraft, PersonaLanding, PersonaLoader, PersonaScreen, PersonaSource, PersonaStorageGetter, } from './personas';
2
+ export { PERSONA_STORAGE_PREFIX, applyEdits, buildPersona, customerAnswers, describeLanding, entryUrlOf, groupPersonas, matchesSearch, passedScreens, personaStorageKey, readAnswers, readEntryUrl, readLanding, readPassed, readSavedPersonas, sitePersonas, startOf, writeSavedPersonas, } from './personas';
3
+ export type { BuildPersonaResult, EditedPersonas, Persona, PersonaCapture, PersonaDefinition, PersonaDraft, PersonaGroup, PersonaLanding, PersonaScreen, PersonaSetup, PersonaSource, PersonaStorageGetter, } from './personas';
@@ -1 +1 @@
1
- export{PERSONA_STORAGE_PREFIX,buildPersona,customerAnswers,describeLanding,entryUrlOf,passedScreens,personaStorageKey,readAnswers,readEntryUrl,readSavedPersonas,sitePersonas,writeSavedPersonas}from"./personas.js";
1
+ export{PERSONA_STORAGE_PREFIX,applyEdits,buildPersona,customerAnswers,describeLanding,entryUrlOf,groupPersonas,matchesSearch,passedScreens,personaStorageKey,readAnswers,readEntryUrl,readLanding,readPassed,readSavedPersonas,sitePersonas,startOf,writeSavedPersonas}from"./personas.js";
@@ -20,18 +20,28 @@ export interface PersonaScreen {
20
20
  experienceKey: string;
21
21
  stepKey: string;
22
22
  }
23
- /** Loads a persona the host's own way, in place of walking its journey with its answers. */
24
- export type PersonaLoader = (client: Picked) => void | Promise<void>;
23
+ /**
24
+ * Puts the page in the state a persona needs before its experience starts, such as a stubbed
25
+ * account or a location. It runs on every page of the persona's session, before the walk.
26
+ */
27
+ export type PersonaSetup = (client: Picked) => void | Promise<void>;
25
28
  /** A customer or situation to try the journey as. */
26
29
  export interface Persona {
27
30
  id: string;
28
31
  name: string;
29
32
  description: string | null;
30
33
  source: PersonaSource;
34
+ /** The heading it is listed under; `null` lists it under its source's heading. */
35
+ group: string | null;
31
36
  /** The answers in place once it loads. */
32
37
  responses: Readonly<Record<string, FieldValue>>;
33
38
  /** Where a load stops; `null` loads the answers only. */
34
39
  landing: PersonaLanding | null;
40
+ /**
41
+ * The experience a walk starts in, such as the page's entry experience. `null` starts in the
42
+ * experience of the first screen moved on from, else the landing's.
43
+ */
44
+ start: string | null;
35
45
  /**
36
46
  * The screens moved on from when it was saved, in the order first left. A load presses Continue
37
47
  * on a screen that asks nothing only when it is listed here.
@@ -49,8 +59,10 @@ export interface Persona {
49
59
  entryUrl: string | null;
50
60
  /** When it was saved, as an ISO time; `null` for a host-defined persona. */
51
61
  savedAt: string | null;
52
- /** The host's own loader; `null` for the standard one. */
53
- load: PersonaLoader | null;
62
+ /** The host's setup; `null` when it needs none. */
63
+ setup: PersonaSetup | null;
64
+ /** `true` for a host-defined persona that a reviewer edited in this browser. */
65
+ edited: boolean;
54
66
  }
55
67
  /** A persona as the host defines it in the plugin's `personas` option. */
56
68
  export interface PersonaDefinition {
@@ -59,16 +71,20 @@ export interface PersonaDefinition {
59
71
  name: string;
60
72
  /** Who they are, in a sentence. */
61
73
  description?: string;
74
+ /** The heading it is listed under, such as `Moving`. */
75
+ group?: string;
62
76
  /** The answers in place once it loads. */
63
77
  responses?: Readonly<Record<string, FieldValue>>;
64
78
  /** The journey it opens. */
65
79
  experienceKey?: string;
66
80
  /** The screen in that journey it lands on. */
67
81
  stepKey?: string;
82
+ /** The experience a walk starts in, such as the page's entry experience; `experienceKey` when absent. */
83
+ startExperienceKey?: string;
68
84
  /** The page a load starts on, such as a campaign landing; the page in front when absent. */
69
85
  entryUrl?: string;
70
- /** Loads it the host's own way, in place of the standard answers-then-journey load. */
71
- load?: PersonaLoader;
86
+ /** Prepares the page for it, such as stubbing an account, before each walk. */
87
+ setup?: PersonaSetup;
72
88
  }
73
89
  /** What the reviewer types when saving where they are. */
74
90
  export interface PersonaDraft {
@@ -117,6 +133,22 @@ export declare function personaStorageKey(projectId: string): string;
117
133
  * @example readAnswers({ age: 42 }) // { age: 42 }
118
134
  */
119
135
  export declare function readAnswers(value: unknown): Record<string, FieldValue>;
136
+ /**
137
+ * Reads a stored landing.
138
+ *
139
+ * @param {unknown} value - The value to read.
140
+ * @returns {PersonaLanding | null} The landing, or `null` when it names no journey.
141
+ * @example readLanding({ experienceKey: 'needs-analysis' })
142
+ */
143
+ export declare function readLanding(value: unknown): PersonaLanding | null;
144
+ /**
145
+ * Reads a stored list of passed screens, keeping only the well-formed entries.
146
+ *
147
+ * @param {unknown} value - The stored list.
148
+ * @returns {PersonaScreen[]} The screens, in order.
149
+ * @example readPassed([{ experienceKey: 'funnel', stepKey: 'postcode' }])
150
+ */
151
+ export declare function readPassed(value: unknown): PersonaScreen[];
120
152
  /**
121
153
  * Reads an entry URL, or `null` when it is not a path on this site.
122
154
  *
@@ -186,7 +218,7 @@ export declare function customerAnswers(responses: Readonly<Record<string, Field
186
218
  */
187
219
  export declare function buildPersona(draft: PersonaDraft, capture: PersonaCapture): BuildPersonaResult;
188
220
  /**
189
- * Reads the host's persona definitions, dropping any without an id or name. A host loader is
221
+ * Reads the host's persona definitions, dropping any without an id or name. A host setup is
190
222
  * wrapped so it can be any function the host passed.
191
223
  *
192
224
  * @param {unknown} definitions - The plugin's `personas` option.
@@ -202,3 +234,52 @@ export declare function sitePersonas(definitions: unknown): Persona[];
202
234
  * @example describeLanding(null) // 'Responses only'
203
235
  */
204
236
  export declare function describeLanding(landing: PersonaLanding | null): string;
237
+ /**
238
+ * The experience a persona's walk starts in.
239
+ *
240
+ * @param {Pick<Persona, 'passed' | 'start' | 'landing'>} persona - The persona.
241
+ * @returns {string | null} The experience key, or `null` for a persona with no journey.
242
+ * @example startOf(persona) // 'needs-analysis'
243
+ */
244
+ export declare function startOf(persona: Pick<Persona, 'passed' | 'start' | 'landing'>): string | null;
245
+ /** The host's personas with this browser's edits applied, and the reviewer's own. */
246
+ export interface EditedPersonas {
247
+ /** The host's personas, each replaced by its edit when this browser holds one. */
248
+ site: Persona[];
249
+ /** The personas this browser saved that edit none of the host's. */
250
+ own: Persona[];
251
+ }
252
+ /**
253
+ * Applies this browser's edits to the host's personas. An edit is a saved persona with a host
254
+ * persona's id; it keeps the host's heading, start and setup.
255
+ *
256
+ * @param {readonly Persona[]} site - The host's personas.
257
+ * @param {readonly Persona[]} saved - The personas saved in this browser.
258
+ * @returns {EditedPersonas} The host's personas as edited, and the reviewer's own.
259
+ * @example applyEdits(options.personas, readSavedPersonas(getStorage, projectId))
260
+ */
261
+ export declare function applyEdits(site: readonly Persona[], saved: readonly Persona[]): EditedPersonas;
262
+ /** Personas listed under one heading. */
263
+ export interface PersonaGroup {
264
+ title: string;
265
+ personas: Persona[];
266
+ }
267
+ /**
268
+ * Lists personas under their headings, in the order each heading first appears.
269
+ *
270
+ * @param {readonly Persona[]} personas - The personas.
271
+ * @param {string} fallback - The heading for a persona with no group.
272
+ * @returns {PersonaGroup[]} The headings and their personas.
273
+ * @example groupPersonas(personas, 'For this site')
274
+ */
275
+ export declare function groupPersonas(personas: readonly Persona[], fallback: string): PersonaGroup[];
276
+ /**
277
+ * Whether a persona matches what the reviewer searched for: every word appears in its name,
278
+ * description or heading.
279
+ *
280
+ * @param {Persona} persona - The persona.
281
+ * @param {string} search - What was typed.
282
+ * @returns {boolean} `true` when it matches, and always for an empty search.
283
+ * @example matchesSearch(persona, 'moving gas')
284
+ */
285
+ export declare function matchesSearch(persona: Persona, search: string): boolean;
@@ -1 +1 @@
1
- import{isBlank as e}from"../answer-history/answer-history.js";import{deslug as t}from"../plain-language/plain-language.js";var n=`picked-devtools-personas`,r=1;function i(e){return`${n}:${e}`}function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function o(e){return e===null||typeof e==`string`||typeof e==`boolean`?!0:typeof e==`number`?Number.isFinite(e):Array.isArray(e)?e.every(o):a(e)&&Object.values(e).every(o)}function s(e){if(!a(e))return{};let t={};for(let[n,r]of Object.entries(e))o(r)&&(t[n]=r);return t}function c(e){if(typeof e!=`string`)return null;let t=e.trim();return t.length>0?t:null}function l(e){if(!a(e))return null;let t=c(e.experienceKey);return t===null?null:{experienceKey:t,experienceName:c(e.experienceName),stepKey:c(e.stepKey),stepName:c(e.stepName)}}function u(e){return Array.isArray(e)?e.flatMap(e=>{if(!a(e))return[];let t=c(e.experienceKey),n=c(e.stepKey);return t===null||n===null?[]:[{experienceKey:t,stepKey:n}]}):[]}function d(e){if(!a(e))return{};let t={};for(let[n,r]of Object.entries(e))typeof r==`boolean`&&(t[n]=r);return t}function f(e){let t=c(e);return t!==null&&t.startsWith(`/`)&&!t.startsWith(`//`)?t:null}function p(e,t){let n=e.trim();if(n.length===0)return null;try{let e=new URL(n,t);return e.origin===t?`${e.pathname}${e.search}`:null}catch{return null}}function m(e){if(!a(e))return null;let t=c(e.id),n=c(e.name);return t===null||n===null?null:{id:t,name:n,description:c(e.description),source:`saved`,responses:s(e.responses),landing:l(e.landing),passed:u(e.passed),choices:d(e.choices),entryUrl:f(e.entryUrl),savedAt:c(e.savedAt),load:null}}function h(e,t){let n;try{n=e().getItem(i(t))}catch{return[]}if(n===null)return[];let r;try{r=JSON.parse(n)}catch{return[]}return!a(r)||!Array.isArray(r.personas)?[]:r.personas.map(m).filter(e=>e!==null)}function g(e,t,n){let a=n.map(e=>({id:e.id,name:e.name,description:e.description,responses:e.responses,landing:e.landing,passed:e.passed,choices:e.choices,entryUrl:e.entryUrl,savedAt:e.savedAt}));try{return e().setItem(i(t),JSON.stringify({version:r,personas:a})),!0}catch{return!1}}function _(e,t){let n=new Set,r=[];for(let{journey:i,screen:a}of e){let e=`${i}/${a}`;t.has(i)||n.has(e)||(n.add(e),r.push({experienceKey:i,stepKey:a}))}return r}function v(t,n){let r={};for(let[i,a]of Object.entries(t))n.has(i)&&!e(a)&&(r[i]=a);return r}function y(e,t){let n=c(e.name);return n===null?{ok:!1,error:`Give it a name.`}:Object.keys(t.responses).length===0&&t.journey===null?{ok:!1,error:`Nothing to save yet. Give a response first.`}:{ok:!0,persona:{id:t.id,name:n,description:c(e.description),source:`saved`,responses:s(t.responses),landing:t.journey,passed:t.passed,choices:d(t.choices),entryUrl:f(t.entryUrl),savedAt:t.savedAt,load:null}}}function b(e){return Array.isArray(e)?e.flatMap(e=>{if(!a(e))return[];let t=c(e.id),n=c(e.name);if(t===null||n===null)return[];let r=c(e.experienceKey),i=e.load;return[{id:t,name:n,description:c(e.description),source:`site`,responses:s(e.responses),landing:r===null?null:{experienceKey:r,experienceName:null,stepKey:c(e.stepKey),stepName:null},passed:[],choices:{},entryUrl:f(e.entryUrl),savedAt:null,load:typeof i==`function`?async e=>{await Reflect.apply(i,void 0,[e])}:null}]}):[]}function x(e){if(e===null)return`Responses only`;let n=e.experienceName??t(e.experienceKey);return e.stepKey===null?`Opens ${n}`:`Lands on ${e.stepName??t(e.stepKey)} in ${n}`}export{n as PERSONA_STORAGE_PREFIX,y as buildPersona,v as customerAnswers,x as describeLanding,p as entryUrlOf,_ as passedScreens,i as personaStorageKey,s as readAnswers,f as readEntryUrl,h as readSavedPersonas,b as sitePersonas,g as writeSavedPersonas};
1
+ import{isBlank as e}from"../answer-history/answer-history.js";import{deslug as t}from"../plain-language/plain-language.js";var n=`picked-devtools-personas`,r=1;function i(e){return`${n}:${e}`}function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function o(e){return e===null||typeof e==`string`||typeof e==`boolean`?!0:typeof e==`number`?Number.isFinite(e):Array.isArray(e)?e.every(o):a(e)&&Object.values(e).every(o)}function s(e){if(!a(e))return{};let t={};for(let[n,r]of Object.entries(e))o(r)&&(t[n]=r);return t}function c(e){if(typeof e!=`string`)return null;let t=e.trim();return t.length>0?t:null}function l(e){if(!a(e))return null;let t=c(e.experienceKey);return t===null?null:{experienceKey:t,experienceName:c(e.experienceName),stepKey:c(e.stepKey),stepName:c(e.stepName)}}function u(e){return Array.isArray(e)?e.flatMap(e=>{if(!a(e))return[];let t=c(e.experienceKey),n=c(e.stepKey);return t===null||n===null?[]:[{experienceKey:t,stepKey:n}]}):[]}function d(e){if(!a(e))return{};let t={};for(let[n,r]of Object.entries(e))typeof r==`boolean`&&(t[n]=r);return t}function f(e){let t=c(e);return t!==null&&t.startsWith(`/`)&&!t.startsWith(`//`)?t:null}function p(e,t){let n=e.trim();if(n.length===0)return null;try{let e=new URL(n,t);return e.origin===t?`${e.pathname}${e.search}`:null}catch{return null}}function m(e){if(!a(e))return null;let t=c(e.id),n=c(e.name);return t===null||n===null?null:{id:t,name:n,description:c(e.description),source:`saved`,group:null,responses:s(e.responses),landing:l(e.landing),start:null,passed:u(e.passed),choices:d(e.choices),entryUrl:f(e.entryUrl),savedAt:c(e.savedAt),setup:null,edited:!1}}function h(e,t){let n;try{n=e().getItem(i(t))}catch{return[]}if(n===null)return[];let r;try{r=JSON.parse(n)}catch{return[]}return!a(r)||!Array.isArray(r.personas)?[]:r.personas.map(m).filter(e=>e!==null)}function g(e,t,n){let a=n.map(e=>({id:e.id,name:e.name,description:e.description,responses:e.responses,landing:e.landing,passed:e.passed,choices:e.choices,entryUrl:e.entryUrl,savedAt:e.savedAt}));try{return e().setItem(i(t),JSON.stringify({version:r,personas:a})),!0}catch{return!1}}function _(e,t){let n=new Set,r=[];for(let{journey:i,screen:a}of e){let e=`${i}/${a}`;t.has(i)||n.has(e)||(n.add(e),r.push({experienceKey:i,stepKey:a}))}return r}function v(t,n){let r={};for(let[i,a]of Object.entries(t))n.has(i)&&!e(a)&&(r[i]=a);return r}function y(e,t){let n=c(e.name);return n===null?{ok:!1,error:`Give it a name.`}:Object.keys(t.responses).length===0&&t.journey===null?{ok:!1,error:`Nothing to save yet. Give a response first.`}:{ok:!0,persona:{id:t.id,name:n,description:c(e.description),source:`saved`,group:null,responses:s(t.responses),landing:t.journey,start:null,passed:t.passed,choices:d(t.choices),entryUrl:f(t.entryUrl),savedAt:t.savedAt,setup:null,edited:!1}}}function b(e){return Array.isArray(e)?e.flatMap(e=>{if(!a(e))return[];let t=c(e.id),n=c(e.name);if(t===null||n===null)return[];let r=c(e.experienceKey),i=e.setup;return[{id:t,name:n,description:c(e.description),source:`site`,group:c(e.group),responses:s(e.responses),landing:r===null?null:{experienceKey:r,experienceName:null,stepKey:c(e.stepKey),stepName:null},start:c(e.startExperienceKey),passed:[],choices:{},entryUrl:f(e.entryUrl),savedAt:null,setup:typeof i==`function`?async e=>{await Reflect.apply(i,void 0,[e])}:null,edited:!1}]}):[]}function x(e){if(e===null)return`Responses only`;let n=e.experienceName??t(e.experienceKey);return e.stepKey===null?`Opens ${n}`:`Lands on ${e.stepName??t(e.stepKey)} in ${n}`}function S(e){return e.passed.at(0)?.experienceKey??e.start??e.landing?.experienceKey??null}function C(e,t){let n=new Map(t.map(e=>[e.id,e])),r=new Set(e.map(e=>e.id));return{site:e.map(e=>{let t=n.get(e.id);return t===void 0?e:{...t,source:`site`,group:e.group,start:e.start,setup:e.setup,edited:!0}}),own:t.filter(e=>!r.has(e.id))}}function w(e,t){let n=new Map;for(let r of e){let e=r.group??t;n.set(e,[...n.get(e)??[],r])}return[...n].map(([e,t])=>({title:e,personas:t}))}function T(e,t){let n=[e.name,e.description,e.group].join(` `).toLowerCase();return t.toLowerCase().split(/\s+/).filter(e=>e.length>0).every(e=>n.includes(e))}export{n as PERSONA_STORAGE_PREFIX,C as applyEdits,y as buildPersona,v as customerAnswers,x as describeLanding,p as entryUrlOf,w as groupPersonas,T as matchesSearch,_ as passedScreens,i as personaStorageKey,s as readAnswers,f as readEntryUrl,l as readLanding,u as readPassed,h as readSavedPersonas,b as sitePersonas,S as startOf,g as writeSavedPersonas};
@@ -1,3 +1,3 @@
1
1
  /** @file Public entrypoint for the review tools' vocabulary contract. */
2
- export { DEVELOPER_LABEL, NODE_LABELS, TAB_INTROS, TAB_LABELS, configSourceLabel, deslug, screenName, } from './plain-language';
2
+ export { NODE_LABELS, TAB_INTROS, TAB_LABELS, configSourceLabel, deslug, screenName, } from './plain-language';
3
3
  export type { ReviewTabId } from './plain-language';
@@ -1 +1 @@
1
- export{DEVELOPER_LABEL,NODE_LABELS,TAB_INTROS,TAB_LABELS,configSourceLabel,deslug,screenName}from"./plain-language.js";
1
+ export{NODE_LABELS,TAB_INTROS,TAB_LABELS,configSourceLabel,deslug,screenName}from"./plain-language.js";