@heyramzi/ui 2.0.1 → 2.2.0

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 (341) hide show
  1. package/package.json +48 -24
  2. package/scripts/sync-shadcn.mjs +3 -4
  3. package/src/agents/agent-avatar.svelte +604 -575
  4. package/src/agents/agent-cast.svelte +179 -183
  5. package/src/agents/demo.svelte +125 -113
  6. package/src/agents/helpers.ts +23 -17
  7. package/src/agents/index.ts +4 -4
  8. package/src/agents/page-state.ts +6 -6
  9. package/src/agents/types.ts +6 -6
  10. package/src/ai/ai-button/AiButton.svelte +4 -12
  11. package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
  12. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
  13. package/src/ai/response/Response.css +2 -3
  14. package/src/animations/avatar-group-hover.css +2 -16
  15. package/src/animations/card-tilt.css +3 -6
  16. package/src/animations/digit-pop-in.css +2 -15
  17. package/src/animations/dropdown.css +4 -17
  18. package/src/animations/input-clear-dissolve.css +5 -18
  19. package/src/animations/input-shake.css +9 -41
  20. package/src/animations/learn-more-hover.css +17 -9
  21. package/src/animations/like-button.css +2 -4
  22. package/src/animations/page-side-by-side.css +2 -10
  23. package/src/animations/plus-menu-morph.css +2 -5
  24. package/src/animations/shimmer-text.css +5 -18
  25. package/src/animations/spinning-counter.css +2 -6
  26. package/src/animations/success-check.css +2 -15
  27. package/src/animations/thinking-states.css +2 -7
  28. package/src/brand/brand.test.ts +21 -5
  29. package/src/brand/index.ts +4 -10
  30. package/src/canvas/README.md +9 -12
  31. package/src/canvas/card/card-frame.svelte +43 -39
  32. package/src/canvas/card/resize-handles.svelte +89 -80
  33. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  34. package/src/canvas/chrome/lock-badge.svelte +8 -8
  35. package/src/canvas/chrome/side-handles.svelte +24 -19
  36. package/src/canvas/chrome/size-chip.svelte +30 -30
  37. package/src/canvas/chrome/snap-guides.svelte +20 -20
  38. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  39. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  40. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  41. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  42. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  43. package/src/canvas/edges/bend-handle.svelte +46 -46
  44. package/src/canvas/edges/edge-gradient.svelte +10 -10
  45. package/src/canvas/edges/gradient-edge.svelte +45 -29
  46. package/src/canvas/editor/lock.test.ts +6 -1
  47. package/src/canvas/editor/style.ts +7 -1
  48. package/src/canvas/headless/svg-figure.test.ts +5 -1
  49. package/src/canvas/lod/lod-switch.svelte +111 -106
  50. package/src/canvas/pen/PenField.svelte +87 -81
  51. package/src/canvas/pen/PenOverlay.svelte +127 -127
  52. package/src/canvas/pen/ShapeNode.svelte +31 -32
  53. package/src/canvas/pen/TextNode.svelte +54 -53
  54. package/src/canvas/pen/host-field.test.svelte +7 -7
  55. package/src/canvas/pen/shapes.ts +7 -1
  56. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  57. package/src/canvas/screen/PartView.svelte +138 -72
  58. package/src/canvas/screen/ScreenBody.svelte +81 -60
  59. package/src/canvas/screen/ScreenCard.svelte +20 -20
  60. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  61. package/src/canvas/ui/FrameLayer.svelte +59 -59
  62. package/src/canvas/ui/PenLayer.svelte +39 -30
  63. package/src/canvas/ui/StylePanel.svelte +119 -102
  64. package/src/canvas/ui/canvas-grid.svelte +137 -129
  65. package/src/charts/animated-value.svelte +1 -1
  66. package/src/charts/area-chart.css +2 -5
  67. package/src/charts/area-chart.svelte +1 -1
  68. package/src/charts/bar-chart.css +2 -3
  69. package/src/charts/bar-chart.svelte +1 -1
  70. package/src/charts/charts.css +5 -17
  71. package/src/charts/crosshair.svelte +1 -1
  72. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  73. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  74. package/src/charts/dashboard/data-panel.svelte +50 -50
  75. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  76. package/src/charts/dashboard/data-table.svelte +138 -138
  77. package/src/charts/dashboard/panel-grid.svelte +30 -30
  78. package/src/charts/dashboard/period-filter.svelte +25 -25
  79. package/src/charts/dashboard/rating-chip.svelte +19 -19
  80. package/src/charts/dashboard/stat-band.svelte +32 -32
  81. package/src/charts/dashboard/stat-tile.svelte +106 -99
  82. package/src/charts/donut-chart.svelte +1 -1
  83. package/src/charts/edge-fade.svelte +1 -1
  84. package/src/charts/figure.svelte +1 -1
  85. package/src/charts/gauge-chart.css +4 -7
  86. package/src/charts/gauge-chart.svelte +1 -1
  87. package/src/charts/hover-focus.svelte +1 -1
  88. package/src/charts/legend.css +1 -3
  89. package/src/charts/legend.svelte +1 -1
  90. package/src/charts/line-chart.svelte +1 -1
  91. package/src/charts/number-chart.css +6 -9
  92. package/src/charts/number-chart.svelte +1 -1
  93. package/src/charts/reference-line.svelte +19 -19
  94. package/src/charts/reveal.svelte +1 -1
  95. package/src/charts/ring-chart.css +2 -4
  96. package/src/charts/ring-chart.svelte +1 -1
  97. package/src/charts/sparkline.svelte +5 -2
  98. package/src/charts/stat-card.svelte +1 -1
  99. package/src/charts/time-series-chart.css +2 -5
  100. package/src/charts/trend-pill.css +2 -4
  101. package/src/charts/trend-pill.svelte +1 -1
  102. package/src/components/copy/copy-button.svelte +93 -98
  103. package/src/components/copy/copy-button.test.ts +10 -2
  104. package/src/components/data-table/infinite-scroll.svelte +42 -42
  105. package/src/components/data-table/pagination.svelte +34 -22
  106. package/src/components/data-table/row-arrow.svelte +19 -16
  107. package/src/components/data-table/row-context-menu.svelte +52 -52
  108. package/src/components/data-table/sortable-head.svelte +25 -25
  109. package/src/components/data-table/table-skeleton.svelte +41 -36
  110. package/src/components/docs/doc-page.svelte +18 -9
  111. package/src/components/docs/on-this-page.svelte +16 -3
  112. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  113. package/src/components/field-row/field-row.svelte +10 -8
  114. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  115. package/src/components/icon/icon.svelte +14 -14
  116. package/src/components/language-flag/language-flag.svelte +3 -1
  117. package/src/components/list-page/detail-empty.svelte +34 -34
  118. package/src/components/list-page/detail-header.svelte +55 -47
  119. package/src/components/list-page/detail-panel.svelte +51 -59
  120. package/src/components/list-page/detail-row.svelte +19 -19
  121. package/src/components/list-page/detail-stack.svelte +21 -21
  122. package/src/components/list-page/empty-state.svelte +30 -30
  123. package/src/components/list-page/filter-item.svelte +20 -20
  124. package/src/components/list-page/filter-popover.svelte +60 -55
  125. package/src/components/list-page/grouped-list.svelte +4 -2
  126. package/src/components/list-page/index.ts +8 -4
  127. package/src/components/list-page/list-page-shell.svelte +2 -5
  128. package/src/components/list-page/master-detail.svelte +87 -88
  129. package/src/components/list-page/notice-bar.svelte +38 -38
  130. package/src/components/list-page/prev-next.svelte +63 -63
  131. package/src/components/list-page/rail-body.svelte +18 -18
  132. package/src/components/list-page/rail-empty.svelte +23 -30
  133. package/src/components/list-page/rail-head.svelte +24 -24
  134. package/src/components/list-page/rail-row.svelte +77 -73
  135. package/src/components/list-page/section-host.ts +2 -3
  136. package/src/components/locale-switch/locale-switch.svelte +41 -41
  137. package/src/components/mark/mark.svelte +34 -38
  138. package/src/components/open-in/index.ts +1 -5
  139. package/src/components/open-in/open-in.svelte +40 -40
  140. package/src/components/picker/mark-picker.svelte +49 -49
  141. package/src/components/picker/picker.svelte +5 -3
  142. package/src/components/section-header/index.ts +1 -0
  143. package/src/components/section-header/section-header.svelte +1 -1
  144. package/src/components/section-header/section-header.test.ts +7 -1
  145. package/src/components/section-header/section-label.svelte +39 -0
  146. package/src/components/segmented/animated-tabs.svelte +31 -2
  147. package/src/components/segmented/collapse.test.ts +2 -1
  148. package/src/components/segmented/index.ts +2 -0
  149. package/src/components/segmented/segment.svelte +52 -49
  150. package/src/components/segmented/segmented-filter.svelte +79 -80
  151. package/src/components/segmented/segmented-nav.svelte +34 -40
  152. package/src/components/segmented/segmented-overflow.svelte +53 -50
  153. package/src/components/segmented/segmented-track.svelte +62 -46
  154. package/src/components/segmented/segmented.ts +2 -3
  155. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  156. package/src/components/svg/svg.svelte +4 -4
  157. package/src/components/table-view/table-view.svelte +17 -2
  158. package/src/components/table-view/table-view.test.ts +7 -1
  159. package/src/components/table-view/types.ts +11 -11
  160. package/src/components/tags-input/index.ts +13 -0
  161. package/src/components/tags-input/tags-input.svelte +305 -0
  162. package/src/components/tags-input/tags.test.ts +64 -0
  163. package/src/components/tags-input/tags.ts +142 -0
  164. package/src/components/theme/index.ts +2 -4
  165. package/src/components/theme/theme-provider.svelte +1 -3
  166. package/src/components/toolbar/index.ts +2 -0
  167. package/src/components/{list-page → toolbar}/toolbar-search.svelte +25 -25
  168. package/src/components/toolbar/toolbar.svelte +13 -9
  169. package/src/components/toolbar/toolbar.test.ts +10 -1
  170. package/src/design/index.css +21 -78
  171. package/src/design/polish.css +26 -35
  172. package/src/editor/editor-toolbar.svelte +104 -99
  173. package/src/editor/editor.svelte +296 -266
  174. package/src/editor/image-toolbar.svelte +20 -22
  175. package/src/editor/index.ts +1 -4
  176. package/src/editor/inline-editor.svelte +280 -238
  177. package/src/editor/markdown.ts +15 -15
  178. package/src/editor/renderer.svelte +71 -71
  179. package/src/editor/slash-menu.svelte +24 -27
  180. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  181. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  182. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  183. package/src/effects/magic-card/MagicCard.css +14 -7
  184. package/src/effects/marquee/Marquee.svelte +3 -5
  185. package/src/effects/reveal/observe.ts +0 -3
  186. package/src/effects/spotlight/Spotlight.svelte +1 -1
  187. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  188. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  189. package/src/gallery.test.ts +14 -4
  190. package/src/inbox/README.md +27 -0
  191. package/src/inbox/inbox.test.ts +214 -0
  192. package/src/inbox/index.ts +31 -0
  193. package/src/inbox/logic.ts +169 -0
  194. package/src/inbox/thread-inbox.svelte +439 -0
  195. package/src/inbox/types.ts +161 -0
  196. package/src/lib/theme-transition.ts +2 -4
  197. package/src/lib/tone.ts +2 -4
  198. package/src/lib/utils.ts +2 -2
  199. package/src/modules.test.ts +105 -0
  200. package/src/onboarding/define-tours.test.ts +55 -40
  201. package/src/onboarding/define-tours.ts +23 -23
  202. package/src/onboarding/demo.svelte +24 -5
  203. package/src/onboarding/index.ts +1 -8
  204. package/src/onboarding/onboarding-provider.svelte +19 -7
  205. package/src/onboarding/onboarding.svelte.ts +16 -16
  206. package/src/onboarding/pick.test.ts +61 -52
  207. package/src/onboarding/pick.ts +16 -13
  208. package/src/onboarding/stores.test.ts +80 -80
  209. package/src/onboarding/stores.ts +41 -39
  210. package/src/onboarding/tour-card.svelte +30 -5
  211. package/src/onboarding/tour-runner.svelte +12 -3
  212. package/src/onboarding/types.ts +20 -12
  213. package/src/onboarding/wait.ts +22 -22
  214. package/src/panel/README.md +5 -0
  215. package/src/panel/index.ts +2 -0
  216. package/src/panel/side-panel.svelte +194 -0
  217. package/src/record/README.md +3 -0
  218. package/src/record/demo-data.ts +352 -0
  219. package/src/record/field-section.svelte +57 -0
  220. package/src/record/fields.ts +194 -0
  221. package/src/record/format.ts +94 -0
  222. package/src/{tasks/record → record}/index.ts +49 -35
  223. package/src/{tasks/record → record}/info-list.svelte +145 -114
  224. package/src/record/kinds.ts +42 -0
  225. package/src/record/logic.ts +326 -0
  226. package/src/record/next-up.svelte +81 -0
  227. package/src/{tasks/record → record}/project-list.svelte +56 -50
  228. package/src/{tasks/record → record}/record-demo.svelte +130 -110
  229. package/src/{tasks/record → record}/record-header.svelte +37 -28
  230. package/src/{tasks/record → record}/record-shell.svelte +54 -54
  231. package/src/record/record-stats.svelte +48 -0
  232. package/src/record/record-timeline.svelte +251 -0
  233. package/src/record/record.test.ts +471 -0
  234. package/src/{tasks/record → record}/steps-rail.svelte +225 -216
  235. package/src/record/types.ts +299 -0
  236. package/src/record/where-we-are.svelte +125 -0
  237. package/src/shadcn/button/button.svelte +1 -1
  238. package/src/shadcn/card/card-title.svelte +1 -5
  239. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  240. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  241. package/src/shadcn/table/table-cell.svelte +2 -3
  242. package/src/shadcn/utilities.css +3 -8
  243. package/src/slides/Controls.svelte +150 -124
  244. package/src/slides/Deck.svelte +243 -238
  245. package/src/slides/Fragment.svelte +64 -57
  246. package/src/slides/Overview.svelte +106 -101
  247. package/src/slides/Presenter.svelte +300 -266
  248. package/src/slides/Slide.svelte +185 -170
  249. package/src/slides/SlideList.svelte +16 -12
  250. package/src/slides/ambient.d.ts +1 -1
  251. package/src/slides/bridges.ts +42 -23
  252. package/src/slides/deck-controller.svelte.ts +47 -35
  253. package/src/slides/demo-slides.svelte +330 -306
  254. package/src/slides/demo.svelte +164 -160
  255. package/src/slides/export/capture.ts +20 -16
  256. package/src/slides/export/ffmpeg.ts +43 -38
  257. package/src/slides/export/index.ts +5 -5
  258. package/src/slides/export/launch.ts +3 -1
  259. package/src/slides/export/pdf.ts +30 -27
  260. package/src/slides/export/types.ts +10 -8
  261. package/src/slides/index.ts +11 -11
  262. package/src/slides/labels.ts +22 -19
  263. package/src/slides/layout.ts +9 -2
  264. package/src/slides/model.ts +4 -4
  265. package/src/slides/navigation.ts +43 -31
  266. package/src/slides/theme.css +5 -7
  267. package/src/slides/types.ts +9 -10
  268. package/src/status/README.md +18 -0
  269. package/src/status/define-statuses.ts +25 -0
  270. package/src/status/index.ts +11 -0
  271. package/src/{tasks → status}/status-badge.svelte +3 -1
  272. package/src/{tasks → status}/status-dot.svelte +1 -3
  273. package/src/{tasks → status}/status-icon.svelte +1 -1
  274. package/src/status/status-types.ts +40 -0
  275. package/src/tasks/README.md +1 -1
  276. package/src/tasks/board-order.ts +2 -4
  277. package/src/tasks/demo.svelte +192 -34
  278. package/src/tasks/detail/detail-demo.svelte +75 -15
  279. package/src/tasks/detail/detail-types.ts +45 -45
  280. package/src/tasks/detail/task-detail.svelte +40 -10
  281. package/src/tasks/detail/task-property.svelte +8 -2
  282. package/src/tasks/detail/task-thread.svelte +53 -12
  283. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  284. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  285. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  286. package/src/tasks/gantt/gantt-header.svelte +9 -3
  287. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  288. package/src/tasks/gantt/gantt.css +2 -1
  289. package/src/tasks/gantt/gantt.test.ts +4 -2
  290. package/src/tasks/gantt/logic.test.ts +110 -20
  291. package/src/tasks/gantt/logic.ts +42 -6
  292. package/src/tasks/inbox/inbox-card.svelte +15 -7
  293. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  294. package/src/tasks/inbox/inbox-logic.ts +6 -40
  295. package/src/tasks/inbox/inbox-statuses.ts +24 -24
  296. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  297. package/src/tasks/inbox/inbox-types.ts +54 -58
  298. package/src/tasks/inbox/index.ts +22 -21
  299. package/src/tasks/index.ts +52 -53
  300. package/src/tasks/kanban-board.svelte +16 -12
  301. package/src/tasks/kanban-types.ts +2 -3
  302. package/src/tasks/presets/sales.ts +7 -7
  303. package/src/tasks/task-gantt.svelte +28 -8
  304. package/src/tasks/task-list.svelte +41 -17
  305. package/src/tasks/time/duration.ts +40 -40
  306. package/src/tasks/time/time-chip.svelte +9 -2
  307. package/src/tasks/time/time-dot.svelte +1 -1
  308. package/src/tasks/time/time-ring.svelte +11 -3
  309. package/src/tasks/time/time-tracker.svelte +57 -15
  310. package/src/tasks/time/time-types.ts +57 -57
  311. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  312. package/src/thread/README.md +31 -0
  313. package/src/thread/index.ts +12 -0
  314. package/src/thread/logic.ts +61 -0
  315. package/src/thread/thread.svelte +432 -0
  316. package/src/thread/thread.test.ts +73 -0
  317. package/src/thread/types.ts +93 -0
  318. package/src/tokens/craft.css +17 -53
  319. package/src/tokens/index.css +3 -13
  320. package/src/tokens/palette.css +13 -11
  321. package/src/tokens/primitives.css +10 -23
  322. package/src/components/list-page/section-label.svelte +0 -39
  323. package/src/tasks/define-statuses.ts +0 -32
  324. package/src/tasks/inbox/inbox.svelte +0 -203
  325. package/src/tasks/record/demo-data.ts +0 -286
  326. package/src/tasks/record/field-section.svelte +0 -53
  327. package/src/tasks/record/fields.ts +0 -142
  328. package/src/tasks/record/format.ts +0 -87
  329. package/src/tasks/record/kinds.ts +0 -42
  330. package/src/tasks/record/logic.ts +0 -324
  331. package/src/tasks/record/next-up.svelte +0 -70
  332. package/src/tasks/record/record-stats.svelte +0 -48
  333. package/src/tasks/record/record-timeline.svelte +0 -231
  334. package/src/tasks/record/record.test.ts +0 -350
  335. package/src/tasks/record/types.ts +0 -280
  336. package/src/tasks/record/where-we-are.svelte +0 -121
  337. package/src/tasks/side-panel.svelte +0 -193
  338. package/src/tasks/status-types.ts +0 -40
  339. /package/src/components/{list-page → toolbar}/toolbar-trigger.ts +0 -0
  340. /package/src/{tasks → panel}/side-panel-types.ts +0 -0
  341. /package/src/{tasks/record → record}/gutter.ts +0 -0
