@picked/svelte 0.0.1-dev.1788925875333 → 0.0.1-next.1

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 (132) hide show
  1. package/dist/component.d.ts +5 -5
  2. package/dist/component.js +1 -1
  3. package/dist/context.js +1 -1
  4. package/dist/form/index.d.ts +15 -3
  5. package/dist/form/index.js +1 -1
  6. package/dist/hooks/client.d.ts +1 -1
  7. package/dist/hooks/client.js +1 -1
  8. package/dist/hooks/field.d.ts +5 -16
  9. package/dist/hooks/field.js +1 -1
  10. package/dist/hooks/index.d.ts +1 -9
  11. package/dist/hooks/index.js +1 -1
  12. package/dist/hooks/step.d.ts +15 -93
  13. package/dist/hooks/step.js +1 -1
  14. package/dist/hooks/storage.d.ts +2 -2
  15. package/dist/hooks/storage.js +1 -1
  16. package/dist/hooks/stores.d.ts +1 -1
  17. package/dist/hooks/stores.js +1 -1
  18. package/dist/index.d.ts +1 -8
  19. package/dist/index.js +1 -1
  20. package/dist/plugins/devtools/DevToolsExperience.svelte +53 -355
  21. package/dist/plugins/devtools/PickedLogo.svelte +58 -0
  22. package/dist/plugins/devtools/components/ExperienceInstanceListItem.svelte +4 -17
  23. package/dist/plugins/devtools/components/ExperienceInstancesTable.svelte +19 -26
  24. package/dist/plugins/devtools/components/Tabs.svelte +12 -69
  25. package/dist/plugins/devtools/experience.d.ts +1 -0
  26. package/dist/plugins/devtools/experience.js +1 -0
  27. package/dist/plugins/devtools/index.d.ts +0 -2
  28. package/dist/plugins/devtools/index.js +1 -1
  29. package/dist/plugins/devtools/plugin.d.ts +5 -37
  30. package/dist/plugins/devtools/plugin.js +1 -1
  31. package/dist/plugins/devtools/screens/components/ComponentsListScreen.svelte +14 -18
  32. package/dist/plugins/devtools/screens/experience-instances/ExperienceInstancesListScreen.svelte +3 -1
  33. package/dist/plugins/devtools/screens/experiences/ExperienceDetailsScreen.svelte +33 -31
  34. package/dist/plugins/devtools/screens/experiences/ExperienceExperienceInstancesListScreen.svelte +3 -1
  35. package/dist/plugins/devtools/screens/experiences/ExperienceTreeScreen.svelte +103 -293
  36. package/dist/plugins/devtools/screens/experiences/ExperiencesListScreen.svelte +1 -2
  37. package/dist/plugins/devtools/screens/frameworks/FrameworksListScreen.svelte +13 -15
  38. package/dist/plugins/devtools/screens/history/HistoryEntriesTable.svelte +16 -22
  39. package/dist/plugins/devtools/screens/history/HistoryEntryListItem.svelte +1 -7
  40. package/dist/plugins/devtools/screens/predicates/PredicatesListScreen.svelte +13 -15
  41. package/dist/plugins/devtools/screens/project/ProjectScreen.svelte +1 -46
  42. package/dist/plugins/framework/index.js +1 -1
  43. package/dist/plugins/framework/plugin.d.ts +4 -4
  44. package/dist/plugins/framework/plugin.js +1 -1
  45. package/dist/render/browser.d.ts +2 -35
  46. package/dist/render/browser.js +1 -1
  47. package/dist/render/server.d.ts +8 -22
  48. package/dist/render/server.js +1 -1
  49. package/dist/router/Step.svelte +4 -6
  50. package/dist/router/StepRouter.svelte +33 -14
  51. package/dist/router/context.d.ts +3 -3
  52. package/dist/router/context.js +1 -1
  53. package/dist/router/index.js +1 -1
  54. package/dist/server/index.d.ts +2 -2
  55. package/dist/server/index.js +1 -1
  56. package/dist/utils/array.js +1 -1
  57. package/dist/vite-env.d.js +1 -0
  58. package/package.json +33 -106
  59. package/dist/.tsbuildinfo +0 -1
  60. package/dist/_virtual/_vite/preload-helper.js +0 -1
  61. package/dist/components/LazyImage.svelte +0 -192
  62. package/dist/components/index.d.ts +0 -1
  63. package/dist/components/index.js +0 -1
  64. package/dist/experience/ExperienceHost.svelte +0 -112
  65. package/dist/experience/ExperienceSlot.svelte +0 -154
  66. package/dist/experience/HtmlRender.svelte +0 -52
  67. package/dist/experience/set-picked-context.d.ts +0 -29
  68. package/dist/experience/set-picked-context.js +0 -1
  69. package/dist/form/__tests__/CreateFormHarness.svelte +0 -30
  70. package/dist/form/create-form.js +0 -1
  71. package/dist/form/create-form.svelte.d.ts +0 -40
  72. package/dist/form/create-form.svelte.js +0 -1
  73. package/dist/form/get-form.d.ts +0 -30
  74. package/dist/form/get-form.js +0 -1
  75. package/dist/hooks/action.d.ts +0 -40
  76. package/dist/hooks/action.js +0 -1
  77. package/dist/hooks/content.d.ts +0 -18
  78. package/dist/hooks/content.js +0 -1
  79. package/dist/hooks/create-query/index.d.ts +0 -145
  80. package/dist/hooks/create-query/index.js +0 -1
  81. package/dist/hooks/events.d.ts +0 -2
  82. package/dist/hooks/events.js +0 -1
  83. package/dist/hooks/feature.d.ts +0 -47
  84. package/dist/hooks/feature.js +0 -1
  85. package/dist/hooks/predicate.d.ts +0 -3
  86. package/dist/hooks/predicate.js +0 -1
  87. package/dist/hooks/reference.d.ts +0 -61
  88. package/dist/hooks/reference.js +0 -1
  89. package/dist/plugins/devtools/PickedIcon.svelte +0 -19
  90. package/dist/plugins/devtools/components/AdaptedToolHost.svelte +0 -103
  91. package/dist/plugins/devtools/customTools.d.ts +0 -27
  92. package/dist/plugins/devtools/customTools.js +0 -1
  93. package/dist/plugins/devtools/screens/config/ConfigScreen.svelte +0 -160
  94. package/dist/plugins/devtools/screens/content/ContentListScreen.svelte +0 -89
  95. package/dist/plugins/devtools/screens/custom-tools/CustomToolsScreen.svelte +0 -29
  96. package/dist/plugins/devtools/screens/events/EventListItem.svelte +0 -216
  97. package/dist/plugins/devtools/screens/events/EventsListScreen.svelte +0 -18
  98. package/dist/plugins/devtools/screens/events/EventsTable.svelte +0 -76
  99. package/dist/plugins/devtools/screens/features/FeaturesListScreen.svelte +0 -239
  100. package/dist/plugins/devtools/screens/fields/FieldTableRow.svelte +0 -276
  101. package/dist/plugins/devtools/screens/fields/FieldsListScreen.svelte +0 -12
  102. package/dist/plugins/devtools/screens/fields/FieldsTable.svelte +0 -78
  103. package/dist/plugins/devtools/screens/identity/IdentityScreen.svelte +0 -66
  104. package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +0 -280
  105. package/dist/plugins/devtools/screens/responses/ResponsesListScreen.svelte +0 -12
  106. package/dist/plugins/devtools/screens/responses/ResponsesTable.svelte +0 -88
  107. package/dist/plugins/devtools/ui/actions/Button.svelte +0 -175
  108. package/dist/plugins/devtools/ui/actions/CopyButton.svelte +0 -88
  109. package/dist/plugins/devtools/ui/data/Badge.svelte +0 -62
  110. package/dist/plugins/devtools/ui/data/CodeBlock.svelte +0 -38
  111. package/dist/plugins/devtools/ui/data/DataTable.svelte +0 -144
  112. package/dist/plugins/devtools/ui/data/EmptyState.svelte +0 -26
  113. package/dist/plugins/devtools/ui/data/ExpandableRow.svelte +0 -66
  114. package/dist/plugins/devtools/ui/data/VisualizationContainer.svelte +0 -55
  115. package/dist/plugins/devtools/ui/feedback/Banner.svelte +0 -116
  116. package/dist/plugins/devtools/ui/feedback/Spinner.svelte +0 -79
  117. package/dist/plugins/devtools/ui/forms/Checkbox.svelte +0 -139
  118. package/dist/plugins/devtools/ui/forms/RadioGroup.svelte +0 -218
  119. package/dist/plugins/devtools/ui/forms/Select.svelte +0 -137
  120. package/dist/plugins/devtools/ui/forms/Switch.svelte +0 -158
  121. package/dist/plugins/devtools/ui/forms/TextField.svelte +0 -125
  122. package/dist/plugins/devtools/ui/index.d.ts +0 -22
  123. package/dist/plugins/devtools/ui/index.js +0 -1
  124. package/dist/plugins/devtools/ui/layout/Card.svelte +0 -55
  125. package/dist/plugins/devtools/ui/layout/Divider.svelte +0 -34
  126. package/dist/plugins/devtools/ui/layout/Spacer.svelte +0 -11
  127. package/dist/plugins/devtools/ui/layout/Stack.svelte +0 -129
  128. package/dist/plugins/devtools/ui/overlays/Tooltip.svelte +0 -90
  129. package/dist/plugins/devtools/ui/tokens.css +0 -144
  130. package/dist/plugins/devtools/ui/typography/Heading.svelte +0 -55
  131. package/dist/plugins/devtools/ui/typography/Text.svelte +0 -93
  132. package/dist/utils/ErrorBoundary.svelte +0 -27
