@heyramzi/ui 2.8.0 → 2.10.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 (426) hide show
  1. package/COMPONENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/agents/helpers.js +52 -0
  4. package/dist/agents/index.js +3 -0
  5. package/dist/agents/page-state.js +65 -0
  6. package/dist/agents/types.js +1 -0
  7. package/dist/agents/vendor/color.js +149 -0
  8. package/dist/agents/vendor/draw.js +548 -0
  9. package/dist/agents/vendor/engine.js +632 -0
  10. package/dist/agents/vendor/mesh.js +727 -0
  11. package/dist/agents/vendor/plastic.js +3273 -0
  12. package/dist/agents/vendor/presets.js +171 -0
  13. package/dist/agents/vendor/shapes.js +24 -0
  14. package/dist/agents/vendor/ticker.js +56 -0
  15. package/dist/agents/vendor/types.js +0 -0
  16. package/dist/agents/vendor/wear.js +1525 -0
  17. package/dist/ai/ai-button/index.js +2 -0
  18. package/dist/ai/confirmation/confirmation-context.svelte.js +12 -0
  19. package/dist/ai/confirmation/index.js +9 -0
  20. package/dist/ai/generation-progress/index.js +2 -0
  21. package/dist/ai/index.js +11 -0
  22. package/dist/ai/loading-state/index.js +3 -0
  23. package/dist/ai/prompt-input/index.js +6 -0
  24. package/dist/ai/reasoning/index.js +4 -0
  25. package/dist/ai/reasoning/reasoning-context.svelte.js +12 -0
  26. package/dist/ai/response/index.js +3 -0
  27. package/dist/ai/suggestion/index.js +3 -0
  28. package/dist/ai/thinking-dots/index.js +3 -0
  29. package/dist/ai/typing-dots/index.js +2 -0
  30. package/dist/ai/voice-waveform/index.js +3 -0
  31. package/dist/animations/text-swap.js +30 -0
  32. package/dist/brand/index.js +65 -0
  33. package/dist/canvas/card/resize.js +10 -0
  34. package/dist/canvas/edges/edge-path.js +284 -0
  35. package/dist/canvas/editor/context-menu.js +42 -0
  36. package/dist/canvas/editor/dispatch.svelte.js +157 -0
  37. package/dist/canvas/editor/editor-context.js +8 -0
  38. package/dist/canvas/editor/editor-mode.js +0 -0
  39. package/dist/canvas/editor/frame.js +87 -0
  40. package/dist/canvas/editor/hand-context.js +8 -0
  41. package/dist/canvas/editor/history-core.js +221 -0
  42. package/dist/canvas/editor/keymap.js +193 -0
  43. package/dist/canvas/editor/layout-actions.js +170 -0
  44. package/dist/canvas/editor/lock.js +65 -0
  45. package/dist/canvas/editor/rebase.js +279 -0
  46. package/dist/canvas/editor/style.js +174 -0
  47. package/dist/canvas/editor/text-field-context.js +8 -0
  48. package/dist/canvas/editor/z-order.js +82 -0
  49. package/dist/canvas/headless/alignment.js +20 -0
  50. package/dist/canvas/headless/break-apart-find.js +324 -0
  51. package/dist/canvas/headless/break-apart-pieces.js +74 -0
  52. package/dist/canvas/headless/break-apart-plan.js +378 -0
  53. package/dist/canvas/headless/break-apart-style.js +93 -0
  54. package/dist/canvas/headless/break-apart.js +206 -0
  55. package/dist/canvas/headless/canvas-cleanup.js +70 -0
  56. package/dist/canvas/headless/edge-anchors.js +134 -0
  57. package/dist/canvas/headless/edge-memo.js +28 -0
  58. package/dist/canvas/headless/edge-serialization.js +31 -0
  59. package/dist/canvas/headless/figure-geometry.js +331 -0
  60. package/dist/canvas/headless/fit-viewport.js +82 -0
  61. package/dist/canvas/headless/grid.js +96 -0
  62. package/dist/canvas/headless/icon-node.js +45 -0
  63. package/dist/canvas/headless/index.js +1 -0
  64. package/dist/canvas/headless/linkify.js +25 -0
  65. package/dist/canvas/headless/pen-marks.js +115 -0
  66. package/dist/canvas/headless/sketch.js +250 -0
  67. package/dist/canvas/headless/snap-guides.js +100 -0
  68. package/dist/canvas/headless/svg-figure.js +137 -0
  69. package/dist/canvas/headless/table-layout.js +161 -0
  70. package/dist/canvas/index.js +25 -0
  71. package/dist/canvas/lod/lod-mode.svelte.js +22 -0
  72. package/dist/canvas/lod/render-all.svelte.js +45 -0
  73. package/dist/canvas/ops/arrange.js +83 -0
  74. package/dist/canvas/ops/ops.js +318 -0
  75. package/dist/canvas/pen/colors.js +41 -0
  76. package/dist/canvas/pen/eraser.js +35 -0
  77. package/dist/canvas/pen/geometry.js +143 -0
  78. package/dist/canvas/pen/index.js +9 -0
  79. package/dist/canvas/pen/ops.js +15 -0
  80. package/dist/canvas/pen/pen-session.js +171 -0
  81. package/dist/canvas/pen/plugin.js +129 -0
  82. package/dist/canvas/pen/shapes.js +80 -0
  83. package/dist/canvas/pen/stroke-path.js +37 -0
  84. package/dist/canvas/pen/types.js +30 -0
  85. package/dist/canvas/pen/ui.js +20 -0
  86. package/dist/canvas/plugin.js +51 -0
  87. package/dist/canvas/screen/glyphs.js +24 -0
  88. package/dist/canvas/screen/labels.js +24 -0
  89. package/dist/canvas/screen/parts.js +140 -0
  90. package/dist/canvas/screen/plugin.js +71 -0
  91. package/dist/canvas/screen/types.js +44 -0
  92. package/dist/canvas/screen/ui.js +13 -0
  93. package/dist/canvas/test-utils.js +18 -0
  94. package/dist/canvas/types.js +0 -0
  95. package/dist/canvas/ui/forward-wheel.js +27 -0
  96. package/dist/canvas/ui/icons.js +133 -0
  97. package/dist/canvas/ui/place-panel.js +57 -0
  98. package/dist/canvas/ui/shortcut-tip.js +18 -0
  99. package/dist/canvas/ui/tools.js +411 -0
  100. package/dist/charts/dashboard/chart.js +1 -0
  101. package/dist/charts/dashboard/data-table.js +7 -0
  102. package/dist/charts/dashboard/index.js +13 -0
  103. package/dist/charts/dashboard/windows.js +5 -0
  104. package/dist/charts/edge-fade.js +7 -0
  105. package/dist/charts/hover-band.js +13 -0
  106. package/dist/charts/index.js +19 -0
  107. package/dist/charts/motion.js +15 -0
  108. package/dist/charts/primitives/chart-utils.js +22 -0
  109. package/dist/charts/primitives/index.js +4 -0
  110. package/dist/components/copy/clipboard.js +38 -0
  111. package/dist/components/copy/copied.svelte.js +26 -0
  112. package/dist/components/copy/index.js +3 -0
  113. package/dist/components/data-table/data-table.js +55 -0
  114. package/dist/components/data-table/index.js +7 -0
  115. package/dist/components/docs/index.js +3 -0
  116. package/dist/components/docs/scroll-spy.svelte.js +71 -0
  117. package/dist/components/field-row/index.js +1 -0
  118. package/dist/components/hold-to-confirm/index.js +1 -0
  119. package/dist/components/icon/index.js +1 -0
  120. package/dist/components/language-flag/index.js +1 -0
  121. package/dist/components/list-page/filter-section.js +4 -0
  122. package/dist/components/list-page/index.js +25 -0
  123. package/dist/components/list-page/list-table.js +5 -0
  124. package/dist/components/list-page/queue-rail.js +3 -0
  125. package/dist/components/list-page/section-host.js +8 -0
  126. package/dist/components/list-page/step.js +5 -0
  127. package/dist/components/list-page/types.js +0 -0
  128. package/dist/components/locale-switch/index.js +1 -0
  129. package/dist/components/mark/index.js +1 -0
  130. package/dist/components/open-in/index.js +1 -0
  131. package/dist/components/picker/index.js +3 -0
  132. package/dist/components/picker/trigger.js +33 -0
  133. package/dist/components/picker/types.js +0 -0
  134. package/dist/components/section-card/index.js +1 -0
  135. package/dist/components/section-header/index.js +2 -0
  136. package/dist/components/segmented/collapse.js +32 -0
  137. package/dist/components/segmented/index.js +8 -0
  138. package/dist/components/segmented/segmented.js +50 -0
  139. package/dist/components/star-rating-input/index.js +2 -0
  140. package/dist/components/svg/index.js +1 -0
  141. package/dist/components/table-view/index.js +1 -0
  142. package/dist/components/table-view/types.js +0 -0
  143. package/dist/components/tags-input/index.js +2 -0
  144. package/dist/components/tags-input/tags.js +90 -0
  145. package/dist/components/theme/index.js +3 -0
  146. package/dist/components/toolbar/index.js +3 -0
  147. package/dist/components/toolbar/toolbar-trigger.js +4 -0
  148. package/dist/editor/anchors.js +17 -0
  149. package/dist/editor/block-id.js +62 -0
  150. package/dist/editor/block-ops.js +105 -0
  151. package/dist/editor/doc-blocks.js +181 -0
  152. package/dist/editor/extensions.js +200 -0
  153. package/dist/editor/index.js +4 -0
  154. package/dist/editor/markdown-test-helpers.js +6 -0
  155. package/dist/editor/markdown.js +486 -0
  156. package/dist/editor/serializer.js +268 -0
  157. package/dist/editor/slash-items.js +104 -0
  158. package/dist/editor/youtube-url.js +27 -0
  159. package/dist/effects/animated-beam/index.js +1 -0
  160. package/dist/effects/animated-beam/path-calculator.svelte.js +42 -0
  161. package/dist/effects/animated-beam/types.js +0 -0
  162. package/dist/effects/border-beam/css.js +420 -0
  163. package/dist/effects/border-beam/presets.js +1792 -0
  164. package/dist/effects/border-beam/register-properties.js +39 -0
  165. package/dist/effects/border-beam/types.js +0 -0
  166. package/dist/effects/image-comparison/compare.js +4 -0
  167. package/dist/effects/image-reveal/index.js +1 -0
  168. package/dist/effects/image-reveal/types.js +0 -0
  169. package/dist/effects/magnetic/magnetic.js +54 -0
  170. package/dist/effects/reveal/index.js +4 -0
  171. package/dist/effects/reveal/observe.js +43 -0
  172. package/dist/effects/reveal/types.js +0 -0
  173. package/dist/effects/shader-gradient/color.js +34 -0
  174. package/dist/effects/shader-gradient/index.js +1 -0
  175. package/dist/effects/shader-gradient/renderer.js +320 -0
  176. package/dist/effects/shader-gradient/shaders.js +280 -0
  177. package/dist/effects/shader-gradient/types.js +0 -0
  178. package/dist/icons/apple-symbol-exports.js +126 -0
  179. package/dist/icons/sf-symbols.js +465 -0
  180. package/dist/icons/trades.js +34 -0
  181. package/dist/inbox/index.js +4 -0
  182. package/dist/inbox/logic.js +156 -0
  183. package/dist/inbox/types.js +46 -0
  184. package/dist/index.js +18 -0
  185. package/dist/lib/arrive.js +13 -0
  186. package/dist/lib/icon-types.js +0 -0
  187. package/dist/lib/initials.js +17 -0
  188. package/dist/lib/layout.js +1 -0
  189. package/dist/lib/panel.js +3 -0
  190. package/dist/lib/result.js +26 -0
  191. package/dist/lib/sliding-pill.js +45 -0
  192. package/dist/lib/theme-transition.js +53 -0
  193. package/dist/lib/tone.js +32 -0
  194. package/dist/lib/utils.js +5 -0
  195. package/dist/onboarding/define-tours.js +28 -0
  196. package/dist/onboarding/index.js +4 -0
  197. package/dist/onboarding/onboarding.svelte.js +15 -0
  198. package/dist/onboarding/pick.js +16 -0
  199. package/dist/onboarding/stores.js +50 -0
  200. package/dist/onboarding/types.js +1 -0
  201. package/dist/onboarding/wait.js +25 -0
  202. package/dist/panel/index.js +1 -0
  203. package/dist/panel/side-panel-types.js +0 -0
  204. package/dist/record/demo-data.js +512 -0
  205. package/dist/record/fields.js +148 -0
  206. package/dist/record/format.js +97 -0
  207. package/dist/record/gutter.js +1 -0
  208. package/dist/record/index.js +15 -0
  209. package/dist/record/kinds.js +110 -0
  210. package/dist/record/logic.js +255 -0
  211. package/dist/record/types.js +52 -0
  212. package/dist/shadcn/accordion/index.js +5 -0
  213. package/dist/shadcn/alert/index.js +5 -0
  214. package/dist/shadcn/alert-dialog/index.js +12 -0
  215. package/dist/shadcn/attachment/index.js +10 -0
  216. package/dist/shadcn/avatar/index.js +7 -0
  217. package/dist/shadcn/badge/index.js +2 -0
  218. package/dist/shadcn/breadcrumb/index.js +8 -0
  219. package/dist/shadcn/bubble/index.js +5 -0
  220. package/dist/shadcn/button/index.js +2 -0
  221. package/dist/shadcn/calendar/index.js +19 -0
  222. package/dist/shadcn/card/index.js +8 -0
  223. package/dist/shadcn/checkbox/index.js +2 -0
  224. package/dist/shadcn/code-block/index.js +9 -0
  225. package/dist/shadcn/collapsible/index.js +4 -0
  226. package/dist/shadcn/command/index.js +13 -0
  227. package/dist/shadcn/context-menu/index.js +17 -0
  228. package/dist/shadcn/dialog/index.js +11 -0
  229. package/dist/shadcn/dropdown-menu/index.js +19 -0
  230. package/dist/shadcn/dropdown-menu/menu-item.js +23 -0
  231. package/dist/shadcn/empty/index.js +7 -0
  232. package/dist/shadcn/field/index.js +12 -0
  233. package/dist/shadcn/hooks/highlighter.js +92 -0
  234. package/dist/shadcn/hooks/is-mobile.svelte.js +7 -0
  235. package/dist/shadcn/hooks/use-highlighted-code.svelte.js +65 -0
  236. package/dist/shadcn/hover-card/index.js +5 -0
  237. package/dist/shadcn/input/index.js +3 -0
  238. package/dist/shadcn/input-otp/index.js +5 -0
  239. package/dist/shadcn/kbd/index.js +3 -0
  240. package/dist/shadcn/label/index.js +2 -0
  241. package/dist/shadcn/marker/index.js +4 -0
  242. package/dist/shadcn/message/index.js +7 -0
  243. package/dist/shadcn/message-scroller/index.js +8 -0
  244. package/dist/shadcn/popover/index.js +9 -0
  245. package/dist/shadcn/progress/index.js +2 -0
  246. package/dist/shadcn/progress/tone.js +11 -0
  247. package/dist/shadcn/radio-group/index.js +3 -0
  248. package/dist/shadcn/scroll-area/index.js +3 -0
  249. package/dist/shadcn/select/index.js +12 -0
  250. package/dist/shadcn/separator/index.js +2 -0
  251. package/dist/shadcn/sheet/index.js +11 -0
  252. package/dist/shadcn/sidebar/constants.js +6 -0
  253. package/dist/shadcn/sidebar/context.svelte.js +38 -0
  254. package/dist/shadcn/sidebar/index.js +25 -0
  255. package/dist/shadcn/skeleton/index.js +2 -0
  256. package/dist/shadcn/slider/index.js +2 -0
  257. package/dist/shadcn/sonner/index.js +1 -0
  258. package/dist/shadcn/spinner/index.js +1 -0
  259. package/dist/shadcn/switch/index.js +2 -0
  260. package/dist/shadcn/table/index.js +9 -0
  261. package/dist/shadcn/tabs/index.js +5 -0
  262. package/dist/shadcn/textarea/index.js +2 -0
  263. package/dist/shadcn/toggle/index.js +3 -0
  264. package/dist/shadcn/toggle-group/index.js +3 -0
  265. package/dist/shadcn/tooltip/context.js +8 -0
  266. package/dist/shadcn/tooltip/index.js +6 -0
  267. package/dist/shadcn/trace/index.js +9 -0
  268. package/dist/slides/bridges.js +66 -0
  269. package/dist/slides/deck-controller.svelte.js +221 -0
  270. package/dist/slides/export/capture.js +81 -0
  271. package/dist/slides/export/ffmpeg.js +85 -0
  272. package/dist/slides/export/index.js +4 -0
  273. package/dist/slides/export/launch.js +8 -0
  274. package/dist/slides/export/pdf.js +78 -0
  275. package/dist/slides/export/types.js +0 -0
  276. package/dist/slides/index.js +7 -0
  277. package/dist/slides/labels.js +26 -0
  278. package/dist/slides/layout.js +47 -0
  279. package/dist/slides/model.js +9 -0
  280. package/dist/slides/navigation.js +89 -0
  281. package/dist/slides/types.js +6 -0
  282. package/dist/status/define-statuses.js +16 -0
  283. package/dist/status/index.js +4 -0
  284. package/dist/status/status-types.js +0 -0
  285. package/dist/test-setup.js +26 -0
  286. package/dist/thread/index.js +3 -0
  287. package/dist/thread/logic.js +94 -0
  288. package/dist/thread/types.js +23 -0
  289. package/dist/work/board-order.js +109 -0
  290. package/dist/work/dates.js +19 -0
  291. package/dist/work/detail/detail-types.js +20 -0
  292. package/dist/work/docs/edited.js +39 -0
  293. package/dist/work/docs/index.js +7 -0
  294. package/dist/work/docs/labels.js +38 -0
  295. package/dist/work/docs/menu.js +121 -0
  296. package/dist/work/docs/outline.js +39 -0
  297. package/dist/work/gantt/logic.js +260 -0
  298. package/dist/work/inbox/inbox-logic.js +94 -0
  299. package/dist/work/inbox/inbox-statuses.js +86 -0
  300. package/dist/work/inbox/inbox-types.js +0 -0
  301. package/dist/work/inbox/index.js +1 -0
  302. package/dist/work/index.js +21 -0
  303. package/dist/work/kanban-drag.js +25 -0
  304. package/dist/work/kanban-types.js +0 -0
  305. package/dist/work/presets/sales.js +35 -0
  306. package/dist/work/reorder.js +10 -0
  307. package/dist/work/task-types.js +0 -0
  308. package/dist/work/time/duration.js +60 -0
  309. package/dist/work/time/time-types.js +25 -0
  310. package/dist/work/use-kanban-dnd.svelte.js +128 -0
  311. package/package.json +102 -76
  312. package/src/agents/vendor/draw.ts +4 -4
  313. package/src/canvas/headless/canvas-cleanup.test.ts +1 -1
  314. package/src/canvas/ops/ops.ts +1 -1
  315. package/src/charts/area-chart.svelte +11 -1
  316. package/src/charts/bar-chart.svelte +18 -3
  317. package/src/charts/line-chart.svelte +11 -1
  318. package/src/components/data-table/data-table.ts +4 -5
  319. package/src/components/data-table/pagination.svelte +5 -3
  320. package/src/components/docs/on-this-page.svelte +82 -5
  321. package/src/components/docs/scroll-spy.svelte.ts +6 -1
  322. package/src/components/list-page/detail-header.svelte +7 -3
  323. package/src/components/list-page/empty-state.svelte +3 -1
  324. package/src/components/list-page/filter-popover.svelte +2 -2
  325. package/src/components/list-page/rail-row.svelte +5 -2
  326. package/src/components/mark/mark.svelte +2 -1
  327. package/src/components/section-card/section-card.svelte +3 -1
  328. package/src/components/section-header/section-header.svelte +5 -5
  329. package/src/components/segmented/segment.svelte +1 -1
  330. package/src/components/table-view/table-view.svelte +6 -1
  331. package/src/components/tags-input/tags-input.svelte +1 -1
  332. package/src/components/toolbar/toolbar-trigger.ts +8 -2
  333. package/src/components/toolbar/toolbar.svelte +1 -1
  334. package/src/editor/anchors.ts +28 -0
  335. package/src/editor/doc-blocks.ts +168 -0
  336. package/src/editor/doc-page.css +389 -0
  337. package/src/editor/editor.svelte +47 -5
  338. package/src/editor/extensions.ts +26 -2
  339. package/src/editor/markdown.ts +27 -2
  340. package/src/editor/slash-items.ts +18 -0
  341. package/src/icons/trades.ts +1 -1
  342. package/src/inbox/README.md +6 -4
  343. package/src/inbox/inbox.test.ts +1 -1
  344. package/src/inbox/index.ts +2 -2
  345. package/src/inbox/logic.ts +29 -5
  346. package/src/inbox/thread-inbox.svelte +219 -92
  347. package/src/inbox/types.ts +27 -1
  348. package/src/lib/initials.test.ts +25 -0
  349. package/src/lib/initials.ts +29 -0
  350. package/src/modules.test.ts +4 -64
  351. package/src/panel/README.md +1 -1
  352. package/src/record/README.md +1 -1
  353. package/src/record/demo-data.ts +4 -4
  354. package/src/record/info-list.svelte +5 -4
  355. package/src/record/next-up.svelte +1 -1
  356. package/src/record/project-list.svelte +1 -1
  357. package/src/record/record-demo.svelte +2 -2
  358. package/src/record/steps-rail.svelte +3 -3
  359. package/src/shadcn/badge/badge.svelte +1 -1
  360. package/src/shadcn/card/card-title.svelte +1 -5
  361. package/src/shadcn/empty/empty-description.svelte +1 -1
  362. package/src/shadcn/progress/progress.svelte +8 -1
  363. package/src/shadcn/table/table-cell.svelte +10 -2
  364. package/src/status/README.md +1 -1
  365. package/src/thread/README.md +2 -0
  366. package/src/thread/index.ts +9 -1
  367. package/src/thread/logic.ts +33 -3
  368. package/src/thread/thread-body.svelte +37 -0
  369. package/src/thread/thread-events.svelte +30 -0
  370. package/src/thread/thread.svelte +74 -59
  371. package/src/thread/types.ts +21 -0
  372. package/src/{tasks → work}/README.md +11 -8
  373. package/src/{tasks → work}/detail/task-thread.svelte +8 -15
  374. package/src/work/docs/doc-editor-page.svelte +194 -0
  375. package/src/work/docs/doc-menu.svelte +333 -0
  376. package/src/work/docs/docs-demo.svelte +227 -0
  377. package/src/work/docs/edited-chip.svelte +125 -0
  378. package/src/work/docs/edited.ts +54 -0
  379. package/src/work/docs/index.ts +38 -0
  380. package/src/work/docs/labels.ts +70 -0
  381. package/src/work/docs/menu.ts +127 -0
  382. package/src/work/docs/outline.ts +60 -0
  383. package/src/{tasks → work}/gantt/gantt-group-row.svelte +1 -1
  384. package/src/{tasks → work}/inbox/inbox-card.svelte +113 -50
  385. package/src/work/inbox/inbox-logic.ts +98 -0
  386. package/src/work/inbox/inbox-trace.svelte +58 -0
  387. package/src/{tasks → work}/inbox/inbox-types.ts +2 -0
  388. package/src/{tasks → work}/index.ts +0 -10
  389. package/src/{tasks → work}/kanban-board.svelte +1 -1
  390. package/src/{tasks → work}/task-list.svelte +1 -1
  391. package/src/{tasks → work}/time/time-tracker.svelte +2 -5
  392. package/src/tasks/inbox/inbox-logic.ts +0 -21
  393. package/src/tasks/inbox/inbox-trace.svelte +0 -51
  394. /package/src/{tasks → work}/ambient.d.ts +0 -0
  395. /package/src/{tasks → work}/board-order.ts +0 -0
  396. /package/src/{tasks → work}/dates.ts +0 -0
  397. /package/src/{tasks → work}/demo.svelte +0 -0
  398. /package/src/{tasks → work}/detail/detail-demo.svelte +0 -0
  399. /package/src/{tasks → work}/detail/detail-types.ts +0 -0
  400. /package/src/{tasks → work}/detail/task-chip.svelte +0 -0
  401. /package/src/{tasks → work}/detail/task-detail.svelte +0 -0
  402. /package/src/{tasks → work}/detail/task-property.svelte +0 -0
  403. /package/src/{tasks → work}/gantt/gantt-bar.svelte +0 -0
  404. /package/src/{tasks → work}/gantt/gantt-grid.svelte +0 -0
  405. /package/src/{tasks → work}/gantt/gantt-header.svelte +0 -0
  406. /package/src/{tasks → work}/gantt/gantt-task-row.svelte +0 -0
  407. /package/src/{tasks → work}/gantt/gantt.css +0 -0
  408. /package/src/{tasks → work}/gantt/gantt.test.ts +0 -0
  409. /package/src/{tasks → work}/gantt/logic.test.ts +0 -0
  410. /package/src/{tasks → work}/gantt/logic.ts +0 -0
  411. /package/src/{tasks → work}/inbox/inbox-draft.svelte +0 -0
  412. /package/src/{tasks → work}/inbox/inbox-statuses.ts +0 -0
  413. /package/src/{tasks → work}/inbox/index.ts +0 -0
  414. /package/src/{tasks → work}/kanban-column.svelte +0 -0
  415. /package/src/{tasks → work}/kanban-drag.ts +0 -0
  416. /package/src/{tasks → work}/kanban-types.ts +0 -0
  417. /package/src/{tasks → work}/presets/sales.ts +0 -0
  418. /package/src/{tasks → work}/reorder.ts +0 -0
  419. /package/src/{tasks → work}/task-gantt.svelte +0 -0
  420. /package/src/{tasks → work}/task-types.ts +0 -0
  421. /package/src/{tasks → work}/time/duration.ts +0 -0
  422. /package/src/{tasks → work}/time/time-chip.svelte +0 -0
  423. /package/src/{tasks → work}/time/time-dot.svelte +0 -0
  424. /package/src/{tasks → work}/time/time-ring.svelte +0 -0
  425. /package/src/{tasks → work}/time/time-types.ts +0 -0
  426. /package/src/{tasks → work}/use-kanban-dnd.svelte.ts +0 -0
