@heyramzi/ui 2.11.1 → 2.11.2

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 (266) hide show
  1. package/dist/agents/helpers.js +38 -38
  2. package/dist/agents/page-state.js +49 -50
  3. package/dist/agents/vendor/color.js +146 -127
  4. package/dist/agents/vendor/draw.js +617 -508
  5. package/dist/agents/vendor/engine.js +698 -599
  6. package/dist/agents/vendor/mesh.js +653 -683
  7. package/dist/agents/vendor/plastic.js +3913 -3112
  8. package/dist/agents/vendor/presets.js +163 -164
  9. package/dist/agents/vendor/shapes.js +21 -20
  10. package/dist/agents/vendor/ticker.js +41 -42
  11. package/dist/agents/vendor/types.js +1 -0
  12. package/dist/agents/vendor/wear.js +1421 -1448
  13. package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
  14. package/dist/ai/confirmation/index.js +3 -1
  15. package/dist/ai/index.js +33 -0
  16. package/dist/ai/prompt-input/index.js +3 -1
  17. package/dist/ai/reasoning/index.js +3 -1
  18. package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
  19. package/dist/ai/suggestion/index.js +3 -1
  20. package/dist/ai/typing-dots/index.js +3 -1
  21. package/dist/animations/text-swap.js +24 -24
  22. package/dist/brand/index.js +54 -48
  23. package/dist/canvas/card/resize.js +5 -3
  24. package/dist/canvas/edges/edge-path.js +255 -242
  25. package/dist/canvas/editor/context-menu.js +39 -36
  26. package/dist/canvas/editor/dispatch.svelte.js +154 -151
  27. package/dist/canvas/editor/editor-context.js +7 -2
  28. package/dist/canvas/editor/editor-mode.js +1 -0
  29. package/dist/canvas/editor/frame.js +73 -76
  30. package/dist/canvas/editor/hand-context.js +5 -2
  31. package/dist/canvas/editor/history-core.js +163 -191
  32. package/dist/canvas/editor/keymap.js +95 -169
  33. package/dist/canvas/editor/layout-actions.js +143 -144
  34. package/dist/canvas/editor/lock.js +52 -51
  35. package/dist/canvas/editor/rebase.js +217 -240
  36. package/dist/canvas/editor/style.js +132 -137
  37. package/dist/canvas/editor/text-field-context.js +7 -2
  38. package/dist/canvas/editor/z-order.js +65 -70
  39. package/dist/canvas/headless/alignment.js +22 -16
  40. package/dist/canvas/headless/break-apart-find.js +286 -279
  41. package/dist/canvas/headless/break-apart-pieces.js +62 -58
  42. package/dist/canvas/headless/break-apart-plan.js +299 -325
  43. package/dist/canvas/headless/break-apart-style.js +75 -70
  44. package/dist/canvas/headless/break-apart.js +188 -181
  45. package/dist/canvas/headless/canvas-cleanup.js +78 -58
  46. package/dist/canvas/headless/edge-anchors.js +115 -116
  47. package/dist/canvas/headless/edge-memo.js +24 -22
  48. package/dist/canvas/headless/edge-serialization.js +17 -27
  49. package/dist/canvas/headless/figure-geometry.js +232 -264
  50. package/dist/canvas/headless/fit-viewport.js +84 -67
  51. package/dist/canvas/headless/grid.js +88 -86
  52. package/dist/canvas/headless/icon-node.js +44 -36
  53. package/dist/canvas/headless/index.js +1 -0
  54. package/dist/canvas/headless/linkify.js +17 -19
  55. package/dist/canvas/headless/pen-marks.js +101 -95
  56. package/dist/canvas/headless/sketch.js +231 -209
  57. package/dist/canvas/headless/snap-guides.js +99 -83
  58. package/dist/canvas/headless/svg-figure.js +133 -117
  59. package/dist/canvas/headless/table-layout.js +146 -107
  60. package/dist/canvas/index.js +2 -0
  61. package/dist/canvas/lod/lod-mode.svelte.js +20 -18
  62. package/dist/canvas/lod/render-all.svelte.js +36 -39
  63. package/dist/canvas/ops/arrange.js +78 -71
  64. package/dist/canvas/ops/ops.js +260 -255
  65. package/dist/canvas/pen/colors.js +37 -35
  66. package/dist/canvas/pen/eraser.js +28 -27
  67. package/dist/canvas/pen/geometry.js +120 -130
  68. package/dist/canvas/pen/index.js +1 -0
  69. package/dist/canvas/pen/ops.js +11 -11
  70. package/dist/canvas/pen/pen-session.js +150 -159
  71. package/dist/canvas/pen/plugin.js +107 -104
  72. package/dist/canvas/pen/shapes.js +73 -68
  73. package/dist/canvas/pen/stroke-path.js +32 -30
  74. package/dist/canvas/pen/types.js +21 -21
  75. package/dist/canvas/pen/ui.js +10 -8
  76. package/dist/canvas/plugin.js +46 -46
  77. package/dist/canvas/screen/glyphs.js +12 -11
  78. package/dist/canvas/screen/labels.js +22 -22
  79. package/dist/canvas/screen/parts.js +98 -111
  80. package/dist/canvas/screen/plugin.js +47 -56
  81. package/dist/canvas/screen/types.js +30 -35
  82. package/dist/canvas/screen/ui.js +7 -7
  83. package/dist/canvas/test-utils.js +14 -14
  84. package/dist/canvas/types.js +1 -0
  85. package/dist/canvas/ui/forward-wheel.js +24 -25
  86. package/dist/canvas/ui/icons.js +64 -131
  87. package/dist/canvas/ui/place-panel.js +58 -49
  88. package/dist/canvas/ui/shortcut-tip.js +14 -12
  89. package/dist/canvas/ui/tools.js +300 -385
  90. package/dist/charts/dashboard/chart.js +4 -0
  91. package/dist/charts/dashboard/data-table.js +11 -0
  92. package/dist/charts/dashboard/windows.js +8 -3
  93. package/dist/charts/edge-fade.js +10 -5
  94. package/dist/charts/hover-band.js +16 -11
  95. package/dist/charts/index.js +0 -1
  96. package/dist/charts/motion.js +11 -7
  97. package/dist/charts/primitives/chart-utils.js +12 -12
  98. package/dist/components/copy/clipboard.js +32 -32
  99. package/dist/components/copy/copied.svelte.js +31 -23
  100. package/dist/components/data-table/data-table.js +39 -39
  101. package/dist/components/docs/index.js +4 -0
  102. package/dist/components/docs/scroll-spy.svelte.js +73 -68
  103. package/dist/components/list-page/filter-section.js +4 -1
  104. package/dist/components/list-page/index.js +2 -0
  105. package/dist/components/list-page/list-table.js +7 -2
  106. package/dist/components/list-page/queue-rail.js +14 -0
  107. package/dist/components/list-page/section-host.js +4 -2
  108. package/dist/components/list-page/step.js +4 -3
  109. package/dist/components/list-page/types.js +1 -0
  110. package/dist/components/picker/trigger.js +48 -30
  111. package/dist/components/picker/types.js +1 -0
  112. package/dist/components/segmented/collapse.js +38 -27
  113. package/dist/components/segmented/index.js +8 -0
  114. package/dist/components/segmented/segmented.js +67 -42
  115. package/dist/components/star-rating-input/index.js +3 -1
  116. package/dist/components/table-view/types.js +1 -0
  117. package/dist/components/tags-input/tags.js +81 -69
  118. package/dist/components/theme/index.js +4 -0
  119. package/dist/components/toolbar/index.js +4 -0
  120. package/dist/components/toolbar/toolbar-trigger.js +14 -1
  121. package/dist/editor/anchors.js +17 -13
  122. package/dist/editor/block-id.js +52 -51
  123. package/dist/editor/block-ops.js +108 -80
  124. package/dist/editor/doc-blocks.js +155 -166
  125. package/dist/editor/extensions.js +139 -180
  126. package/dist/editor/index.js +4 -0
  127. package/dist/editor/markdown-test-helpers.js +4 -2
  128. package/dist/editor/markdown.js +420 -408
  129. package/dist/editor/serializer.js +204 -200
  130. package/dist/editor/slash-items.js +89 -101
  131. package/dist/editor/youtube-url.js +25 -20
  132. package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
  133. package/dist/effects/animated-beam/types.js +1 -0
  134. package/dist/effects/border-beam/css.js +92 -78
  135. package/dist/effects/border-beam/presets.js +1705 -1720
  136. package/dist/effects/border-beam/register-properties.js +43 -33
  137. package/dist/effects/border-beam/types.js +1 -0
  138. package/dist/effects/image-comparison/compare.js +3 -2
  139. package/dist/effects/image-reveal/types.js +1 -0
  140. package/dist/effects/magnetic/magnetic.js +58 -52
  141. package/dist/effects/reveal/observe.js +50 -40
  142. package/dist/effects/reveal/types.js +1 -0
  143. package/dist/effects/shader-gradient/color.js +36 -29
  144. package/dist/effects/shader-gradient/renderer.js +384 -295
  145. package/dist/effects/shader-gradient/shaders.js +31 -0
  146. package/dist/effects/shader-gradient/types.js +1 -0
  147. package/dist/icons/apple-symbol-exports.js +99 -124
  148. package/dist/icons/sf-symbols.js +357 -461
  149. package/dist/icons/trades.js +43 -28
  150. package/dist/inbox/index.js +1 -0
  151. package/dist/inbox/logic.js +126 -122
  152. package/dist/inbox/types.js +43 -43
  153. package/dist/lib/arrive.js +19 -12
  154. package/dist/lib/icon-types.js +1 -0
  155. package/dist/lib/initials.js +16 -10
  156. package/dist/lib/layout.js +5 -0
  157. package/dist/lib/panel.js +13 -0
  158. package/dist/lib/result.js +22 -22
  159. package/dist/lib/sliding-pill.js +59 -44
  160. package/dist/lib/theme-transition.js +43 -43
  161. package/dist/lib/tone.js +31 -24
  162. package/dist/lib/utils.js +3 -1
  163. package/dist/onboarding/define-tours.js +24 -23
  164. package/dist/onboarding/onboarding.svelte.js +16 -12
  165. package/dist/onboarding/pick.js +10 -6
  166. package/dist/onboarding/stores.js +40 -41
  167. package/dist/onboarding/wait.js +24 -23
  168. package/dist/panel/side-panel-types.js +1 -0
  169. package/dist/record/demo-data.js +459 -486
  170. package/dist/record/fields.js +129 -131
  171. package/dist/record/format.js +70 -82
  172. package/dist/record/gutter.js +1 -0
  173. package/dist/record/kinds.js +95 -105
  174. package/dist/record/logic.js +238 -219
  175. package/dist/record/types.js +49 -48
  176. package/dist/shadcn/accordion/index.js +3 -1
  177. package/dist/shadcn/alert/index.js +3 -1
  178. package/dist/shadcn/alert-dialog/index.js +3 -1
  179. package/dist/shadcn/attachment/index.js +3 -1
  180. package/dist/shadcn/avatar/index.js +3 -1
  181. package/dist/shadcn/breadcrumb/index.js +3 -1
  182. package/dist/shadcn/bubble/index.js +3 -1
  183. package/dist/shadcn/button/index.js +3 -1
  184. package/dist/shadcn/calendar/index.js +3 -1
  185. package/dist/shadcn/card/index.js +3 -1
  186. package/dist/shadcn/checkbox/index.js +3 -1
  187. package/dist/shadcn/code-block/index.js +3 -1
  188. package/dist/shadcn/collapsible/index.js +3 -1
  189. package/dist/shadcn/command/index.js +4 -1
  190. package/dist/shadcn/context-menu/index.js +3 -1
  191. package/dist/shadcn/dialog/index.js +3 -1
  192. package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
  193. package/dist/shadcn/empty/index.js +3 -1
  194. package/dist/shadcn/field/index.js +3 -1
  195. package/dist/shadcn/hooks/highlighter.js +77 -75
  196. package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
  197. package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
  198. package/dist/shadcn/input/index.js +3 -1
  199. package/dist/shadcn/kbd/index.js +3 -1
  200. package/dist/shadcn/label/index.js +3 -1
  201. package/dist/shadcn/marker/index.js +3 -1
  202. package/dist/shadcn/message/index.js +3 -1
  203. package/dist/shadcn/message-scroller/index.js +3 -1
  204. package/dist/shadcn/popover/index.js +3 -1
  205. package/dist/shadcn/progress/index.js +3 -1
  206. package/dist/shadcn/progress/tone.js +8 -7
  207. package/dist/shadcn/radio-group/index.js +3 -1
  208. package/dist/shadcn/scroll-area/index.js +3 -1
  209. package/dist/shadcn/select/index.js +3 -1
  210. package/dist/shadcn/separator/index.js +3 -1
  211. package/dist/shadcn/sheet/index.js +3 -1
  212. package/dist/shadcn/sidebar/context.svelte.js +42 -28
  213. package/dist/shadcn/sidebar/index.js +3 -1
  214. package/dist/shadcn/skeleton/index.js +3 -1
  215. package/dist/shadcn/slider/index.js +3 -1
  216. package/dist/shadcn/switch/index.js +3 -1
  217. package/dist/shadcn/table/index.js +3 -1
  218. package/dist/shadcn/tabs/index.js +3 -1
  219. package/dist/shadcn/textarea/index.js +3 -1
  220. package/dist/shadcn/toggle/index.js +3 -1
  221. package/dist/shadcn/toggle-group/index.js +3 -1
  222. package/dist/shadcn/tooltip/context.js +7 -2
  223. package/dist/shadcn/tooltip/index.js +3 -1
  224. package/dist/shadcn/trace/index.js +3 -1
  225. package/dist/slides/bridges.js +65 -59
  226. package/dist/slides/deck-controller.svelte.js +210 -207
  227. package/dist/slides/export/capture.js +66 -71
  228. package/dist/slides/export/ffmpeg.js +76 -72
  229. package/dist/slides/export/index.js +3 -0
  230. package/dist/slides/export/launch.js +9 -6
  231. package/dist/slides/export/pdf.js +60 -54
  232. package/dist/slides/export/types.js +1 -0
  233. package/dist/slides/labels.js +24 -24
  234. package/dist/slides/layout.js +39 -41
  235. package/dist/slides/model.js +9 -7
  236. package/dist/slides/navigation.js +78 -73
  237. package/dist/slides/types.js +2 -2
  238. package/dist/status/define-statuses.js +18 -14
  239. package/dist/status/status-types.js +1 -0
  240. package/dist/test-setup.js +24 -20
  241. package/dist/thread/logic.js +73 -77
  242. package/dist/thread/types.js +21 -21
  243. package/dist/work/board-order.js +94 -90
  244. package/dist/work/dates.js +16 -9
  245. package/dist/work/detail/detail-types.js +18 -18
  246. package/dist/work/docs/edited.js +32 -29
  247. package/dist/work/docs/index.js +4 -0
  248. package/dist/work/docs/labels.js +34 -34
  249. package/dist/work/docs/menu.js +113 -106
  250. package/dist/work/docs/outline.js +39 -29
  251. package/dist/work/gantt/logic.js +211 -235
  252. package/dist/work/inbox/inbox-logic.js +71 -75
  253. package/dist/work/inbox/inbox-statuses.js +75 -72
  254. package/dist/work/inbox/inbox-types.js +1 -0
  255. package/dist/work/kanban-drag.js +27 -22
  256. package/dist/work/kanban-types.js +1 -0
  257. package/dist/work/presets/sales.js +36 -32
  258. package/dist/work/reorder.js +9 -8
  259. package/dist/work/task-types.js +1 -0
  260. package/dist/work/time/duration.js +51 -46
  261. package/dist/work/time/time-types.js +23 -23
  262. package/dist/work/use-kanban-dnd.svelte.js +126 -125
  263. package/package.json +3 -1
  264. package/src/components/list-page/master-detail.svelte +3 -1
  265. package/src/components/list-page/rail-row.svelte +4 -4
  266. package/src/editor/markdown.ts +30 -35