@@ -0,0 +1,105 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, it, vi } from "vite-plus/test";
5
+ import * as status from "./status/index.js";
6
+ import * as panel from "./panel/index.js";
7
+ import * as record from "./record/index.js";
8
+ import * as inbox from "./inbox/index.js";
9
+ import * as thread from "./thread/index.js";
10
+ import type {
11
+ PanelStat as FromTasksPanelStat,
12
+ SidePanelWidth as FromTasksWidth,
13
+ StatusDef as FromTasksStatusDef,
14
+ StatusDotVariant as FromTasksVariant,
15
+ StatusId as FromTasksId,
16
+ StatusMap as FromTasksMap,
17
+ Statuses as FromTasksStatuses,
18
+ } from "./tasks/index.js";
19
+ import type { PanelStat, SidePanelWidth } from "./panel/index.js";
20
+ import type {
21
+ StatusDef,
22
+ StatusDotVariant,
23
+ StatusId,
24
+ StatusMap,
25
+ Statuses,
26
+ } from "./status/index.js";
27
+
28
+ // Both spellings of every moved type have to be the same type.
29
+ type Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
30
+ const sameTypes: [
31
+ Same<PanelStat, FromTasksPanelStat>,
32
+ Same<SidePanelWidth, FromTasksWidth>,
33
+ Same<StatusDef, FromTasksStatusDef>,
34
+ Same<StatusDotVariant, FromTasksVariant>,
35
+ Same<StatusId<{ a: StatusDef }>, FromTasksId<{ a: StatusDef }>>,
36
+ Same<StatusMap, FromTasksMap>,
37
+ Same<Statuses, FromTasksStatuses>,
38
+ ] = [true, true, true, true, true, true, true];
39
+
40
+ const pkg = JSON.parse(
41
+ readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8"),
42
+ ) as {
43
+ exports: Record<string, string | Record<string, string>>;
44
+ };
45
+
46
+ describe("module split", () => {
47
+ it("keeps every moved name on tasks, the same object as on its new module", async () => {
48
+ // The kanban board reads matchMedia on import, and jsdom has none.
49
+ vi.stubGlobal("matchMedia", () => ({
50
+ matches: false,
51
+ addEventListener() {},
52
+ removeEventListener() {},
53
+ }));
54
+ const tasks = await import("./tasks/index.js");
55
+ expect(sameTypes).toHaveLength(7);
56
+ expect(tasks.StatusBadge).toBe(status.StatusBadge);
57
+ expect(tasks.StatusDot).toBe(status.StatusDot);
58
+ expect(tasks.StatusIcon).toBe(status.StatusIcon);
59
+ expect(tasks.defineStatuses).toBe(status.defineStatuses);
60
+ expect(tasks.StatusBadge).toBeDefined();
61
+ expect(tasks.SidePanel).toBe(panel.SidePanel);
62
+ });
63
+
64
+ it("holds the record page's names", () => {
65
+ expect(record.RecordShell).toBeDefined();
66
+ expect(record.DEFAULT_RECORD_LABELS).toBeDefined();
67
+ });
68
+
69
+ it("names what the new modules export", () => {
70
+ expect(thread.Thread).toBeDefined();
71
+ expect(inbox.ThreadInbox).toBeDefined();
72
+ expect(inbox.InboxCard).toBeDefined();
73
+ expect(inbox.threadsOf).toBeTypeOf("function");
74
+ });
75
+
76
+ it("resolves every export path to a file", () => {
77
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
78
+ const missing: string[] = [];
79
+ for (const [name, target] of Object.entries(pkg.exports)) {
80
+ const path = typeof target === "string" ? target : (target.default ?? target.svelte);
81
+ if (path.includes("*") || path.endsWith(".css")) continue;
82
+ if (!existsSync(join(root, path))) missing.push(`${name} -> ${path}`);
83
+ }
84
+ expect(missing).toEqual([]);
85
+ });
86
+
87
+ it("keeps the old subpaths and adds the new ones", () => {
88
+ for (const name of [
89
+ "./tasks",
90
+ "./tasks/status",
91
+ "./tasks/status-dot",
92
+ "./tasks/statuses",
93
+ "./tasks/side-panel",
94
+ "./tasks/record",
95
+ "./tasks/inbox",
96
+ "./status",
97
+ "./panel",
98
+ "./record",
99
+ "./thread",
100
+ "./inbox",
101
+ ]) {
102
+ expect(pkg.exports[name], name).toBeDefined();
103
+ }
104
+ });
105
+ });
@@ -2,49 +2,64 @@ import { describe, expect, it } from "vite-plus/test";
2
2
  import { defineTours } from "./define-tours.js";