@@ -1,8 +1,9 @@
1
- import { isOpen, isTyping } from "../tasks/inbox/inbox-logic.js";
1
+ import { isOpen, isTyping } from "../work/inbox/inbox-logic.js";
2
2
  import {
3
3
  ALL,
4
4
  DEFAULT_INBOX_LABELS,
5
5
  type InboxEntry,
6
+ type InboxEntryKind,
6
7
  type InboxFilter,
7
8
  type InboxKey,
8
9
  type InboxLabels,
@@ -97,13 +98,15 @@ export function subjectTypes(threads: InboxThread[]): string[] {
97
98
  }
98
99
 
99
100
  export function actorNames(threads: InboxThread[]): string[] {
100
- return [...new Set(threads.flatMap((thread) => thread.entries.map((entry) => entry.actor.name)))];
101
+ return [
102
+ ...new Set(threads.flatMap((thread) => thread.entries.map((entry) => entry.actor.name))),
103
+ ].filter(Boolean);
101
104
  }
102
105
 
103
106
  type KeyEventLike = Pick<
104
107
  KeyboardEvent,
105
108
  "key" | "metaKey" | "ctrlKey" | "altKey" | "defaultPrevented" | "target"
106
- >;
109
+ > & { shiftKey?: boolean; code?: string };
107
110
 
108
111
  const KEYS: Record<string, InboxKey> = {
109
112
  j: "next",
@@ -113,13 +116,20 @@ const KEYS: Record<string, InboxKey> = {
113
116
  a: "approve",
114
117
  d: "decline",
115
118
  s: "steer",
119
+ S: "unfollow",
116
120
  e: "edit",
117
121
  };
118
122
 
119
- /** What a key press asks for, or null when it isn't ours: a modifier is held, or focus is in a text field. */
123
+ /**
124
+ * What a key press asks for, or null when it isn't ours: focus is in a text field, or a modifier
125
+ * other than Linear's Shift+S, Shift+Backspace and Alt+U is held.
126
+ */
120
127
  export function inboxKeyOf(event: KeyEventLike): InboxKey | null {
121
- if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) return null;
128
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey) return null;
122
129
  if (isTyping(event.target)) return null;
130
+ // Option+U on a Mac types a dead key, so the physical key decides.
131
+ if (event.altKey) return event.code === "KeyU" && !event.shiftKey ? "readAll" : null;
132
+ if (event.key === "Backspace") return event.shiftKey ? "clearRead" : "clear";
123
133
  return KEYS[event.key] ?? null;
124
134
  }
