@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
@@ -1,8 +1,28 @@
1
1
  import { describe, expect, it } from "vite-plus/test";
2
2
  import { dayNumber } from "../dates.js";
3
- import { barWidth, buildArrows, buildMonths, buildDays, buildRows, liveSpan, rangeLabel, rangeOf, roundedPath, spanOf, type Drag, type Layout } from "./logic.js";
3
+ import {
4
+ barWidth,
5
+ buildArrows,
6
+ buildMonths,
7
+ buildDays,
8
+ buildRows,
9
+ liveSpan,
10
+ rangeLabel,
11
+ rangeOf,
12
+ roundedPath,
13
+ spanOf,
14
+ type Drag,
15
+ type Layout,
16
+ } from "./logic.js";
4
17
 
5
- type Task = { id: string; start?: string; end?: string; parent?: string; group?: string; after?: string[] };
18
+ type Task = {
19
+ id: string;
20
+ start?: string;
21
+ end?: string;
22
+ parent?: string;
23
+ group?: string;
24
+ after?: string[];
25
+ };
6
26
 
7
27
  const dates = { getStart: (t: Task) => t.start, getEnd: (t: Task) => t.end };
8
28
  const source = (overrides: Partial<Parameters<typeof buildRows<Task>>[1]> = {}) => ({
@@ -19,12 +39,20 @@ describe("spanOf", () => {
19
39
  expect(span && span.end - span.start).toBe(2);
20
40
  });
21
41
  it("lets a lone date stand for a one-day task", () => {
22
- expect(spanOf({ id: "a", end: "2026-10-05" }, dates)).toEqual({ start: dayNumber("2026-10-05"), end: dayNumber("2026-10-05") });
23
- expect(spanOf({ id: "a", start: "2026-10-05" }, dates)).toEqual({ start: dayNumber("2026-10-05"), end: dayNumber("2026-10-05") });
42
+ expect(spanOf({ id: "a", end: "2026-10-05" }, dates)).toEqual({
43
+ start: dayNumber("2026-10-05"),
44
+ end: dayNumber("2026-10-05"),
45
+ });
46
+ expect(spanOf({ id: "a", start: "2026-10-05" }, dates)).toEqual({
47
+ start: dayNumber("2026-10-05"),
48
+ end: dayNumber("2026-10-05"),
49
+ });
24
50
  });
25
51
  it("gives an undated task no span, and never an end before the start", () => {
26
52
  expect(spanOf({ id: "a" }, dates)).toBeNull();
27
- expect(spanOf({ id: "a", start: "2026-10-09", end: "2026-10-05" }, dates)?.end).toBe(dayNumber("2026-10-09"));
53
+ expect(spanOf({ id: "a", start: "2026-10-09", end: "2026-10-05" }, dates)?.end).toBe(
54
+ dayNumber("2026-10-09"),
55
+ );
28
56
  });
29
57
  });
30
58
 