@@ -1,34 +1,49 @@
1
+ /**
2
+ * Trade icon map. Tabler covers the domain Lucide and Phosphor miss.
3
+ */
4
+ /*
5
+ * Lucide/Phosphor gap audit (Sept 2026) against the 14 trade pills in the "NOT SURE? TAP WHAT YOU DO" strip. Lucide has `hammer`, `wrench`, `hard-hat`, `bolt`, `brick-wall`,
6
+ */
7
+ /*
8
+ * `building`, `car`, but no `trowel`, no tiling grid, no `window` cleaning `spray/squeegee`, no `crane`, no dedicated `handyman` toolset. Phosphor has `hammer`, `wrench`, `hard-hat`, `paint-roller`, and also no `trowel`.
9
+ */
10
+ /*
11
+ * Tabler 3.46 (6184 icons, MIT) has all 14 at a consistent 2px stroke on 24×24: droplet/pipe, bolt, home (roof), tools, key, window,
12
+ */
13
+ /*
14
+ * car-garage, hammer / wave-saw-tool, helmet, trowel, grid-dots, leaf, car, crane. Svelte 5 port is ` @tabler/icons-svelte-runes` (runes, not the Svelte 4 ` @tabler/icons-svelte`).
15
+ */
1
16
  import { IconBolt, IconCar, IconCarGarage, IconCrane, IconDroplet, IconGridDots, IconHammer, IconHelmet, IconHome, IconKey, IconLeaf, IconTools, IconTrowel, IconWindow } from "@tabler/icons-svelte-runes";