3
3
  import type { Step, Tour } from "./types.js";
4
4
 
5
- const step = (n: number): Step => ({ target: `[data-tour="s${n}"]`, title: `Title ${n}`, body: `Body ${n}` });
5
+ const step = (n: number): Step => ({
6
+ target: `[data-tour="s${n}"]`,
7
+ title: `Title ${n}`,
8
+ body: `Body ${n}`,
9
+ });
6
10
  const steps = (count: number) => Array.from({ length: count }, (_, i) => step(i + 1));
7
11
  const board = (over: Partial<Extract<Tour, { kind: "steps" }>> = {}): Tour => ({
8
- id: "board",
9
- route: "/(app)/projects",
10
- kind: "steps",
11
- steps: steps(3),
12
- ...over,
12
+ id: "board",
13
+ route: "/(app)/projects",
14
+ kind: "steps",
15
+ steps: steps(3),
16
+ ...over,
13
17
  });
14
18
 
15
19
  describe("defineTours", () => {
16
- it("returns the tours it was given", () => {
17
- const list = [board(), { id: "home", route: "/(app)", kind: "intro", title: "Hi", body: "Welcome." } as const];
18
- expect(defineTours(list)).toBe(list);
19
- });
20
-
21
- it("rejects a repeated id", () => {
22
- expect(() => defineTours([board(), board({ route: "/(app)/other" })])).toThrow(/"board".*used twice/);
23
- });
24
-
25
- it("takes 1 to 6 steps", () => {
26
- expect(() => defineTours([board({ steps: steps(1) })])).not.toThrow();
27
- expect(() => defineTours([board({ steps: steps(6) })])).not.toThrow();
28
- expect(() => defineTours([board({ steps: [] })])).toThrow(/0 steps/);
29
- expect(() => defineTours([board({ steps: steps(7) })])).toThrow(/7 steps/);
30
- });
31
-
32
- it("rejects a route that isn't a route id", () => {
33
- expect(() => defineTours([board({ route: "projects" })])).toThrow(/not a route id/);
34
- });
35
-
36
- it("rejects a bad version", () => {
37
- expect(() => defineTours([board({ version: 0 })])).toThrow(/version/);
38
- expect(() => defineTours([board({ version: 1.5 })])).toThrow(/version/);
39
- expect(() => defineTours([board({ version: 2 })])).not.toThrow();
40
- });
41
-
42
- it("rejects a step with no target or no words", () => {
43
- expect(() => defineTours([board({ steps: [{ ...step(1), target: " " }] })])).toThrow(/step 1 has no target/);
44
- expect(() => defineTours([board({ steps: [{ ...step(1), body: "" }] })])).toThrow(/title and a body/);
45
- });
46
-
47
- it("rejects an intro with no title", () => {
48
- expect(() => defineTours([{ id: "home", route: "/(app)", kind: "intro", title: "", body: "x" }])).toThrow(/intro/);
49
- });
20
+ it("returns the tours it was given", () => {
21
+ const list = [
22
+ board(),
23
+ { id: "home", route: "/(app)", kind: "intro", title: "Hi", body: "Welcome." } as const,
24
+ ];
25
+ expect(defineTours(list)).toBe(list);
26
+ });
27
+
28
+ it("rejects a repeated id", () => {
29
+ expect(() => defineTours([board(), board({ route: "/(app)/other" })])).toThrow(
30
+ /"board".*used twice/,
31
+ );
32
+ });
33
+
34
+ it("takes 1 to 6 steps", () => {
35
+ expect(() => defineTours([board({ steps: steps(1) })])).not.toThrow();
36
+ expect(() => defineTours([board({ steps: steps(6) })])).not.toThrow();
37
+ expect(() => defineTours([board({ steps: [] })])).toThrow(/0 steps/);
38
+ expect(() => defineTours([board({ steps: steps(7) })])).toThrow(/7 steps/);
39
+ });
40
+
41
+ it("rejects a route that isn't a route id", () => {
42
+ expect(() => defineTours([board({ route: "projects" })])).toThrow(/not a route id/);
43
+ });
44
+
45
+ it("rejects a bad version", () => {
46
+ expect(() => defineTours([board({ version: 0 })])).toThrow(/version/);
47
+ expect(() => defineTours([board({ version: 1.5 })])).toThrow(/version/);
48
+ expect(() => defineTours([board({ version: 2 })])).not.toThrow();
49
+ });
50
+
51
+ it("rejects a step with no target or no words", () => {
52
+ expect(() => defineTours([board({ steps: [{ ...step(1), target: " " }] })])).toThrow(
53
+ /step 1 has no target/,
54
+ );
55
+ expect(() => defineTours([board({ steps: [{ ...step(1), body: "" }] })])).toThrow(
56
+ /title and a body/,
57
+ );
58
+ });
59
+
60
+ it("rejects an intro with no title", () => {
61
+ expect(() =>
62
+ defineTours([{ id: "home", route: "/(app)", kind: "intro", title: "", body: "x" }]),
63
+ ).toThrow(/intro/);
64
+ });
50
65
  });