@@ -46,13 +74,20 @@ describe("liveSpan", () => {
46
74
  describe("rangeOf", () => {
47
75
  const today = dayNumber("2026-10-21");
48
76
  it("pads the tasks' span and starts on a Monday", () => {
49
- const { first, last } = rangeOf([{ id: "a", start: "2026-10-14", end: "2026-10-30" }], dates, today);
77
+ const { first, last } = rangeOf(
78
+ [{ id: "a", start: "2026-10-14", end: "2026-10-30" }],
79
+ dates,
80
+ today,
81
+ );
50
82
  expect(new Date(first * 86_400_000).getUTCDay()).toBe(1);
51
83
  expect(first).toBeLessThanOrEqual(dayNumber("2026-10-07"));
52
84
  expect(last).toBe(dayNumber("2026-10-30") + 21);
53
85
  });
54
86
  it("honours from and to, and never ends before it starts", () => {
55
- expect(rangeOf([], dates, today, "2026-10-21", "2026-10-01")).toEqual({ first: today, last: today });
87
+ expect(rangeOf([], dates, today, "2026-10-21", "2026-10-01")).toEqual({
88
+ first: today,
89
+ last: today,
90
+ });
56
91
  });
57
92
  });
58
93
 
@@ -91,15 +126,30 @@ describe("buildRows", () => {
91
126
  { id: "b", group: "one", start: "2026-10-01", end: "2026-10-02" },
92
127
  { id: "c", group: "one", start: "2026-10-10", end: "2026-10-12" },
93
128
  ];
94
- const rows = buildRows(grouped, source({
95
- groups: [{ id: "one", label: "One" }, { id: "two", label: "Two" }],
96
- getGroup: (t) => t.group ?? "",
97
- }));
98
- expect(rows.map((r) => (r.kind === "group" ? `#${r.group.id}:${r.count}` : r.id))).toEqual(["#one:2", "b", "c", "#two:1", "a"]);
129
+ const rows = buildRows(
130
+ grouped,
131
+ source({
132
+ groups: [
133
+ { id: "one", label: "One" },
134
+ { id: "two", label: "Two" },
135
+ ],
136
+ getGroup: (t) => t.group ?? "",
137
+ }),
138
+ );
139
+ expect(rows.map((r) => (r.kind === "group" ? `#${r.group.id}:${r.count}` : r.id))).toEqual([
140
+ "#one:2",
141
+ "b",
142
+ "c",
143
+ "#two:1",
144
+ "a",
145
+ ]);
99
146
  expect(rows[0].span).toEqual({ start: dayNumber("2026-10-01"), end: dayNumber("2026-10-12") });
100
147
  });
101
148
  it("applies a live drag to the row it moves", () => {
102
- const rows = buildRows([{ id: "a", start: "2026-10-05", end: "2026-10-06" }], source({ drag: { id: "a", mode: "move", originX: 0, dx: 2 } }));
149
+ const rows = buildRows(
150
+ [{ id: "a", start: "2026-10-05", end: "2026-10-06" }],
151
+ source({ drag: { id: "a", mode: "move", originX: 0, dx: 2 } }),
152
+ );
103
153
  const row = rows[0];
104
154
  if (row.kind !== "task") throw new Error("expected a task row");
105
155
  expect(row.span).toEqual({ start: dayNumber("2026-10-07"), end: dayNumber("2026-10-08") });
@@ -108,7 +158,15 @@ describe("buildRows", () => {
108
158
 
109
159
  describe("buildArrows", () => {
110
160
  const first = dayNumber("2026-10-05");
111
- const layout: Layout = { first, last: first + 30, dayWidth: 32, rowHeight: 40, inset: 3, timelineWidth: 31 * 32, todayDay: first };
161
+ const layout: Layout = {
162
+ first,
163
+ last: first + 30,
164
+ dayWidth: 32,
165
+ rowHeight: 40,
166
+ inset: 3,
167
+ timelineWidth: 31 * 32,
168
+ todayDay: first,
169
+ };
112
170
  const rows = (tasks: Task[]) => buildRows(tasks, source());
113
171
  it("draws one arrow per known dependency and skips unknown or undated ones", () => {
114
172
  const tasks: Task[] = [
@@ -124,7 +182,11 @@ describe("buildArrows", () => {
124
182
  { id: "a", start: "2026-10-05", end: "2026-10-05" },
125
183
  { id: "b", start: "2026-10-12", end: "2026-10-12", after: ["a"] },
126
184
  ];
127
- const [arrow] = buildArrows(rows(tasks), layout, (t) => tasks.find((x) => x.id === t.id)?.after);
185
+ const [arrow] = buildArrows(
186
+ rows(tasks),
187
+ layout,
188
+ (t) => tasks.find((x) => x.id === t.id)?.after,
189
+ );
128
190
  expect(arrow.d.match(/Q/g)).toHaveLength(2);
129
191
  });
130
192
  it("routes round the row gap when the bars touch", () => {
@@ -132,30 +194,58 @@ describe("buildArrows", () => {
132
194
  { id: "a", start: "2026-10-05", end: "2026-10-08" },
133
195
  { id: "b", start: "2026-10-06", end: "2026-10-09", after: ["a"] },
134
196
  ];
135
- const [arrow] = buildArrows(rows(tasks), layout, (t) => tasks.find((x) => x.id === t.id)?.after);
197
+ const [arrow] = buildArrows(
198
+ rows(tasks),
199
+ layout,
200
+ (t) => tasks.find((x) => x.id === t.id)?.after,
201
+ );
136
202
  expect(arrow.d.match(/Q/g)).toHaveLength(4);
137
203
  });
138
204
  });
139
205
 
140
206
  describe("roundedPath", () => {
141
207
  it("rounds each corner by up to the radius and ends on the last point", () => {
142
- expect(roundedPath([[0, 0], [20, 0], [20, 20]], 6)).toBe("M0,0 L14,0 Q20,0 20,6 L20,20");
208
+ expect(
209
+ roundedPath(
210
+ [
211
+ [0, 0],
212
+ [20, 0],
213
+ [20, 20],
214
+ ],
215
+ 6,
216
+ ),
217
+ ).toBe("M0,0 L14,0 Q20,0 20,6 L20,20");
143
218
  });
144
219
  });
145
220
 
146
221
  describe("timeline helpers", () => {
147
222
  it("groups days under their month", () => {
148
223
  const first = dayNumber("2026-10-30");
149
- const months = buildMonths(buildDays(first, 4), new Intl.DateTimeFormat("en-US", { month: "long", year: "numeric", timeZone: "UTC" }));
224
+ const months = buildMonths(
225
+ buildDays(first, 4),
226
+ new Intl.DateTimeFormat("en-US", { month: "long", year: "numeric", timeZone: "UTC" }),
227
+ );
150
228
  expect(months).toEqual([
151
229
  { key: "2026-10", label: "October 2026", i: 0, length: 2 },
152
230
  { key: "2026-11", label: "November 2026", i: 2, length: 2 },
153
231
  ]);
154
232
  });
155
233
  it("never draws a bar under 8px, and labels the span", () => {
156
- const layout: Layout = { first: 0, last: 9, dayWidth: 4, rowHeight: 40, inset: 0, timelineWidth: 40, todayDay: 0 };
234
+ const layout: Layout = {
235
+ first: 0,
236
+ last: 9,
237
+ dayWidth: 4,
238
+ rowHeight: 40,
239
+ inset: 0,
240
+ timelineWidth: 40,
241
+ todayDay: 0,
242
+ };
157
243
  expect(barWidth({ start: 1, end: 1 }, layout)).toBe(8);
158
- const format = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
244
+ const format = new Intl.DateTimeFormat("en-US", {
245
+ month: "short",
246
+ day: "numeric",
247
+ timeZone: "UTC",
248
+ });
159
249
  const start = dayNumber("2026-10-05");
160
250
  expect(rangeLabel({ start, end: start }, format)).toBe("Oct 5 to Oct 5, 1 day");
161
251
  expect(rangeLabel({ start, end: start + 2 }, format)).toBe("Oct 5 to Oct 7, 3 days");
@@ -68,7 +68,8 @@ export function spanOf<T>(item: T, { getStart, getEnd }: Dates<T>): Span | null
68
68
  export function liveSpan(span: Span | null, drag: Drag | null, id: string): Span | null {
69
69
  if (!span || drag?.id !== id || drag.dx === 0) return span;
70
70
  if (drag.mode === "move") return { start: span.start + drag.dx, end: span.end + drag.dx };
71
- if (drag.mode === "start") return { start: Math.min(span.start + drag.dx, span.end), end: span.end };
71
+ if (drag.mode === "start")
72
+ return { start: Math.min(span.start + drag.dx, span.end), end: span.end };
72
73
  return { start: span.start, end: Math.max(span.end + drag.dx, span.start) };
73
74
  }
74
75
 
@@ -103,7 +104,13 @@ export function buildDays(first: number, count: number) {
103
104
  return Array.from({ length: count }, (_, i) => {
104
105
  const day = first + i;
105
106
  const wd = weekday(day);
106
- return { day, i, date: Number(isoFromDay(day).slice(8, 10)), wd, weekend: wd === 0 || wd === 6 };
107
+ return {
108
+ day,
109
+ i,
110
+ date: Number(isoFromDay(day).slice(8, 10)),
111
+ wd,
112
+ weekend: wd === 0 || wd === 6,
113
+ };
107
114
  });
108
115
  }
109
116
  export type GanttDay = ReturnType<typeof buildDays>[number];
@@ -133,7 +140,17 @@ export function buildRows<T>(items: T[], source: RowSource<T>): Row<T>[] {
133
140
  for (const item of members) {
134
141
  const id = getId(item);
135
142
  const own = live(item, id);
136
- out.push({ kind: "task", item, id, order: order++, depth: 0, hasChildren: false, open: true, own, span: own });
143
+ out.push({
144
+ kind: "task",
145
+ item,
146
+ id,
147
+ order: order++,
148
+ depth: 0,
149
+ hasChildren: false,
150
+ open: true,
151
+ own,
152
+ span: own,
153
+ });
137
154
  }
138
155
  return;
139
156
  }
@@ -149,7 +166,17 @@ export function buildRows<T>(items: T[], source: RowSource<T>): Row<T>[] {
149
166
  const children = kids.get(id) ?? [];
150
167
  const open = !folded.has(id);
151
168
  const own = live(item, id);
152
- const row: TaskRow<T> = { kind: "task", item, id, order: 0, depth, hasChildren: children.length > 0, open, own, span: own };
169
+ const row: TaskRow<T> = {
170
+ kind: "task",
171
+ item,
172
+ id,
173
+ order: 0,
174
+ depth,
175
+ hasChildren: children.length > 0,
176
+ open,
177
+ own,
178
+ span: own,
179
+ };
153
180
  if (visible) {
154
181
  row.order = order++;
155
182
  out.push(row);
@@ -195,7 +222,11 @@ export function roundedPath(points: [number, number][], radius: number): string
195
222
  }
196
223
 
197
224
  /** Each arrow runs from the end of the task waited on to the start of the one waiting. */
198
- export function buildArrows<T>(rows: Row<T>[], layout: Layout, getDependsOn: (item: T) => string[] | undefined): Arrow[] {
225
+ export function buildArrows<T>(
226
+ rows: Row<T>[],
227
+ layout: Layout,
228
+ getDependsOn: (item: T) => string[] | undefined,
229
+ ): Arrow[] {
199
230
  const { rowHeight, inset } = layout;
200
231
  const at = new Map<string, { row: number; span: Span }>();
201
232
  rows.forEach((row, index) => {
@@ -216,7 +247,12 @@ export function buildArrows<T>(rows: Row<T>[], layout: Layout, getDependsOn: (it
216
247
  // Room between the bars: one bend in the gap. No room: out, round the row gap, and back in.
217
248
  const points: [number, number][] =
218
249
  x2 - x1 >= 24
219
- ? [[x1, y1], [x1 + 12, y1], [x1 + 12, y2], [x2, y2]]
250
+ ? [
251
+ [x1, y1],
252
+ [x1 + 12, y1],
253
+ [x1 + 12, y2],
254
+ [x2, y2],
255
+ ]
220
256
  : [
221
257
  [x1, y1],
222
258
  [x1 + 10, y1],
@@ -5,8 +5,8 @@
5
5
  import { Button } from "../../shadcn/button/index.js";
6
6
  import { Textarea } from "../../shadcn/textarea/index.js";
7
7
  import { cn } from "../../lib/utils.js";
8
- import StatusBadge from "../status-badge.svelte";
9
- import StatusDot from "../status-dot.svelte";
8
+ import StatusBadge from "../../status/status-badge.svelte";
9
+ import StatusDot from "../../status/status-dot.svelte";
10
10
  import { TONE_TEXT } from "../../lib/tone.js";
11
11
  import InboxDraftEditor from "./inbox-draft.svelte";
12
12
  import { canUndo, isOpen, startDraft } from "./inbox-logic.js";
@@ -29,7 +29,7 @@
29
29
  onverdict: (verdict: InboxVerdict) => void;
30
30
  /** Replaces the summary lines, for a host that renders its own proposal. */
31
31
  renderProposal?: Snippet<[InboxItem]>;
32
- };
32
+ }
33
33
 
34
34
  let { item, selected = false, onselect, onverdict, renderProposal }: Props = $props();
35
35
 
@@ -158,7 +158,9 @@
158
158
  class={cn(
159
159
  "flex flex-col gap-3 rounded-xl p-3 outline-none transition-[background-color,border-color,box-shadow] duration-[var(--duration-quick,120ms)] ease-[var(--ease-smooth-out,ease-out)]",
160
160
  "focus-visible:ring-ring/50 focus-visible:ring-3",
161
- open ? "border-border bg-card border shadow-[var(--elev-card,none)]" : "bg-muted/30 border border-transparent",
161
+ open
162
+ ? "border-border bg-card border shadow-[var(--elev-card,none)]"
163
+ : "bg-muted/30 border border-transparent",
162
164
  selected && open && "border-ring",
163
165
  )}
164
166
  >
@@ -228,7 +230,9 @@
228
230
  <div class="flex flex-col gap-1.5 pl-6">
229
231
  <PixelLoader label="Agent is re-running with your note" />
230
232
  {#if item.steer}
231
- <p class="text-muted-foreground border-border border-l-2 pl-3 text-sm">&ldquo;{item.steer}&rdquo;</p>
233
+ <p class="text-muted-foreground border-border border-l-2 pl-3 text-sm">
234
+ &ldquo;{item.steer}&rdquo;
235
+ </p>
232
236
  {/if}
233
237
  </div>
234
238
  {/if}
@@ -259,14 +263,18 @@
259
263
  <div class="flex flex-wrap items-center gap-2">
260
264
  <Button size="sm" onclick={submitSteer} disabled={!note.trim()}>Send to agent</Button>
261
265
  <Button size="sm" variant="ghost" onclick={() => (steering = false)}>Cancel</Button>
262
- <span class="text-muted-foreground text-xs max-sm:hidden">The agent retries with your note.</span>
266
+ <span class="text-muted-foreground text-xs max-sm:hidden"
267
+ >The agent retries with your note.</span
268
+ >
263
269
  </div>
264
270
  </div>
265
271
  {:else if open}
266
272
  <div class="flex flex-wrap items-center gap-2 pl-6">
267
273
  <Button size="sm" onclick={approve} aria-keyshortcuts="a">{sendLabel}</Button>
268
274
  {#if !drafts.some((d) => d.outcome === "decline")}
269
- <Button size="sm" variant="destructive" onclick={decline} aria-keyshortcuts="d">Decline</Button>
275
+ <Button size="sm" variant="destructive" onclick={decline} aria-keyshortcuts="d"
276
+ >Decline</Button
277
+ >
270
278
  {/if}
271
279
  <Button size="sm" variant="outline" onclick={steer} aria-keyshortcuts="s">Steer</Button>
272
280
  {#if drafts.length}
@@ -19,7 +19,7 @@
19
19
  bodyEl?: HTMLTextAreaElement | null;
20
20
  disabled?: boolean;
21
21
  class?: string;
22
- };
22
+ }
23
23
 
24
24
  let {
25
25
  drafts,
@@ -1,55 +1,21 @@
1
- import type {
2
- InboxDraft,
3
- InboxItem,
4
- InboxKindFilter,
5
- InboxLane,
6
- InboxProject,
7
- } from "./inbox-types.js";
8
-
9
- /** Which lane an item sits in. The agent's own work and the person's decisions never share one. */
10
- export function laneOf(item: InboxItem): InboxLane {
11
- if (item.tier === "auto") return "auto";
12
- return item.state === "proposed" || item.state === "working" ? "needs" : "decided";
13
- }
14
-
15
- export function inLane(items: InboxItem[], lane: InboxLane, kind: InboxKindFilter): InboxItem[] {
16
- return items.filter((item) => laneOf(item) === lane && (kind === "all" || item.kind === kind));
17
- }
18
-
19
- export type InboxGroup = { project: InboxProject; items: InboxItem[] };
20
-
21
- /** Group by project in order of first appearance, so the oldest waiting item keeps its place. */
22
- export function groupByProject(items: InboxItem[]): InboxGroup[] {
23
- const groups = new Map<string, InboxGroup>();
24
- for (const item of items) {
25
- const group = groups.get(item.project.id);
26
- if (group) group.items.push(item);
27
- else groups.set(item.project.id, { project: item.project, items: [item] });
28
- }
29
- return [...groups.values()];
30
- }
31
-
32
- /** Items in the order the screen shows them, so j and k walk what the eye sees. */
33
- export function flatten(groups: InboxGroup[]): InboxItem[] {
34
- return groups.flatMap((group) => group.items);
35
- }
1
+ import type { InboxDraft, InboxItem } from "./inbox-types.js";
36
2
 
37
3
  /** The draft to open on: the agent's pick, else the first. */
38
4
  export function startDraft(drafts: InboxDraft[], recommended?: string): InboxDraft | undefined {
39
- return drafts.find((draft) => draft.templateId === recommended) ?? drafts[0];
5
+ return drafts.find((draft) => draft.templateId === recommended) ?? drafts[0];
40
6
  }
41
7
 
42
8
  /** A person can act on an item only while it waits for them. */
43
9
  export function isOpen(item: InboxItem): boolean {
44
- return item.tier === "ask" && item.state === "proposed";
10
+ return item.tier === "ask" && item.state === "proposed";
45
11
  }
46
12
 
47
13
  export function canUndo(item: InboxItem): boolean {
48
- return item.tier === "auto" && item.state === "done";
14
+ return item.tier === "auto" && item.state === "done";
49
15
  }
50
16
 
51
17
  /** True when a key press belongs to a text field and so isn't a shortcut. */
52
18
  export function isTyping(target: EventTarget | null): boolean {
53
- if (!(target instanceof HTMLElement)) return false;
54
- return target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName);
19
+ if (!(target instanceof HTMLElement)) return false;
20
+ return target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName);
55
21
  }
@@ -1,48 +1,48 @@
1
- import { defineStatuses } from "../define-statuses.js";
1
+ import { defineStatuses } from "../../status/define-statuses.js";
2
2
  import { ClipboardText, EnvelopeSimple, Hash, Phone } from "phosphor-svelte";
3
3
  import type { PhosphorIcon } from "../../lib/icon-types.js";
4
4
  import type { InboxSourceType } from "./inbox-types.js";
5
5
 
6
6
  /** The lifecycle of an item. The dot and the badge both read this one map. */
7
7
  export const inboxStates = defineStatuses({
8
- proposed: { label: "Needs you", tone: "warning", variant: "open" },
9
- working: { label: "Agent working", tone: "accent", variant: "progress", progress: 0.6 },
10
- approved: { label: "Approved", tone: "success", variant: "completed" },
11
- declined: { label: "Declined", tone: "neutral", variant: "lost" },
12
- done: { label: "Done by agent", tone: "success", variant: "completed" },
13
- undone: { label: "Undone", tone: "neutral", variant: "lost" },
8
+ proposed: { label: "Needs you", tone: "warning", variant: "open" },
9
+ working: { label: "Agent working", tone: "accent", variant: "progress", progress: 0.6 },
10
+ approved: { label: "Approved", tone: "success", variant: "completed" },
11
+ declined: { label: "Declined", tone: "neutral", variant: "lost" },
12
+ done: { label: "Done by agent", tone: "success", variant: "completed" },
13
+ undone: { label: "Undone", tone: "neutral", variant: "lost" },
14
14
  });
15
15
 
16
16
  /** What the agent made of the touchpoint. */
17
17
  export const inboxKinds = defineStatuses({
18
- information: { label: "Information", tone: "info", variant: "filled" },
19
- action: { label: "Action", tone: "accent", variant: "filled" },
20
- decision: { label: "Decision", tone: "warning", variant: "filled" },
18
+ information: { label: "Information", tone: "info", variant: "filled" },
19
+ action: { label: "Action", tone: "accent", variant: "filled" },
20
+ decision: { label: "Decision", tone: "warning", variant: "filled" },
21
21
  });
22
22
 
23
23
  /** How sure the agent is, bucketed. The dot's fill carries the exact score. */
24
24
  export const inboxConfidence = defineStatuses({
25
- high: { label: "Confident", tone: "success", variant: "progress" },
26
- medium: { label: "Unsure", tone: "warning", variant: "progress" },
27
- low: { label: "Guessing", tone: "destructive", variant: "progress" },
25
+ high: { label: "Confident", tone: "success", variant: "progress" },
26
+ medium: { label: "Unsure", tone: "warning", variant: "progress" },
27
+ low: { label: "Guessing", tone: "destructive", variant: "progress" },
28
28
  });
29
29
 
30
30
  export function confidenceLevel(score: number): "high" | "medium" | "low" {
31
- if (score >= 0.8) return "high";
32
- if (score >= 0.5) return "medium";
33
- return "low";
31
+ if (score >= 0.8) return "high";
32
+ if (score >= 0.5) return "medium";
33
+ return "low";
34
34
  }
35
35
 
36
36
  export const SOURCE_ICON: Record<InboxSourceType, PhosphorIcon> = {
37
- call: Phone,
38
- email: EnvelopeSimple,
39
- slack: Hash,
40
- form: ClipboardText,
37
+ call: Phone,
38
+ email: EnvelopeSimple,
39
+ slack: Hash,
40
+ form: ClipboardText,
41
41
  };
42
42
 
43
43
  export const SOURCE_LABEL: Record<InboxSourceType, string> = {
44
- call: "Call",
45
- email: "Email",
46
- slack: "Slack",
47
- form: "Form",
44
+ call: "Call",
45
+ email: "Email",
46
+ slack: "Slack",
47
+ form: "Form",
48
48
  };
@@ -3,7 +3,9 @@
3
3
  import { SOURCE_ICON, SOURCE_LABEL } from "./inbox-statuses.js";
4
4
  import type { InboxTrace } from "./inbox-types.js";
5
5
 
6
- interface Props { trace: InboxTrace };
6
+ interface Props {
7
+ trace: InboxTrace;
8
+ }
7
9
 
8
10
  let { trace }: Props = $props();
9
11
 
@@ -26,7 +28,9 @@
26
28
  <p class="text-foreground">
27
29
  {trace.source.label}
28
30
  {#if trace.source.href}
29
- <a href={trace.source.href} class="text-foreground ml-1 underline underline-offset-2">Open source</a>
31
+ <a href={trace.source.href} class="text-foreground ml-1 underline underline-offset-2"
32
+ >Open source</a
33
+ >
30
34
  {/if}
31
35
  </p>
32
36
  <ul class="flex flex-col gap-2">
@@ -34,7 +38,9 @@
34
38
  <li>
35
39
  <blockquote class="text-foreground">&ldquo;{evidence.quote}&rdquo;</blockquote>
36
40
  {#if evidence.by || evidence.at}
37
- <span class="text-xs tabular-nums">{[evidence.by, evidence.at].filter(Boolean).join(", ")}</span>
41
+ <span class="text-xs tabular-nums"
42
+ >{[evidence.by, evidence.at].filter(Boolean).join(", ")}</span
43
+ >
38
44
  {/if}
39
45
  </li>
40
46
  {/each}