2
17
  export const tradeIconMap = {
3
- plumbers: IconDroplet,
4
- electricians: IconBolt,
5
- roofers: IconHome,
6
- handymen: IconTools,
7
- locksmiths: IconKey,
8
- "window-cleaners": IconWindow,
9
- garages: IconCarGarage,
10
- carpenters: IconHammer,
11
- builders: IconHelmet,
12
- plasterers: IconTrowel,
13
- tilers: IconGridDots,
14
- gardeners: IconLeaf,
15
- "mobile-mechanics": IconCar,
16
- "construction-companies": IconCrane
18
+ plumbers: IconDroplet,
19
+ electricians: IconBolt,
20
+ roofers: IconHome,
21
+ handymen: IconTools,
22
+ locksmiths: IconKey,
23
+ "window-cleaners": IconWindow,
24
+ garages: IconCarGarage,
25
+ carpenters: IconHammer,
26
+ builders: IconHelmet,
27
+ plasterers: IconTrowel,
28
+ tilers: IconGridDots,
29
+ gardeners: IconLeaf,
30
+ "mobile-mechanics": IconCar,
31
+ "construction-companies": IconCrane
17
32
  };
18
33
  export const tradeLabels = {
19
- plumbers: "PLUMBERS",
20
- electricians: "ELECTRICIANS",
21
- roofers: "ROOFERS",
22
- handymen: "HANDYMEN",
23
- locksmiths: "LOCKSMITHS",
24
- "window-cleaners": "WINDOW CLEANERS",
25
- garages: "GARAGES",
26
- carpenters: "CARPENTERS",
27
- builders: "BUILDERS",
28
- plasterers: "PLASTERERS",
29
- tilers: "TILERS",
30
- gardeners: "GARDENERS",
31
- "mobile-mechanics": "MOBILE MECHANICS",
32
- "construction-companies": "CONSTRUCTION COMPANIES"
34
+ plumbers: "PLUMBERS",
35
+ electricians: "ELECTRICIANS",
36
+ roofers: "ROOFERS",
37
+ handymen: "HANDYMEN",
38
+ locksmiths: "LOCKSMITHS",
39
+ "window-cleaners": "WINDOW CLEANERS",
40
+ garages: "GARAGES",
41
+ carpenters: "CARPENTERS",
42
+ builders: "BUILDERS",
43
+ plasterers: "PLASTERERS",
44
+ tilers: "TILERS",
45
+ gardeners: "GARDENERS",
46
+ "mobile-mechanics": "MOBILE MECHANICS",
47
+ "construction-companies": "CONSTRUCTION COMPANIES"
33
48
  };