125
135
 
@@ -167,3 +177,17 @@ export function entryLine(entry: InboxEntry, labels: InboxLabels): string {
167
177
  return labels.commented;
168
178
  }
169
179
  }
180
+
181
+ /** An entry as a sentence: who, then what. With no known actor it starts with the verb, capitalised. */
182
+ export function sentenceOf(entry: InboxEntry, labels: InboxLabels): string {
183
+ const line = entryLine(entry, labels);
184
+ if (entry.actor.name) return `${entry.actor.name} ${line}`;
185
+ return line.charAt(0).toUpperCase() + line.slice(1);
186
+ }
187
+
188
+ /** What happened to the subject, as against something a person wrote. */
189
+ const EVENT_KINDS: ReadonlySet<InboxEntryKind> = new Set(["status", "assigned", "proposal"]);
190
+
191
+ export function isEvent(entry: InboxEntry): boolean {
192
+ return EVENT_KINDS.has(entry.kind);
193
+ }
@@ -1,7 +1,19 @@
1
1
  <script lang="ts">
2
2
  import { tick, type Snippet } from "svelte";
3
- import { ArrowSquareOut, BellSlash, Circle, Envelope, EnvelopeOpen } from "phosphor-svelte";
4
- import { AgentAvatar } from "../agents/index.js";
3
+ import {
4
+ ArrowRight,
5
+ ArrowSquareOut,
6
+ Bell,
7
+ BellSlash,
8
+ Check,
9
+ Checks,
10
+ Circle,
11
+ Envelope,
12
+ EnvelopeOpen,
13
+ EyeSlash,
14
+ Sparkle,
15
+ UserPlus,
16
+ } from "phosphor-svelte";
5
17
  import DetailHeader from "../components/list-page/detail-header.svelte";
