@picked/svelte 0.0.1-dev.1793758300441 → 0.0.1-dev.1793769511104

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 (36) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/plugins/devtools/DevToolsExperience.svelte +142 -30
  3. package/dist/plugins/devtools/customTools.d.ts +24 -5
  4. package/dist/plugins/devtools/customTools.js +1 -1
  5. package/dist/plugins/devtools/derive/guide-sections/guide-sections.js +1 -1
  6. package/dist/plugins/devtools/derive/index.d.ts +4 -0
  7. package/dist/plugins/devtools/derive/index.js +1 -1
  8. package/dist/plugins/devtools/derive/persona-builder/index.d.ts +3 -0
  9. package/dist/plugins/devtools/derive/persona-builder/index.js +1 -0
  10. package/dist/plugins/devtools/derive/persona-builder/persona-builder.d.ts +177 -0
  11. package/dist/plugins/devtools/derive/persona-builder/persona-builder.js +1 -0
  12. package/dist/plugins/devtools/derive/personas/index.d.ts +3 -0
  13. package/dist/plugins/devtools/derive/personas/index.js +1 -0
  14. package/dist/plugins/devtools/derive/personas/personas.d.ts +197 -0
  15. package/dist/plugins/devtools/derive/personas/personas.js +1 -0
  16. package/dist/plugins/devtools/derive/plain-language/plain-language.d.ts +2 -0
  17. package/dist/plugins/devtools/derive/plain-language/plain-language.js +1 -1
  18. package/dist/plugins/devtools/index.d.ts +2 -1
  19. package/dist/plugins/devtools/options.d.ts +33 -1
  20. package/dist/plugins/devtools/options.js +1 -1
  21. package/dist/plugins/devtools/plugin.d.ts +2 -1
  22. package/dist/plugins/devtools/review/index.d.ts +5 -1
  23. package/dist/plugins/devtools/review/index.js +1 -1
  24. package/dist/plugins/devtools/review/personas/ChoiceButton.svelte +50 -0
  25. package/dist/plugins/devtools/review/personas/PersonaBuilder.svelte +450 -0
  26. package/dist/plugins/devtools/review/personas/PersonaCard.svelte +86 -0
  27. package/dist/plugins/devtools/review/personas/PersonasPanel.svelte +546 -0
  28. package/dist/plugins/devtools/review/personas/load-persona.d.ts +226 -0
  29. package/dist/plugins/devtools/review/personas/load-persona.js +1 -0
  30. package/dist/plugins/devtools/session/session.js +1 -1
  31. package/dist/plugins/devtools/sidebar/SidebarFooter.svelte +114 -0
  32. package/dist/plugins/devtools/sidebar/ToolIcon.svelte +32 -0
  33. package/dist/plugins/devtools/sidebar/nav.d.ts +19 -9
  34. package/dist/plugins/devtools/sidebar/nav.js +1 -1
  35. package/dist/plugins/devtools/ui/forms/TextField.svelte +33 -6
  36. package/package.json +2 -2