34
49
  export const tradeSlugs = Object.keys(tradeIconMap);
@@ -1,4 +1,5 @@
1
1
  export { default as ThreadInbox } from "../../src/inbox/thread-inbox.svelte";
2
2
  export { NO_FILTER, actorNames, entryLine, filterThreads, inboxKeyOf, isPending, resolveInboxLabels, snoozeChoices, subjectTypes, threadsOf } from "./logic.js";
3
3
  export { ALL, DEFAULT_INBOX_LABELS } from "./types.js";
4
+ // The approval card a pinned proposal is drawn with, and the types its `proposal` field carries.
4
5
  export { default as InboxCard } from "../../src/work/inbox/inbox-card.svelte";
@@ -1,156 +1,160 @@
1
1
  import { isOpen, isTyping } from "../work/inbox/inbox-logic.js";
2
2
  import { ALL, DEFAULT_INBOX_LABELS } from "./types.js";
3
+ /** A timestamp as a number, with NaN for one that doesn't parse. */
3
4
  function ms(at) {
4
- return Date.parse(at);
5
+ return Date.parse(at);
5
6
  }
6
7
  function latestOf(thread) {
7
- const time = ms(thread.latestAt);
8
- return Number.isNaN(time) ? Number.NEGATIVE_INFINITY : time;
8
+ const time = ms(thread.latestAt);
9
+ return Number.isNaN(time) ? Number.NEGATIVE_INFINITY : time;
9
10
  }