6
18
  import DetailPanel from "../components/list-page/detail-panel.svelte";
7
19
  import FilterItem from "../components/list-page/filter-item.svelte";
@@ -18,12 +30,20 @@
18
30
  import { Kbd, KbdGroup } from "../shadcn/kbd/index.js";
19
31
  import { cn } from "../lib/utils.js";
20
32
  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";
33
+ import {
34
+ Thread,
35
+ type ThreadComment,
36
+ type ThreadEvent,
37
+ type ThreadLabels,
38
+ } from "../thread/index.js";
39
+ import InboxCard from "../work/inbox/inbox-card.svelte";
40
+ import { whenOf } from "../work/inbox/inbox-logic.js";
41
+ import type { InboxItem, InboxVerdict } from "../work/inbox/inbox-types.js";
23
42
  import {
24
43
  NO_FILTER,
25
44
  actorNames,
26
- entryLine,
45
+ sentenceOf,
46
+ isEvent,
27
47
  filterThreads,
28
48
  inboxKeyOf,
29
49
  resolveInboxLabels,
@@ -57,12 +77,33 @@
57
77
  status?: Snippet<[InboxSubject]>;
58
78
  /** Replaces a pinned proposal's summary lines. */
59
79
  renderProposal?: Snippet<[InboxItem]>;
60
- /** How a timestamp reads. Defaults to a short date and time. */
80
+ /** Content under an entry's text, such as links to what it mentions. */
81
+ attachments?: Snippet<[InboxEntry]>;
82
+ /** The words of the thread under the pinned card, such as its "Activity" heading. */
83
+ threadLabels?: Partial<ThreadLabels>;
84
+ /** How a timestamp reads. Defaults to a relative time. */
61
85
  when?: (at: string) => string;
62
86
  onSelect?: (thread: InboxThread) => void;
63
87
  /** `read` is true when the thread had unread entries, so the host marks them all read. */
64
88
  onRead?: (thread: InboxThread, read: boolean) => void;
89
+ /** Alt+U: mark every thread read. */
90
+ onReadAll?: () => void;
65
91
  onSnooze?: (request: InboxSnooze) => void;
92
+ /** The host is passing snoozed threads too, each with `subject.snoozedUntil`. */
93
+ showSnoozed?: boolean;
94
+ /** Adds "Show snoozed" to the filter. The host passes the snoozed entries or stops. */
95
+ onShowSnoozed?: (show: boolean) => void;
96
+ /** `h` on a snoozed thread brings it back now. */
97
+ onUnsnooze?: (thread: InboxThread) => void;
98
+ /** Shift+S on a thread whose `subject.followed` is set. */
99
+ onUnfollow?: (thread: InboxThread) => void;
100
+ /**
101
+ * Backspace: take the thread out of the inbox. The host stops passing its entries, and new
102
+ * activity can bring it back. A thread holding a pending proposal can't be cleared.
103
+ */
104
+ onClear?: (thread: InboxThread) => void;
105
+ /** Shift+Backspace: clear every thread with nothing unread and no pending proposal. */
106
+ onClearRead?: (threads: InboxThread[]) => void;
66
107
  onDecide?: (verdict: InboxVerdict, thread: InboxThread) => void;
67
108
  class?: string;
68
109
  }