@@ -1,42 +1,30 @@
1
1
  <script lang="ts">
2
- /** @file D3-based tree visualization of an experience's node graph in the devtools panel. */
3
- import { onDestroy } from 'svelte'
4
2
  import * as d3 from 'd3'
5
3
  import type { ExperienceEdge, ExperienceNode, ExperienceStore } from '@picked/client'
6
- import { Badge, Button, Spinner, Stack, Text, VisualizationContainer } from '../../ui'
7
4
 
8
5
  export let experience: ExperienceStore
9
6
 
10
7
  let svgEl: SVGSVGElement
11
8
  let zoom = 1
12
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- reserved for future node details panel
13
9
  let selectedNode = null
14
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, sonarjs/no-unused-collection -- reserved for future path highlighting
15
10
  let highlightedPath: string[][] = []
16
- let error: string | null = null
11
+ let error = null
17
12
  const loading = false
18
13
 
19
14
  let treeRenderingDone = false
20
- let tooltipEl: d3.Selection<HTMLDivElement, unknown, null, undefined> | null = null
21
-
22
- onDestroy(() => {
23
- tooltipEl?.remove()
24
- tooltipEl = null
25
- })
26
15
 
27
16
  const tree = experience.treeStore
28
17
 
18
+ $: steps = $tree.steps
29
19
  $: nodes = $tree.nodes