11
+ /** True while a proposal entry still waits for a verdict. */
10
12
  export function isPending(entry) {
11
- return entry.kind === "proposal" && !!entry.proposal && isOpen(entry.proposal);
13
+ return entry.kind === "proposal" && !!entry.proposal && isOpen(entry.proposal);
12
14
  }
15
+ /**
16
+ * One thread per subject, newest first. An entry with no subject id is dropped, and a malformed
17
+ * timestamp sorts last.
18
+ */
13
19
  export function threadsOf(entries, options = {}) {
14
- const groups = new Map();
15
- entries.forEach((entry, index)=>{
16
- const subject = entry.subject;
17
- if (!subject?.id) return;
18
- const key = `${subject.type}:${subject.id}`;
19
- groups.set(key, [
20
- ...groups.get(key) ?? [],
21
- {
22
- entry,
23
- subject,
24
- index,
25
- time: ms(entry.at)
26
- }
27
- ]);
28
- });
29
- const threads = [];
30
- for (const [key, rows] of groups){
31
- const ordered = rows.toSorted((a, b)=>{
32
- const aBad = Number.isNaN(a.time);
33
- const bBad = Number.isNaN(b.time);
34
- if (aBad !== bBad) return aBad ? 1 : -1;
35
- return (aBad ? 0 : a.time - b.time) || a.index - b.index;
36
- });
37
- const newest = ordered.findLast((row)=>!Number.isNaN(row.time)) ?? ordered[0];
38
- const list = ordered.map((row)=>row.entry);
39
- threads.push({
40
- key,
41
- subject: newest.subject,
42
- entries: list,
43
- unread: list.filter((entry)=>!entry.read).length,
44
- latestAt: newest.entry.at,
45
- pending: list.filter(isPending).toReversed()
46
- });
47
- }
48
- return threads.map((thread, index)=>({
49
- thread,
50
- index
51
- })).toSorted((a, b)=>{
52
- if (options.unreadFirst) {
53
- const unread = Number(b.thread.unread > 0) - Number(a.thread.unread > 0);
54
- if (unread) return unread;
55
- }
56
- const aTime = latestOf(a.thread);
57
- const bTime = latestOf(b.thread);
58
- if (aTime === bTime) return a.index - b.index;
59
- return bTime - aTime;
60
- }).map((row)=>row.thread);
20
+ const groups = new Map();
21
+ entries.forEach((entry, index) => {
22
+ const subject = entry.subject;
23
+ if (!subject?.id) return;
24
+ const key = `${subject.type}:${subject.id}`;
25
+ groups.set(key, [...groups.get(key) ?? [], {
26
+ entry,
27
+ subject,
28
+ index,
29
+ time: ms(entry.at)
30
+ }]);
31
+ });
32
+ const threads = [];
33
+ for (const [key, rows] of groups) {
34
+ const ordered = rows.toSorted((a, b) => {
35
+ const aBad = Number.isNaN(a.time);
36
+ const bBad = Number.isNaN(b.time);
37
+ if (aBad !== bBad) return aBad ? 1 : -1;
38
+ return (aBad ? 0 : a.time - b.time) || a.index - b.index;
39
+ });
40
+ const newest = ordered.findLast((row) => !Number.isNaN(row.time)) ?? ordered[0];
41
+ const list = ordered.map((row) => row.entry);
42
+ threads.push({
43
+ key,
44
+ // The newest entry carries the freshest title and status.
45
+ subject: newest.subject,
46
+ entries: list,
47
+ unread: list.filter((entry) => !entry.read).length,
48
+ latestAt: newest.entry.at,
49
+ pending: list.filter(isPending).toReversed()
50
+ });
51
+ }
52
+ return threads.map((thread, index) => ({
53
+ thread,
54
+ index
55
+ })).toSorted((a, b) => {
56
+ if (options.unreadFirst) {
57
+ const unread = Number(b.thread.unread > 0) - Number(a.thread.unread > 0);
58
+ if (unread) return unread;
59
+ }
60
+ const aTime = latestOf(a.thread);
61
+ const bTime = latestOf(b.thread);
62
+ if (aTime === bTime) return a.index - b.index;
63
+ return bTime - aTime;
64
+ }).map((row) => row.thread);
61
65
  }
62
66
  export const NO_FILTER = {
63
- type: ALL,
64
- who: ALL,
65
- needsYou: false
67
+ type: ALL,
68
+ who: ALL,
69
+ needsYou: false
66
70
  };
71
+ /** Keep a thread when it matches the type, holds an entry from the person or agent, and has a pending proposal if asked. */
67
72
  export function filterThreads(threads, filter) {
68
- return threads.filter((thread)=>(filter.type === ALL || thread.subject.type === filter.type) && (filter.who === ALL || thread.entries.some((entry)=>entry.actor.name === filter.who)) && (!filter.needsYou || thread.pending.length > 0));
73
+ return threads.filter((thread) => (filter.type === ALL || thread.subject.type === filter.type) && (filter.who === ALL || thread.entries.some((entry) => entry.actor.name === filter.who)) && (!filter.needsYou || thread.pending.length > 0));
69
74
  }
70
75
  export function subjectTypes(threads) {
71
- return [
72
- ...new Set(threads.map((thread)=>thread.subject.type))
73
- ];
76
+ return [...new Set(threads.map((thread) => thread.subject.type))];
74
77
  }
