@picked/svelte 0.0.1-dev.1789541668638 → 0.0.1-dev.1793753513002
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/README.md +51 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/_virtual/_vite/preload-helper.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/experience/ExperienceHost.svelte +29 -13
- package/dist/experience/ExperienceSlot.svelte +21 -3
- package/dist/experience/ExperienceWrapper.svelte +23 -0
- package/dist/form/index.js +1 -1
- package/dist/hooks/action.d.ts +47 -18
- package/dist/hooks/action.js +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/step.d.ts +14 -0
- package/dist/hooks/step.js +1 -1
- package/dist/plugins/devtools/DevToolsExperience.svelte +545 -203
- package/dist/plugins/devtools/components/AdaptedToolHost.svelte +2 -2
- package/dist/plugins/devtools/constants.d.ts +5 -0
- package/dist/plugins/devtools/constants.js +1 -0
- package/dist/plugins/devtools/customTools.d.ts +42 -6
- package/dist/plugins/devtools/customTools.js +1 -1
- package/dist/plugins/devtools/derive/answer-display/answer-display.d.ts +112 -0
- package/dist/plugins/devtools/derive/answer-display/answer-display.js +1 -0
- package/dist/plugins/devtools/derive/answer-display/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/answer-display/index.js +1 -0
- package/dist/plugins/devtools/derive/answer-history/answer-history.d.ts +76 -0
- package/dist/plugins/devtools/derive/answer-history/answer-history.js +1 -0
- package/dist/plugins/devtools/derive/answer-history/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/answer-history/index.js +1 -0
- package/dist/plugins/devtools/derive/guide-sections/guide-sections.d.ts +67 -0
- package/dist/plugins/devtools/derive/guide-sections/guide-sections.js +1 -0
- package/dist/plugins/devtools/derive/guide-sections/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/guide-sections/index.js +1 -0
- package/dist/plugins/devtools/derive/index.d.ts +28 -0
- package/dist/plugins/devtools/derive/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-outline/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/journey-outline/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-outline/journey-outline.d.ts +86 -0
- package/dist/plugins/devtools/derive/journey-outline/journey-outline.js +1 -0
- package/dist/plugins/devtools/derive/journey-pick/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/journey-pick/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-pick/journey-pick.d.ts +73 -0
- package/dist/plugins/devtools/derive/journey-pick/journey-pick.js +1 -0
- package/dist/plugins/devtools/derive/journey-timeline/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/journey-timeline/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-timeline/journey-timeline.d.ts +70 -0
- package/dist/plugins/devtools/derive/journey-timeline/journey-timeline.js +1 -0
- package/dist/plugins/devtools/derive/journey-tree/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/journey-tree/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-tree/journey-tree.d.ts +191 -0
- package/dist/plugins/devtools/derive/journey-tree/journey-tree.js +1 -0
- package/dist/plugins/devtools/derive/plain-language/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/plain-language/index.js +1 -0
- package/dist/plugins/devtools/derive/plain-language/plain-language.d.ts +73 -0
- package/dist/plugins/devtools/derive/plain-language/plain-language.js +1 -0
- package/dist/plugins/devtools/derive/plain-logic/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/plain-logic/index.js +1 -0
- package/dist/plugins/devtools/derive/plain-logic/plain-logic.d.ts +48 -0
- package/dist/plugins/devtools/derive/plain-logic/plain-logic.js +1 -0
- package/dist/plugins/devtools/derive/safety-posture/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/safety-posture/index.js +1 -0
- package/dist/plugins/devtools/derive/safety-posture/safety-posture.d.ts +43 -0
- package/dist/plugins/devtools/derive/safety-posture/safety-posture.js +1 -0
- package/dist/plugins/devtools/derive/visited-steps/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/visited-steps/index.js +1 -0
- package/dist/plugins/devtools/derive/visited-steps/visited-steps.d.ts +35 -0
- package/dist/plugins/devtools/derive/visited-steps/visited-steps.js +1 -0
- package/dist/plugins/devtools/derive/walk-route/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/walk-route/index.js +1 -0
- package/dist/plugins/devtools/derive/walk-route/walk-route.d.ts +22 -0
- package/dist/plugins/devtools/derive/walk-route/walk-route.js +1 -0
- package/dist/plugins/devtools/index.d.ts +5 -2
- package/dist/plugins/devtools/index.js +1 -1
- package/dist/plugins/devtools/launcher/Launcher.svelte +150 -0
- package/dist/plugins/devtools/launcher/launcher.d.ts +36 -0
- package/dist/plugins/devtools/launcher/launcher.js +1 -0
- package/dist/plugins/devtools/options.d.ts +106 -0
- package/dist/plugins/devtools/options.js +1 -0
- package/dist/plugins/devtools/plugin.d.ts +8 -8
- package/dist/plugins/devtools/plugin.js +1 -1
- package/dist/plugins/devtools/posture-context.d.ts +15 -0
- package/dist/plugins/devtools/posture-context.js +1 -0
- package/dist/plugins/devtools/review/answers/AnswersPanel.svelte +526 -0
- package/dist/plugins/devtools/review/guide/GuideLine.svelte +20 -0
- package/dist/plugins/devtools/review/guide/GuidePanel.svelte +133 -0
- package/dist/plugins/devtools/review/index.d.ts +6 -0
- package/dist/plugins/devtools/review/index.js +1 -0
- package/dist/plugins/devtools/review/journey/JourneyMap.svelte +1076 -0
- package/dist/plugins/devtools/review/journey/JourneyPanel.svelte +584 -0
- package/dist/plugins/devtools/review/journey/JourneyTimeline.svelte +750 -0
- package/dist/plugins/devtools/review/journey/TimelineMarker.svelte +59 -0
- package/dist/plugins/devtools/review/session/SessionPanel.svelte +214 -0
- package/dist/plugins/devtools/review/variations/VariationsPanel.svelte +279 -0
- package/dist/plugins/devtools/screens/config/ConfigScreen.svelte +6 -0
- package/dist/plugins/devtools/screens/custom-tools/CustomToolsScreen.svelte +3 -3
- package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +6 -0
- package/dist/plugins/devtools/session/index.d.ts +3 -0
- package/dist/plugins/devtools/session/index.js +1 -0
- package/dist/plugins/devtools/session/session.d.ts +90 -0
- package/dist/plugins/devtools/session/session.js +1 -0
- package/dist/plugins/devtools/session-access.d.ts +32 -0
- package/dist/plugins/devtools/session-access.js +1 -0
- package/dist/plugins/devtools/sidebar/Sidebar.svelte +105 -0
- package/dist/plugins/devtools/sidebar/SidebarRow.svelte +117 -0
- package/dist/plugins/devtools/sidebar/nav.d.ts +105 -0
- package/dist/plugins/devtools/sidebar/nav.js +1 -0
- package/dist/plugins/devtools/ui/actions/Button.svelte +10 -1
- package/dist/plugins/devtools/ui/actions/CopyButton.svelte +13 -1
- package/dist/plugins/devtools/ui/data/Badge.svelte +3 -2
- package/dist/plugins/devtools/ui/data/EmptyState.svelte +16 -1
- package/dist/plugins/devtools/ui/feedback/Banner.svelte +3 -2
- package/dist/plugins/framework/index.js +1 -1
- package/dist/render/browser.js +1 -1
- package/package.json +23 -12
- package/dist/form/__tests__/CreateFormHarness.svelte +0 -30
- package/dist/hooks/events.d.ts +0 -2
- package/dist/hooks/events.js +0 -1
- package/dist/vite-env.d.js +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ScreenState } from '../journey-outline';
|
|
2
|
+
import type { FlowEnd, TreeInput, TreeNodeKind } from '../journey-tree';
|
|
3
|
+
/** Where a node leads, as the details pane lists it, loops included. */
|
|
4
|
+
export interface TimelineNext {
|
|
5
|
+
to: string;
|
|
6
|
+
/** The arm's label off a decision ("Yes" or "Otherwise"), null otherwise. */
|
|
7
|
+
label: string | null;
|
|
8
|
+
arm: 'yes' | 'no' | null;
|
|
9
|
+
/** Goes back up the timeline: a loop, not drawn as a line. */
|
|
10
|
+
loop: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** One row of the timeline. */
|
|
13
|
+
export interface TimelineNode {
|
|
14
|
+
/** The graph node's own id. Every node is drawn exactly once, so it is unique. */
|
|
15
|
+
id: string;
|
|
16
|
+
kind: TreeNodeKind;
|
|
17
|
+
label: string;
|
|
18
|
+
detail: string | null;
|
|
19
|
+
rule: string | null;
|
|
20
|
+
stepKey: string | null;
|
|
21
|
+
state: ScreenState;
|
|
22
|
+
row: number;
|
|
23
|
+
lane: number;
|
|
24
|
+
/** The dot's centre in the graph column. */
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
/** The colour of the branch this row sits on: an index into the component's palette. */
|
|
28
|
+
colour: number;
|
|
29
|
+
next: TimelineNext[];
|
|
30
|
+
/** Hidden actions this row hands off to, where its route stops. */
|
|
31
|
+
ends: FlowEnd[];
|
|
32
|
+
}
|
|
33
|
+
/** One drawn line between two rows. */
|
|
34
|
+
export interface TimelineEdge {
|
|
35
|
+
id: string;
|
|
36
|
+
from: string;
|
|
37
|
+
to: string;
|
|
38
|
+
arm: 'yes' | 'no' | null;
|
|
39
|
+
/** The lane the edge travels in between its two rows. */
|
|
40
|
+
lane: number;
|
|
41
|
+
/** The colour of the branch that lane carries. */
|
|
42
|
+
colour: number;
|
|
43
|
+
path: string;
|
|
44
|
+
travelled: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** The laid-out timeline, ready to render. */
|
|
47
|
+
export interface TimelineLayout {
|
|
48
|
+
nodes: TimelineNode[];
|
|
49
|
+
edges: TimelineEdge[];
|
|
50
|
+
/** How many lanes the widest row needs. */
|
|
51
|
+
lanes: number;
|
|
52
|
+
/** The graph column's width; the labels start at its right edge. */
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
}
|
|
56
|
+
/** How many branch colours the component's palette holds; a fork takes the next in turn. */
|
|
57
|
+
export declare const COLOURS = 8;
|
|
58
|
+
/** The height of one row. */
|
|
59
|
+
export declare const ROW_HEIGHT = 34;
|
|
60
|
+
/** The width of one lane. */
|
|
61
|
+
export declare const LANE_WIDTH = 14;
|
|
62
|
+
/**
|
|
63
|
+
* Lays an experience's flow out as a timeline. Pure: it takes the same input the tree takes.
|
|
64
|
+
*
|
|
65
|
+
* @param {TreeInput} input - The flow, its conditions, the field index and the visitor's trail.
|
|
66
|
+
* @returns {TimelineLayout} The laid-out timeline, or an empty layout for a flow with no origin.
|
|
67
|
+
* @example
|
|
68
|
+
* buildTimeline({ nodes, edges, conditions, fieldLookup, visited, currentStepKey })
|
|
69
|
+
*/
|
|
70
|
+
export declare function buildTimeline({ nodes, edges, conditions, fieldLookup, visited, currentStepKey, }: TreeInput): TimelineLayout;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ARMS as e,FALLBACK as t,describeNode as n,graphStates as r,namedEnds as i,orderedOutgoing as a,withoutActions as o}from"../journey-tree/journey-tree.js";import"../journey-tree/index.js";var s=8,c=34,l=14,u=14,d={nodes:[],edges:[],lanes:0,width:0,height:0};function f(e,t,n){let r=new Map([[e,1]]),i=new Set,a=[],o=[{id:e,next:0}];for(;o.length>0;){let e=o[o.length-1],s=(t.get(e.id)??[]).filter(e=>n.has(e.destinationNodeId));if(e.next<s.length){let t=s[s.length-1-e.next];e.next+=1;let n=r.get(t.destinationNodeId);n===void 0?(r.set(t.destinationNodeId,1),o.push({id:t.destinationNodeId,next:0})):n===1&&i.add(t)}else r.set(e.id,2),a.push(e.id),o.pop()}return{order:a.toReversed(),loops:i}}function p(e){let t=e.indexOf(null);return t===-1?(e.push(null),e.length-1):t}function m(e,t,n,r,i){if(i-t<=34){let n=(t+i)/2;return`M ${e} ${t} C ${e} ${n}, ${r} ${n}, ${r} ${i}`}let a=t+34,o=i-34;return[`M ${e} ${t}`,`C ${e} ${t+17}, ${n} ${a-17}, ${n} ${a}`,`L ${n} ${o}`,`C ${n} ${o+17}, ${r} ${i-17}, ${r} ${i}`].join(` `)}function h({nodes:s,edges:c,conditions:l,fieldLookup:h,visited:g,currentStepKey:_}){let v=s.find(e=>e.type===`origin`);if(v===void 0)return d;let y=new Map(l.map(e=>[e.id,e])),b=new Map(s.map(e=>[e.id,n(e,y,h)])),x=r(b,c,g,_),S=e=>b.get(e)?.kind===`system`,{through:C,ends:w}=o(a(c),S,new Set(b.keys())),T=new Set([...b.keys()].filter(e=>!S(e))),{order:E,loops:D}=f(v.id,C,T),O=new Map(E.map((e,t)=>[e,t])),k=e=>(C.get(e)??[]).filter(e=>!D.has(e)&&O.has(e.destinationNodeId)),A=[],j=[],M=new Map,N=new Map,P=new Map,F=1,I=0,L=()=>{let e=I;return I=(I+1)%8,e};for(let e of E){let t=A.indexOf(e);t===-1&&(t=p(A),j[t]=L());for(let t=0;t<A.length;t+=1)A[t]===e&&(A[t]=null);M.set(e,t);let n=j[t]??0;for(N.set(e,n),k(e).forEach((e,r)=>{let i=r===0?t:p(A);j[i]=r===0?n:L(),A[i]=e.destinationNodeId,P.set(e,{lane:i,colour:j[i]})}),F=Math.max(F,A.length,t+1);A.length>0&&A[A.length-1]===null;)A.pop()}let R=e=>u+e*14,z=e=>e*34+17,B=E.map((n,r)=>{let a=b.get(n)??t,o=M.get(n)??0,s=(C.get(n)??[]).filter(e=>O.has(e.destinationNodeId)).map(t=>{let n=a.kind===`decision`&&t.sourceHandle!==void 0?e.get(t.sourceHandle):void 0;return{to:t.destinationNodeId,label:n?.label??null,arm:n?.arm??null,loop:D.has(t)}});return{...a,id:n,state:x.get(n)??`ahead`,row:r,lane:o,x:R(o),y:z(r),colour:N.get(n)??0,next:s,ends:i(w.get(n)??[],a,b)}}),V=new Map(B.map(e=>[e.id,e]));return{nodes:B,edges:E.flatMap(t=>k(t).flatMap(n=>{let r=V.get(t),i=V.get(n.destinationNodeId),a=P.get(n);if(r===void 0||i===void 0||a===void 0)return[];let o=r.kind===`decision`&&n.sourceHandle!==void 0?e.get(n.sourceHandle):void 0;return[{id:`${r.id}>${i.id}`,from:r.id,to:i.id,arm:o?.arm??null,lane:a.lane,colour:a.colour,path:m(r.x,r.y,R(a.lane),i.x,i.y),travelled:r.state!==`ahead`&&i.state!==`ahead`}]})),lanes:F,width:28+(F-1)*14,height:E.length*34}}export{s as COLOURS,l as LANE_WIDTH,c as ROW_HEIGHT,h as buildTimeline};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** @file Public entrypoint for the flow graph to laid-out tree. */
|
|
2
|
+
export { ARMS, BREADTH, DEPTH, FALLBACK, buildTree, describeNode, graphStates, namedEnds, orderedOutgoing, withoutActions, } from './journey-tree';
|
|
3
|
+
export type { Described, FlowEnd, HiddenEnd, TreeInput, TreeJump, TreeLayout, TreeLink, TreeNode, TreeNodeKind, } from './journey-tree';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{ARMS,BREADTH,DEPTH,FALLBACK,buildTree,describeNode,graphStates,namedEnds,orderedOutgoing,withoutActions}from"./journey-tree.js";
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { OutlineCondition, OutlineEdge, OutlineNode, ScreenState } from '../journey-outline';
|
|
2
|
+
import type { FieldLookup } from '../plain-logic';
|
|
3
|
+
/** Everything {@link buildTree} reads. The timeline reads the same input. */
|
|
4
|
+
export interface TreeInput {
|
|
5
|
+
nodes: readonly OutlineNode[];
|
|
6
|
+
edges: readonly OutlineEdge[];
|
|
7
|
+
conditions: readonly OutlineCondition[];
|
|
8
|
+
fieldLookup: FieldLookup;
|
|
9
|
+
/** Step keys the visitor has entered this session. */
|
|
10
|
+
visited: ReadonlySet<string>;
|
|
11
|
+
/** The step the visitor is on right now, or null when this experience is not the open one. */
|
|
12
|
+
currentStepKey: string | null;
|
|
13
|
+
}
|
|
14
|
+
/** What a drawn node is, as a reviewer reads it. `system` is an action. */
|
|
15
|
+
export type TreeNodeKind = 'start' | 'screen' | 'decision' | 'system' | 'journey';
|
|
16
|
+
/** One laid-out dot on the map. */
|
|
17
|
+
export interface TreeNode {
|
|
18
|
+
/** Unique within the tree. A reference leaf gets its own id; `nodeId` names the graph node. */
|
|
19
|
+
id: string;
|
|
20
|
+
nodeId: string;
|
|
21
|
+
kind: TreeNodeKind;
|
|
22
|
+
label: string;
|
|
23
|
+
/** The machine name behind the label: an action key, a step key, a condition key. */
|
|
24
|
+
detail: string | null;
|
|
25
|
+
/** A decision's verbatim JSON Logic. The sentence in `label` is lossy; this is what makes it safe. */
|
|
26
|
+
rule: string | null;
|
|
27
|
+
stepKey: string | null;
|
|
28
|
+
/** Read off the visited trail for screens; machinery inherits it from the screens around it. */
|
|
29
|
+
state: ScreenState;
|
|
30
|
+
/** A second arrival at a node already drawn: a rejoin or a loop. Always a leaf. */
|
|
31
|
+
reference: boolean;
|
|
32
|
+
/** Has no children: the end of a branch. A leaf is labelled below its dot, not above. */
|
|
33
|
+
leaf: boolean;
|
|
34
|
+
/** Hidden actions this node hands off to, where the route stops. Empty on a reference. */
|
|
35
|
+
ends: FlowEnd[];
|
|
36
|
+
depth: number;
|
|
37
|
+
/** The dot's centre on the canvas. */
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
}
|
|
41
|
+
/** An action a route ends on. Not drawn; named on the node that hands off to it. */
|
|
42
|
+
export interface FlowEnd {
|
|
43
|
+
/** The action's graph node id. NOT unique on its node: both arms of a decision can end on one. */
|
|
44
|
+
id: string;
|
|
45
|
+
/** Unique on its node: the route it left on and the action it stopped at. Key a list on this. */
|
|
46
|
+
key: string;
|
|
47
|
+
label: string;
|
|
48
|
+
/** The decision arm's label ("Yes" or "Otherwise") when one ends here, null otherwise. */
|
|
49
|
+
armLabel: string | null;
|
|
50
|
+
arm: 'yes' | 'no' | null;
|
|
51
|
+
}
|
|
52
|
+
/** One drawn link between a parent dot and a child dot. */
|
|
53
|
+
export interface TreeLink {
|
|
54
|
+
id: string;
|
|
55
|
+
from: string;
|
|
56
|
+
to: string;
|
|
57
|
+
/** Which arm of a decision this is, by the engine's own handle. Null off a decision. */
|
|
58
|
+
arm: 'yes' | 'no' | null;
|
|
59
|
+
/** The arm's label on a decision's arms, null everywhere else. */
|
|
60
|
+
label: string | null;
|
|
61
|
+
/** An SVG path in d3's `linkHorizontal` shape, so a link leaves and enters its dots level. */
|
|
62
|
+
path: string;
|
|
63
|
+
labelX: number;
|
|
64
|
+
labelY: number;
|
|
65
|
+
/** Both ends are on the trail the visitor has walked. */
|
|
66
|
+
travelled: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A reference leaf's way back to the node it names, drawn dashed, so a rejoin or a loop reads as
|
|
70
|
+
* one route on the map without breaking the tree's shape.
|
|
71
|
+
*/
|
|
72
|
+
export interface TreeJump {
|
|
73
|
+
/** The reference leaf's id; a leaf has exactly one way back. */
|
|
74
|
+
id: string;
|
|
75
|
+
from: string;
|
|
76
|
+
/** The node drawn in full. */
|
|
77
|
+
to: string;
|
|
78
|
+
path: string;
|
|
79
|
+
/** The target sits at or before the leaf's level: a loop, or a rejoin onto an earlier branch. */
|
|
80
|
+
back: boolean;
|
|
81
|
+
}
|
|
82
|
+
/** The laid-out tree, ready to render. */
|
|
83
|
+
export interface TreeLayout {
|
|
84
|
+
nodes: TreeNode[];
|
|
85
|
+
links: TreeLink[];
|
|
86
|
+
jumps: TreeJump[];
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
/** The tree outgrew its size cap, and the deepest branches were cut short. */
|
|
90
|
+
truncated: boolean;
|
|
91
|
+
}
|
|
92
|
+
/** Between neighbouring dots on one level: the devtools `nodeSize` breadth, plus room for 2 lines. */
|
|
93
|
+
export declare const BREADTH = 88;
|
|
94
|
+
/** Between levels: the devtools `nodeSize` depth, plus room for a 30-character label. */
|
|
95
|
+
export declare const DEPTH = 220;
|
|
96
|
+
/**
|
|
97
|
+
* A condition's arms, as the runtime picks them by `edge.sourceHandle`. A Map, not a record: an
|
|
98
|
+
* index signature would type a miss as a hit.
|
|
99
|
+
*/
|
|
100
|
+
export declare const ARMS: Map<string, {
|
|
101
|
+
arm: 'yes' | 'no';
|
|
102
|
+
label: string;
|
|
103
|
+
order: number;
|
|
104
|
+
}>;
|
|
105
|
+
/** The reviewer-facing shape of one graph node. */
|
|
106
|
+
export type Described = Pick<TreeNode, 'kind' | 'label' | 'detail' | 'rule' | 'stepKey'>;
|
|
107
|
+
/** What a node id with no node behind it becomes. */
|
|
108
|
+
export declare const FALLBACK: Described;
|
|
109
|
+
/**
|
|
110
|
+
* The reviewer-facing shape of one node: what it is called, and what sits behind the name. An
|
|
111
|
+
* action is named by its key, so a map with several says which branch does what.
|
|
112
|
+
*
|
|
113
|
+
* @param {OutlineNode} node - The graph node.
|
|
114
|
+
* @param {ReadonlyMap<string, OutlineCondition>} conditionById - Registered conditions by id.
|
|
115
|
+
* @param {FieldLookup} fieldLookup - The field index.
|
|
116
|
+
* @returns {Described} The node's kind, label, detail, rule and step key.
|
|
117
|
+
* @example
|
|
118
|
+
* describeNode({ id: 's', type: 'step', stepKey: 'postcode' }, new Map(), () => undefined).label // 'Postcode'
|
|
119
|
+
*/
|
|
120
|
+
export declare function describeNode(node: OutlineNode, conditionById: ReadonlyMap<string, OutlineCondition>, fieldLookup: FieldLookup): Described;
|
|
121
|
+
/**
|
|
122
|
+
* Every graph node's state. A screen reads the trail directly. Machinery is on the trail when it
|
|
123
|
+
* sits after somewhere the flow has certainly been (a seen screen, or the start) and before a
|
|
124
|
+
* screen the visitor has certainly reached, with only machinery in between.
|
|
125
|
+
*
|
|
126
|
+
* Worked on the GRAPH, not the tree: in the tree a rejoined node sits under whichever arm the walk
|
|
127
|
+
* reached first, which need not be the arm the visitor took. It UNDER-claims: an arm that leads
|
|
128
|
+
* only to screens not yet reached stays unmarked rather than guessed at.
|
|
129
|
+
*
|
|
130
|
+
* @param {ReadonlyMap<string, Described>} described - Every node's shape, by id.
|
|
131
|
+
* @param {readonly OutlineEdge[]} edges - The flow's edges.
|
|
132
|
+
* @param {ReadonlySet<string>} visited - Step keys entered this session.
|
|
133
|
+
* @param {string | null} currentStepKey - The step the visitor is on.
|
|
134
|
+
* @returns {Map<string, ScreenState>} Each node's state, by id.
|
|
135
|
+
* @example
|
|
136
|
+
* graphStates(described, edges, new Set(['postcode']), 'address')
|
|
137
|
+
*/
|
|
138
|
+
export declare function graphStates(described: ReadonlyMap<string, Described>, edges: readonly OutlineEdge[], visited: ReadonlySet<string>, currentStepKey: string | null): Map<string, ScreenState>;
|
|
139
|
+
/**
|
|
140
|
+
* Edges out of each node in the order they read: a decision's Yes before its Otherwise,
|
|
141
|
+
* everything else as authored. A repeated edge to the same node is kept once.
|
|
142
|
+
*
|
|
143
|
+
* @param {readonly OutlineEdge[]} edges - The flow's edges.
|
|
144
|
+
* @returns {Map<string, OutlineEdge[]>} Each node's outgoing edges, in reading order.
|
|
145
|
+
* @example
|
|
146
|
+
* orderedOutgoing(edges).get('decision-1')
|
|
147
|
+
*/
|
|
148
|
+
export declare function orderedOutgoing(edges: readonly OutlineEdge[]): Map<string, OutlineEdge[]>;
|
|
149
|
+
/** Where a route through actions stops: the edge it left on, and the action it stopped at. */
|
|
150
|
+
export interface HiddenEnd {
|
|
151
|
+
edge: OutlineEdge;
|
|
152
|
+
id: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The flow with its actions taken out. Each edge into an action is followed through every action
|
|
156
|
+
* after it to the drawn nodes it reaches, and becomes an edge straight to each, carrying the
|
|
157
|
+
* handle it LEFT on, so a decision's arm stays its arm. An action with nowhere to go is an END.
|
|
158
|
+
* Takes and answers `orderedOutgoing`'s shape, so a caller swaps one for the other.
|
|
159
|
+
*
|
|
160
|
+
* @param {ReadonlyMap<string, OutlineEdge[]>} outgoing - Each node's outgoing edges.
|
|
161
|
+
* @param {(id: string) => boolean} isAction - Whether a node id is an action.
|
|
162
|
+
* @param {ReadonlySet<string>} known - The node ids that exist.
|
|
163
|
+
* @returns {{ through: Map<string, OutlineEdge[]>, ends: Map<string, HiddenEnd[]> }} The drawn edges, and each node's hidden ends.
|
|
164
|
+
* @example
|
|
165
|
+
* withoutActions(orderedOutgoing(edges), (id) => kindOf(id) === 'system', new Set(ids))
|
|
166
|
+
*/
|
|
167
|
+
export declare function withoutActions(outgoing: ReadonlyMap<string, OutlineEdge[]>, isAction: (id: string) => boolean, known: ReadonlySet<string>): {
|
|
168
|
+
through: Map<string, OutlineEdge[]>;
|
|
169
|
+
ends: Map<string, HiddenEnd[]>;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* A node's hidden ends, named for a reader, with the arm they hang off.
|
|
173
|
+
*
|
|
174
|
+
* @param {readonly HiddenEnd[]} hidden - The node's hidden ends.
|
|
175
|
+
* @param {Described} from - The node they hang off.
|
|
176
|
+
* @param {ReadonlyMap<string, Described>} described - Every node's shape, by id.
|
|
177
|
+
* @returns {FlowEnd[]} The ends, ready to render.
|
|
178
|
+
* @example
|
|
179
|
+
* namedEnds(ends.get(nodeId) ?? [], shape, described)
|
|
180
|
+
*/
|
|
181
|
+
export declare function namedEnds(hidden: readonly HiddenEnd[], from: Described, described: ReadonlyMap<string, Described>): FlowEnd[];
|
|
182
|
+
/**
|
|
183
|
+
* Lays an experience's flow out as the devtools tree. Pure: everything it needs arrives as an
|
|
184
|
+
* argument.
|
|
185
|
+
*
|
|
186
|
+
* @param {TreeInput} input - The flow, its conditions, the field index and the visitor's trail.
|
|
187
|
+
* @returns {TreeLayout} The laid-out tree, or an empty layout for a flow with no origin.
|
|
188
|
+
* @example
|
|
189
|
+
* buildTree({ nodes, edges, conditions, fieldLookup, visited, currentStepKey })
|
|
190
|
+
*/
|
|
191
|
+
export declare function buildTree({ nodes, edges, conditions, fieldLookup, visited, currentStepKey, }: TreeInput): TreeLayout;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{NODE_LABELS as e,deslug as t,screenName as n}from"../plain-language/plain-language.js";import{describeRule as r}from"../plain-logic/plain-logic.js";var i=88,a=220,o=110,s=56,c=2e3,l=new Map([[`true`,{arm:`yes`,label:`Yes`,order:0}],[`false`,{arm:`no`,label:`Otherwise`,order:1}]]),u={kind:`system`,label:e.action,detail:null,rule:null,stepKey:null};function d(e,n,i){let a=e.conditionId===void 0?void 0:n.get(e.conditionId),o=r(a?.logic,i),s=a===void 0?o:t(a.name);return{kind:`decision`,label:o===`a rule this panel can't put into words`?s:o,detail:a?.name??null,rule:JSON.stringify(a?.logic??null,null,2),stepKey:null}}function f(r,i,a){if(r.type===`origin`)return{kind:`start`,label:e.origin,detail:null,rule:null,stepKey:null};if(r.type===`step`)return{kind:`screen`,label:n(r.name,r.stepKey),detail:r.stepKey??null,rule:null,stepKey:r.stepKey??null};if(r.type===`condition`)return d(r,i,a);if(r.type===`experience`){let e=r.experienceKey??`another journey`;return{kind:`journey`,label:t(e),detail:e,rule:null,stepKey:null}}let o=r.actionKey??r.name??r.type;return{kind:`system`,label:t(o),detail:o,rule:null,stepKey:null}}function p(e,t,n){return e!==null&&e===n?`current`:e!==null&&t.has(e)?`visited`:`ahead`}function m(e,t,n){let r=new Set,i=[...e];for(let e=0;e<i.length;e+=1)for(let a of t.get(i[e])??[]){if(r.has(a))continue;let e=n.get(a);e!==void 0&&e!==`screen`&&e!==`start`&&(r.add(a),i.push(a))}return r}function h(e,t,n,r){let i=new Map,a=new Map;for(let[t,o]of e)a.set(t,o.kind),i.set(t,p(o.stepKey,n,r));let o=[...e].filter(([e,t])=>t.kind===`screen`&&i.get(e)!==`ahead`).map(([e])=>e);if(o.length===0)return i;let s=new Map,c=new Map;for(let e of t)s.set(e.sourceNodeId,[...s.get(e.sourceNodeId)??[],e.destinationNodeId]),c.set(e.destinationNodeId,[...c.get(e.destinationNodeId)??[],e.sourceNodeId]);let l=[...e].filter(([,e])=>e.kind===`start`).map(([e])=>e),u=m([...o,...l],s,a),d=m(o,c,a);for(let t of e.keys())u.has(t)&&d.has(t)&&i.set(t,`visited`);for(let e of l)i.set(e,`visited`);return i}function g(e,t,n,r){return{id:e,nodeId:t,reference:n,handle:r,parent:null,children:[],index:0,depth:0,prelim:0,mod:0,change:0,shift:0,thread:null,ancestor:null,defaultAncestor:null,x:0}}function _(e){let t=new Map;for(let n of e){let e=t.get(n.sourceNodeId)??[];e.some(e=>e.destinationNodeId===n.destinationNodeId)||(e.push(n),t.set(n.sourceNodeId,e))}let n=e=>e.sourceHandle===void 0?2:l.get(e.sourceHandle)?.order??2;for(let e of t.values())e.sort((e,t)=>n(e)-n(t));return t}function v(e,t,n,r){let i=[],a=[],o=new Set([e.destinationNodeId]),s=[e.destinationNodeId];for(let c=0;c<s.length;c+=1){let l=(t.get(s[c])??[]).filter(e=>r.has(e.destinationNodeId));l.length===0&&a.push({edge:e,id:s[c]});for(let t of l){let r=t.destinationNodeId;o.has(r)||(o.add(r),n(r)?s.push(r):i.push({...e,id:`${e.id}~${r}`,destinationNodeId:r}))}}return{out:i,ended:a}}function y(e,t,n){let r=new Map,i=new Map;for(let[a,o]of e){if(t(a))continue;let s=[],c=[];for(let r of o){if(!n.has(r.destinationNodeId))continue;if(!t(r.destinationNodeId)){s.push(r);continue}let i=v(r,e,t,n);s.push(...i.out),c.push(...i.ended)}r.set(a,s.filter((e,t)=>s.findIndex(t=>t.destinationNodeId===e.destinationNodeId)===t)),c.length>0&&i.set(a,c)}return{through:r,ends:i}}function b(e,t,n){return e.map(({edge:e,id:r})=>{let i=t.kind===`decision`&&e.sourceHandle!==void 0?l.get(e.sourceHandle):void 0;return{id:r,key:`${e.id}~${r}`,label:n.get(r)?.label??r,armLabel:i?.label??null,arm:i?.arm??null}})}function x(e,t,n,r){let i=r;for(let r of t){if(!n.has(r.destinationNodeId))continue;if(i>=c)return{count:i,cut:!0};let t=g(r.destinationNodeId,r.destinationNodeId,!1,r.sourceHandle);t.parent=e,t.index=e.children.length,t.depth=e.depth+1,e.children.push(t),i+=1}return{count:i,cut:!1}}function S(e,t,n){let r=g(e,e,!1,void 0),i=new Set,a=[r],o=1,s=!1;for(let e=a.pop();e!==void 0;e=a.pop()){if(i.has(e.nodeId)){e.reference=!0,e.id=`${e.parent?.id??``}>${e.nodeId}`;continue}i.add(e.nodeId);let r=x(e,t.get(e.nodeId)??[],n,o);o=r.count,s||=r.cut;for(let t=e.children.length-1;t>=0;--t)a.push(e.children[t])}return{root:r,truncated:s}}function C(e,t){return(e.parent===t.parent?1:2)*88}function w(e){return e.children.at(0)??e.thread}function T(e){return e.children.at(-1)??e.thread}function E(e,t,n){let r=n/(t.index-e.index);t.change-=r,t.shift+=n,e.change+=r,t.prelim+=n,t.mod+=n}function D(e){let t=0,n=0;for(let r=e.children.length-1;r>=0;--r){let i=e.children[r];i.prelim+=t,i.mod+=t,n+=i.change,t+=i.shift+n}}function O(e,t,n){let r=e.ancestor??e;return r.parent===t.parent?r:n}function k(e,t,n){if(t===null||e.parent===null)return n;let r=n,i=e,a=e,o=t,s=e.parent.children[0],c=i.mod,l=a.mod,u=o.mod,d=s.mod;for(o=T(o),i=w(i);o!==null&&i!==null;){s=w(s)??s,a=T(a)??a,a.ancestor=e;let t=o.prelim+u-i.prelim-c+C(o,i);t>0&&(E(O(o,e,r),e,t),c+=t,l+=t),u+=o.mod,c+=i.mod,d+=s.mod,l+=a.mod,o=T(o),i=w(i)}return o!==null&&T(a)===null&&(a.thread=o,a.mod+=u-l),i!==null&&w(s)===null&&(s.thread=i,s.mod+=c-d,r=e),r}function A(e,t){let n=e.parent??t,r=e.index>0?n.children[e.index-1]:null;if(e.children.length>0){D(e);let t=(e.children[0].prelim+(e.children.at(-1)?.prelim??0))/2;r===null?e.prelim=t:(e.prelim=r.prelim+C(e,r),e.mod=e.prelim-t)}else r!==null&&(e.prelim=r.prelim+C(e,r));n.defaultAncestor=k(e,r,n.defaultAncestor??n.children[0])}function j(e){let t=[e],n=[];for(let e=t.pop();e!==void 0;e=t.pop())n.push(e),t.push(...e.children);return n.reverse()}function M(e){let t=[e],n=[];for(let e=t.pop();e!==void 0;e=t.pop()){n.push(e);for(let n=e.children.length-1;n>=0;--n)t.push(e.children[n])}return n}function N(e){let t=g(``,``,!1,void 0);t.children=[e];let n=j(e);for(let e of n)A(e,t);e.mod=-e.prelim,e.x=e.prelim+e.mod;for(let e=n.length-1;e>=0;--e){let t=n[e];t.parent!==null&&(t.x=t.prelim+t.parent.mod,t.mod+=t.parent.mod)}return n}function P(e,t,n,r){let i=(e+n)/2;return`M ${e} ${t} C ${i} ${t}, ${i} ${r}, ${n} ${r}`}function F(e,t,n,r){let i=n<=e,a=i?60+(e-n)*.15:Math.max(60,(n-e)/2),o=i?n+a:n-a;return{path:`M ${e} ${t} C ${e+a} ${t}, ${o} ${r}, ${n} ${r}`,back:i}}var I={nodes:[],links:[],jumps:[],width:0,height:0,truncated:!1};function L({nodes:e,edges:t,conditions:n,fieldLookup:r,visited:i,currentStepKey:a}){let c=e.find(e=>e.type===`origin`);if(c===void 0)return I;let d=new Map(n.map(e=>[e.id,e])),p=new Map(e.map(e=>[e.id,f(e,d,r)])),m=h(p,t,i,a),g=e=>p.get(e)?.kind===`system`,{through:v,ends:x}=y(_(t),g,new Set(p.keys())),C=new Set([...p.keys()].filter(e=>!g(e))),{root:w,truncated:T}=S(c.id,v,C),E=N(w),D=1/0,O=-1/0,k=0;for(let e of E)D=Math.min(D,e.x),O=Math.max(O,e.x),k=Math.max(k,e.depth);let A=M(w),j=A.map(e=>{let t=p.get(e.nodeId)??u;return{...t,id:e.id,nodeId:e.nodeId,state:m.get(e.nodeId)??`ahead`,reference:e.reference,leaf:e.children.length===0,ends:e.reference?[]:b(x.get(e.nodeId)??[],t,p),depth:e.depth,x:o+e.depth*220,y:s+(e.x-D)}}),L=new Map(j.map(e=>[e.id,e])),R=A.flatMap(e=>{let t=e.parent===null?void 0:L.get(e.parent.id),n=L.get(e.id);if(t===void 0||n===void 0)return[];let r=t.kind===`decision`&&e.handle!==void 0?l.get(e.handle):void 0;return[{id:n.id,from:t.id,to:n.id,arm:r?.arm??null,label:r?.label??null,path:P(t.x,t.y,n.x,n.y),labelX:(t.x+n.x)/2,labelY:(t.y+n.y)/2-6,travelled:t.state!==`ahead`&&n.state!==`ahead`}]}),z=new Map(j.filter(e=>!e.reference).map(e=>[e.nodeId,e]));return{nodes:j,links:R,jumps:j.flatMap(e=>{let t=e.reference?z.get(e.nodeId):void 0;return t===void 0?[]:[{id:e.id,from:e.id,to:t.id,...F(e.x,e.y,t.x,t.y)}]}),width:220+k*220,height:112+(O-D),truncated:T}}export{l as ARMS,i as BREADTH,a as DEPTH,u as FALLBACK,L as buildTree,f as describeNode,h as graphStates,b as namedEnds,_ as orderedOutgoing,y as withoutActions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{DEVELOPER_LABEL,NODE_LABELS,TAB_INTROS,TAB_LABELS,configSourceLabel,deslug,screenName}from"./plain-language.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The review tools' vocabulary contract: Picked's nouns translated into a reviewer's.
|
|
3
|
+
*
|
|
4
|
+
* One module owns every translation, so the mapping is reviewable in one place and cannot drift
|
|
5
|
+
* into a component as a hard-coded string. The audience is a product, marketing or QA reviewer
|
|
6
|
+
* who knows the client's journey and its customers, and has no reason to learn what an
|
|
7
|
+
* experience, a field key or a bucket is. A word that only means something once you have seen the
|
|
8
|
+
* Picked dashboard does not belong on a reviewer's screen.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Each node type, named the way a reviewer would describe what the journey is doing there.
|
|
12
|
+
*/
|
|
13
|
+
export declare const NODE_LABELS: {
|
|
14
|
+
readonly origin: 'Start';
|
|
15
|
+
readonly step: 'Screen';
|
|
16
|
+
readonly condition: 'Decision';
|
|
17
|
+
readonly action: 'Checking details';
|
|
18
|
+
readonly experience: 'Opens another journey';
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Turns a kebab or snake key into a sentence. It is a fallback for a step whose author gave it no
|
|
22
|
+
* name, not a naming scheme.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} key - The machine key to read.
|
|
25
|
+
* @returns {string} The key as a capitalised phrase, or the key untouched when nothing is left.
|
|
26
|
+
* @example
|
|
27
|
+
* deslug('signup-address') // 'Signup address'
|
|
28
|
+
*/
|
|
29
|
+
export declare function deslug(key: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* A step's display name. The authored `name` is written for a human, so it wins; a blank name
|
|
32
|
+
* falls back to a de-slugged key, which reads badly but still tells the reviewer where they are.
|
|
33
|
+
*
|
|
34
|
+
* @param {string | undefined} name - The step's authored name.
|
|
35
|
+
* @param {string | undefined} key - The step's key.
|
|
36
|
+
* @returns {string} A non-empty label for the screen.
|
|
37
|
+
* @example
|
|
38
|
+
* screenName(undefined, 'plan-comparison') // 'Plan comparison'
|
|
39
|
+
*/
|
|
40
|
+
export declare function screenName(name: string | undefined, key: string | undefined): string;
|
|
41
|
+
/**
|
|
42
|
+
* How the config source is described. A session carrying a preview token reads UNPUBLISHED
|
|
43
|
+
* dashboard config, and a reviewer who does not know that reports a difference as a bug.
|
|
44
|
+
*
|
|
45
|
+
* @param {string | undefined} previewToken - The session's preview token, if any.
|
|
46
|
+
* @returns {string} The sentence the shell shows beside its badge.
|
|
47
|
+
* @example
|
|
48
|
+
* configSourceLabel(client.searchParams['preview-token']?.[0])
|
|
49
|
+
*/
|
|
50
|
+
export declare function configSourceLabel(previewToken: string | undefined): string;
|
|
51
|
+
/**
|
|
52
|
+
* Each review tab's name, in tab-bar order. The guide's "Open …" buttons read these too, so a
|
|
53
|
+
* rename reaches both.
|
|
54
|
+
*/
|
|
55
|
+
export declare const TAB_LABELS: {
|
|
56
|
+
readonly guide: 'How to use';
|
|
57
|
+
readonly session: 'This session';
|
|
58
|
+
readonly answers: 'Answers';
|
|
59
|
+
readonly journey: 'Journey map';
|
|
60
|
+
readonly variations: 'Variations';
|
|
61
|
+
};
|
|
62
|
+
/** The id of one of the review tools' own tabs. */
|
|
63
|
+
export type ReviewTabId = keyof typeof TAB_LABELS;
|
|
64
|
+
/** The name of the sidebar section that holds the developer tools, apart from the review tabs. */
|
|
65
|
+
export declare const DEVELOPER_LABEL = "Developer";
|
|
66
|
+
/** The one-sentence purpose each tab leads with. Kept together so the tone stays consistent. */
|
|
67
|
+
export declare const TAB_INTROS: {
|
|
68
|
+
readonly guide: 'How to use each tab. Open a tab straight from its section.';
|
|
69
|
+
readonly session: 'Where you are right now, and what you answered on each screen. Start here if something looks wrong.';
|
|
70
|
+
readonly answers: 'Every question this journey can ask, and the answers given. Change one to see what the customer would see.';
|
|
71
|
+
readonly journey: 'Every route this journey can take, with your route marked. Go back to any screen you have already visited.';
|
|
72
|
+
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
|
+
};
|
|
@@ -0,0 +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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{UNDESCRIBABLE,describeRule,varKeyOf}from"./plain-logic.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file JSON Logic rendered as a sentence a reviewer can read.
|
|
3
|
+
*
|
|
4
|
+
* The devtools tree prints a rule straight off its operators (`state = WA`) because it has no
|
|
5
|
+
* access to the field index. This module resolves `{"var":"state"}` through the registered FIELD
|
|
6
|
+
* and the literal through that field's OPTION labels, so the same rule reads "State is Western
|
|
7
|
+
* Australia".
|
|
8
|
+
*
|
|
9
|
+
* It uses a field's `name` (the short noun, "Purpose"), never its `label` (the question the
|
|
10
|
+
* customer was asked): a question embedded mid-sentence reads as nonsense. An operator it does not
|
|
11
|
+
* know degrades to {@link UNDESCRIBABLE} rather than guessing, and every caller pairs the sentence
|
|
12
|
+
* with a "Show rule" disclosure carrying the verbatim JSON, which is what makes degrading safe.
|
|
13
|
+
*/
|
|
14
|
+
/** The slice of a registered field this module reads. */
|
|
15
|
+
export interface FieldSummary {
|
|
16
|
+
/** The short noun ("Purpose"). What a sentence is built from. */
|
|
17
|
+
name?: string;
|
|
18
|
+
/** The question the customer was asked. Used by the Answers table, not here. */
|
|
19
|
+
label?: string;
|
|
20
|
+
options?: readonly {
|
|
21
|
+
value: unknown;
|
|
22
|
+
label: string;
|
|
23
|
+
}[] | null;
|
|
24
|
+
}
|
|
25
|
+
/** Resolves a response key to the registered field behind it, or `undefined` when none is. */
|
|
26
|
+
export type FieldLookup = (key: string) => FieldSummary | undefined;
|
|
27
|
+
/** Shown when the rule uses an operator this module cannot put into words. */
|
|
28
|
+
export declare const UNDESCRIBABLE = "a rule this panel can't put into words";
|
|
29
|
+
/**
|
|
30
|
+
* The response key a `{"var": "…"}` node reads, with the `responses.` prefix removed.
|
|
31
|
+
*
|
|
32
|
+
* @param {unknown} rule - The node to read.
|
|
33
|
+
* @returns {string | undefined} The key, or `undefined` for anything that is not a var node.
|
|
34
|
+
* @example
|
|
35
|
+
* varKeyOf({ var: 'responses.state' }) // 'state'
|
|
36
|
+
*/
|
|
37
|
+
export declare function varKeyOf(rule: unknown): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Renders a JSON Logic rule as a sentence. Never throws, and never returns an empty string: an
|
|
40
|
+
* unreadable rule still has to say something, because the row it labels is on screen either way.
|
|
41
|
+
*
|
|
42
|
+
* @param {unknown} rule - The JSON Logic rule.
|
|
43
|
+
* @param {FieldLookup} lookup - Resolves a response key to its registered field.
|
|
44
|
+
* @returns {string} The rule in words, or {@link UNDESCRIBABLE}.
|
|
45
|
+
* @example
|
|
46
|
+
* describeRule({ '==': [{ var: 'responses.state' }, 'WA'] }, lookup) // 'State is Western Australia'
|
|
47
|
+
*/
|
|
48
|
+
export declare function describeRule(rule: unknown, lookup: FieldLookup): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=`a rule this panel can't put into words`,t={"==":`is`,"===":`is`,"!=":`is not`,"!==":`is not`,">":`is more than`,">=":`is at least`,"<":`is less than`,"<=":`is at most`},n=`responses.`;function r(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function i(e){if(!r(e))return;let t=Object.keys(e);return t.length===1?t[0]:void 0}function a(e){if(typeof e==`string`)return e;if(Array.isArray(e)&&typeof e[0]==`string`)return e[0]}function o(e){if(!r(e)||i(e)!==`var`)return;let t=a(e.var);if(t!==void 0)return t.startsWith(n)?t.slice(10):t}function s(e,t){let n=t(e)?.name?.trim();return n!==void 0&&n.length>0?n:e.replace(/[-_]+/g,` `)}function c(t,n,r){if(n!==void 0){let e=r(n)?.options?.find(e=>e.value===t);if(e!==void 0)return e.label}return t===null?`nothing`:t===!0?`yes`:t===!1?`no`:typeof t==`string`?t.length===0?`nothing`:t:typeof t==`number`?String(t):e}function l(e,t){return e.length<=1?e.join(``):e.length===2?`${e[0]} ${t} ${e[1]}`:`${e.slice(0,-1).join(`, `)} ${t} ${e[e.length-1]}`}function u(n,r,i){let[a,l]=r,u=o(a);return u===void 0?e:`${s(u,i)} ${t[n]} ${c(l,u,i)}`}function d(t,n){let[r,i]=t,a=o(r);if(a===void 0||!Array.isArray(i))return e;let u=i.map(e=>c(e,a,n));return`${s(a,n)} is ${l(u,`or`)}`}function f(e){return e===!0?`Always`:e===!1?`Never`:e==null?`Always`:null}function p(e){return e.includes(` is not `)?e.replace(` is not `,` is `):e.includes(` is `)?e.replace(` is `,` is not `):e.endsWith(` has an answer`)?`${e.slice(0,-14)} has no answer`:`not: ${e}`}function m(n,r,i){if(n in t)return r.length>=2?u(n,r,i):e;if(n===`and`||n===`or`)return r.length===0?e:l(r.map(e=>h(e,i)),n);if(n===`!`||n===`not`){let t=h(r[0],i);return t===`a rule this panel can't put into words`?e:p(t)}if(n===`!!`){let t=o(r[0]);return t===void 0?e:`${s(t,i)} has an answer`}return n===`in`?d(r,i):e}function h(t,n){let a=f(t);if(a!==null)return a;let c=o(t);if(c!==void 0)return`${s(c,n)} has an answer`;let l=i(t);if(l===void 0||!r(t))return e;let u=t[l];return m(l,Array.isArray(u)?u:[u],n)}export{e as UNDESCRIBABLE,h as describeRule,o as varKeyOf};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{derivePosture}from"./safety-posture.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file What this session is allowed to do.
|
|
3
|
+
*
|
|
4
|
+
* The host may open the review tools on a live site (for a preview-token session, to review staged
|
|
5
|
+
* config against the real site). That session belongs to a real visitor: an edited answer or a
|
|
6
|
+
* jumped step is a corrupted customer journey, not a test. So the host decides, once, whether the
|
|
7
|
+
* session is read-only, and every panel reads the posture derived from that.
|
|
8
|
+
*
|
|
9
|
+
* The reviewer is TOLD the posture rather than meeting silently dead controls: the shell renders a
|
|
10
|
+
* badge from `label`, and each disabled control carries `reason` as its tooltip.
|
|
11
|
+
*/
|
|
12
|
+
/** What the review tools may do in this session, and how the shell explains it. */
|
|
13
|
+
export interface SafetyPosture {
|
|
14
|
+
/** True when the session may belong to a real visitor. */
|
|
15
|
+
readonly readOnly: boolean;
|
|
16
|
+
/** Commit or clear an answer. */
|
|
17
|
+
readonly canEditAnswers: boolean;
|
|
18
|
+
/** Jump back to a screen already visited. */
|
|
19
|
+
readonly canNavigate: boolean;
|
|
20
|
+
/** Why, in the reviewer's words. `null` when nothing is withheld. */
|
|
21
|
+
readonly reason: string | null;
|
|
22
|
+
/** The environment name for the shell's badge. `null` shows no badge. */
|
|
23
|
+
readonly label: string | null;
|
|
24
|
+
}
|
|
25
|
+
/** The host's say on this session, read by {@link derivePosture}. */
|
|
26
|
+
export interface PostureOptions {
|
|
27
|
+
/** The session may belong to a real visitor, so nothing may change it. */
|
|
28
|
+
readOnly: boolean;
|
|
29
|
+
/** The environment name the host wants on the badge. */
|
|
30
|
+
environmentLabel?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derives the posture from the host's read-only decision. Pure, so the policy is unit-testable
|
|
34
|
+
* without the build-time values a host reads to make that decision.
|
|
35
|
+
*
|
|
36
|
+
* @param {PostureOptions} options - The host's say on this session.
|
|
37
|
+
* @param {boolean} options.readOnly - Whether the session may belong to a real visitor.
|
|
38
|
+
* @param {string} [options.environmentLabel] - The badge text; defaults to "Live site" when read-only and to no badge otherwise.
|
|
39
|
+
* @returns {SafetyPosture} What the session may do, and why not when it may not.
|
|
40
|
+
* @example
|
|
41
|
+
* derivePosture({ readOnly: import.meta.env.PROD, environmentLabel: 'Staging' })
|
|
42
|
+
*/
|
|
43
|
+
export declare function derivePosture(options: PostureOptions): SafetyPosture;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){return e.readOnly?{readOnly:!0,canEditAnswers:!1,canNavigate:!1,reason:`You're on the live site — this is read-only so a real customer's session can't be changed.`,label:e.environmentLabel??`Live site`}:{readOnly:!1,canEditAnswers:!0,canNavigate:!0,reason:null,label:e.environmentLabel??null}}export{e as derivePosture};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{nextTrails}from"./visited-steps.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The screens this session has been through, as a REDUCER over the SDK's event bus.
|
|
3
|
+
*
|
|
4
|
+
* `experience.treeStore` is the AUTHORED graph: it says what CAN happen, never what did, so the
|
|
5
|
+
* journey map needs its own record of "you came this way". The trail is scoped per experience,
|
|
6
|
+
* because "screens I have seen" means nothing across journeys, and `experience_close` drops that
|
|
7
|
+
* journey's trail, so re-opening a journey starts clean. Session state only, never persisted: a
|
|
8
|
+
* stale trail would offer a jump back to a screen this page has never rendered.
|
|
9
|
+
*
|
|
10
|
+
* IMMUTABLE. A change answers a NEW map holding a new set; no change answers the SAME map. An
|
|
11
|
+
* effector store only updates on a new reference, and a Svelte read only re-renders on one, so
|
|
12
|
+
* this rule is what makes an event that changes nothing cost nothing.
|
|
13
|
+
*/
|
|
14
|
+
/** Step keys entered, per experience key, in the order they were first entered. */
|
|
15
|
+
export type Trails = ReadonlyMap<string, ReadonlySet<string>>;
|
|
16
|
+
/** The event shapes this reducer reads, narrowed from the client's `PayloadMap`. */
|
|
17
|
+
export interface VisitedEvent {
|
|
18
|
+
type: string;
|
|
19
|
+
step?: {
|
|
20
|
+
key?: string;
|
|
21
|
+
};
|
|
22
|
+
experience?: {
|
|
23
|
+
experienceKey?: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The trail after one event.
|
|
28
|
+
*
|
|
29
|
+
* @param {Trails} trails - The trails so far.
|
|
30
|
+
* @param {VisitedEvent} event - The bus event.
|
|
31
|
+
* @returns {Trails} The next trails, or the same reference when the event changes nothing.
|
|
32
|
+
* @example
|
|
33
|
+
* createStore<Trails>(new Map()).on(client.eventBus, nextTrails)
|
|
34
|
+
*/
|
|
35
|
+
export declare function nextTrails(trails: Trails, event: VisitedEvent): Trails;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,t){let n=t.experience?.experienceKey;if(n===void 0||n.length===0)return e;if(t.type===`experience_close`){if(!e.has(n))return e;let t=new Map(e);return t.delete(n),t}if(t.type!==`step_enter`)return e;let r=t.step?.key;if(r===void 0||r.length===0)return e;let i=e.get(n);return i?.has(r)===!0?e:new Map(e).set(n,new Set([...i??[],r]))}export{e as nextTrails};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{nextRoute}from"./walk-route.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { HistoryEvent } from '../answer-history';
|
|
2
|
+
/** One screen moved on from, and what the visitor gave there. */
|
|
3
|
+
export interface RouteEntry {
|
|
4
|
+
journey: string;
|
|
5
|
+
screen: string;
|
|
6
|
+
/** The screen's authored name, for saying where a saved walk stops. */
|
|
7
|
+
screenName: string;
|
|
8
|
+
/** What the visitor gave on this screen; empty for a screen that asks nothing. */
|
|
9
|
+
answers: Readonly<Record<string, unknown>>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The route after one event, given the responses held at that moment.
|
|
13
|
+
*
|
|
14
|
+
* @param {readonly RouteEntry[]} route - The route so far.
|
|
15
|
+
* @param {Readonly<Record<string, unknown>>} responses - The responses held at the event.
|
|
16
|
+
* @param {HistoryEvent} event - The bus event.
|
|
17
|
+
* @param {ReadonlySet<string>} [codeOwned] - Keys a screen carries only for its code.
|
|
18
|
+
* @returns {readonly RouteEntry[]} The next route, or the same reference when nothing is recorded.
|
|
19
|
+
* @example
|
|
20
|
+
* routeStore.on(bus, (route, event) => nextRoute(route, responses, event))
|
|
21
|
+
*/
|
|
22
|
+
export declare function nextRoute(route: readonly RouteEntry[], responses: Readonly<Record<string, unknown>>, event: HistoryEvent, codeOwned?: ReadonlySet<string>): readonly RouteEntry[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{submittedAnswers as e}from"../answer-history/answer-history.js";function t(t,n,r,i=new Set){if(r.type!==`step_submit`)return t;let a=r.experience?.experienceKey,o=r.step?.key;if(a===void 0||o===void 0||o.length===0)return t;let s=Object.fromEntries(e(r.step?.fieldKeys??[],n,i).map(({key:e,value:t})=>[e,t])),c={journey:a,screen:o,screenName:r.step?.name??o,answers:s};return[...t,c].slice(-300)}export{t as nextRoute};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/** @file Public entrypoint for the Svelte devtools plugin — the plugin factory and
|
|
1
|
+
/** @file Public entrypoint for the Svelte devtools plugin — the plugin factory, session access and consumer-facing types. */
|
|
2
2
|
export { svelteDevtoolsPlugin } from './plugin';
|
|
3
|
+
export { getDevtoolsSession } from './session-access';
|
|
3
4
|
export type * from './plugin';
|
|
4
|
-
export type
|
|
5
|
+
export type * from './options';
|
|
6
|
+
export type { CustomTool, CustomToolGuide, CustomToolPlacement } from './customTools';
|
|
7
|
+
export type { DevtoolsSession } from './session-access';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{svelteDevtoolsPlugin as e}from"./plugin.js";export{e as svelteDevtoolsPlugin};
|
|
1
|
+
import{svelteDevtoolsPlugin as e}from"./plugin.js";import{getDevtoolsSession as t}from"./session-access.js";export{t as getDevtoolsSession,e as svelteDevtoolsPlugin};
|