@@ -1,31 +1,31 @@
1
1
  import { MAX_STEPS, type Tour } from "./types.js";
2
2
 
3
3
  function fail(id: string, problem: string): never {
4
- throw new Error(`tour "${id}": ${problem}`);
4
+ throw new Error(`tour "${id}": ${problem}`);
5
5
  }
6
6
 
7
7
  /** Returns the tours as written, after checking what a typo would hide until a user hits it. */
8
8
  export function defineTours<const T extends readonly Tour[]>(tours: T): T {
9
- const ids = new Set<string>();
10
- for (const tour of tours) {
11
- const { id } = tour;
12
- if (!id || !id.trim()) throw new Error("a tour needs an id");
13
- if (ids.has(id)) fail(id, "its id is used twice");
14
- ids.add(id);
15
- if (!tour.route.startsWith("/")) fail(id, `route "${tour.route}" is not a route id`);
16
- const version = tour.version ?? 1;
17
- if (!Number.isInteger(version) || version < 1) fail(id, "version is a whole number from 1");
18
- if (tour.kind === "intro") {
19
- if (!tour.title || !tour.body) fail(id, "an intro needs a title and a body");
20
- continue;
21
- }
22
- if (tour.steps.length < 1 || tour.steps.length > MAX_STEPS) {
23
- fail(id, `has ${tour.steps.length} steps, and a tour takes 1 to ${MAX_STEPS}`);
24
- }
25
- tour.steps.forEach((step, i) => {
26
- if (!step.target.trim()) fail(id, `step ${i + 1} has no target`);
27
- if (!step.title || !step.body) fail(id, `step ${i + 1} needs a title and a body`);
28
- });
29
- }
30
- return tours;
9
+ const ids = new Set<string>();
10
+ for (const tour of tours) {
11
+ const { id } = tour;
12
+ if (!id || !id.trim()) throw new Error("a tour needs an id");
13
+ if (ids.has(id)) fail(id, "its id is used twice");
14
+ ids.add(id);
15
+ if (!tour.route.startsWith("/")) fail(id, `route "${tour.route}" is not a route id`);
16
+ const version = tour.version ?? 1;
17
+ if (!Number.isInteger(version) || version < 1) fail(id, "version is a whole number from 1");
18
+ if (tour.kind === "intro") {
19
+ if (!tour.title || !tour.body) fail(id, "an intro needs a title and a body");
20
+ continue;
21
+ }
22
+ if (tour.steps.length < 1 || tour.steps.length > MAX_STEPS) {
23
+ fail(id, `has ${tour.steps.length} steps, and a tour takes 1 to ${MAX_STEPS}`);
24
+ }
25
+ tour.steps.forEach((step, i) => {
26
+ if (!step.target.trim()) fail(id, `step ${i + 1} has no target`);
27
+ if (!step.title || !step.body) fail(id, `step ${i + 1} needs a title and a body`);
28
+ });
29
+ }
30
+ return tours;
31
31
  }