75
78
  export function actorNames(threads) {
76
- return [
77
- ...new Set(threads.flatMap((thread)=>thread.entries.map((entry)=>entry.actor.name)))
78
- ].filter(Boolean);
79
+ return [...new Set(threads.flatMap((thread) => thread.entries.map((entry) => entry.actor.name)))].filter(Boolean);
79
80
  }
80
81
  const KEYS = {
81
- j: "next",
82
- k: "previous",
83
- u: "toggleRead",
84
- h: "snooze",
85
- a: "approve",
86
- d: "decline",
87
- s: "steer",
88
- S: "unfollow",
89
- e: "edit"
82
+ j: "next",
83
+ k: "previous",
84
+ u: "toggleRead",
85
+ h: "snooze",
86
+ a: "approve",
87
+ d: "decline",
88
+ s: "steer",
89
+ S: "unfollow",
90
+ e: "edit"
90
91
  };
92
+ /**
93
+ * What a key press asks for, or null when it isn't ours: focus is in a text field, or a modifier
94
+ * other than Linear's Shift+S, Shift+Backspace and Alt+U is held.
95
+ */
91
96
  export function inboxKeyOf(event) {
92
- if (event.defaultPrevented || event.metaKey || event.ctrlKey) return null;
93
- if (isTyping(event.target)) return null;
94
- if (event.altKey) return event.code === "KeyU" && !event.shiftKey ? "readAll" : null;
95
- if (event.key === "Backspace") return event.shiftKey ? "clearRead" : "clear";
96
- return KEYS[event.key] ?? null;
97
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey) return null;
98
+ if (isTyping(event.target)) return null;
99
+ // Option+U on a Mac types a dead key, so the physical key decides.
100
+ if (event.altKey) return event.code === "KeyU" && !event.shiftKey ? "readAll" : null;
101
+ if (event.key === "Backspace") return event.shiftKey ? "clearRead" : "clear";
102
+ return KEYS[event.key] ?? null;
97
103
  }
104
+ /** The three times a snooze offers: 3 hours on, tomorrow at 9, next Monday at 9. */
98
105
  export function snoozeChoices(now) {
99
- const later = new Date(now.getTime() + 3 * 60 * 60 * 1000);
100
- const tomorrow = new Date(now);
101
- tomorrow.setDate(tomorrow.getDate() + 1);
102
- tomorrow.setHours(9, 0, 0, 0);
103
- const nextWeek = new Date(now);
104
- nextWeek.setDate(nextWeek.getDate() + ((8 - nextWeek.getDay()) % 7 || 7));
105
- nextWeek.setHours(9, 0, 0, 0);
106
- return [
107
- {
108
- id: "later",
109
- until: later
110
- },
111
- {
112
- id: "tomorrow",
113
- until: tomorrow
114
- },
115
- {
116
- id: "nextWeek",
117
- until: nextWeek
118
- }
119
- ];
106
+ const later = new Date(now.getTime() + 3 * 60 * 60 * 1e3);
107
+ const tomorrow = new Date(now);
108
+ tomorrow.setDate(tomorrow.getDate() + 1);
109
+ tomorrow.setHours(9, 0, 0, 0);
110
+ const nextWeek = new Date(now);
111
+ nextWeek.setDate(nextWeek.getDate() + ((8 - nextWeek.getDay()) % 7 || 7));
112
+ nextWeek.setHours(9, 0, 0, 0);
113
+ return [
114
+ {
115
+ id: "later",
116
+ until: later
117
+ },
118
+ {
119
+ id: "tomorrow",
120
+ until: tomorrow
121
+ },
122
+ {
123
+ id: "nextWeek",
124
+ until: nextWeek
125
+ }
126
+ ];
120
127
  }
121
128
  export function resolveInboxLabels(overrides) {
122
- return {
123
- ...DEFAULT_INBOX_LABELS,
124
- ...overrides
125
- };
129
+ return {
130
+ ...DEFAULT_INBOX_LABELS,
131
+ ...overrides
132
+ };
126
133
  }
134
+ /** One line for an entry, in the host's words. `{from}`, `{to}` and `{assignee}` are filled. */
127
135
  export function entryLine(entry, labels) {
128
- const fill = (text)=>text.replaceAll("{from}", entry.status?.from ?? "").replaceAll("{to}", entry.status?.to ?? "").replaceAll("{assignee}", entry.assignee ?? "");
129
- switch(entry.kind){
130
- case "status":
131
- return fill(entry.status?.from ? labels.statusMoved : labels.statusSet);
132
- case "assigned":
133
- return fill(labels.assigned);
134
- case "mention":
135
- return labels.mentioned;
136
- case "agent_response":
137
- return labels.responded;
138
- case "proposal":
139
- return labels.proposed;
140
- default:
141
- return labels.commented;
142
- }
136
+ const fill = (text) => text.replaceAll("{from}", entry.status?.from ?? "").replaceAll("{to}", entry.status?.to ?? "").replaceAll("{assignee}", entry.assignee ?? "");
137
+ switch (entry.kind) {
138
+ case "status": return fill(entry.status?.from ? labels.statusMoved : labels.statusSet);
139
+ case "assigned": return fill(labels.assigned);
140
+ case "mention": return labels.mentioned;
141
+ case "agent_response": return labels.responded;
142
+ case "proposal": return labels.proposed;
143
+ default: return labels.commented;
144
+ }
143
145
  }