30
20
  $: edges = $tree.edges
31
- $: conditions = $tree.conditions
32
-
21
+ $: conditionals = $tree.conditionals
33
22
 
34
23
  let zoomBehavior: d3.ZoomBehavior<SVGSVGElement, unknown>
35
24
 
36
25
  interface ProcessedNode {
37
26
  id: string
38
27
  name: string
39
- tooltip?: string
40
28
  type: string
41
29
  children: ProcessedNode[]
42
30
  _originalNode: ExperienceNode
@@ -46,98 +34,10 @@
46
34
  _referenceTo?: string
47
35
  }
48
36
 
49
- /**
50
- * Type guard that checks if a value is a non-null, non-array object.
51
- *
52
- * @param val - The value to check.
53
- * @example
54
- * ```typescript
55
- * isRecord({ key: 'value' }) // true
56
- * isRecord([1, 2]) // false
57
- * ```
58
- */
59
- function isRecord(val: unknown): val is Record<string, unknown> {
60
- return val !== null && typeof val === 'object' && !Array.isArray(val)
61
- }
62
-
63
- /**
64
- * Recursively formats a JSON Logic rule into a human-readable string.
65
- *
66
- * @param rule - The JSON Logic rule object to format.
67
- * @example
68
- * ```typescript
69
- * formatLogicRule({ '==': [{ var: 'age' }, 18] }) // 'age = 18'
70
- * ```
71
- */
72
- // eslint-disable-next-line sonarjs/cognitive-complexity -- recursive logic rule formatter handles all operator types
73
- function formatLogicRule(rule: unknown): string {
74
- if (rule === null || rule === undefined) return '?'
75
- if (typeof rule === 'string') return rule
76
- if (typeof rule === 'number' || typeof rule === 'boolean') return String(rule)
77
- if (Array.isArray(rule)) {
78
- return rule.map(formatLogicRule).join(', ')
79
- }
80
-
81
- if (isRecord(rule)) {
82
- const keys = Object.keys(rule)
83
- if (keys.length === 0) return 'Condition'
84
- const op = keys[0]
85
- const args = rule[op]
86
-
87
- if (op === 'var') {
88
- return String(Array.isArray(args) ? args[0] : args)
89
- }
90
-
91
- const compOps: Record<string, string> = {
92
- '==': '=',
93
- '===': '=',
94
- '!=': '≠',
95
- '!==': '≠',
96
- '>': '>',
97
- '<': '<',
98
- '>=': '≥',
99
- '<=': '≤',
100
- }
101
- if (op in compOps && Array.isArray(args) && args.length >= 2) {
102
- return `${formatLogicRule(args[0])} ${compOps[op]} ${formatLogicRule(args[1])}`
103
- }
104
-
105
- if (op === 'and' && Array.isArray(args)) {
106
- return args.map(formatLogicRule).join(' & ')
107
- }
108
-
109
- if (op === 'or' && Array.isArray(args)) {
110
- return args.map(formatLogicRule).join(' | ')
111
- }
112
-
113
- if ((op === '!' || op === 'not') && Array.isArray(args)) {
114
- return `!${formatLogicRule(args[0])}`
115
- }
116
-
117
- if (op === 'in' && Array.isArray(args) && args.length >= 2) {
118
- return `${formatLogicRule(args[0])} in ${formatLogicRule(args[1])}`
119
- }
120
-
121
- return `${op}(?)`
122
- }
123
-
124
- return '?'
125
- }
126
-
127
- /**
128
- * Renders the experience node graph as an interactive D3 tree visualization.
129
- *
130
- * @example
131
- * ```typescript
132
- * renderGraph() // builds and renders the SVG tree
133
- * ```
134
- */
135
37
  function renderGraph() {
136
38
  d3.select(svgEl).selectAll('*').remove()
137
39
 
138
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- D3 visualization runs imperatively outside Svelte reactivity
139
- const conditionsMap = new Map(conditions.map((c) => [c.id, c]))
140
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- D3 visualization runs imperatively outside Svelte reactivity
40
+ const stepsMap = new Map(steps.map((s) => [s.id, s]))
141
41
  const outgoingEdges = new Map<string, ExperienceEdge[]>()
142
42
  edges.forEach((e) => {
143
43
  if (!outgoingEdges.has(e.sourceNodeId)) outgoingEdges.set(e.sourceNodeId, [])
@@ -146,32 +46,17 @@
146
46
 
147
47
  const getNodeLabel = (node: ExperienceNode): string => {
148
48
  if (node.type === 'origin') return 'Start'
149
- if (node.type === 'step') return node.name ?? 'Unknown Step'
150
-
151
- if (node.type === 'condition') {
152
- const conditionItem = node.conditionId ? conditionsMap.get(node.conditionId) : undefined
153
- return conditionItem?.name ?? 'Condition'
154
- }
155
-
156
- if (node.type === 'action') return node.name ?? node.actionKey ?? 'Action'
157
-
49
+ if (node.type === 'step' && node.stepId)
50
+ return stepsMap.get(node.stepId)?.label ?? 'Unknown Step'
51
+ if (node.type === 'conditional' && node.conditionalId) return `Condition'}`
158
52
  return 'Unknown Node'
159
53
  }
160
54
 
161
- const getNodeTooltip = (node: ExperienceNode): string | undefined => {
162
- if (node.type !== 'condition') return undefined
163
- const conditionItem = node.conditionId ? conditionsMap.get(node.conditionId) : undefined
164
- if (conditionItem?.logic) return formatLogicRule(conditionItem.logic)
165
- return undefined
166
- }
167
-
168
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- D3 visualization runs imperatively outside Svelte reactivity
169
55
  const processedNodes = new Map<string, ProcessedNode>()
170
56
  nodes.forEach((n) => {
171
57
  processedNodes.set(n.id, {
172
58
  id: n.id,
173
59
  name: getNodeLabel(n),
174
- tooltip: getNodeTooltip(n),
175
60
  type: n.type,
176
61
  children: [],
177
62
  _originalNode: n,
@@ -195,17 +80,15 @@
195
80
  }
196
81
 
197
82
  if (rootNode) {
198
- // Use a global visited set so join nodes (multiple incoming branches converging
199
- // to the same node) only appear once in the tree, with reference nodes on
200
- // subsequent visits to indicate the convergence point.
201
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- D3 visualization runs imperatively outside Svelte reactivity
202
- const globalVisited = new Set<string>()
203
- const convertGraphToTree = (node: ProcessedNode): ProcessedNode => {
204
- if (globalVisited.has(node.id)) {
83
+ const convertGraphToTree = (
84
+ node: ProcessedNode,
85
+ visited = new Set<string>(),
86
+ ): ProcessedNode => {
87
+ if (visited.has(node.id)) {
205
88
  return {
206
89
  ...node,
207
90
  id: `${node.id}_ref`,
208
- name: `↩ ${node.name}`,
91
+ name: `${node.name} (ref)`,
209
92
  type: `${node.type}_ref`,
210
93
  children: [],
211
94
  _isReference: true,
@@ -213,8 +96,9 @@
213
96
  }
214
97
  }
215
98
 
216
- globalVisited.add(node.id)
217
- return { ...node, children: node.children.map((c) => convertGraphToTree(c)) }
99
+ const newVisited = new Set(visited)
100
+ newVisited.add(node.id)
101
+ return { ...node, children: node.children.map((c) => convertGraphToTree(c, newVisited)) }
218
102
  }
219
103
 
220
104
  const treeData = convertGraphToTree(rootNode)
@@ -243,143 +127,50 @@
243
127
 
244
128
  const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`)
245
129
 
246
- const linkGenerator = d3
247
- .linkHorizontal<d3.HierarchyPointLink<ProcessedNode>, d3.HierarchyPointNode<ProcessedNode>>()
248
- .x((d) => d.y)
249
- .y((d) => d.x)
250
-
251
130
  g.selectAll('.link')
252
131
  .data(treeRoot.links())
253
132
  .enter()
254
133
  .append('path')
255
134
  .attr('class', 'link')
256
135
  .attr('fill', 'none')
257
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
258
- .attr('stroke', (d: any) => {
259
- if (d.source.data.type !== 'condition') return '#94a3b8'
260
- const targetId = d.target.data._isReference
261
- ? d.target.data._referenceTo
262
- : d.target.data.id
263
- const edge = d.source.data._originalEdges.find(
264
- (e: ExperienceEdge) => e.destinationNodeId === targetId,
265
- )
266
- if (edge?.sourceHandle === 'true') return '#10b981'
267
- if (edge?.sourceHandle === 'false') return '#ef4444'
268
- return '#94a3b8'
269
- })
136
+ .attr('stroke', '#94a3b8')
270
137
  .attr('stroke-width', 2)
271
- .attr('d', linkGenerator)
272
-
273
- // Label true/false branches from condition nodes
274
- g.selectAll('.edge-label')
275
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
276
- .data(treeRoot.links().filter((d: any) => d.source.data.type === 'condition'))
277
- .enter()
278
- .append('text')
279
- .attr('class', 'edge-label')
280
- .attr('text-anchor', 'middle')
281
- .attr('font-size', '10px')
282
- .attr('font-weight', 'bold')
283
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
284
- .attr('x', (d: any) => (d.source.y + d.target.y) / 2)
285
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
286
- .attr('y', (d: any) => (d.source.x + d.target.x) / 2 - 6)
287
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
288
- .attr('fill', (d: any) => {
289
- const targetId = d.target.data._isReference
290
- ? d.target.data._referenceTo
291
- : d.target.data.id
292
- const edge = d.source.data._originalEdges.find(
293
- (e: ExperienceEdge) => e.destinationNodeId === targetId,
294
- )
295
- if (edge?.sourceHandle === 'true') return '#10b981'
296
- if (edge?.sourceHandle === 'false') return '#ef4444'
297
- return '#64748b'
298
- })
299
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
300
- .text((d: any) => {
301
- const targetId = d.target.data._isReference
302
- ? d.target.data._referenceTo
303
- : d.target.data.id
304
- const edge = d.source.data._originalEdges.find(
305
- (e: ExperienceEdge) => e.destinationNodeId === targetId,
306
- )
307
- if (edge?.sourceHandle === 'true') return 'Yes'
308
- if (edge?.sourceHandle === 'false') return 'No'
309
- return ''
310
- })
138
+ .attr(
139
+ 'd',
140
+ d3
141
+ .linkHorizontal<d3.HierarchyPointLink<ProcessedNode>, ProcessedNode>()
142
+ .x((d) => d.y)
143
+ .y((d) => d.x),
144
+ )
311
145
 
312
- const nodesSelection = g
146
+ const nodes = g
313
147
  .selectAll('.node')
314
148
  .data(treeRoot.descendants())
315
149
  .enter()
316
150
  .append('g')
317
151
  .attr('class', 'node')
318
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
319
- .attr('transform', (d: any) => `translate(${d.y},${d.x})`)
152
+ .attr('transform', (d: any) => `translate(${d.y},${d.x})`) // d3.HierarchyPointNode uses x, y
320
153
 
321
- const circles = nodesSelection
154
+ nodes
322
155
  .append('circle')
323
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
324
156
  .attr('r', (d: any) => (d.data.type === 'origin' ? 15 : 10))
325
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
326
157
  .attr('fill', (d: any) => {
327
158
  if (d.data._isReference) return '#38bdf8'
328
- if (d.data.type === 'origin') return '#6b7280'
329
- if (d.data.type === 'condition') return '#f59e0b'
330
- if (d.data.type === 'action') return '#22c55e'
331
- return '#3b82f6'
159
+ if (d.data.type === 'origin') return '#00cd90'
160
+ if (d.data.type === 'conditional') return '#f59e0b'
161
+ return '#1748ff'
332
162
  })
333
163
  .attr('stroke', '#fff')
334
164
  .attr('stroke-width', 2)
335
165
 
336
- tooltipEl?.remove()
337
- tooltipEl = d3
338
- .select(svgEl.parentElement)
339
- .append('div')
340
-
341
- const tooltip = tooltipEl
342
- .style('position', 'absolute')
343
- .style('pointer-events', 'none')
344
- .style('background', '#1e293b')
345
- .style('color', '#f8fafc')
346
- .style('padding', '6px 10px')
347
- .style('border-radius', '6px')
348
- .style('font-size', '11px')
349
- .style('max-width', '260px')
350
- .style('white-space', 'pre-wrap')
351
- .style('opacity', '0')
352
- .style('z-index', '10')
353
-
354
- circles
355
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
356
- .filter((d: any) => d.data.tooltip)
357
- .style('cursor', 'default')
358
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
359
- .on('mouseenter', function showTooltip(event: MouseEvent, d: any) {
360
- const container = svgEl.parentElement
361
- if (!container || !d.data.tooltip) return
362
- const rect = container.getBoundingClientRect()
363
- tooltip
364
- .text(d.data.tooltip)
365
- .style('left', `${event.clientX - rect.left + 12}px`)
366
- .style('top', `${event.clientY - rect.top - 8}px`)
367
- .style('opacity', '1')
368
- })
369
- .on('mouseleave', function hideTooltip() {
370
- tooltip.style('opacity', '0')
371
- })
372
-
373
- nodesSelection
166
+ nodes
374
167
  .append('text')
375
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
376
168
  .attr('dy', (d: any) => (d.children ? -15 : 25))
377
169
  .attr('text-anchor', 'middle')
378
170
  .attr('font-size', '12px')
379
171
  .attr('fill', '#1e293b')
380
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
381
172
  .text((d: any) =>
382
- d.data.name.length > 30 ? `${d.data.name.slice(0, 27)}...` : d.data.name,
173
+ d.data.name.length > 25 ? `${d.data.name.slice(0, 22)}...` : d.data.name,
383
174
  )
384
175
 
385
176
  svg.on('dblclick.zoom', null) // Remove default double click zoom
@@ -408,27 +199,11 @@
408
199
  treeRenderingDone = true
409
200
  }
410
201
 
411
- /**
412
- * Zooms the tree visualization in by 20%.
413
- *
414
- * @example
415
- * ```typescript
416
- * handleZoomIn() // increases zoom level
417
- * ```
418
- */
419
202
  function handleZoomIn() {
420
203
  if (!svgEl || !zoomBehavior) return
421
204
  d3.select(svgEl).transition().duration(250).call(zoomBehavior.scaleBy, 1.2)
422
205
  }
423
206
 
424
- /**
425
- * Zooms the tree visualization out by 20%.
426
- *
427
- * @example
428
- * ```typescript
429
- * handleZoomOut() // decreases zoom level
430
- * ```
431
- */
432
207
  function handleZoomOut() {
433
208
  if (!svgEl || !zoomBehavior) return
434
209
  d3.select(svgEl)
@@ -437,14 +212,6 @@
437
212
  .call(zoomBehavior.scaleBy, 1 / 1.2)
438
213
  }
439
214
 
440
- /**
441
- * Resets the tree visualization zoom and position to the default centered view.
442
- *
443
- * @example
444
- * ```typescript
445
- * handleReset() // resets zoom to default
446
- * ```
447
- */
448
215
  function handleReset() {
449
216
  if (!svgEl || !zoomBehavior) return
450
217
  const width = svgEl.clientWidth
@@ -458,15 +225,6 @@
458
225
  d3.select(svgEl).transition().duration(750).call(zoomBehavior.transform, initialTransform)
459
226
  }
460
227
 
461
- /**
462
- * Closes the node details panel and resets all link/node highlights.
463
- *
464
- * @example
465
- * ```typescript
466
- * closeNodeDetails() // resets selection and highlighting
467
- * ```
468
- */
469
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- reserved for future node details panel
470
228
  function closeNodeDetails() {
471
229
  selectedNode = null
472
230
  if (svgEl) {
@@ -479,22 +237,74 @@
479
237
  }
480
238
  </script>
481
239
 
482
- {#if loading}
483
- <Stack direction="horizontal" align="center" justify="center" fill>
484
- <Spinner label="Loading visualization" />
485
- </Stack>
486
- {:else if error}
487
- <Stack direction="horizontal" align="center" justify="center" fill>
488
- <Text color="error">{error}</Text>
489
- </Stack>
490
- {:else}
491
- <VisualizationContainer>
492
- {#snippet controls()}
493
- <Button variant="secondary" size="small" onclick={handleZoomIn}>Zoom in</Button>
494
- <Button variant="secondary" size="small" onclick={handleZoomOut}>Zoom out</Button>
495
- <Button variant="secondary" size="small" onclick={handleReset}>Reset</Button>
496
- <Badge status="default" size="small">{Math.round(zoom * 100)}%</Badge>
497
- {/snippet}
498
- <svg bind:this={svgEl}></svg>
499
- </VisualizationContainer>
500
- {/if}
240
+ <div class="graph">
241
+ {#if loading}
242
+ <div class="loading-container">Loading visualization...</div>
243
+ {:else if error}
244
+ <div class="error-container">{error}</div>
245
+ {:else}
246
+ <div class="tree-container">
247
+ <div class="controls">
248
+ <button on:click={handleZoomIn} title="Zoom In">
249
+ <span>Zoom in</span>
250
+ </button>
251
+ <button on:click={handleZoomOut} title="Zoom Out">
252
+ <span>Zoom out</span>
253
+ </button>
254
+ <button on:click={handleReset} title="Reset View">
255
+ <span>Reset</span>
256
+ </button>
257
+ <span class="zoom-level">{Math.round(zoom * 100)}%</span>
258
+ </div>
259
+ <svg bind:this={svgEl} class="tree-svg"></svg>
260
+ </div>
261
+ {/if}
262
+ </div>
263
+
264
+ <style>
265
+ .graph {
266
+ border: 1px solid lightgray;
267
+ }
268
+
269
+ .loading-container,
270
+ .error-container {
271
+ display: flex;
272
+ height: 100%;
273
+ align-items: center;
274
+ justify-content: center;
275
+ }
276
+
277
+ .error-container {
278
+ color: #ef4444;
279
+ }
280
+
281
+ .tree-container {
282
+ position: relative;
283
+ height: 100%;
284
+ width: 100%;
285
+ }
286
+
287
+ .controls {
288
+ position: absolute;
289
+ right: 1rem;
290
+ top: 1rem;
291
+ z-index: 10;
292
+ display: flex;
293
+ gap: 0.5rem;
294
+ }
295
+
296
+ .zoom-level {
297
+ display: flex;
298
+ align-items: center;
299
+ border-radius: 0.25rem;
300
+ background-color: rgba(255, 255, 255, 0.8);
301
+ padding: 0.25rem 0.5rem;
302
+ font-size: 0.75rem;
303
+ }
304
+
305
+ .tree-svg {
306
+ height: 100%;
307
+ width: 100%;
308
+ background-color: white;
309
+ }
310
+ </style>
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- /** @file Screen listing all registered experiences as tabs in the devtools panel. */
3
2
  import Tabs from '../../components/Tabs.svelte'
4
3
  import { getPicked } from '../../../../hooks'
5
4
  import ExperienceScreen from './ExperienceScreen.svelte'
@@ -10,7 +9,7 @@
10
9
  $: tabs = $experiences.map((experience) => {
11
10
  return {
12
11
  id: experience.id,
13
- label: experience.name,
12
+ label: experience.id,
14
13
  component: ExperienceScreen,
15
14
  props: {
16
15
  experience,
@@ -1,26 +1,24 @@
1
1
  <script lang="ts">
2
2
  import { getPicked } from '../../../../hooks'
3
- import { Card, DataTable, EmptyState, Stack } from '../../ui'
4
3
 
5
4
  const client = getPicked()
6
5
 
7
6
  $: frameworks = client.frameworks.listStore
8
-
9
- const columns = [{ key: 'name', header: 'Name' }]
10
7
  </script>
11
8
 
12
- <Stack padding="medium">
13
- <Card sectioned>
14
- <DataTable columns={columns} rows={$frameworks}>
15
- {#snippet row(item)}
16
- {@const framework = item}
9
+ <div>
10
+ <table>
11
+ <thead>
12
+ <tr>
13
+ <th>Name</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ {#each $frameworks as framework (framework.name)}
17
18
  <tr>
18
19
  <td>{framework.name}</td>
19
20
  </tr>
20
- {/snippet}
21
- {#snippet empty()}
22
- <EmptyState message="No frameworks" />
23
- {/snippet}
24
- </DataTable>
25
- </Card>
26
- </Stack>
21
+ {/each}
22
+ </tbody>
23
+ </table>
24
+ </div>
@@ -1,29 +1,23 @@
1
1
  <script lang="ts">
2
2
  import type { HistoryEntry } from '@picked/client'
3
3
  import HistoryEntryListItem from './HistoryEntryListItem.svelte'
4
- import { Card, DataTable, EmptyState, Stack } from '../../ui'
5
4
 
6
5
  export let historyEntries: HistoryEntry[]
7
-
8
- const columns = [
9
- { key: 'id', header: 'ID' },
10
- { key: 'experienceId', header: 'Experience ID' },
11
- { key: 'nodeId', header: 'Node ID' },
12
- { key: 'nodeType', header: 'Node Type' },
13
- { key: 'at', header: 'At' },
14
- ]
15
6
  </script>
16
7
 
17
- <Stack padding="medium">
18
- <Card sectioned>
19
- <DataTable columns={columns} rows={historyEntries}>
20
- {#snippet row(item)}
21
- {@const historyEntry = item}
22
- <HistoryEntryListItem {historyEntry} />
23
- {/snippet}
24
- {#snippet empty()}
25
- <EmptyState message="No history entries" />
26
- {/snippet}
27
- </DataTable>
28
- </Card>
29
- </Stack>
8
+ <table>
9
+ <thead>
10
+ <tr>
11
+ <th>ID</th>
12
+ <th>Experience ID</th>
13
+ <th>Node ID</th>
14
+ <th>Node Type</th>
15
+ <th>At</th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ {#each historyEntries as historyEntry (historyEntry.id)}
20
+ <HistoryEntryListItem {historyEntry} />
21
+ {/each}
22
+ </tbody>
23
+ </table>
@@ -1,18 +1,12 @@
1
1
  <script lang="ts">
2
- /** @file List item row displaying a navigation history entry in the devtools history screen. */
3
2
  import type { HistoryEntry } from '@picked/client'
4
- import { getPicked } from '../../../../hooks'
5
3
 
6
4
  export let historyEntry: HistoryEntry
7
-
8
- const client = getPicked()
9
- const experienceStore = client.experiences.getById(historyEntry.experienceId)
10
- $: experienceName = $experienceStore?.name ?? historyEntry.experienceId
11
5
  </script>
12
6
 
13
7
  <tr>
14
8
  <td>{historyEntry.id}</td>
15
- <td>{experienceName}</td>
9
+ <td>{historyEntry.experienceId}</td>
16
10
  <td>{historyEntry.node.id}</td>
17
11
  <td>{historyEntry.node.type}</td>
18
12
  <td>{historyEntry.at}</td>