@@ -20,9 +20,24 @@
20
20
  route: "/demo/board",
21
21
  kind: "steps",
22
22
  steps: [
23
- { target: '[data-tour="demo-new"]', title: "Start here", body: "A new project takes a name and a client. That's all.", placement: "bottom-end" },
24
- { target: '[data-tour="demo-filter"]', title: "Narrow it down", body: "Late shows what slipped. Mine shows what's yours.", placement: "bottom-start" },
25
- { target: '[data-tour="demo-list"]', title: "Open a row", body: "Click a project to see its tasks, files and notes.", placement: "top" },
23
+ {
24
+ target: '[data-tour="demo-new"]',
25
+ title: "Start here",
26
+ body: "A new project takes a name and a client. That's all.",
27
+ placement: "bottom-end",
28
+ },
29
+ {
30
+ target: '[data-tour="demo-filter"]',
31
+ title: "Narrow it down",
32
+ body: "Late shows what slipped. Mine shows what's yours.",
33
+ placement: "bottom-start",
34
+ },
35
+ {
36
+ target: '[data-tour="demo-list"]',
37
+ title: "Open a row",
38
+ body: "Click a project to see its tasks, files and notes.",
39
+ placement: "top",
40
+ },
26
41
  ],
27
42
  },
28
43
  ]);