146
+ /** An entry as a sentence: who, then what. With no known actor it starts with the verb, capitalised. */
144
147
  export function sentenceOf(entry, labels) {
145
- const line = entryLine(entry, labels);
146
- if (entry.actor.name) return `${entry.actor.name} ${line}`;
147
- return line.charAt(0).toUpperCase() + line.slice(1);
148
+ const line = entryLine(entry, labels);
149
+ if (entry.actor.name) return `${entry.actor.name} ${line}`;
150
+ return line.charAt(0).toUpperCase() + line.slice(1);
148
151
  }
152
+ /** What happened to the subject, as against something a person wrote. */
149
153
  const EVENT_KINDS = new Set([
150
- "status",
151
- "assigned",
152
- "proposal"
154
+ "status",
155
+ "assigned",
156
+ "proposal"
153
157
  ]);
154
158
  export function isEvent(entry) {
155
- return EVENT_KINDS.has(entry.kind);
159
+ return EVENT_KINDS.has(entry.kind);
156
160
  }
@@ -1,46 +1,46 @@
1
1
  export const ALL = "all";
2
2
  export const DEFAULT_INBOX_LABELS = {
3
- title: "Inbox",
4
- all: "All",
5
- anyone: "Anyone",
6
- needsYou: "Needs you",
7
- empty: "Nothing here",
8
- emptyNeedsYou: "Nothing is waiting on you",
9
- emptyFiltered: "No thread matches these filters",
10
- clearFilters: "Clear filters",
11
- unread: "unread",
12
- markRead: "Mark read",
13
- markUnread: "Mark unread",
14
- snooze: "Snooze",
15
- snoozeLater: "Later today",
16
- snoozeTomorrow: "Tomorrow",
17
- snoozeNextWeek: "Next week",
18
- snoozed: "Snoozed",
19
- showSnoozed: "Show snoozed",
20
- unsnooze: "Unsnooze",
21
- unfollow: "Unfollow",
22
- markAllRead: "Mark all read",
23
- clear: "Clear",
24
- clearRead: "Clear read",
25
- selectThread: "Select a thread",
26
- filterType: "Filter by type",
27
- filterWho: "Filter by person or agent",
28
- filter: "Filter",
29
- kind: "Type",
30
- from: "From",
31
- open: "Open",
32
- agent: "AI",
33
- statusMoved: "moved this from {from} to {to}",
34
- statusSet: "set this to {to}",
35
- assigned: "assigned this to {assignee}",
36
- mentioned: "mentioned you",
37
- commented: "commented",
38
- responded: "replied",
39
- proposed: "asks for your call",
40
- hintMove: "move",
41
- hintRead: "read",
42
- hintReadAll: "all read",
43
- hintSnooze: "snooze",
44
- hintClear: "clear, with shift every read thread",
45
- hintAct: "approve, decline, steer, edit"
3
+ title: "Inbox",
4
+ all: "All",
5
+ anyone: "Anyone",
6
+ needsYou: "Needs you",
7
+ empty: "Nothing here",
8
+ emptyNeedsYou: "Nothing is waiting on you",
9
+ emptyFiltered: "No thread matches these filters",
10
+ clearFilters: "Clear filters",
11
+ unread: "unread",
12
+ markRead: "Mark read",
13
+ markUnread: "Mark unread",
14
+ snooze: "Snooze",
15
+ snoozeLater: "Later today",
16
+ snoozeTomorrow: "Tomorrow",
17
+ snoozeNextWeek: "Next week",
18
+ snoozed: "Snoozed",
19
+ showSnoozed: "Show snoozed",
20
+ unsnooze: "Unsnooze",
21
+ unfollow: "Unfollow",
22
+ markAllRead: "Mark all read",
23
+ clear: "Clear",
24
+ clearRead: "Clear read",
25
+ selectThread: "Select a thread",
26
+ filterType: "Filter by type",
27
+ filterWho: "Filter by person or agent",
28
+ filter: "Filter",
29
+ kind: "Type",
30
+ from: "From",
31
+ open: "Open",
32
+ agent: "AI",
33
+ statusMoved: "moved this from {from} to {to}",
34
+ statusSet: "set this to {to}",
35
+ assigned: "assigned this to {assignee}",
36
+ mentioned: "mentioned you",
37
+ commented: "commented",
38
+ responded: "replied",
39
+ proposed: "asks for your call",
40
+ hintMove: "move",
41
+ hintRead: "read",
42
+ hintReadAll: "all read",
43
+ hintSnooze: "snooze",
44
+ hintClear: "clear, with shift every read thread",
45
+ hintAct: "approve, decline, steer, edit"
46
46
  };