@@ -76,20 +117,19 @@
76
117
  avatar,
77
118
  status,
78
119
  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
- },
120
+ attachments,
121
+ threadLabels,
122
+ when = (at) => whenOf(at),
90
123
  onSelect,
91
124
  onRead,
125
+ onReadAll,
92
126
  onSnooze,
127
+ showSnoozed = false,
128
+ onShowSnoozed,
129
+ onUnsnooze,
130
+ onUnfollow,
131
+ onClear,
132
+ onClearRead,
93
133
  onDecide,
94
134
  class: className,
95
135
  }: Props = $props();
@@ -110,14 +150,37 @@
110
150
  let current = $derived(visible.find((thread) => thread.key === selected) ?? visible[0]);
111
151
  let needsYouCount = $derived(all.filter((thread) => thread.pending.length > 0).length);
112
152
  let filtered = $derived(filter.type !== ALL || filter.who !== ALL || filter.needsYou);
113
- let narrowed = $derived(Number(filter.type !== ALL) + Number(filter.who !== ALL));
153
+ let narrowed = $derived(
154
+ Number(filter.type !== ALL) + Number(filter.who !== ALL) + Number(showSnoozed),
155
+ );
156
+ let anyUnread = $derived(all.some((thread) => thread.unread > 0));
114
157
  let views = $derived([
115
158
  { value: ALL, label: labels.all, count: all.length },
116
159
  { value: NEEDS_YOU, label: labels.needsYou, count: needsYouCount },
117
160
  ]);
