@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,439 @@
1
+ <script lang="ts">
2
+ import { tick, type Snippet } from "svelte";
3
+ import { ArrowSquareOut, BellSlash, Circle, Envelope, EnvelopeOpen } from "phosphor-svelte";
4
+ import { AgentAvatar } from "../agents/index.js";
5
+ import DetailHeader from "../components/list-page/detail-header.svelte";
6
+ import DetailPanel from "../components/list-page/detail-panel.svelte";
7
+ import FilterItem from "../components/list-page/filter-item.svelte";
8
+ import FilterPopover from "../components/list-page/filter-popover.svelte";
9
+ import MasterDetail from "../components/list-page/master-detail.svelte";
10
+ import RailBody from "../components/list-page/rail-body.svelte";
11
+ import RailEmpty from "../components/list-page/rail-empty.svelte";
12
+ import RailHead from "../components/list-page/rail-head.svelte";
13
+ import RailRow from "../components/list-page/rail-row.svelte";
14
+ import { filterSectionClass } from "../components/list-page/filter-section.js";
15
+ import { queueRailClass, queueRailWidth } from "../components/list-page/queue-rail.js";
16
+ import SegmentedFilter from "../components/segmented/segmented-filter.svelte";
17
+ import { Button } from "../shadcn/button/index.js";
18
+ import { Kbd, KbdGroup } from "../shadcn/kbd/index.js";
19
+ import { cn } from "../lib/utils.js";
20
+ import { TONE_CLASS } from "../lib/tone.js";
21
+ import InboxCard from "../tasks/inbox/inbox-card.svelte";
22
+ import type { InboxItem, InboxVerdict } from "../tasks/inbox/inbox-types.js";
23
+ import {
24
+ NO_FILTER,
25
+ actorNames,
26
+ entryLine,
27
+ filterThreads,
28
+ inboxKeyOf,
29
+ resolveInboxLabels,
30
+ snoozeChoices,
31
+ subjectTypes,
32
+ threadsOf,
33
+ } from "./logic.js";
34
+ import {
35
+ ALL,
36
+ type InboxActor,
37
+ type InboxEntry,
38
+ type InboxFilter,
39
+ type InboxLabels,
40
+ type InboxSnooze,
41
+ type InboxSubject,
42
+ type InboxThread,
43
+ } from "./types.js";
44
+
45
+ interface Props {
46
+ entries: InboxEntry[];
47
+ /** The key of the open thread. Without a match the first thread opens. */
48
+ selected?: string;
49
+ /** Threads with unread entries come first. */
50
+ unreadFirst?: boolean;
51
+ labels?: Partial<InboxLabels>;
52
+ /** Display names for subject types, such as `{ task: "Tasks" }`. A missing type shows as written. */
53
+ typeLabels?: Record<string, string>;
54
+ /** Replaces the default avatar for a person or an agent, drawn in a 24px slot. */
55
+ avatar?: Snippet<[InboxActor]>;
56
+ /** A badge for the subject's status, drawn by the host with its own status map. */
57
+ status?: Snippet<[InboxSubject]>;
58
+ /** Replaces a pinned proposal's summary lines. */
59
+ renderProposal?: Snippet<[InboxItem]>;
60
+ /** How a timestamp reads. Defaults to a short date and time. */
61
+ when?: (at: string) => string;
62
+ onSelect?: (thread: InboxThread) => void;
63
+ /** `read` is true when the thread had unread entries, so the host marks them all read. */
64
+ onRead?: (thread: InboxThread, read: boolean) => void;
65
+ onSnooze?: (request: InboxSnooze) => void;
66
+ onDecide?: (verdict: InboxVerdict, thread: InboxThread) => void;
67
+ class?: string;
68
+ }
69
+
70
+ let {
71
+ entries,
72
+ selected = $bindable(),
73
+ unreadFirst = false,
74
+ labels: labelOverrides,
75
+ typeLabels = {},
76
+ avatar,
77
+ status,
78
+ renderProposal,
79
+ when = (at) => {
80
+ const date = new Date(at);
81
+ return Number.isNaN(date.getTime())
82
+ ? ""
83
+ : date.toLocaleString(undefined, {
84
+ month: "short",
85
+ day: "numeric",
86
+ hour: "numeric",
87
+ minute: "2-digit",
88
+ });
89
+ },
90
+ onSelect,
91
+ onRead,
92
+ onSnooze,
93
+ onDecide,
94
+ class: className,
95
+ }: Props = $props();
96
+
97
+ const NEEDS_YOU = "needs-you";
98
+
99
+ let labels = $derived(resolveInboxLabels(labelOverrides));
100
+ let filter = $state<InboxFilter>({ ...NO_FILTER });
101
+ let snoozing = $state(false);
102
+ let pane = $state<"rail" | "detail">("rail");
103
+ let root = $state<HTMLDivElement>();
104
+ let cards = $state<Record<string, ReturnType<typeof InboxCard> | null>>({});
105
+
106
+ let all = $derived(threadsOf(entries, { unreadFirst }));
107
+ let visible = $derived(filterThreads(all, filter));
108
+ let types = $derived(subjectTypes(all));
109
+ let people = $derived(actorNames(all));
110
+ let current = $derived(visible.find((thread) => thread.key === selected) ?? visible[0]);
111
+ let needsYouCount = $derived(all.filter((thread) => thread.pending.length > 0).length);
112
+ let filtered = $derived(filter.type !== ALL || filter.who !== ALL || filter.needsYou);
113
+ let narrowed = $derived(Number(filter.type !== ALL) + Number(filter.who !== ALL));
114
+ let views = $derived([
115
+ { value: ALL, label: labels.all, count: all.length },
116
+ { value: NEEDS_YOU, label: labels.needsYou, count: needsYouCount },
117
+ ]);
118
+ let pinned = $derived(current?.pending[0]);
119
+ let timeline = $derived(
120
+ current?.entries.filter((entry) => !current.pending.includes(entry)) ?? [],
121
+ );
122
+
123
+ function open(thread: InboxThread): void {
124
+ selected = thread.key;
125
+ snoozing = false;
126
+ onSelect?.(thread);
127
+ }
128
+
129
+ async function move(step: number): Promise<void> {
130
+ if (!visible.length) return;
131
+ const from = current ? visible.indexOf(current) : -1;
132
+ open(visible[Math.max(0, Math.min(visible.length - 1, from + step))]);
133
+ await tick();
134
+ root?.querySelector<HTMLElement>('.md-rail [aria-current="page"]')?.focus();
135
+ }
136
+
137
+ function toggleRead(): void {
138
+ if (current) onRead?.(current, current.unread > 0);
139
+ }
140
+
141
+ function snooze(until: Date): void {
142
+ if (current) onSnooze?.({ thread: current, until });
143
+ snoozing = false;
144
+ }
145
+
146
+ function onkeydown(event: KeyboardEvent): void {
147
+ if (snoozing && !event.defaultPrevented && !event.metaKey && !event.ctrlKey && !event.altKey) {
148
+ const choice = snoozeChoices(new Date())[Number(event.key) - 1];
149
+ if (choice) {
150
+ event.preventDefault();
151
+ snooze(choice.until);
152
+ return;
153
+ }
154
+ if (event.key === "Escape") {
155
+ snoozing = false;
156
+ return;
157
+ }
158
+ }
159
+ const key = inboxKeyOf(event);
160
+ if (!key) return;
161
+ const card = pinned ? cards[pinned.id] : null;
162
+ const actions = {
163
+ next: () => move(1),
164
+ previous: () => move(-1),
165
+ toggleRead,
166
+ snooze: () => (snoozing = !snoozing),
167
+ approve: () => card?.approve(),
168
+ decline: () => card?.decline(),
169
+ steer: () => card?.steer(),
170
+ edit: () => card?.edit(),
171
+ };
172
+ event.preventDefault();
173
+ actions[key]();
174
+ }
175
+
176
+ function snoozeLabel(id: string): string {
177
+ return id === "later"
178
+ ? labels.snoozeLater
179
+ : id === "tomorrow"
180
+ ? labels.snoozeTomorrow
181
+ : labels.snoozeNextWeek;
182
+ }
183
+
184
+ function initial(name: string): string {
185
+ return name.trim().charAt(0).toLocaleUpperCase() || "?";
186
+ }
187
+
188
+ function lastEntry(thread: InboxThread): InboxEntry {
189
+ return thread.entries[thread.entries.length - 1];
190
+ }
191
+ </script>
192
+
193
+ {#snippet face(actor: InboxActor)}
194
+ <span class="relative size-6 shrink-0">
195
+ {#if avatar}
196
+ {@render avatar(actor)}
197
+ {:else if actor.kind === "agent"}
198
+ <AgentAvatar name={actor.name} size={24} paused interactive={false} />
199
+ {:else if actor.avatar}
200
+ <img src={actor.avatar} alt="" class="size-6 rounded-full object-cover" />
201
+ {:else}
202
+ <span
203
+ class="grid size-6 place-items-center rounded-full bg-muted text-xs font-medium text-muted-foreground"
204
+ aria-hidden="true"
205
+ >
206
+ {initial(actor.name)}
207
+ </span>
208
+ {/if}
209
+ </span>
210
+ {/snippet}
211
+
212
+ {#snippet subjectStatus()}
213
+ {#if current}{@render status?.(current.subject)}{/if}
214
+ {/snippet}
215
+
216
+ <!-- WHY the key handler sits on the region: the shortcuts work wherever focus is inside the inbox and nowhere else on the page. -->
217
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
218
+ <div
219
+ role="region"
220
+ aria-label={labels.title}
221
+ bind:this={root}
222
+ class={cn("flex h-full min-h-0 flex-col gap-3", className)}
223
+ {onkeydown}
224
+ >
225
+ <MasterDetail
226
+ bind:pane
227
+ railWidth={queueRailWidth}
228
+ railClass={queueRailClass}
229
+ detailClass="flex flex-col gap-4 overflow-y-auto"
230
+ pad={false}
231
+ backLabel={labels.title}
232
+ class="min-h-0 flex-1"
233
+ >
234
+ {#snippet rail()}
235
+ <RailHead>
236
+ <div class="flex items-center gap-2">
237
+ <SegmentedFilter
238
+ options={views}
239
+ value={filter.needsYou ? NEEDS_YOU : ALL}
240
+ onValueChange={(view) => (filter = { ...filter, needsYou: view === NEEDS_YOU })}
241
+ ariaLabel={labels.title}
242
+ class="min-w-0 flex-1"
243
+ />
244
+ <FilterPopover
245
+ label={labels.filter}
246
+ count={narrowed}
247
+ align="end"
248
+ onClear={() => (filter = { ...NO_FILTER, needsYou: filter.needsYou })}
249
+ clearLabel={labels.clearFilters}
250
+ >
251
+ {#if types.length > 1}
252
+ <p class={filterSectionClass()}>{labels.kind}</p>
253
+ {#each [ALL, ...types] as type (type)}
254
+ <FilterItem
255
+ variant="radio"
256
+ checked={filter.type === type}
257
+ onclick={() => (filter = { ...filter, type })}
258
+ >
259
+ {type === ALL ? labels.all : (typeLabels[type] ?? type)}
260
+ </FilterItem>
261
+ {/each}
262
+ {/if}
263
+ <p class={filterSectionClass()}>{labels.from}</p>
264
+ {#each [ALL, ...people] as who (who)}
265
+ <FilterItem
266
+ variant="radio"
267
+ checked={filter.who === who}
268
+ onclick={() => (filter = { ...filter, who })}
269
+ >
270
+ {who === ALL ? labels.anyone : who}
271
+ </FilterItem>
272
+ {/each}
273
+ </FilterPopover>
274
+ </div>
275
+ </RailHead>
276
+ <RailBody>
277
+ <ol aria-label={labels.title}>
278
+ {#each visible as thread (thread.key)}
279
+ {@const last = lastEntry(thread)}
280
+ <li>
281
+ <RailRow
282
+ title={thread.subject.title}
283
+ titleLines={2}
284
+ selected={current?.key === thread.key}
285
+ onclick={() => open(thread)}
286
+ preview="{last.actor.name} {entryLine(last, labels)}"
287
+ meta={[thread.subject.ref, when(thread.latestAt)]}
288
+ metaTruncate="last"
289
+ >
290
+ {#snippet trailing()}
291
+ {#if thread.pending.length > 0}
292
+ <span
293
+ class={cn(
294
+ "inline-flex h-5 items-center rounded-sm border px-1.5 font-medium",
295
+ TONE_CLASS.warning,
296
+ )}
297
+ >
298
+ {labels.needsYou}
299
+ </span>
300
+ {:else if thread.unread > 0}
301
+ <Circle
302
+ weight="fill"
303
+ class="size-2 text-primary"
304
+ aria-label="{thread.unread} {labels.unread}"
305
+ />
306
+ {/if}
307
+ {/snippet}
308
+ </RailRow>
309
+ </li>
310
+ {:else}
311
+ <li class="flex h-full flex-col items-center">
312
+ <RailEmpty
313
+ title={filtered
314
+ ? filter.needsYou
315
+ ? labels.emptyNeedsYou
316
+ : labels.emptyFiltered
317
+ : labels.empty}
318
+ />
319
+ {#if filtered}
320
+ <Button size="sm" variant="outline" onclick={() => (filter = { ...NO_FILTER })}
321
+ >{labels.clearFilters}</Button
322
+ >
323
+ {/if}
324
+ </li>
325
+ {/each}
326
+ </ol>
327
+ </RailBody>
328
+ {/snippet}
329
+
330
+ {#snippet detail()}
331
+ {#if current}
332
+ <DetailHeader
333
+ title={current.subject.title}
334
+ meta={[current.subject.ref]}
335
+ badge={status ? subjectStatus : undefined}
336
+ >
337
+ {#snippet actions()}
338
+ <Button size="sm" variant="ghost" onclick={toggleRead} aria-keyshortcuts="u">
339
+ {#if current.unread > 0}
340
+ <EnvelopeOpen aria-hidden="true" />{labels.markRead}
341
+ {:else}
342
+ <Envelope aria-hidden="true" />{labels.markUnread}
343
+ {/if}
344
+ </Button>
345
+ <Button
346
+ size="sm"
347
+ variant="ghost"
348
+ onclick={() => (snoozing = !snoozing)}
349
+ aria-expanded={snoozing}
350
+ aria-keyshortcuts="h"
351
+ >
352
+ <BellSlash aria-hidden="true" />{labels.snooze}
353
+ </Button>
354
+ {#if current.subject.href}
355
+ <Button size="sm" variant="ghost" href={current.subject.href}>
356
+ <ArrowSquareOut aria-hidden="true" />{labels.open}
357
+ </Button>
358
+ {/if}
359
+ {/snippet}
360
+ </DetailHeader>
361
+
362
+ {#if snoozing}
363
+ <div class="flex flex-wrap gap-2" role="group" aria-label={labels.snooze}>
364
+ {#each snoozeChoices(new Date()) as choice, index (choice.id)}
365
+ <Button size="sm" variant="outline" onclick={() => snooze(choice.until)}>
366
+ <Kbd>{index + 1}</Kbd>{snoozeLabel(choice.id)}
367
+ </Button>
368
+ {/each}
369
+ </div>
370
+ {/if}
371
+
372
+ {#each current.pending as entry (entry.id)}
373
+ <InboxCard
374
+ bind:this={cards[entry.id]}
375
+ item={entry.proposal!}
376
+ selected={entry === pinned}
377
+ onverdict={(verdict) => onDecide?.(verdict, current)}
378
+ {renderProposal}
379
+ />
380
+ {/each}
381
+
382
+ {#if timeline.length > 0}
383
+ <DetailPanel>
384
+ <ol class="flex flex-col gap-4">
385
+ {#each timeline as entry (entry.id)}
386
+ <li class={cn("flex gap-3", !entry.read && "font-medium")}>
387
+ {@render face(entry.actor)}
388
+ <div class="min-w-0 flex-1">
389
+ <div class="flex flex-wrap items-center gap-x-2 text-[13px]">
390
+ <span class="truncate font-medium">{entry.actor.name}</span>
391
+ {#if entry.actor.kind === "agent"}
392
+ <span
393
+ class={cn(
394
+ "inline-flex h-5 items-center rounded-sm border px-1.5 text-xs font-medium",
395
+ TONE_CLASS.accent,
396
+ )}
397
+ >
398
+ {labels.agent}
399
+ </span>
400
+ {/if}
401
+ <span class="font-normal text-muted-foreground"
402
+ >{entryLine(entry, labels)}</span
403
+ >
404
+ <span class="ms-auto text-xs font-normal text-muted-foreground tabular-nums"
405
+ >{when(entry.at)}</span
406
+ >
407
+ </div>
408
+ {#if entry.body}
409
+ <p
410
+ class="mt-1 text-sm leading-relaxed font-normal whitespace-pre-wrap text-pretty"
411
+ >
412
+ {entry.body}
413
+ </p>
414
+ {/if}
415
+ </div>
416
+ </li>
417
+ {/each}
418
+ </ol>
419
+ </DetailPanel>
420
+ {/if}
421
+ {:else}
422
+ <RailEmpty title={labels.selectThread} />
423
+ {/if}
424
+ {/snippet}
425
+ </MasterDetail>
426
+
427
+ <footer
428
+ class="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground max-sm:hidden"
429
+ >
430
+ <span class="inline-flex items-center gap-1.5"
431
+ ><KbdGroup><Kbd>j</Kbd><Kbd>k</Kbd></KbdGroup> {labels.hintMove}</span
432
+ >
433
+ <span class="inline-flex items-center gap-1.5"><Kbd>u</Kbd> {labels.hintRead}</span>
434
+ <span class="inline-flex items-center gap-1.5"><Kbd>h</Kbd> {labels.hintSnooze}</span>
435
+ <span class="inline-flex items-center gap-1.5"
436
+ ><KbdGroup><Kbd>a</Kbd><Kbd>d</Kbd><Kbd>s</Kbd><Kbd>e</Kbd></KbdGroup> {labels.hintAct}</span
437
+ >
438
+ </footer>
439
+ </div>
@@ -0,0 +1,161 @@
1
+ import type { InboxItem } from "../tasks/inbox/inbox-types.js";
2
+
3
+ export type InboxActorKind = "user" | "agent";
4
+
5
+ export type InboxActor = {
6
+ kind: InboxActorKind;
7
+ name: string;
8
+ /** Image URL for a person. The host's own portrait goes through the `avatar` snippet. */
9
+ avatar?: string;
10
+ };
11
+
12
+ export type InboxEntryKind =
13
+ | "comment"
14
+ | "status"
15
+ | "assigned"
16
+ | "mention"
17
+ | "agent_response"
18
+ | "proposal";
19
+
20
+ /** What the thread is about: one task, project or doc. Entries with the same type and id share a thread. */
21
+ export type InboxSubject = {
22
+ /** "task", "project", "doc" or whatever the host calls it. */
23
+ type: string;
24
+ id: string;
25
+ /** The short reference, such as "TSK-7". */
26
+ ref?: string;
27
+ title: string;
28
+ /** A status id the host draws itself, for a badge in the thread header. */
29
+ status?: string;
30
+ href?: string;
31
+ };
32
+
33
+ export type InboxEntry = {
34
+ id: string;
35
+ kind: InboxEntryKind;
36
+ /** An entry with no subject id belongs to no thread and is dropped. */
37
+ subject?: InboxSubject;
38
+ actor: InboxActor;
39
+ /** ISO timestamp. A malformed one sorts last. */
40
+ at: string;
41
+ read?: boolean;
42
+ /** Text of a comment, a mention or an agent response. */
43
+ body?: string;
44
+ /** A `status` entry: where the subject moved. */
45
+ status?: { from?: string; to: string };
46
+ /** An `assigned` entry: who got the work. */
47
+ assignee?: string;
48
+ /** A `proposal` entry: the agent's ask, drawn as an InboxCard while it waits. */
49
+ proposal?: InboxItem;
50
+ };
51
+
52
+ export type InboxThread = {
53
+ /** `type:id`, unique across subjects. */
54
+ key: string;
55
+ subject: InboxSubject;
56
+ /** Oldest first. */
57
+ entries: InboxEntry[];
58
+ unread: number;
59
+ /** The newest valid timestamp, as written. The raw first one when none parses. */
60
+ latestAt: string;
61
+ /** Proposals still waiting for a verdict, newest first. */
62
+ pending: InboxEntry[];
63
+ };
64
+
65
+ export type InboxFilter = {
66
+ /** A subject type, or "all". */
67
+ type: string;
68
+ /** An actor name, or "all". */
69
+ who: string;
70
+ /** Only threads holding a pending proposal. */
71
+ needsYou: boolean;
72
+ };
73
+
74
+ export const ALL = "all";
75
+
76
+ export type InboxKey =
77
+ | "next"
78
+ | "previous"
79
+ | "toggleRead"
80
+ | "snooze"
81
+ | "approve"
82
+ | "decline"
83
+ | "steer"
84
+ | "edit";
85
+
86
+ export type InboxSnooze = { thread: InboxThread; until: Date };
87
+
88
+ export type InboxLabels = {
89
+ title: string;
90
+ all: string;
91
+ anyone: string;
92
+ needsYou: string;
93
+ empty: string;
94
+ emptyNeedsYou: string;
95
+ emptyFiltered: string;
96
+ clearFilters: string;
97
+ unread: string;
98
+ markRead: string;
99
+ markUnread: string;
100
+ snooze: string;
101
+ snoozeLater: string;
102
+ snoozeTomorrow: string;
103
+ snoozeNextWeek: string;
104
+ selectThread: string;
105
+ filterType: string;
106
+ filterWho: string;
107
+ filter: string;
108
+ kind: string;
109
+ from: string;
110
+ open: string;
111
+ agent: string;
112
+ statusMoved: string;
113
+ statusSet: string;
114
+ assigned: string;
115
+ mentioned: string;
116
+ commented: string;
117
+ responded: string;
118
+ proposed: string;
119
+ /** Keyboard hint words. */
120
+ hintMove: string;
121
+ hintRead: string;
122
+ hintSnooze: string;
123
+ hintAct: string;
124
+ };
125
+
126
+ export const DEFAULT_INBOX_LABELS: InboxLabels = {
127
+ title: "Inbox",
128
+ all: "All",
129
+ anyone: "Anyone",
130
+ needsYou: "Needs you",
131
+ empty: "Nothing here",
132
+ emptyNeedsYou: "Nothing is waiting on you",
133
+ emptyFiltered: "No thread matches these filters",
134
+ clearFilters: "Clear filters",
135
+ unread: "unread",
136
+ markRead: "Mark read",
137
+ markUnread: "Mark unread",
138
+ snooze: "Snooze",
139
+ snoozeLater: "Later today",
140
+ snoozeTomorrow: "Tomorrow",
141
+ snoozeNextWeek: "Next week",
142
+ selectThread: "Select a thread",
143
+ filterType: "Filter by type",
144
+ filterWho: "Filter by person or agent",
145
+ filter: "Filter",
146
+ kind: "Type",
147
+ from: "From",
148
+ open: "Open",
149
+ agent: "AI",
150
+ statusMoved: "moved this from {from} to {to}",
151
+ statusSet: "set this to {to}",
152
+ assigned: "assigned this to {assignee}",
153
+ mentioned: "mentioned you",
154
+ commented: "commented",
155
+ responded: "replied",
156
+ proposed: "asks for your call",
157
+ hintMove: "move",
158
+ hintRead: "read",
159
+ hintSnooze: "snooze",
160
+ hintAct: "approve, decline, steer, edit",
161
+ };
@@ -14,10 +14,8 @@ const DEFAULT_DURATION = 400;
14
14
  const STYLE_ID = "heyramzi-reveal-theme";
15
15
 
16
16
  /**
17
- * Run `apply` inside a circular wipe that grows from the centre of the clicked
18
- * element, the element, or a point. Falls back to a plain `apply()` when the
19
- * browser has no view transitions, the user prefers reduced motion, or `skip`
20
- * is set. It adds the `::view-transition-*(root)` reset itself.
17
+ * Run `apply` inside a circular wipe from the clicked element or a point, falling back to
18
+ * plain `apply()` without view transitions, under reduced motion, or with `skip`.
21
19
  */
22
20
  export function revealTheme(
23
21
  origin: RevealOrigin,
package/src/lib/tone.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  export type Tone = "neutral" | "accent" | "info" | "success" | "warning" | "destructive";
2
2
 
3
3
  /**
4
- * The severity scale, as Tailwind classes for a chip and as CSS colors for the
5
- * dot. Tokens are plain CSS variables, so a host app can retheme them. The
6
- * accent tone reads `--tone-accent` and falls back to `--color-primary`, for a
7
- * host whose primary is a neutral and whose accent is a brand color.
4
+ * The severity scale as chip classes and dot colors. Accent reads `--tone-accent` before
5
+ * `--color-primary`, for a host whose primary is a neutral.
8
6
  */
9
7
 
10
8
  export const TONE_CLASS: Record<Tone, string> = {
package/src/lib/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
- // The class merge and prop types every shadcn primitive is written against. They live here, not in
2
- // a shared utils package, so this public package installs with nothing private behind it.
1
+ // The class merge and prop types every shadcn primitive uses, kept here, not in a shared utils
2
+ // package, so this public package installs with nothing private behind it.
3
3
  import { type ClassValue, clsx } from "clsx";
4
4
  import { twMerge } from "tailwind-merge";
5
5