@@ -1,13 +1,20 @@
1
- export const arrive = (node, value)=>{
2
- let previous = value;
3
- node.classList.add("t-enter");
4
- return {
5
- update (next) {
6
- if (next === previous) return;
7
- previous = next;
8
- node.classList.remove("t-enter");
9
- void node.offsetWidth;
10
- node.classList.add("t-enter");
11
- }
12
- };
1
+ /**
2
+ * Replays `.t-enter` whenever the value handed in changes, so a panel that swaps
3
+ * its record reads as a new one arriving. `<div use:arrive={selectedId}>`
4
+ */
5
+ // Prefer it to `{#key}`, which rebuilds the subtree and loses scroll, focus and
6
+ // open disclosures. `use:` works on elements only, so a component still needs `{#key}`.
7
+ export const arrive = (node, value) => {
8
+ let previous = value;
9
+ // The first paint is the mount, which already animates on its own class.
10
+ node.classList.add("t-enter");
11
+ return { update(next) {
12
+ if (next === previous) return;
13
+ previous = next;
14
+ // Remove, force a reflow, re-add. A CSS animation runs once per element,
15
+ // and only a reflow between the two writes makes the browser replay it.
16
+ node.classList.remove("t-enter");
17
+ void node.offsetWidth;
18
+ node.classList.add("t-enter");
19
+ } };
13
20
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,17 +1,23 @@
1
1
  const segmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter() : null;
2
+ /** The first whole character of a string: an emoji or an accented letter stays in one piece. */
2
3
  function firstGrapheme(text) {
3
- if (!text) return "";
4
- if (segmenter) return segmenter.segment(text)[Symbol.iterator]().next().value?.segment ?? "";
5
- return Array.from(text)[0] ?? "";
4
+ if (!text) return "";
5
+ if (segmenter) return segmenter.segment(text)[Symbol.iterator]().next().value?.segment ?? "";
6
+ return Array.from(text)[0] ?? "";
6
7
  }
8
+ /** One letter for an avatar fallback. Falls back to `fallback` for an empty or missing name. */
7
9
  export function initial(name, fallback = "?") {
8
- const first = firstGrapheme((name ?? "").trim());
9
- return first ? first.toLocaleUpperCase() : fallback;
10
+ const first = firstGrapheme((name ?? "").trim());
11
+ return first ? first.toLocaleUpperCase() : fallback;
10
12
  }
13
+ /**
14
+ * Two letters for an avatar fallback, first and last word, skipping suffixes: "Christopher
15
+ * Montgomery III" gives "CM". A one-word name gives one letter.
16
+ */
11
17
  export function initials(name, fallback = "?") {
12
- const words = (name ?? "").trim().split(/\s+/).filter((word)=>word && !/^(?:[IVX]{1,4}|jr\.?|sr\.?)$/i.test(word));
13
- if (words.length === 0) return initial(name, fallback);
14
- const head = firstGrapheme(words[0]);
15
- const tail = words.length > 1 ? firstGrapheme(words[words.length - 1]) : "";
16
- return (head + tail).toLocaleUpperCase();
18
+ const words = (name ?? "").trim().split(/\s+/).filter((word) => word && !/^(?:[IVX]{1,4}|jr\.?|sr\.?)$/i.test(word));
19
+ if (words.length === 0) return initial(name, fallback);
20
+ const head = firstGrapheme(words[0]);
21
+ const tail = words.length > 1 ? firstGrapheme(words[words.length - 1]) : "";
22
+ return (head + tail).toLocaleUpperCase();
17
23
  }
@@ -1 +1,6 @@
1
+ /** The app's one horizontal gutter. */
2
+ // WHY a token: the tab strip said `px-4 md:px-6` and page bodies `p-4`, so from
3
+ // `md` up the nav sat 8px right of the content. Same fact typed twice.
4
+ // The content edge won: sixty page bodies against one strip. Anything lining up
5
+ // with the page body names this instead of retyping a padding.
1
6
  export const pageGutterClass = "px-4";
package/dist/lib/panel.js CHANGED
@@ -1,3 +1,16 @@
1
+ /** The one panel shell: a titled box holding a table, a chart or a stack. */
2
+ // WHY tokens, not markup: `DetailPanel` and `DataPanel` draw the same box with
3
+ // different props, and their edges drifted because the classes were typed twice.
4
+ // Dashboards used `Card`, so /ads had a ring and a shadow where /social had a
5
+ // hairline. Flat, one hairline, `rounded-xl`.
1
6
  export const panelShellClass = "overflow-hidden rounded-xl border border-border bg-card";
7
+ /**
8
+ * The panel's header bar: label left, count or controls right. `min-h-11` so a
9
+ * label-only header matches one with a button.
10
+ */
2
11
  export const panelHeaderClass = "flex min-h-11 flex-wrap items-center justify-between gap-x-3 gap-y-1 border-b border-border px-4 py-2";
12
+ /**
13
+ * The inset well for a control block inside a panel. A visible step at the
14
+ * same depth, for filter rows and grouped controls.
15
+ */
3
16
  export const insetWellClass = "rounded-lg border border-border bg-muted/40 p-2";
@@ -1,26 +1,26 @@
1
1
  export function tryCatch(fn) {
2
- try {
3
- return {
4
- ok: true,
5
- value: fn()
6
- };
7
- } catch (e) {
8
- return {
9
- ok: false,
10
- error: e instanceof Error ? e : new Error(String(e))
11
- };
12
- }
2
+ try {
3
+ return {
4
+ ok: true,
5
+ value: fn()
6
+ };
7
+ } catch (e) {
8
+ return {
9
+ ok: false,
10
+ error: e instanceof Error ? e : new Error(String(e))
11
+ };
12
+ }
13
13
  }
14
14
  export async function tryCatchAsync(fn) {
15
- try {
16
- return {
17
- ok: true,
18
- value: await fn()
19
- };
20
- } catch (e) {
21
- return {
22
- ok: false,
23
- error: e instanceof Error ? e : new Error(String(e))
24
- };
25
- }
15
+ try {
16
+ return {
17
+ ok: true,
18
+ value: await fn()
19
+ };
20
+ } catch (e) {
21
+ return {
22
+ ok: false,
23
+ error: e instanceof Error ? e : new Error(String(e))
24
+ };
25
+ }
26
26
  }