@@ -1 +1 @@
1
- import{DEVTOOLS_PLUGIN_NAME as e}from"./constants.js";function t(e){return typeof e==`object`&&!!e}function n(e){return t(e)&&`frameworkName`in e&&typeof e.frameworkName==`string`&&`component`in e}function r(e){return Array.isArray(e)&&e.every(e=>typeof e==`string`)}function i(e){if(t(e)&&r(e.steps))return r(e.notes)?{steps:e.steps,notes:e.notes}:{steps:e.steps}}function a(e){if(!t(e))return null;let{id:r,label:a,component:o,placement:s,description:c,guide:l}=e;if(typeof r!=`string`||typeof a!=`string`||!n(o))return null;let u={id:r,label:a,component:o};(s===`review`||s===`developer`)&&(u.placement=s),typeof c==`string`&&(u.description=c);let d=i(l);return d&&(u.guide=d),u}function o(t){let n=t.getPluginConfig(e).customTools;if(!Array.isArray(n))return[];let r=new Set;return n.map(a).filter(e=>e!==null).filter(e=>!r.has(e.id)&&(r.add(e.id),!0))}function s(e){return e.filter(e=>e.placement===`review`)}function c(e){return e.filter(e=>e.placement!==`review`)}export{c as developerTools,o as readCustomTools,s as reviewTools};
1
+ import{DEVTOOLS_PLUGIN_NAME as e}from"./constants.js";var t=[`bug`,`wrench`];function n(e){return typeof e==`object`&&!!e}function r(e){return n(e)&&`frameworkName`in e&&typeof e.frameworkName==`string`&&`component`in e}function i(e){return t.some(t=>t===e)}function a(e){return Array.isArray(e)&&e.every(e=>typeof e==`string`)}function o(e){if(n(e)&&a(e.steps))return a(e.notes)?{steps:e.steps,notes:e.notes}:{steps:e.steps}}function s(e){if(!n(e))return null;let{id:t,label:a,component:s,placement:c,icon:l,description:u,guide:d}=e;if(typeof t!=`string`||typeof a!=`string`||!r(s))return null;let f={id:t,label:a,component:s};(c===`review`||c===`footer`||c===`developer`)&&(f.placement=c),i(l)&&(f.icon=l),typeof u==`string`&&(f.description=u);let p=o(d);return p&&(f.guide=p),f}function c(t){let n=t.getPluginConfig(e).customTools;if(!Array.isArray(n))return[];let r=new Set;return n.map(s).filter(e=>e!==null).filter(e=>!r.has(e.id)&&(r.add(e.id),!0))}function l(e){return e.filter(e=>e.placement===`review`)}function u(e){return e.filter(e=>e.placement===`footer`)}function d(e){return e.filter(e=>e.placement===void 0||e.placement===`developer`)}export{t as CUSTOM_TOOL_ICONS,d as developerTools,u as footerTools,c as readCustomTools,l as reviewTools};
@@ -1 +1 @@
1
- var e=[{id:`session`,title:`See where you are`,steps:[],notes:[`To see the screen you are on and every answer so far, open **This session**.`],tab:`session`},{id:`answers`,title:`Change an answer`,steps:[`Open **Answers**.`,`Find the question. Use **Answered**, **Not answered** or **All** to narrow the list.`,`Click **Change**, or **Answer** if it has none yet.`,`Pick or type the new answer, then click **Save**.`],notes:[`To remove one answer, click **Clear** beside it.`,`To start the whole journey again, click **Start over**, then **Clear everything**.`,`If a question says **Change it on its screen**, go back to that screen and change it there.`],tab:`answers`},{id:`journey`,title:`Go back to an earlier screen`,steps:[`Open **Journey map**.`,`Find a screen on your route. Your route is blue, and your screen says **You are here**.`,`Click the screen, then click **Go back to this screen**.`],notes:[`You can only go back to screens you have already visited.`,`To change how the map looks, pick **Map**, **Timeline** or **Outline** at the top.`],tab:`journey`},{id:`variations`,title:`View a test version of a page`,steps:[`Open **Variations**.`,`Find the test and pick the version you want.`,`Click **Show me this version**. 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 test, click **Leave this one alone**.`],tab:`variations`}],t=[{tab:`journey`,entry:{problem:`I can't click a screen on the Journey map`,fix:`Go back only to screens 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 screen you are on and every answer so far, open **This session**.`],tab:`session`},{id:`answers`,title:`Change an answer`,steps:[`Open **Answers**.`,`Find the question. Use **Answered**, **Not answered** or **All** to narrow the list.`,`Click **Change**, or **Answer** if it has none yet.`,`Pick or type the new answer, then click **Save**.`],notes:[`To remove one answer, click **Clear** beside it.`,`To start the whole journey again, click **Start over**, then **Clear everything**.`,`If a question says **Change it on its screen**, go back to that screen and change it there.`],tab:`answers`},{id:`journey`,title:`Go back to an earlier screen`,steps:[`Open **Journey map**.`,`Find a screen on your route. Your route is blue, and your screen says **You are here**.`,`Click the screen, then click **Go back to this screen**.`],notes:[`You can only go back to screens you have already visited.`,`To change how the map looks, pick **Map**, **Timeline** or **Outline** at the top.`],tab:`journey`},{id:`personas`,title:`Try the journey as a customer`,steps:[`Open **Personas & Scenarios**.`,`Click **Load** beside the customer you want. The journey 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 test version of a page`,steps:[`Open **Variations**.`,`Find the test and pick the version you want.`,`Click **Show me this version**. 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 test, click **Leave this one alone**.`],tab:`variations`}],t=[{tab:`journey`,entry:{problem:`I can't click a screen on the Journey map`,fix:`Go back only to screens 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};
@@ -18,6 +18,10 @@ export { BREADTH, DEPTH, buildTree } from './journey-tree';
18
18
  export type { FlowEnd, TreeInput, TreeJump, TreeLayout, TreeLink, TreeNode, TreeNodeKind, } from './journey-tree';
19
19
  export { DEVELOPER_LABEL, NODE_LABELS, TAB_INTROS, TAB_LABELS, configSourceLabel, deslug, screenName, } from './plain-language';
20
20
  export type { ReviewTabId } from './plain-language';
21
+ export { builderGraph, responseKeysOf, walkDraft } from './persona-builder';
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';
21
25
  export { UNDESCRIBABLE, describeRule, varKeyOf } from './plain-logic';
22
26
  export type { FieldLookup, FieldSummary } from './plain-logic';
23
27
  export { derivePosture } from './safety-posture';
@@ -1 +1 @@
1
- import{DEVELOPER_LABEL as e,NODE_LABELS as t,TAB_INTROS as n,TAB_LABELS as r,configSourceLabel as i,deslug as a,screenName as o}from"./plain-language/plain-language.js";import{askedFieldKeys as s,describeAnswer as c,kindOf as l,nounOf as u,questionOf as d,renderAnswer as f,systemPayload as p}from"./answer-display/answer-display.js";import{isBlank as m,nextHistory as h,submittedAnswers as g}from"./answer-history/answer-history.js";import{emphasis as _,guideFixes as v,guideSections as y}from"./guide-sections/guide-sections.js";import{UNDESCRIBABLE as b,describeRule as x,varKeyOf as S}from"./plain-logic/plain-logic.js";import{buildOutline as C}from"./journey-outline/journey-outline.js";import{nextOpenOrder as w,pickJourney as T}from"./journey-pick/journey-pick.js";import{BREADTH as E,DEPTH as D,buildTree as O}from"./journey-tree/journey-tree.js";import"./journey-tree/index.js";import{COLOURS as k,LANE_WIDTH as A,ROW_HEIGHT as j,buildTimeline as M}from"./journey-timeline/journey-timeline.js";import"./journey-timeline/index.js";import{derivePosture as N}from"./safety-posture/safety-posture.js";import{nextTrails as P}from"./visited-steps/visited-steps.js";import{nextRoute as F}from"./walk-route/walk-route.js";export{E as BREADTH,k as COLOURS,D as DEPTH,e as DEVELOPER_LABEL,A as LANE_WIDTH,t as NODE_LABELS,j as ROW_HEIGHT,n as TAB_INTROS,r as TAB_LABELS,b as UNDESCRIBABLE,s as askedFieldKeys,C as buildOutline,M as buildTimeline,O as buildTree,i as configSourceLabel,N as derivePosture,c as describeAnswer,x as describeRule,a as deslug,_ as emphasis,v as guideFixes,y as guideSections,m as isBlank,l as kindOf,h as nextHistory,w as nextOpenOrder,F as nextRoute,P as nextTrails,u as nounOf,T as pickJourney,d as questionOf,f as renderAnswer,o as screenName,g as submittedAnswers,p as systemPayload,S as varKeyOf};
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};
@@ -0,0 +1,3 @@
1
+ /** @file Public entrypoint for the persona builder: the flow walked on draft answers. */
2
+ export { builderGraph, responseKeysOf, walkDraft } from './persona-builder';
3
+ export type { BuilderCondition, BuilderEdge, BuilderField, BuilderInput, BuilderJourney, BuilderNode, BuilderRow, BuilderStep, BuilderWalk, GraphSource, } from './persona-builder';
@@ -0,0 +1 @@
1
+ export{builderGraph,responseKeysOf,walkDraft}from"./persona-builder.js";
@@ -0,0 +1,177 @@
1
+ /**
2
+ * @file Builds a persona without leaving the panel: the real flow graph walked on draft answers, one
3
+ * screen at a time, with nothing sent to the live session.
4
+ *
5
+ * The walk follows the engine's own rules. The origin and a screen take their first edge. A decision
6
+ * takes its `true` edge when its rule holds, else its `false` edge. An action takes its success edge,
7
+ * falling back to its edge with no outcome. A launch node carries on at the other journey's origin,
8
+ * and so does an action the host names as a hand-off.
9
+ *
10
+ * It is progressive, like a visitor. A decision sees only the answers given on the screens already
11
+ * passed, so a rule that tests whether an answer is there yet reads the same as on the site.
12
+ *
13
+ * Actions call real services, so their outcomes are not in a draft. A decision on an answer no
14
+ * screen asks, and an action with an outcome edge each way, are shown as a choice the reviewer can
15
+ * flip, starting at what the draft alone gives. The real outcome decides once the persona loads.
16
+ */
17
+ import type { FieldValue } from '@picked/client';
18
+ import type { PersonaLanding, PersonaScreen } from '../personas';
19
+ /** A node of a journey's flow, as far as the walk reads one. */
20
+ export interface BuilderNode {
21
+ id: string;
22
+ type: string;
23
+ name?: string;
24
+ stepId?: string;
25
+ conditionId?: string;
26
+ actionKey?: string;
27
+ experienceKey?: string;
28
+ }
29
+ /** An edge of a journey's flow. */
30
+ export interface BuilderEdge {
31
+ sourceNodeId: string;
32
+ destinationNodeId: string;
33
+ sourceHandle?: string | null;
34
+ }
35
+ /** A journey: its flow graph, and the names the panel shows. */
36
+ export interface BuilderJourney {
37
+ key: string;
38
+ name: string;
39
+ nodes: readonly BuilderNode[];
40
+ edges: readonly BuilderEdge[];
41
+ }
42
+ /** A screen, as the flow's step nodes name it. */
43
+ export interface BuilderStep {
44
+ id: string;
45
+ key: string;
46
+ name?: string;
47
+ fieldKeys: readonly string[];
48
+ }
49
+ /** A decision. `TRule` is the host's rule type, handed back to `evaluate`. */
50
+ export interface BuilderCondition<TRule> {
51
+ id: string;
52
+ key?: string;
53
+ name?: string;
54
+ logic: TRule;
55
+ }
56
+ /** The parts of a field the walk reads: when it shows, and when each choice shows. */
57
+ export interface BuilderField<TRule> {
58
+ condition?: TRule | null;
59
+ options?: readonly {
60
+ value: unknown;
61
+ condition?: TRule | null;
62
+ }[] | null;
63
+ }
64
+ /** Everything one walk reads. */
65
+ export interface BuilderInput<TRule> {
66
+ journeys: ReadonlyMap<string, BuilderJourney>;
67
+ steps: ReadonlyMap<string, BuilderStep>;
68
+ conditions: ReadonlyMap<string, BuilderCondition<TRule>>;
69
+ /** The journey the walk starts in. */
70
+ start: string;
71
+ answers: Readonly<Record<string, FieldValue>>;
72
+ /** The reviewer's choice on each decision the site's code decides, by row id. */
73
+ choices: Readonly<Record<string, boolean>>;
74
+ /** Every key a screen asks. A decision on any other answer is the site code's to make. */
75
+ asked: ReadonlySet<string>;
76
+ /** Keys a screen carries only for its code. */
77
+ codeOwned: ReadonlySet<string>;
78
+ /** Screens that move on by themselves; the walk passes them without a row. */
79
+ selfAdvancing: ReadonlySet<string>;
80
+ /** Actions that open another journey, by action key. */
81
+ handoffs: Readonly<Record<string, string>>;
82
+ /**
83
+ * A field as the workspace defines it.
84
+ *
85
+ * @param key - The field key.
86
+ */
87
+ field: (key: string) => BuilderField<TRule> | undefined;
88
+ /**
89
+ * Evaluates a rule against draft answers, on the persona's entry page.
90
+ *
91
+ * @param rule - The rule.
92
+ * @param responses - The answers the rule reads.
93
+ */
94
+ evaluate: (rule: TRule, responses: Readonly<Record<string, FieldValue>>) => Promise<boolean>;
95
+ /**
96
+ * The response keys a rule reads.
97
+ *
98
+ * @param rule - The rule.
99
+ */
100
+ readsOf: (rule: TRule) => readonly string[];
101
+ }
102
+ /** One row the panel shows for a walk. */
103
+ export type BuilderRow = {
104
+ kind: 'screen';
105
+ id: string;
106
+ experienceKey: string;
107
+ experienceName: string;
108
+ stepKey: string;
109
+ stepName: string | null;
110
+ /** The questions it asks, given the answers before it. Empty: Continue is all it takes. */
111
+ fields: string[];
112
+ /** For a question with choices, the positions of the choices it offers. */
113
+ options: Partial<Record<string, number[]>>;
114
+ /** Every question answered, so the walk moved on. */
115
+ complete: boolean;
116
+ } | {
117
+ /** An outcome the site's code decides; the reviewer picks the one to follow. */
118
+ kind: 'choice';
119
+ id: string;
120
+ question: string;
121
+ value: boolean;
122
+ } | {
123
+ kind: 'end';
124
+ id: string;
125
+ reason: 'end' | 'loop' | 'handoff' | 'limit';
126
+ name: string | null;
127
+ };
128
+ /** What a walk comes to. */
129
+ export interface BuilderWalk {
130
+ rows: BuilderRow[];
131
+ /** The answers given on the screens walked. */
132
+ answers: Record<string, FieldValue>;
133
+ /** The screens moved on from, in order. */
134
+ passed: PersonaScreen[];
135
+ /** Where a load of it stops: the first unanswered screen, else the last screen reached. */
136
+ landing: PersonaLanding | null;
137
+ }
138
+ /**
139
+ * Walks the start journey from its origin until a screen is left unanswered, or the route ends.
140
+ *
141
+ * @template TRule
142
+ * @param {BuilderInput<TRule>} input - The graph, the draft and how to read rules.
143
+ * @returns {Promise<BuilderWalk>} The rows to show, and the persona the walk makes.
144
+ * @example await walkDraft({ ...input, answers: { purpose: 'home' } })
145
+ */
146
+ export declare function walkDraft<TRule>(input: BuilderInput<TRule>): Promise<BuilderWalk>;
147
+ /**
148
+ * The response keys a JSON Logic rule reads: every `responses.*` variable, by its first segment.
149
+ *
150
+ * @param {unknown} rule - The rule.
151
+ * @returns {string[]} The keys, each once.
152
+ * @example responseKeysOf({ '==': [{ var: 'responses.purpose' }, 'home'] }) // ['purpose']
153
+ */
154
+ export declare function responseKeysOf(rule: unknown): string[];
155
+ /** An experience as the client lists it: its key, name and flow. */
156
+ export interface GraphSource<TRule> {
157
+ key: string;
158
+ name: string;
159
+ treeStore: {
160
+ getState: () => {
161
+ nodes: readonly BuilderNode[];
162
+ edges: readonly BuilderEdge[];
163
+ steps: readonly BuilderStep[];
164
+ conditions: readonly BuilderCondition<TRule>[];
165
+ };
166
+ };
167
+ }
168
+ /**
169
+ * The journeys, screens and decisions a walk reads, from the client's experiences.
170
+ *
171
+ * @template TRule
172
+ * @param {readonly GraphSource<TRule>[]} experiences - Every experience the client holds.
173
+ * @param {ReadonlySet<string>} hidden - Experience keys never built in.
174
+ * @returns {Pick<BuilderInput<TRule>, 'journeys' | 'steps' | 'conditions'>} The graph.
175
+ * @example builderGraph(client.experiences.listStore.getState(), new Set(['devtools']))
176
+ */
177
+ export declare function builderGraph<TRule>(experiences: readonly GraphSource<TRule>[], hidden: ReadonlySet<string>): Pick<BuilderInput<TRule>, 'journeys' | 'steps' | 'conditions'>;
@@ -0,0 +1 @@
1
+ import{isBlank as e}from"../answer-history/answer-history.js";var t=400;function n(e,t,n){let r=e.edges.filter(e=>e.sourceNodeId===t.id),i=r.find(e=>e.sourceHandle===n),a=r.at(0);return n!==null&&(a=t.type===`condition`?i:i??r.find(e=>e.sourceHandle==null)),e.nodes.find(e=>e.id===a?.destinationNodeId)}function r(e){return e?.nodes.find(e=>e.type===`origin`)}function i(t,n){return t===void 0||e(t)?!1:n===void 0?!0:Array.isArray(t)?t.every(e=>n.includes(e)):n.includes(t)}async function a(e,t,n){let r=[],i=[];for(let[a,o]of e.entries())(o.condition==null||await n.evaluate(o.condition,t))&&(r.push(o.value),i.push(a));return{values:r,positions:i}}async function o(t,n,r){let i={...n};for(let n of t.fieldKeys)Object.hasOwn(r.answers,n)&&!e(r.answers[n])&&(i[n]=r.answers[n]);let o={fields:[],offered:{},options:{}};for(let e of t.fieldKeys){let t=r.field(e),n=t?.condition!=null&&!await r.evaluate(t.condition,i);if(r.codeOwned.has(e)||n||(o.fields.push(e),t?.options==null||t.options.length===0))continue;let{values:s,positions:c}=await a(t.options,i,r);o.offered[e]=s,o.options[e]=c}return o}function s(e,t){let r=n(e.journey,e.node,t);return r===void 0?void 0:{journey:e.journey,node:r}}function c(e,t){let n=e.journeys.get(t),i=r(n);return n===void 0||i===void 0?void 0:{journey:n,node:i}}async function l(e,t,n,r){let a=e.node.stepId===void 0?void 0:n.steps.get(e.node.stepId);if(a===void 0||n.selfAdvancing.has(a.key))return{next:s(e,null)};let{fields:c,offered:l,options:u}=await o(a,r.held,n),d=c.every(e=>i(n.answers[e],l[e]));r.rows.push({kind:`screen`,id:t,experienceKey:e.journey.key,experienceName:e.journey.name,stepKey:a.key,stepName:a.name??null,fields:c,options:u,complete:d});for(let e of c)i(n.answers[e],l[e])&&(r.held[e]=n.answers[e]);let f={experienceKey:e.journey.key,experienceName:e.journey.name,stepKey:a.key,stepName:a.name??null};return r.lastScreen=f,d?(r.passed.push({experienceKey:e.journey.key,stepKey:a.key}),{next:s(e,null)}):{stop:f}}async function u(e,t,n,r){let i=e.node.conditionId===void 0?void 0:n.conditions.get(e.node.conditionId);if(i===void 0)return r.rows.push({kind:`end`,id:t,reason:`end`,name:e.node.name??null}),{stop:null};let a=await n.evaluate(i.logic,r.held);if(n.readsOf(i.logic).some(e=>!n.asked.has(e)||n.codeOwned.has(e))){a=n.choices[t]??a;let e=i.name??i.key??`A decision the site makes`;r.rows.push({kind:`choice`,id:t,question:e,value:a})}return{next:s(e,a?`true`:`false`)}}function d(e,t,n,r){let{node:i}=e,a=i.type===`experience`?i.experienceKey:n.handoffs[i.actionKey??``];if(a!==void 0){let e=c(n,a);return e===void 0?(r.rows.push({kind:`end`,id:t,reason:`handoff`,name:i.name??a}),{stop:null}):{next:e}}if(i.type!==`action`)return{next:s(e,null)};let o=e.journey.edges.some(e=>e.sourceNodeId===i.id&&e.sourceHandle===`false`),l=!0;if(o){l=n.choices[t]??!0;let e=`${i.name??i.actionKey??`This step`} works`;r.rows.push({kind:`choice`,id:t,question:e,value:l})}return{next:s(e,l?`true`:`false`)}}async function f(e,t,n,r){switch(e.node.type){case`step`:return l(e,t,n,r);case`condition`:return u(e,t,n,r);case`action`:case`experience`:return d(e,t,n,r);default:return{next:s(e,null)}}}async function p(e){let n={held:{},rows:[],passed:[],lastScreen:null},r=e=>({rows:n.rows,answers:{...n.held},passed:n.passed,landing:e??n.lastScreen}),i=new Set,a=c(e,e.start);for(let o=0;o<t;o++){if(a===void 0)return n.rows.push({kind:`end`,id:`end-${o}`,reason:`end`,name:null}),r(null);let t=`${a.journey.key}/${a.node.id}`;if(i.has(t)){let i=a.node.stepId,o=i===void 0?void 0:e.steps.get(i);return n.rows.push({kind:`end`,id:`${t}#loop`,reason:`loop`,name:o?.name??null}),r(null)}i.add(t);let s=await f(a,t,e,n);if(`stop`in s)return r(s.stop);a=s.next}return n.rows.push({kind:`end`,id:`limit`,reason:`limit`,name:null}),r(null)}function m(e){let t=Array.isArray(e)?e.at(0):e;if(typeof t!=`string`||!t.startsWith(`responses.`))return;let n=t.slice(10).split(`.`).at(0);return n===void 0||n.length===0?void 0:n}function h(e){let t=new Set,n=e=>{if(Array.isArray(e))for(let t of e)n(t);else if(typeof e==`object`&&e)for(let[r,i]of Object.entries(e)){let e=r===`var`?m(i):void 0;e!==void 0&&t.add(e),n(i)}};return n(e),[...t]}function g(e,t){let n=e.filter(e=>!t.has(e.key)).map(e=>({experience:e,tree:e.treeStore.getState()}));return{journeys:new Map(n.map(({experience:e,tree:t})=>[e.key,{key:e.key,name:e.name,nodes:t.nodes,edges:t.edges}])),steps:new Map(n.flatMap(({tree:e})=>e.steps.map(e=>[e.id,e]))),conditions:new Map(n.flatMap(({tree:e})=>e.conditions.map(e=>[e.id,e])))}}export{g as builderGraph,h as responseKeysOf,p as walkDraft};
@@ -0,0 +1,3 @@
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';
@@ -0,0 +1 @@
1
+ export{PERSONA_STORAGE_PREFIX,buildPersona,customerAnswers,describeLanding,entryUrlOf,passedScreens,personaStorageKey,readAnswers,readEntryUrl,readSavedPersonas,sitePersonas,writeSavedPersonas}from"./personas.js";
@@ -0,0 +1,197 @@
1
+ /**
2
+ * @file Personas and scenarios: customers and situations a reviewer tries the journey as. The host
3
+ * defines some for every viewer; a reviewer saves their own, from where they are, into this
4
+ * browser's `localStorage`, one list per project. A saved persona keeps its answers, the screens
5
+ * the reviewer moved on from, and the screen it stops at, which is what a replay walks.
6
+ */
7
+ import type { FieldValue, Picked } from '@picked/client';
8
+ /** Where a persona comes from: the host's definitions, or this browser. */
9
+ export type PersonaSource = 'site' | 'saved';
10
+ /** The journey a persona opens, and the screen it lands on. */
11
+ export interface PersonaLanding {
12
+ experienceKey: string;
13
+ experienceName: string | null;
14
+ /** `null` opens the journey wherever its own flow puts the answers. */
15
+ stepKey: string | null;
16
+ stepName: string | null;
17
+ }
18
+ /** A screen in a journey. */
19
+ export interface PersonaScreen {
20
+ experienceKey: string;
21
+ stepKey: string;
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>;
25
+ /** A customer or situation to try the journey as. */
26
+ export interface Persona {
27
+ id: string;
28
+ name: string;
29
+ description: string | null;
30
+ source: PersonaSource;
31
+ /** The answers in place once it loads. */
32
+ responses: Readonly<Record<string, FieldValue>>;
33
+ /** Where a load stops; `null` loads the answers only. */
34
+ landing: PersonaLanding | null;
35
+ /**
36
+ * The screens moved on from when it was saved, in the order first left. A load presses Continue
37
+ * on a screen that asks nothing only when it is listed here.
38
+ */
39
+ passed: readonly PersonaScreen[];
40
+ /**
41
+ * The page a load starts on: a path on this site, with its query, so a campaign landing keeps its
42
+ * campaign. `null` starts on the page in front.
43
+ */
44
+ entryUrl: string | null;
45
+ /** When it was saved, as an ISO time; `null` for a host-defined persona. */
46
+ savedAt: string | null;
47
+ /** The host's own loader; `null` for the standard one. */
48
+ load: PersonaLoader | null;
49
+ }
50
+ /** A persona as the host defines it in the plugin's `personas` option. */
51
+ export interface PersonaDefinition {
52
+ /** Unique among the host's personas. */
53
+ id: string;
54
+ name: string;
55
+ /** Who they are, in a sentence. */
56
+ description?: string;
57
+ /** The answers in place once it loads. */
58
+ responses?: Readonly<Record<string, FieldValue>>;
59
+ /** The journey it opens. */
60
+ experienceKey?: string;
61
+ /** The screen in that journey it lands on. */
62
+ stepKey?: string;
63
+ /** The page a load starts on, such as a campaign landing; the page in front when absent. */
64
+ entryUrl?: string;
65
+ /** Loads it the host's own way, in place of the standard answers-then-journey load. */
66
+ load?: PersonaLoader;
67
+ }
68
+ /** What the reviewer types when saving where they are. */
69
+ export interface PersonaDraft {
70
+ name: string;
71
+ description: string;
72
+ }
73
+ /** The session as it stands when the reviewer saves. */
74
+ export interface PersonaCapture {
75
+ responses: Readonly<Record<string, FieldValue>>;
76
+ /** The journey in front and its screen; `null` when none is open. */
77
+ journey: PersonaLanding | null;
78
+ /** The screens moved on from this session. */
79
+ passed: readonly PersonaScreen[];
80
+ /** The page a load starts on, as typed or as the page in front. */
81
+ entryUrl: string;
82
+ id: string;
83
+ savedAt: string;
84
+ }
85
+ /** A saved persona, or why it could not be saved. */
86
+ export type BuildPersonaResult = {
87
+ ok: true;
88
+ persona: Persona;
89
+ } | {
90
+ ok: false;
91
+ error: string;
92
+ };
93
+ /** Reads the storage the saved personas live in. */
94
+ export type PersonaStorageGetter = () => Pick<Storage, 'getItem' | 'setItem'>;
95
+ /** The `localStorage` key prefix; the project id follows it. */
96
+ export declare const PERSONA_STORAGE_PREFIX = "picked-devtools-personas";
97
+ /**
98
+ * The `localStorage` key one project's saved personas live under.
99
+ *
100
+ * @param {string} projectId - The Picked project id.
101
+ * @returns {string} The storage key.
102
+ * @example personaStorageKey('abc') // 'picked-devtools-personas:abc'
103
+ */
104
+ export declare function personaStorageKey(projectId: string): string;
105
+ /**
106
+ * Reads a set of answers, keeping only the ones that are JSON values.
107
+ *
108
+ * @param {unknown} value - The value to read.
109
+ * @returns {Record<string, FieldValue>} The answers, keyed by field key.
110
+ * @example readAnswers({ age: 42 }) // { age: 42 }
111
+ */
112
+ export declare function readAnswers(value: unknown): Record<string, FieldValue>;
113
+ /**
114
+ * Reads an entry URL, or `null` when it is not a path on this site.
115
+ *
116
+ * @param {unknown} value - The stored or typed value.
117
+ * @returns {string | null} A path starting with `/`, with its query.
118
+ * @example readEntryUrl('/campaign?utm=spring') // '/campaign?utm=spring'
119
+ */
120
+ export declare function readEntryUrl(value: unknown): string | null;
121
+ /**
122
+ * An entry URL from what a reviewer types: a path, or a full URL on this site.
123
+ *
124
+ * @param {string} typed - What was typed.
125
+ * @param {string} origin - This site's origin.
126
+ * @returns {string | null} The path and query, or `null` for another site or no URL at all.
127
+ * @example entryUrlOf('https://shop.example/sale?x=1', 'https://shop.example') // '/sale?x=1'
128
+ */
129
+ export declare function entryUrlOf(typed: string, origin: string): string | null;
130
+ /**
131
+ * The personas saved in this browser for one project. Malformed entries are dropped, and missing,
132
+ * unreadable or blocked storage reads as none.
133
+ *
134
+ * @param {PersonaStorageGetter} getStorage - Reads the storage to use.
135
+ * @param {string} projectId - The Picked project id.
136
+ * @returns {Persona[]} The saved personas, oldest first.
137
+ * @example readSavedPersonas(() => window.localStorage, client.projectId)
138
+ */
139
+ export declare function readSavedPersonas(getStorage: PersonaStorageGetter, projectId: string): Persona[];
140
+ /**
141
+ * Saves one project's personas in this browser.
142
+ *
143
+ * @param {PersonaStorageGetter} getStorage - Reads the storage to use.
144
+ * @param {string} projectId - The Picked project id.
145
+ * @param {readonly Persona[]} personas - Every saved persona, oldest first.
146
+ * @returns {boolean} `false` when storage refused the write: blocked, or full.
147
+ * @example writeSavedPersonas(() => window.localStorage, client.projectId, personas)
148
+ */
149
+ export declare function writeSavedPersonas(getStorage: PersonaStorageGetter, projectId: string, personas: readonly Persona[]): boolean;
150
+ /**
151
+ * The screens a session moved on from, each once, in the order first left, minus the journeys the
152
+ * devtools never follow.
153
+ *
154
+ * @param {readonly { journey: string; screen: string }[]} route - Every screen moved on from, in order.
155
+ * @param {ReadonlySet<string>} hidden - Experience keys never followed.
156
+ * @returns {PersonaScreen[]} The passed screens.
157
+ * @example passedScreens([{ journey: 'funnel', screen: 'postcode' }], new Set())
158
+ */
159
+ export declare function passedScreens(route: readonly {
160
+ journey: string;
161
+ screen: string;
162
+ }[], hidden: ReadonlySet<string>): PersonaScreen[];
163
+ /**
164
+ * The answers a customer gave: the held values of the fields they can see, minus the blank ones.
165
+ *
166
+ * @param {Readonly<Record<string, FieldValue>>} responses - Every held response.
167
+ * @param {ReadonlySet<string>} visibleKeys - The keys of the fields a journey's screens show now.
168
+ * @returns {Record<string, FieldValue>} The answers to keep, keyed by field key.
169
+ * @example customerAnswers(client.responses.getAllStore.getState(), new Set(['coverage-type']))
170
+ */
171
+ export declare function customerAnswers(responses: Readonly<Record<string, FieldValue>>, visibleKeys: ReadonlySet<string>): Record<string, FieldValue>;
172
+ /**
173
+ * Builds a persona from where the reviewer is: their answers so far, and the screen in front.
174
+ *
175
+ * @param {PersonaDraft} draft - The name and description typed.
176
+ * @param {PersonaCapture} capture - The session as it stands.
177
+ * @returns {BuildPersonaResult} The persona, or why it cannot be saved.
178
+ * @example buildPersona({ name: 'Young family', description: '' }, capture)
179
+ */
180
+ export declare function buildPersona(draft: PersonaDraft, capture: PersonaCapture): BuildPersonaResult;
181
+ /**
182
+ * Reads the host's persona definitions, dropping any without an id or name. A host loader is
183
+ * wrapped so it can be any function the host passed.
184
+ *
185
+ * @param {unknown} definitions - The plugin's `personas` option.
186
+ * @returns {Persona[]} The host's personas, in the order given.
187
+ * @example sitePersonas([{ id: 'retiree', name: 'Retiree', responses: { age: 67 } }])
188
+ */
189
+ export declare function sitePersonas(definitions: unknown): Persona[];
190
+ /**
191
+ * Where a persona lands, in the reviewer's words.
192
+ *
193
+ * @param {PersonaLanding | null} landing - The persona's landing.
194
+ * @returns {string} A short phrase naming the screen and journey.
195
+ * @example describeLanding(null) // 'Answers only'
196
+ */
197
+ export declare function describeLanding(landing: PersonaLanding | null): string;
@@ -0,0 +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){let t=c(e);return t!==null&&t.startsWith(`/`)&&!t.startsWith(`//`)?t:null}function f(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 p(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),entryUrl:d(e.entryUrl),savedAt:c(e.savedAt),load:null}}function m(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(p).filter(e=>e!==null)}function h(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,entryUrl:e.entryUrl,savedAt:e.savedAt}));try{return e().setItem(i(t),JSON.stringify({version:r,personas:a})),!0}catch{return!1}}function g(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 _(t,n){let r={};for(let[i,a]of Object.entries(t))n.has(i)&&!e(a)&&(r[i]=a);return r}function v(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. Answer a question 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,entryUrl:d(t.entryUrl),savedAt:t.savedAt,load:null}}}function y(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:[],entryUrl:d(e.entryUrl),savedAt:null,load:typeof i==`function`?async e=>{await Reflect.apply(i,void 0,[e])}:null}]}):[]}function b(e){if(e===null)return`Answers 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,v as buildPersona,_ as customerAnswers,b as describeLanding,f as entryUrlOf,g as passedScreens,i as personaStorageKey,s as readAnswers,d as readEntryUrl,m as readSavedPersonas,y as sitePersonas,h as writeSavedPersonas};
@@ -57,6 +57,7 @@ export declare const TAB_LABELS: {
57
57
  readonly session: 'This session';
58
58
  readonly answers: 'Answers';
59
59
  readonly journey: 'Journey map';
60
+ readonly personas: 'Personas & Scenarios';
60
61
  readonly variations: 'Variations';
61
62
  };
62
63
  /** The id of one of the review tools' own tabs. */
@@ -69,5 +70,6 @@ export declare const TAB_INTROS: {
69
70
  readonly session: 'Where you are right now, and what you answered on each screen. Start here if something looks wrong.';
70
71
  readonly answers: 'Every question this journey can ask, and the answers given. Change one to see what the customer would see.';
71
72
  readonly journey: 'Every route this journey can take, with your route marked. Go back to any screen you have already visited.';
73
+ readonly personas: 'Customers and situations to try this journey as. Load one to land where it would, or save where you are as a new one.';
72
74
  readonly variations: 'The variations running on this site. Force one on to review it, then copy the link to show someone else exactly what you saw.';
73
75
  };
@@ -1 +1 @@
1
- var e={origin:`Start`,step:`Screen`,condition:`Decision`,action:`Checking details`,experience:`Opens another journey`};function t(e){let t=e.replace(/[-_]+/g,` `).trim();return t.length===0?e:t.charAt(0).toUpperCase()+t.slice(1)}function n(e,n){let r=e?.trim();return r!==void 0&&r.length>0?r:n!==void 0&&n.length>0?t(n):`Untitled screen`}function r(e){return e!==void 0&&e.length>0?`Viewing unpublished changes`:`Viewing published content`}var i={guide:`How to use`,session:`This session`,answers:`Answers`,journey:`Journey map`,variations:`Variations`},a=`Developer`,o={guide:`How to use each tab. Open a tab straight from its section.`,session:`Where you are right now, and what you answered on each screen. Start here if something looks wrong.`,answers:`Every question this journey can ask, and the answers given. Change one to see what the customer would see.`,journey:`Every route this journey can take, with your route marked. Go back to any screen you have already visited.`,variations:`The variations running on this site. Force one on to review it, then copy the link to show someone else exactly what you saw.`};export{a as DEVELOPER_LABEL,e as NODE_LABELS,o as TAB_INTROS,i as TAB_LABELS,r as configSourceLabel,t as deslug,n as screenName};
1
+ var e={origin:`Start`,step:`Screen`,condition:`Decision`,action:`Checking details`,experience:`Opens another journey`};function t(e){let t=e.replace(/[-_]+/g,` `).trim();return t.length===0?e:t.charAt(0).toUpperCase()+t.slice(1)}function n(e,n){let r=e?.trim();return r!==void 0&&r.length>0?r:n!==void 0&&n.length>0?t(n):`Untitled screen`}function r(e){return e!==void 0&&e.length>0?`Viewing unpublished changes`:`Viewing published content`}var i={guide:`How to use`,session:`This session`,answers:`Answers`,journey:`Journey map`,personas:`Personas & Scenarios`,variations:`Variations`},a=`Developer`,o={guide:`How to use each tab. Open a tab straight from its section.`,session:`Where you are right now, and what you answered on each screen. Start here if something looks wrong.`,answers:`Every question this journey can ask, and the answers given. Change one to see what the customer would see.`,journey:`Every route this journey can take, with your route marked. Go back to any screen you have already visited.`,personas:`Customers and situations to try this journey as. Load one to land where it would, or save where you are as a new one.`,variations:`The variations running on this site. Force one on to review it, then copy the link to show someone else exactly what you saw.`};export{a as DEVELOPER_LABEL,e as NODE_LABELS,o as TAB_INTROS,i as TAB_LABELS,r as configSourceLabel,t as deslug,n as screenName};
@@ -3,5 +3,6 @@ export { svelteDevtoolsPlugin } from './plugin';
3
3
  export { getDevtoolsSession } from './session-access';
4
4
  export type * from './plugin';
5
5
  export type * from './options';
6
- export type { CustomTool, CustomToolGuide, CustomToolPlacement } from './customTools';
6
+ export type { CustomTool, CustomToolGuide, CustomToolIcon, CustomToolPlacement, } from './customTools';
7
7
  export type { DevtoolsSession } from './session-access';
8
+ export type { PersonaDefinition, PersonaLoader } from './derive/personas';
@@ -1,5 +1,6 @@
1
1
  /** @file The devtools plugin's options, and how the devtools panel reads them back off the client. */
2
- import type { Picked } from '@picked/client';
2
+ import type { FieldValue, Picked } from '@picked/client';
3
+ import { type Persona, type PersonaDefinition } from './derive/personas';
3
4
  export { DEVTOOLS_EXPERIENCE_KEY } from './constants';
4
5
  /** The corner the floating devtools button sits in. */
5
6
  export type LauncherPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
@@ -19,6 +20,24 @@ export interface LauncherOptions {
19
20
  */
20
21
  autoHide?: boolean;
21
22
  }
23
+ /** How loading a persona walks the journey. */
24
+ export interface PersonaWalkOptions {
25
+ /**
26
+ * Step keys that move on by themselves, such as a loader that continues once its fetch lands.
27
+ * The walk waits on them, where it presses Continue on any other screen.
28
+ */
29
+ selfAdvancingSteps?: readonly string[];
30
+ /**
31
+ * Answers every walk gives when a screen asks them, over the persona's own, such as "start over"
32
+ * on a resume prompt.
33
+ */
34
+ answers?: Readonly<Record<string, FieldValue>>;
35
+ /**
36
+ * Actions that open another journey, by action key, with the journey's experience key. Building a
37
+ * persona in the panel carries on in that journey where the action is; a launch node needs none.
38
+ */
39
+ handoffs?: Readonly<Record<string, string>>;
40
+ }
22
41
  /** A button the Answers tab shows after an answer is edited. */
23
42
  export interface AnswerFollowUp {
24
43
  /** The button's text. */
@@ -76,6 +95,13 @@ export interface DevtoolsPanelOptions {
76
95
  guide?: DevtoolsGuide;
77
96
  /** The floating button's corner and auto-hide. */
78
97
  launcher?: LauncherOptions;
98
+ /**
99
+ * Customers and situations every viewer can load from the Personas & Scenarios tab. Reviewers
100
+ * also save their own there, in their browser.
101
+ */
102
+ personas?: readonly PersonaDefinition[];
103
+ /** How loading a persona walks the journey. */
104
+ personaWalk?: PersonaWalkOptions;
79
105
  }
80
106
  /** The devtools options with every default applied. */
81
107
  export interface ResolvedDevtoolsOptions {
@@ -91,6 +117,12 @@ export interface ResolvedDevtoolsOptions {
91
117
  fixes: DevtoolsGuideFix[];
92
118
  };
93
119
  launcher: Required<LauncherOptions>;
120
+ personas: Persona[];
121
+ personaWalk: {
122
+ selfAdvancingSteps: ReadonlySet<string>;
123
+ answers: Readonly<Record<string, FieldValue>>;
124
+ handoffs: Readonly<Record<string, string>>;
125
+ };
94
126
  }
95
127
  /** Every corner the floating button can sit in. */
96
128
  export declare const LAUNCHER_POSITIONS: readonly LauncherPosition[];
@@ -1 +1 @@
1
- import{DEVTOOLS_EXPERIENCE_KEY as e,DEVTOOLS_PLUGIN_NAME as t}from"./constants.js";var n=[`top-left`,`top-right`,`bottom-left`,`bottom-right`];function r(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function i(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`):[]}function a(e){return n.some(t=>t===e)}function o(e){if(!r(e))return null;let{id:t,title:n,steps:a,notes:o}=e;return typeof t!=`string`||typeof n!=`string`||!Array.isArray(a)?null:{id:t,title:n,steps:i(a),notes:i(o)}}function s(e){if(!r(e))return null;let{problem:t,fix:n}=e;return typeof t!=`string`||typeof n!=`string`?null:{problem:t,fix:n}}function c(e){if(!r(e))return null;let{label:t,run:n}=e;return typeof t!=`string`||typeof n!=`function`?null:{label:t,run:e=>{Reflect.apply(n,void 0,[e])}}}function l(n){let l=n.getPluginConfig(t).options,u=r(l)?l:{},d=r(u.guide)?u.guide:{},f=r(u.launcher)?u.launcher:{},p=u.environmentLabel;return{readOnly:u.readOnly===!0,environmentLabel:typeof p==`string`&&p.length>0?p:void 0,hiddenExperienceKeys:new Set([e,...i(u.hiddenExperienceKeys)]),journeyPrecedence:i(u.journeyPrecedence),codeOwnedFields:new Set(i(u.codeOwnedFields)),answerFollowUp:c(u.answerFollowUp),guide:{sections:(Array.isArray(d.sections)?d.sections:[]).map(o).filter(e=>e!==null),fixes:(Array.isArray(d.fixes)?d.fixes:[]).map(s).filter(e=>e!==null)},launcher:{position:a(f.position)?f.position:`bottom-right`,autoHide:typeof f.autoHide!=`boolean`||f.autoHide}}}export{e as DEVTOOLS_EXPERIENCE_KEY,n as LAUNCHER_POSITIONS,l as readDevtoolsOptions};
1
+ import{DEVTOOLS_EXPERIENCE_KEY as e,DEVTOOLS_PLUGIN_NAME as t}from"./constants.js";import{readAnswers as n,sitePersonas as r}from"./derive/personas/personas.js";var i=[`top-left`,`top-right`,`bottom-left`,`bottom-right`];function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function o(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`):[]}function s(e){if(!a(e))return{};let t={};for(let[n,r]of Object.entries(e))typeof r==`string`&&(t[n]=r);return t}function c(e){return i.some(t=>t===e)}function l(e){if(!a(e))return null;let{id:t,title:n,steps:r,notes:i}=e;return typeof t!=`string`||typeof n!=`string`||!Array.isArray(r)?null:{id:t,title:n,steps:o(r),notes:o(i)}}function u(e){if(!a(e))return null;let{problem:t,fix:n}=e;return typeof t!=`string`||typeof n!=`string`?null:{problem:t,fix:n}}function d(e){if(!a(e))return null;let{label:t,run:n}=e;return typeof t!=`string`||typeof n!=`function`?null:{label:t,run:e=>{Reflect.apply(n,void 0,[e])}}}function f(i){let f=i.getPluginConfig(t).options,p=a(f)?f:{},m=a(p.guide)?p.guide:{},h=a(p.launcher)?p.launcher:{},g=a(p.personaWalk)?p.personaWalk:{},_=p.environmentLabel;return{readOnly:p.readOnly===!0,environmentLabel:typeof _==`string`&&_.length>0?_:void 0,hiddenExperienceKeys:new Set([e,...o(p.hiddenExperienceKeys)]),journeyPrecedence:o(p.journeyPrecedence),codeOwnedFields:new Set(o(p.codeOwnedFields)),answerFollowUp:d(p.answerFollowUp),guide:{sections:(Array.isArray(m.sections)?m.sections:[]).map(l).filter(e=>e!==null),fixes:(Array.isArray(m.fixes)?m.fixes:[]).map(u).filter(e=>e!==null)},launcher:{position:c(h.position)?h.position:`bottom-right`,autoHide:typeof h.autoHide!=`boolean`||h.autoHide},personas:r(p.personas),personaWalk:{selfAdvancingSteps:new Set(o(g.selfAdvancingSteps)),answers:n(g.answers),handoffs:s(g.handoffs)}}}export{e as DEVTOOLS_EXPERIENCE_KEY,i as LAUNCHER_POSITIONS,f as readDevtoolsOptions};
@@ -17,7 +17,8 @@ export interface SvelteDevtoolsPluginOptions extends DevtoolsPanelOptions {
17
17
  * Project-specific custom tools. Each tool's `component` is framework-adapted (via
18
18
  * `adaptReactComponent` or `adaptSvelteComponent`), so it can be authored in the host app's
19
19
  * framework and still receive the live Picked client the way an experience component does. A
20
- * tool's `placement` decides whether it is a tab of its own or sits in the Custom Tools tab.
20
+ * tool's `placement` decides whether it is a tab of its own, an icon at the sidebar's foot, or
21
+ * sits in the Custom Tools tab.
21
22
  *
22
23
  * @default []
23
24
  */
@@ -1,6 +1,10 @@
1
- /** @file The review tabs' panels: How to use, This session, Answers, Journey map and Variations. */
1
+ /**
2
+ * @file The review tabs' panels: How to use, This session, Answers, Journey map, Personas &
3
+ * Scenarios and Variations.
4
+ */
2
5
  export { default as GuidePanel } from './guide/GuidePanel.svelte';
3
6
  export { default as SessionPanel } from './session/SessionPanel.svelte';
4
7
  export { default as AnswersPanel } from './answers/AnswersPanel.svelte';
5
8
  export { default as JourneyPanel } from './journey/JourneyPanel.svelte';
9
+ export { default as PersonasPanel } from './personas/PersonasPanel.svelte';
6
10
  export { default as VariationsPanel } from './variations/VariationsPanel.svelte';
@@ -1 +1 @@
1
- import e from"./guide/GuidePanel.svelte";import t from"./session/SessionPanel.svelte";import n from"./answers/AnswersPanel.svelte";import r from"./journey/JourneyPanel.svelte";import i from"./variations/VariationsPanel.svelte";export{n as AnswersPanel,e as GuidePanel,r as JourneyPanel,t as SessionPanel,i as VariationsPanel};
1
+ import e from"./guide/GuidePanel.svelte";import t from"./session/SessionPanel.svelte";import n from"./answers/AnswersPanel.svelte";import r from"./journey/JourneyPanel.svelte";import i from"./personas/PersonasPanel.svelte";import a from"./variations/VariationsPanel.svelte";export{n as AnswersPanel,e as GuidePanel,r as JourneyPanel,i as PersonasPanel,t as SessionPanel,a as VariationsPanel};