@@ -55,8 +70,12 @@
55
70
 
56
71
  <div class="flex flex-col gap-4">
57
72
  <div class="flex flex-wrap gap-2">
58
- <Button variant="outline" size="sm" onclick={() => start("/demo/board")}>Show the page tour</Button>
59
- <Button variant="outline" size="sm" onclick={() => start("/demo/intro")}>Show the intro card</Button>
73
+ <Button variant="outline" size="sm" onclick={() => start("/demo/board")}
74
+ >Show the page tour</Button
75
+ >
76
+ <Button variant="outline" size="sm" onclick={() => start("/demo/intro")}
77
+ >Show the intro card</Button
78
+ >
60
79
  </div>
61
80
 
62
81
  <div class="bg-card ring-foreground/10 flex flex-col gap-4 rounded-xl p-4 ring-1">
@@ -2,11 +2,4 @@ export { default as OnboardingProvider } from "./onboarding-provider.svelte";
2
2
  export { onboarding } from "./onboarding.svelte.js";
3
3
  export { defineTours } from "./define-tours.js";
4
4
  export { localStorageStore, memoryStore } from "./stores.js";
5
- export type {
6
- Outcome,
7
- Placement,
8
- SeenStore,
9
- Step,
10
- Strings,
11
- Tour,
12
- } from "./types.js";
5
+ export type { Outcome, Placement, SeenStore, Step, Strings, Tour } from "./types.js";
@@ -2,7 +2,13 @@
2
2
  import { tryCatchAsync } from "../lib/result.js";
3
3
  import { untrack } from "svelte";
4
4
  import { onboarding } from "./onboarding.svelte.js";
5
- import { keepFoundSteps, pickForReplay, pickTour, toursForRoute, type SeenMap } from "./pick.js";
5
+ import {
6
+ keepFoundSteps,
7
+ pickForReplay,
8
+ pickTour,
9
+ toursForRoute,
10
+ type SeenMap,
11
+ } from "./pick.js";
6
12
  import TourRunner from "./tour-runner.svelte";
7
13
  import type { Outcome, SeenStore, Step, Strings, Tour } from "./types.js";
8
14
  import { waitForElement } from "./wait.js";
@@ -71,17 +77,23 @@
71
77
  controller = mine;
72
78
  void loadSeen().then((map) => {
73
79
  if (!map || mine.signal.aborted) return;
74
- const tour = pickTour(untrack(() => tours), id, map);
80
+ const tour = pickTour(
81
+ untrack(() => tours),
82
+ id,
83
+ map,
84
+ );
75
85
  if (tour) void launch(tour, false, mine.signal);
76
86
  });
77
87
  return () => mine.abort();
78
88
  });
79
89
 
80
- $effect(() => onboarding.bind(async (tourId) => {
81
- const tour = pickForReplay(tours, routeId, tourId);
82
- if (!tour || !controller || controller.signal.aborted) return false;
83
- return launch(tour, true, controller.signal);
84
- }));
90
+ $effect(() =>
91
+ onboarding.bind(async (tourId) => {
92
+ const tour = pickForReplay(tours, routeId, tourId);
93
+ if (!tour || !controller || controller.signal.aborted) return false;
94
+ return launch(tour, true, controller.signal);
95
+ }),
96
+ );
85
97
 