161
+ let readThreads = $derived(
162
+ all.filter((thread) => thread.unread === 0 && thread.pending.length === 0),
163
+ );
118
164
  let pinned = $derived(current?.pending[0]);
119
- let timeline = $derived(
120
- current?.entries.filter((entry) => !current.pending.includes(entry)) ?? [],
165
+ let rest = $derived(current?.entries.filter((entry) => !current.pending.includes(entry)) ?? []);
166
+ let byId = $derived(new Map(rest.map((entry) => [entry.id, entry])));
167
+ let comments = $derived(
168
+ rest
169
+ .filter((entry) => !isEvent(entry))
170
+ .map((entry): ThreadComment => ({
171
+ id: entry.id,
172
+ author: entry.actor,
173
+ body: entry.body ?? "",
174
+ createdAt: entry.at,
175
+ })),
176
+ );
177
+ let events = $derived(
178
+ rest.filter(isEvent).map((entry): ThreadEvent => ({
179
+ id: entry.id,
180
+ title: sentenceOf(entry, labels),
181
+ at: entry.at,
182
+ icon: entry.kind === "status" ? ArrowRight : entry.kind === "assigned" ? UserPlus : Sparkle,
183
+ })),
121
184
  );
122
185
 
123
186
  function open(thread: InboxThread): void {
@@ -143,6 +206,35 @@
143
206
  snoozing = false;
144
207
  }
145
208
 
209
+ /** A snoozed thread comes back at once. Any other opens the snooze row. */
210
+ function snoozeKey(): void {
211
+ if (current?.subject.snoozedUntil && onUnsnooze) onUnsnooze(current);
212
+ else snoozing = !snoozing;
213
+ }
214
+
215
+ function unfollow(): void {
216
+ if (current?.subject.followed) onUnfollow?.(current);
217
+ }
218
+
219
+ function readAll(): void {
220
+ if (anyUnread) onReadAll?.();
221
+ }
222
+
223
+ /** Clears the open thread and opens the one below it, or above it at the end of the list. */
224
+ function clear(): void {
225
+ if (!current || !onClear || current.pending.length > 0) return;
226
+ const gone = current;
227
+ const index = visible.indexOf(gone);
228
+ const next = visible[index + 1] ?? visible[index - 1];
229
+ if (next) selected = next.key;
230
+ snoozing = false;
231
+ onClear(gone);
232
+ }
233
+
234
+ function clearRead(): void {
235
+ if (onClearRead && readThreads.length > 0) onClearRead(readThreads);
236
+ }
237
+
146
238
  function onkeydown(event: KeyboardEvent): void {
147
239
  if (snoozing && !event.defaultPrevented && !event.metaKey && !event.ctrlKey && !event.altKey) {
148
240
  const choice = snoozeChoices(new Date())[Number(event.key) - 1];
@@ -163,7 +255,11 @@
163
255
  next: () => move(1),
164
256
  previous: () => move(-1),
165
257
  toggleRead,
166
- snooze: () => (snoozing = !snoozing),
258
+ readAll,
259
+ snooze: snoozeKey,
260
+ unfollow,
261
+ clear,
262
+ clearRead,
167
263
  approve: () => card?.approve(),
168
264
  decline: () => card?.decline(),
169
265
  steer: () => card?.steer(),
@@ -181,32 +277,18 @@
181
277
  : labels.snoozeNextWeek;
182
278
  }
183
279
 
184
- function initial(name: string): string {
185
- return name.trim().charAt(0).toLocaleUpperCase() || "?";
186
- }
187
-
188
280
  function lastEntry(thread: InboxThread): InboxEntry {
189
281
  return thread.entries[thread.entries.length - 1];
190
282
  }
191
283
  </script>
192
284
 
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>
285
+ {#snippet threadAvatar(comment: ThreadComment)}
286
+ {@render avatar?.(comment.author)}
287
+ {/snippet}
288
+
289
+ {#snippet threadAttachments(comment: ThreadComment)}
290
+ {@const entry = byId.get(comment.id)}
291
+ {#if entry}{@render attachments?.(entry)}{/if}
210
292
  {/snippet}
211
293
 
212
294
  {#snippet subjectStatus()}
@@ -222,11 +304,13 @@
222
304
  class={cn("flex h-full min-h-0 flex-col gap-3", className)}
223
305
  {onkeydown}
224
306
  >
307
+ <!-- WHY *:shrink-0: the header and panels clip their overflow, so they squashed to a sliver
308
+ when an expanded card filled the pane. Now the pane scrolls. -->
225
309
  <MasterDetail
226
310
  bind:pane
227
311
  railWidth={queueRailWidth}
228
312
  railClass={queueRailClass}
229
- detailClass="flex flex-col gap-4 overflow-y-auto"
313
+ detailClass="flex flex-col gap-4 overflow-y-auto *:shrink-0"
230
314
  pad={false}
231
315
  backLabel={labels.title}
232
316
  class="min-h-0 flex-1"
@@ -245,9 +329,17 @@
245
329
  label={labels.filter}
246
330
  count={narrowed}
247
331
  align="end"
248
- onClear={() => (filter = { ...NO_FILTER, needsYou: filter.needsYou })}
332
+ onClear={() => {
333
+ filter = { ...NO_FILTER, needsYou: filter.needsYou };
334
+ if (showSnoozed) onShowSnoozed?.(false);
335
+ }}
249
336
  clearLabel={labels.clearFilters}
250
337
  >
338
+ {#if onShowSnoozed}
339
+ <FilterItem checked={showSnoozed} onclick={() => onShowSnoozed(!showSnoozed)}>
340
+ {labels.showSnoozed}
341
+ </FilterItem>
342
+ {/if}
251
343
  {#if types.length > 1}
252
344
  <p class={filterSectionClass()}>{labels.kind}</p>
253
345
  {#each [ALL, ...types] as type (type)}
@@ -271,6 +363,32 @@
271
363
  </FilterItem>
272
364
  {/each}
273
365
  </FilterPopover>
366
+ {#if onReadAll && anyUnread}
367
+ <Button
368
+ size="sm"
369
+ variant="ghost"
370
+ icon
371
+ onclick={readAll}
372
+ aria-label={labels.markAllRead}
373
+ title={labels.markAllRead}
374
+ aria-keyshortcuts="Alt+U"
375
+ >
376
+ <EnvelopeOpen aria-hidden="true" />
377
+ </Button>
378
+ {/if}
379
+ {#if onClearRead && readThreads.length > 0}
380
+ <Button
381
+ size="sm"
382
+ variant="ghost"
383
+ icon
384
+ onclick={clearRead}
385
+ aria-label={labels.clearRead}
386
+ title={labels.clearRead}
387
+ aria-keyshortcuts="Shift+Backspace"
388
+ >
389
+ <Checks aria-hidden="true" />
390
+ </Button>
391
+ {/if}
274
392
  </div>
275
393
  </RailHead>
276
394
  <RailBody>
@@ -283,12 +401,19 @@
283
401
  titleLines={2}
284
402
  selected={current?.key === thread.key}
285
403
  onclick={() => open(thread)}
286
- preview="{last.actor.name} {entryLine(last, labels)}"
287
- meta={[thread.subject.ref, when(thread.latestAt)]}
404
+ preview={sentenceOf(last, labels)}
405
+ meta={[
406
+ thread.subject.ref,
407
+ thread.subject.snoozedUntil
408
+ ? `${labels.snoozed} ${when(thread.subject.snoozedUntil)}`
409
+ : when(thread.latestAt),
410
+ ]}
288
411
  metaTruncate="last"
289
412
  >
290
413
  {#snippet trailing()}
291
- {#if thread.pending.length > 0}
414
+ {#if thread.subject.snoozedUntil}
415
+ <BellSlash class="size-3.5 text-muted-foreground" aria-label={labels.snoozed} />
416
+ {:else if thread.pending.length > 0}
292
417
  <span
293
418
  class={cn(
294
419
  "inline-flex h-5 items-center rounded-sm border px-1.5 font-medium",
@@ -342,15 +467,31 @@
342
467
  <Envelope aria-hidden="true" />{labels.markUnread}
343
468
  {/if}
344
469
  </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>
470
+ {#if current.subject.snoozedUntil && onUnsnooze}
471
+ <Button size="sm" variant="ghost" onclick={snoozeKey} aria-keyshortcuts="h">
472
+ <Bell aria-hidden="true" />{labels.unsnooze}
473
+ </Button>
474
+ {:else}
475
+ <Button
476
+ size="sm"
477
+ variant="ghost"
478
+ onclick={snoozeKey}
479
+ aria-expanded={snoozing}
480
+ aria-keyshortcuts="h"
481
+ >
482
+ <BellSlash aria-hidden="true" />{labels.snooze}
483
+ </Button>
484
+ {/if}
485
+ {#if onUnfollow && current.subject.followed}
486
+ <Button size="sm" variant="ghost" onclick={unfollow} aria-keyshortcuts="Shift+S">
487
+ <EyeSlash aria-hidden="true" />{labels.unfollow}
488
+ </Button>
489
+ {/if}
490
+ {#if onClear && current.pending.length === 0}
491
+ <Button size="sm" variant="ghost" onclick={clear} aria-keyshortcuts="Backspace">
492
+ <Check aria-hidden="true" />{labels.clear}
493
+ </Button>
494
+ {/if}
354
495
  {#if current.subject.href}
355
496
  <Button size="sm" variant="ghost" href={current.subject.href}>
356
497
  <ArrowSquareOut aria-hidden="true" />{labels.open}
@@ -376,46 +517,24 @@
376
517
  selected={entry === pinned}
377
518
  onverdict={(verdict) => onDecide?.(verdict, current)}
378
519
  {renderProposal}
520
+ when={(at) => when(at)}
379
521
  />
380
522
  {/each}
381
523
 
382
- {#if timeline.length > 0}
524
+ {#if comments.length + events.length > 0}
383
525
  <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>
526
+ <Thread
527
+ {comments}
528
+ {events}
529
+ labels={{
530
+ agent: labels.agent,
531
+ ...threadLabels,
532
+ }}
533
+ avatar={avatar ? threadAvatar : undefined}
534
+ attachments={attachments ? threadAttachments : undefined}
535
+ when={(comment) => when(comment.createdAt)}
536
+ whenEvent={(event) => when(event.at)}
537
+ />
419
538
  </DetailPanel>
420
539
  {/if}
421
540
  {:else}
@@ -431,7 +550,15 @@
431
550
  ><KbdGroup><Kbd>j</Kbd><Kbd>k</Kbd></KbdGroup> {labels.hintMove}</span
432
551
  >
433
552
  <span class="inline-flex items-center gap-1.5"><Kbd>u</Kbd> {labels.hintRead}</span>
553
+ {#if onReadAll}
554
+ <span class="inline-flex items-center gap-1.5"
555
+ ><KbdGroup><Kbd>⌥</Kbd><Kbd>u</Kbd></KbdGroup> {labels.hintReadAll}</span
556
+ >
557
+ {/if}
434
558
  <span class="inline-flex items-center gap-1.5"><Kbd>h</Kbd> {labels.hintSnooze}</span>
559
+ {#if onClear}
560
+ <span class="inline-flex items-center gap-1.5"><Kbd>⌫</Kbd> {labels.hintClear}</span>
561
+ {/if}
435
562
  <span class="inline-flex items-center gap-1.5"
436
563
  ><KbdGroup><Kbd>a</Kbd><Kbd>d</Kbd><Kbd>s</Kbd><Kbd>e</Kbd></KbdGroup> {labels.hintAct}</span
437
564
  >
@@ -1,4 +1,4 @@
1
- import type { InboxItem } from "../tasks/inbox/inbox-types.js";
1
+ import type { InboxItem } from "../work/inbox/inbox-types.js";
2
2
 
3
3
  export type InboxActorKind = "user" | "agent";
4
4
 
@@ -28,6 +28,10 @@ export type InboxSubject = {
28
28
  /** A status id the host draws itself, for a badge in the thread header. */
29
29
  status?: string;
30
30
  href?: string;
31
+ /** ISO time a snoozed thread comes back. Set only when the host shows snoozed threads. */
32
+ snoozedUntil?: string;
33
+ /** The person follows this subject, so the thread offers Unfollow. */
34
+ followed?: boolean;
31
35
  };
32
36
 
33
37
  export type InboxEntry = {
@@ -77,7 +81,11 @@ export type InboxKey =
77
81
  | "next"
78
82
  | "previous"
79
83
  | "toggleRead"
84
+ | "readAll"
80
85
  | "snooze"
86
+ | "unfollow"
87
+ | "clear"
88
+ | "clearRead"
81
89
  | "approve"
82
90
  | "decline"
83
91
  | "steer"
@@ -101,6 +109,13 @@ export type InboxLabels = {
101
109
  snoozeLater: string;
102
110
  snoozeTomorrow: string;
103
111
  snoozeNextWeek: string;
112
+ snoozed: string;
113
+ showSnoozed: string;
114
+ unsnooze: string;
115
+ unfollow: string;
116
+ markAllRead: string;
117
+ clear: string;
118
+ clearRead: string;
104
119
  selectThread: string;
105
120
  filterType: string;
106
121
  filterWho: string;
@@ -119,7 +134,9 @@ export type InboxLabels = {
119
134
  /** Keyboard hint words. */
120
135
  hintMove: string;
121
136
  hintRead: string;
137
+ hintReadAll: string;
122
138
  hintSnooze: string;
139
+ hintClear: string;
123
140
  hintAct: string;
124
141
  };
125
142
 
@@ -139,6 +156,13 @@ export const DEFAULT_INBOX_LABELS: InboxLabels = {
139
156
  snoozeLater: "Later today",
140
157
  snoozeTomorrow: "Tomorrow",
141
158
  snoozeNextWeek: "Next week",
159
+ snoozed: "Snoozed",
160
+ showSnoozed: "Show snoozed",
161
+ unsnooze: "Unsnooze",
162
+ unfollow: "Unfollow",
163
+ markAllRead: "Mark all read",
164
+ clear: "Clear",
165
+ clearRead: "Clear read",
142
166
  selectThread: "Select a thread",
143
167
  filterType: "Filter by type",
144
168
  filterWho: "Filter by person or agent",
@@ -156,6 +180,8 @@ export const DEFAULT_INBOX_LABELS: InboxLabels = {
156
180
  proposed: "asks for your call",
157
181
  hintMove: "move",
158
182
  hintRead: "read",
183
+ hintReadAll: "all read",
159
184
  hintSnooze: "snooze",
185
+ hintClear: "clear, with shift every read thread",
160
186
  hintAct: "approve, decline, steer, edit",
161
187
  };
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { initial, initials } from "./initials.js";
3
+
4
+ describe("initial", () => {
5
+ it("keeps an emoji whole and uppercases a lowercase name", () => {
6
+ expect(initial("🦊 Fox")).toBe("🦊");
7
+ expect(initial("dana")).toBe("D");
8
+ expect(initial("Ólafur Darri")).toBe("Ó");
9
+ });
10
+ it("falls back for a missing or blank name", () => {
11
+ expect(initial(undefined)).toBe("?");
12
+ expect(initial(" ")).toBe("?");
13
+ });
14
+ });
15
+
16
+ describe("initials", () => {
17
+ it("takes the first and last word and skips a suffix", () => {
18
+ expect(initials("Christopher Alexander Montgomery III")).toBe("CM");
19
+ expect(initials(" Sam Lee ")).toBe("SL");
20
+ });
21
+ it("gives one letter for one word and one script without spaces", () => {
22
+ expect(initials("Jo")).toBe("J");
23
+ expect(initials("王秀英")).toBe("王");
24
+ });
25
+ });
@@ -0,0 +1,29 @@
1
+ const segmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter() : null;
2
+
3
+ /** The first whole character of a string: an emoji or an accented letter stays in one piece. */
4
+ function firstGrapheme(text: string): string {
5
+ if (!text) return "";
6
+ if (segmenter) return segmenter.segment(text)[Symbol.iterator]().next().value?.segment ?? "";
7
+ return Array.from(text)[0] ?? "";
8
+ }
9
+
10
+ /** One letter for an avatar fallback. Falls back to `fallback` for an empty or missing name. */
11
+ export function initial(name: string | null | undefined, fallback = "?"): string {
12
+ const first = firstGrapheme((name ?? "").trim());
13
+ return first ? first.toLocaleUpperCase() : fallback;
14
+ }
15
+
16
+ /**
17
+ * Two letters for an avatar fallback, first and last word, skipping suffixes: "Christopher
18
+ * Montgomery III" gives "CM". A one-word name gives one letter.
19
+ */
20
+ export function initials(name: string | null | undefined, fallback = "?"): string {
21
+ const words = (name ?? "")
22
+ .trim()
23
+ .split(/\s+/)
24
+ .filter((word) => word && !/^(?:[IVX]{1,4}|jr\.?|sr\.?)$/i.test(word));
25
+ if (words.length === 0) return initial(name, fallback);
26
+ const head = firstGrapheme(words[0]);
27
+ const tail = words.length > 1 ? firstGrapheme(words[words.length - 1]) : "";
28
+ return (head + tail).toLocaleUpperCase();
29
+ }