86
98
  $effect(() => {
87
99
  onboarding.running = active !== null;
@@ -1,24 +1,24 @@
1
1
  type Replay = (tourId?: string) => Promise<boolean>;
2
2
 
3
3
  class Onboarding {
4
- /** True when the current page has a tour, so a menu item can hide itself. */
5
- hasTour = $state(false);
6
- /** True while a tour is on screen. */
7
- running = $state(false);
8
- #replay: Replay | null = null;
4
+ /** True when the current page has a tour, so a menu item can hide itself. */
5
+ hasTour = $state(false);
6
+ /** True while a tour is on screen. */
7
+ running = $state(false);
8
+ #replay: Replay | null = null;
9
9
 
10
- /** The provider registers itself here. Returns the undo for cleanup. */
11
- bind(replay: Replay): () => void {
12
- this.#replay = replay;
13
- return () => {
14
- if (this.#replay === replay) this.#replay = null;
15
- };
16
- }
10
+ /** The provider registers itself here. Returns the undo for cleanup. */
11
+ bind(replay: Replay): () => void {
12
+ this.#replay = replay;
13
+ return () => {
14
+ if (this.#replay === replay) this.#replay = null;
15
+ };
16
+ }
17
17
 
18
- /** Restarts this page's tour, or the one named. Writes no seen-state. False when nothing started. */
19
- replay(tourId?: string): Promise<boolean> {
20
- return this.#replay ? this.#replay(tourId) : Promise.resolve(false);
21
- }
18
+ /** Restarts this page's tour, or the one named. Writes no seen-state. False when nothing started. */
19
+ replay(tourId?: string): Promise<boolean> {
20
+ return this.#replay ? this.#replay(tourId) : Promise.resolve(false);
21
+ }
22
22
  }
23
23
 
24
24
  export const onboarding = new Onboarding();
@@ -1,79 +1,88 @@
1
1
  import { describe, expect, it } from "vite-plus/test";
2
- import { isSeen, keepFoundSteps, pickForReplay, pickTour, toursForRoute } from "./pick.js";
2
+ import {
3
+ isSeen,
4
+ keepFoundSteps,
5
+ pickForReplay,
6
+ pickTour,
7
+ toursForRoute,
8
+ } from "./pick.js";
3
9
  import type { Tour } from "./types.js";
4
10
 
5
11
  const step = { target: "#a", title: "A", body: "a" };
6
12
  const tours: Tour[] = [
7
- { id: "home", route: "/(app)", kind: "intro", title: "Hi", body: "Welcome." },
8
- { id: "board", route: "/(app)/projects", kind: "steps", steps: [step] },
9
- { id: "board-extra", route: "/(app)/projects", kind: "steps", steps: [step], version: 2 },
10
- { id: "record", route: "/(app)/projects/[id]", kind: "steps", steps: [step] },
13
+ { id: "home", route: "/(app)", kind: "intro", title: "Hi", body: "Welcome." },
14
+ { id: "board", route: "/(app)/projects", kind: "steps", steps: [step] },
15
+ { id: "board-extra", route: "/(app)/projects", kind: "steps", steps: [step], version: 2 },
16
+ { id: "record", route: "/(app)/projects/[id]", kind: "steps", steps: [step] },
11
17
  ];
12
18
 
13
19
  describe("isSeen", () => {
14
- it("counts a tour as seen once the stored version reaches its own", () => {
15
- expect(isSeen(tours[1], {})).toBe(false);
16
- expect(isSeen(tours[1], { board: 1 })).toBe(true);
17
- expect(isSeen(tours[2], { "board-extra": 1 })).toBe(false);
18
- expect(isSeen(tours[2], { "board-extra": 2 })).toBe(true);
19
- });
20
+ it("counts a tour as seen once the stored version reaches its own", () => {
21
+ expect(isSeen(tours[1], {})).toBe(false);
22
+ expect(isSeen(tours[1], { board: 1 })).toBe(true);
23
+ expect(isSeen(tours[2], { "board-extra": 1 })).toBe(false);
24
+ expect(isSeen(tours[2], { "board-extra": 2 })).toBe(true);
25
+ });
20
26
 
21
- it("shows a tour again after its version is bumped", () => {
22
- const v1: Tour = { id: "t", route: "/", kind: "steps", steps: [step] };
23
- const v2: Tour = { ...v1, version: 2 };
24
- expect(isSeen(v1, { t: 1 })).toBe(true);
25
- expect(isSeen(v2, { t: 1 })).toBe(false);
26
- });
27
+ it("shows a tour again after its version is bumped", () => {
28
+ const v1: Tour = { id: "t", route: "/", kind: "steps", steps: [step] };
29
+ const v2: Tour = { ...v1, version: 2 };
30
+ expect(isSeen(v1, { t: 1 })).toBe(true);
31
+ expect(isSeen(v2, { t: 1 })).toBe(false);
32
+ });
27
33
  });
28
34
 
29
35
  describe("pickTour", () => {
30
- it("matches the route id exactly, so a params route never borrows its parent's tour", () => {
31
- expect(pickTour(tours, "/(app)", {})?.id).toBe("home");
32
- expect(pickTour(tours, "/(app)/projects/[id]", {})?.id).toBe("record");
33
- expect(pickTour(tours, "/(app)/settings", {})).toBeUndefined();
34
- });
36
+ it("matches the route id exactly, so a params route never borrows its parent's tour", () => {
37
+ expect(pickTour(tours, "/(app)", {})?.id).toBe("home");
38
+ expect(pickTour(tours, "/(app)/projects/[id]", {})?.id).toBe("record");
39
+ expect(pickTour(tours, "/(app)/settings", {})).toBeUndefined();
40
+ });
35
41
 
36
- it("takes the first unseen tour on a route, one at a time", () => {
37
- expect(pickTour(tours, "/(app)/projects", {})?.id).toBe("board");
38
- expect(pickTour(tours, "/(app)/projects", { board: 1 })?.id).toBe("board-extra");
39
- expect(pickTour(tours, "/(app)/projects", { board: 1, "board-extra": 2 })).toBeUndefined();
40
- });
42
+ it("takes the first unseen tour on a route, one at a time", () => {
43
+ expect(pickTour(tours, "/(app)/projects", {})?.id).toBe("board");
44
+ expect(pickTour(tours, "/(app)/projects", { board: 1 })?.id).toBe("board-extra");
45
+ expect(pickTour(tours, "/(app)/projects", { board: 1, "board-extra": 2 })).toBeUndefined();
46
+ });
41
47
 
42
- it("picks nothing without a route", () => {
43
- expect(pickTour(tours, null, {})).toBeUndefined();
44
- expect(pickTour(tours, undefined, {})).toBeUndefined();
45
- });
48
+ it("picks nothing without a route", () => {
49
+ expect(pickTour(tours, null, {})).toBeUndefined();
50
+ expect(pickTour(tours, undefined, {})).toBeUndefined();
51
+ });
46
52
  });
47
53
 
48
54
  describe("toursForRoute", () => {
49
- it("lists every tour on the route in definition order", () => {
50
- expect(toursForRoute(tours, "/(app)/projects").map((t) => t.id)).toEqual(["board", "board-extra"]);
51
- });
55
+ it("lists every tour on the route in definition order", () => {
56
+ expect(toursForRoute(tours, "/(app)/projects").map((t) => t.id)).toEqual([
57
+ "board",
58
+ "board-extra",
59
+ ]);
60
+ });
52
61
  });
53
62
 
54
63
  describe("pickForReplay", () => {
55
- it("restarts the page's first tour even when it was seen", () => {
56
- expect(pickForReplay(tours, "/(app)/projects")?.id).toBe("board");
57
- });
64
+ it("restarts the page's first tour even when it was seen", () => {
65
+ expect(pickForReplay(tours, "/(app)/projects")?.id).toBe("board");
66
+ });
58
67
 
59
- it("finds a tour by id on any route", () => {
60
- expect(pickForReplay(tours, "/(app)", "record")?.id).toBe("record");
61
- expect(pickForReplay(tours, "/(app)", "missing")).toBeUndefined();
62
- });
68
+ it("finds a tour by id on any route", () => {
69
+ expect(pickForReplay(tours, "/(app)", "record")?.id).toBe("record");
70
+ expect(pickForReplay(tours, "/(app)", "missing")).toBeUndefined();
71
+ });
63
72
  });
64
73
 
65
74
  describe("keepFoundSteps", () => {
66
- const steps = [
67
- { ...step, target: "#a" },
68
- { ...step, target: "#b" },
69
- { ...step, target: "#c" },
70
- ];
75
+ const steps = [
76
+ { ...step, target: "#a" },
77
+ { ...step, target: "#b" },
78
+ { ...step, target: "#c" },
79
+ ];
71
80
 
72
- it("drops the steps whose target isn't on the page", () => {
73
- expect(keepFoundSteps(steps, (s) => s !== "#b").map((s) => s.target)).toEqual(["#a", "#c"]);
74
- });
81
+ it("drops the steps whose target isn't on the page", () => {
82
+ expect(keepFoundSteps(steps, (s) => s !== "#b").map((s) => s.target)).toEqual(["#a", "#c"]);
83
+ });
75
84
 
76
- it("returns nothing when no target is found, so no empty spotlight starts", () => {
77
- expect(keepFoundSteps(steps, () => false)).toEqual([]);
78
- });
85
+ it("returns nothing when no target is found, so no empty spotlight starts", () => {
86
+ expect(keepFoundSteps(steps, () => false)).toEqual([]);
87
+ });
79
88
  });
@@ -4,33 +4,36 @@ export type SeenMap = Record<string, number>;
4
4
 
5
5
  /** The seen version has to reach the tour's own, so bumping `version` shows it again. */
6
6
  export function isSeen(tour: Tour, seen: SeenMap): boolean {
7
- return (seen[tour.id] ?? 0) >= (tour.version ?? 1);
7
+ return (seen[tour.id] ?? 0) >= (tour.version ?? 1);
8
8
  }
9
9
 
10
10
  export function toursForRoute(tours: readonly Tour[], routeId: string | null | undefined): Tour[] {
11
- return routeId ? tours.filter((tour) => tour.route === routeId) : [];
11
+ return routeId ? tours.filter((tour) => tour.route === routeId) : [];
12
12
  }
13
13
 
14
14
  /** The first unseen tour on a route, in the order they were defined. One per page view. */
15
15
  export function pickTour(
16
- tours: readonly Tour[],
17
- routeId: string | null | undefined,
18
- seen: SeenMap,
16
+ tours: readonly Tour[],
17
+ routeId: string | null | undefined,
18
+ seen: SeenMap,
19
19
  ): Tour | undefined {
20
- return toursForRoute(tours, routeId).find((tour) => !isSeen(tour, seen));
20
+ return toursForRoute(tours, routeId).find((tour) => !isSeen(tour, seen));
21
21
  }
22
22
 
23
23
  /** What replay starts: a named tour, or the page's first one whether or not it was seen. */
24
24
  export function pickForReplay(
25
- tours: readonly Tour[],
26
- routeId: string | null | undefined,
27
- tourId?: string,
25
+ tours: readonly Tour[],
26
+ routeId: string | null | undefined,
27
+ tourId?: string,
28
28
  ): Tour | undefined {
29
- if (tourId) return tours.find((tour) => tour.id === tourId);
30
- return toursForRoute(tours, routeId)[0];
29
+ if (tourId) return tours.find((tour) => tour.id === tourId);
30
+ return toursForRoute(tours, routeId)[0];
31
31
  }
32
32
 
33
33
  /** Keeps the steps whose target is on the page. A tour with none left starts nothing. */
34
- export function keepFoundSteps(steps: readonly Step[], exists: (selector: string) => boolean): Step[] {
35
- return steps.filter((step) => exists(step.target));
34
+ export function keepFoundSteps(
35
+ steps: readonly Step[],
36
+ exists: (selector: string) => boolean,
37
+ ): Step[] {
38
+ return steps.filter((step) => exists(step.target));
36
39
  }