@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,299 @@
1
+ // The view types of a client or project record. Plain data in, nothing loaded:
2
+ // the host maps its own rows onto these and passes them down.
3
+ import type { PhosphorIcon } from "../lib/icon-types.js";
4
+ import type { Tone } from "../lib/tone.js";
5
+
6
+ /** Where a stage sits in the pipeline. Structure, not an agency's own names. */
7
+ export type StageCategory = "open" | "proposal" | "ongoing" | "completed" | "lost";
8
+
9
+ /** One stage an agency declares. The record draws its label and tone. */
10
+ export type StageDef = {
11
+ key: string;
12
+ label: string;
13
+ tone: Tone;
14
+ category: StageCategory;
15
+ };
16
+
17
+ /**
18
+ * One thing that happened. Append-only. It belongs to a project, or to a client
19
+ * when no project exists yet. `occurredAt` is an ISO timestamp.
20
+ */
21
+ export type RecordEvent = {
22
+ id: string;
23
+ /** A registry key: `call`, `note`, `stage`, or a module's own `slug:name`. */
24
+ kind: string;
25
+ occurredAt: string;
26
+ title: string;
27
+ body?: string | null;
28
+ /** Money, in the currency's major unit. */
29
+ amount?: number | null;
30
+ currency?: string | null;
31
+ /** Who or what wrote it. The host resolves the name. */
32
+ actor?: string | null;
33
+ /** A note that changed after posting. */
34
+ edited?: boolean;
35
+ /** The kind's own declared fields, already formatted by the host for display. */
36
+ details?: { label: string; value: string }[];
37
+ /** The project it belongs to. Set in client mode, so each entry can say which. */
38
+ projectId?: string | null;
39
+ projectName?: string | null;
40
+ projectHref?: string | null;
41
+ /** Where the event's source lives: the call, the invoice, the deck. */
42
+ href?: string | null;
43
+ };
44
+
45
+ /** The numbers and lines one project rolls up to. The host computes them. */
46
+ export type RecordSummary = {
47
+ id: string;
48
+ name: string;
49
+ href?: string;
50
+ stageKey: string;
51
+ /** Short qualifier drawn after the stage, such as a loss reason. */
52
+ stageDetail?: string | null;
53
+ /** Money, in the currency's major unit. */
54
+ value?: number | null;
55
+ currency?: string | null;
56
+ /** Money received, from payment events. */
57
+ paid?: number | null;
58
+ startDate?: string | null;
59
+ endDate?: string | null;
60
+ whereWeAre?: {
61
+ text: string;
62
+ /** ISO timestamp of the status event. */
63
+ at?: string | null;
64
+ actor?: string | null;
65
+ health?: { label: string; tone: Tone } | null;
66
+ } | null;
67
+ next?: RecordNext | null;
68
+ lastCallAt?: string | null;
69
+ lastTouchAt?: string | null;
70
+ progress?: { done: number; total: number } | null;
71
+ /** A cancelled or archived project: dropped from "active" and "next". */
72
+ closed?: boolean;
73
+ };
74
+
75
+ /** The next milestone, or the next dated task when there are no milestones. */
76
+ export type RecordNext = {
77
+ label: string;
78
+ /** ISO date. */
79
+ date?: string | null;
80
+ /** ISO date, for a range. */
81
+ endDate?: string | null;
82
+ owner?: string | null;
83
+ href?: string | null;
84
+ projectId?: string | null;
85
+ projectName?: string | null;
86
+ };
87
+
88
+ /**
89
+ * Each field type with its config and value; after adding one, fix what `pnpm check` raises in
90
+ * `fieldRow` and `INPUT` (`fields.ts`) and `InfoList`.
91
+ */
92
+ export type FieldTypes = {
93
+ text: { config: Record<never, never>; value: string };
94
+ longtext: { config: Record<never, never>; value: string };
95
+ choice: { config: { options: FieldOption[] }; value: string };
96
+ multi: { config: { options: FieldOption[] }; value: string[] };
97
+ money: { config: { currency?: string }; value: number };
98
+ date: { config: Record<never, never>; value: string };
99
+ url: { config: Record<never, never>; value: string };
100
+ email: { config: Record<never, never>; value: string };
101
+ timezone: { config: Record<never, never>; value: string };
102
+ tags: { config: TagsConfig; value: string[] };
103
+ };
104
+
105
+ export type FieldType = keyof FieldTypes;
106
+
107
+ /** Where a tags field's labels come from, and whether a new one may be made. */
108
+ export type TagsConfig = {
109
+ /** `"existing-values"`: every value used on this field before. Or a fixed list. */
110
+ options: "existing-values" | readonly string[];
111
+ /** Whether the "Create" row is offered. A fixed list with `false` is a closed set. */
112
+ allowCreate: boolean;
113
+ max?: number;
114
+ };
115
+
116
+ export type FieldOption = {
117
+ key: string;
118
+ label: string;
119
+ tone?: Tone;
120
+ /** Still reads on old records, cannot be picked. */
121
+ archived?: boolean;
122
+ };
123
+
124
+ /** What a field holds. A choice is its option key, a multi is its keys, tags are the labels. */
125
+ export type FieldValue = string | number | string[] | null;
126
+
127
+ type FieldBase = {
128
+ key: string;
129
+ label: string;
130
+ section?: string;
131
+ required?: boolean;
132
+ /** Shown only while another field has this value. Without `equals`, any value. */
133
+ showWhen?: { key: string; equals?: string | string[] };
134
+ placeholder?: string;
135
+ };
136
+
137
+ /** One attribute a module declares. `type` picks the config, so a field can't hold another type's. */
138
+ export type FieldDef = {
139
+ [T in FieldType]: FieldBase & { type: T } & FieldTypes[T]["config"];
140
+ }[FieldType];
141
+
142
+ export type FieldSectionDef = { key: string; label: string };
143
+
144
+ /** One step of the rail. A step is a milestone task. */
145
+ export type StepDef = {
146
+ id: string;
147
+ label: string;
148
+ done: boolean;
149
+ /** Which group header it sits under, such as Sales or Delivery. */
150
+ group?: string;
151
+ /** ISO dates. Both set draws a range, one draws a day. */
152
+ start?: string | null;
153
+ end?: string | null;
154
+ /** Hidden from the rail, still in the edit menu. */
155
+ hidden?: boolean;
156
+ /** The label was renamed: the menu offers a reset. */
157
+ renamed?: boolean;
158
+ };
159
+
160
+ export type StepGroupDef = { key: string; label: string };
161
+
162
+ export type StepMarker = "complete" | "current" | "late" | "pending";
163
+
164
+ /** One row of an `InfoList`. The value is already formatted for display. */
165
+ export type InfoRow = {
166
+ /** Names the row for a host to find it. Falls back to the label. */
167
+ id?: string;
168
+ label: string;
169
+ icon?: PhosphorIcon;
170
+ value?: string | null;
171
+ /** A coloured chip in place of plain text. */
172
+ tone?: Tone;
173
+ /** Chips for a multi value. Wins over `value`. */
174
+ chips?: { label: string; tone?: Tone }[];
175
+ /** What the editor starts from, when it differs from what is drawn. */
176
+ editValue?: string | string[] | null;
177
+ href?: string;
178
+ muted?: boolean;
179
+ /** `select` becomes a searchable list past seven options. */
180
+ input?: "text" | "longtext" | "select" | "multi" | "date" | "number" | "url" | "email" | "tags";
181
+ options?: { value: string; label: string; tone?: Tone }[];
182
+ /** For `tags`: the labels to pick from, and whether a new one may be made. */
183
+ tags?: { options: readonly string[]; allowCreate: boolean; max?: number };
184
+ placeholder?: string;
185
+ required?: boolean;
186
+ /** Without it the row is read-only. A multi row passes the picked keys. */
187
+ onSave?: (value: string | string[]) => void | Promise<void>;
188
+ divider?: boolean;
189
+ };
190
+
191
+ /** A kind of event: how its filter chip and its default row read. */
192
+ export type RecordKindDef = {
193
+ key: string;
194
+ label: string;
195
+ icon?: PhosphorIcon;
196
+ tone?: Tone;
197
+ };
198
+
199
+ /** A filter chip: a label over the kinds it shows. */
200
+ export type RecordFilterDef = { key: string; label: string; kinds: string[] };
201
+
202
+ export type RecordLabels = {
203
+ whereWeAre: string;
204
+ more: string;
205
+ less: string;
206
+ next: string;
207
+ late: string;
208
+ steps: string;
209
+ stepsCount: (done: number, total: number) => string;
210
+ editSteps: string;
211
+ rename: string;
212
+ resetName: string;
213
+ hide: string;
214
+ show: string;
215
+ moveUp: string;
216
+ moveDown: string;
217
+ shownCount: (shown: number, total: number) => string;
218
+ openSteps: string;
219
+ activity: string;
220
+ all: string;
221
+ noEvents: string;
222
+ older: (count: number) => string;
223
+ today: string;
224
+ yesterday: string;
225
+ by: (name: string) => string;
226
+ projects: string;
227
+ projectCount: (count: number) => string;
228
+ noProjects: string;
229
+ details: string;
230
+ edit: (field: string) => string;
231
+ add: (field: string) => string;
232
+ save: string;
233
+ cancel: string;
234
+ search: string;
235
+ noMatch: string;
236
+ pipeline: string;
237
+ won: string;
238
+ paid: string;
239
+ lastCall: string;
240
+ lastTouch: string;
241
+ noCall: string;
242
+ open: string;
243
+ edited: string;
244
+ noSteps: string;
245
+ saveFailed: string;
246
+ required: string;
247
+ };
248
+
249
+ export const DEFAULT_RECORD_LABELS: RecordLabels = {
250
+ whereWeAre: "Where we are",
251
+ more: "More",
252
+ less: "Less",
253
+ next: "Next",
254
+ late: "Late",
255
+ steps: "Steps",
256
+ stepsCount: (done, total) => `${done}/${total}`,
257
+ editSteps: "Choose visible steps",
258
+ rename: "Rename",
259
+ resetName: "Reset name",
260
+ hide: "Hide",
261
+ show: "Show",
262
+ moveUp: "Move up",
263
+ moveDown: "Move down",
264
+ shownCount: (shown, total) => `${shown} of ${total} shown`,
265
+ openSteps: "Open steps",
266
+ activity: "Activity",
267
+ all: "All",
268
+ noEvents: "Nothing logged yet",
269
+ older: (count) => `Show ${count} older`,
270
+ today: "Today",
271
+ yesterday: "Yesterday",
272
+ by: (name) => `by ${name}`,
273
+ projects: "Projects",
274
+ projectCount: (count) => (count === 1 ? "1 project" : `${count} projects`),
275
+ noProjects: "No projects",
276
+ details: "Details",
277
+ edit: (field) => `Edit ${field}`,
278
+ add: (field) => `Add ${field.toLowerCase()}`,
279
+ save: "Save",
280
+ cancel: "Cancel",
281
+ search: "Search",
282
+ noMatch: "No match",
283
+ pipeline: "Pipeline",
284
+ won: "Won",
285
+ paid: "Paid",
286
+ lastCall: "Last call",
287
+ lastTouch: "Last touch",
288
+ noCall: "None yet",
289
+ open: "Open",
290
+ edited: "edited",
291
+ noSteps: "No steps yet",
292
+ saveFailed: "Couldn't save",
293
+ required: "Required",
294
+ };
295
+
296
+ /** Merge a partial label set over the defaults. */
297
+ export function resolveLabels(labels?: Partial<RecordLabels>): RecordLabels {
298
+ return labels ? { ...DEFAULT_RECORD_LABELS, ...labels } : DEFAULT_RECORD_LABELS;
299
+ }
@@ -0,0 +1,125 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { NavigationArrow } from "phosphor-svelte";
6
+ import { cn } from "../lib/utils.js";
7
+ import { Button } from "../shadcn/button/index.js";
8
+ import StatusBadge from "../status/status-badge.svelte";
9
+ import type { Tone } from "../lib/tone.js";
10
+ import { gutterClass } from "./gutter.js";
11
+ import { formatAge } from "./format.js";
12
+ import { resolveLabels, type RecordLabels } from "./types.js";
13
+
14
+ // =========================
15
+ // PROPS
16
+ // =========================
17
+ interface Props {
18
+ /** The status note. Nothing draws without it. */
19
+ note?: string | null;
20
+ /** ISO timestamp of the note. */
21
+ at?: string | null;
22
+ actor?: string | null;
23
+ health?: { label: string; tone: Tone } | null;
24
+ /** Client mode: the project this note belongs to. */
25
+ project?: { name: string; href?: string | null } | null;
26
+ labels?: Partial<RecordLabels>;
27
+ locale?: string;
28
+ now?: Date | number;
29
+ class?: string;
30
+ }
31
+ let { note, at, actor, health, project, labels, locale, now, class: className }: Props = $props();
32
+
33
+ // =========================
34
+ // STATE
35
+ // =========================
36
+ let expanded = $state(false);
37
+ let clamped = $state(false);
38
+ let text = $state<HTMLElement | null>(null);
39
+
40
+ // =========================
41
+ // DERIVED
42
+ // =========================
43
+ let t = $derived(resolveLabels(labels));
44
+ let age = $derived(at ? formatAge(at, now, locale) : null);
45
+ let hasMeta = $derived(Boolean(health || actor || age));
46
+
47
+ // =========================
48
+ // EFFECTS
49
+ // =========================
50
+ // Measured, not guessed from length: a note that fits two lines at 1280px
51
+ // overflows at 390px, and "More" shows only when text is cut.
52
+ $effect(() => {
53
+ if (!text) return;
54
+ const el = text;
55
+ const measure = () => {
56
+ if (!expanded) clamped = el.scrollHeight > el.clientHeight + 1;
57
+ };
58
+ measure();
59
+ const observer = new ResizeObserver(measure);
60
+ observer.observe(el);
61
+ return () => observer.disconnect();
62
+ });
63
+ </script>
64
+
65
+ <!-- =========================== -->
66
+ <!-- MARKUP -->
67
+ <!-- =========================== -->
68
+ {#if note}
69
+ <section
70
+ aria-label={t.whereWeAre}
71
+ class={cn(
72
+ "flex items-start gap-2.5 border-b border-border bg-muted/40 py-2.5",
73
+ gutterClass,
74
+ className,
75
+ )}
76
+ >
77
+ <NavigationArrow
78
+ class="mt-0.5 size-3.5 shrink-0 text-[color:var(--tone-accent,var(--color-primary))]"
79
+ weight="fill"
80
+ aria-hidden="true"
81
+ />
82
+ <div class="min-w-0 flex-1">
83
+ <!-- No `title` attribute: a native tooltip would repeat the whole note
84
+ over the steps list. "More" opens it in place. -->
85
+ <p
86
+ bind:this={text}
87
+ class={cn("text-sm leading-snug text-muted-foreground", !expanded && "line-clamp-2")}
88
+ >
89
+ <span class="font-medium text-foreground">{t.whereWeAre}</span>
90
+ {#if project}
91
+ {#if project.href}
92
+ <a
93
+ href={project.href}
94
+ class="font-medium text-foreground underline-offset-2 hover:underline"
95
+ >{project.name}</a
96
+ >
97
+ {:else}
98
+ <span class="font-medium text-foreground">{project.name}</span>
99
+ {/if}
100
+ {/if}
101
+ <span class="whitespace-pre-line">{note}</span>
102
+ </p>
103
+ {#if hasMeta}
104
+ <p class="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
105
+ {#if health}
106
+ <StatusBadge tone={health.tone} dot capitalize={false}>{health.label}</StatusBadge>
107
+ {/if}
108
+ {#if actor}<span>{t.by(actor)}</span>{/if}
109
+ {#if age}<span class="tabular-nums">{age}</span>{/if}
110
+ </p>
111
+ {/if}
112
+ </div>
113
+ {#if clamped || expanded}
114
+ <Button
115
+ variant="ghost"
116
+ size="sm"
117
+ class="-mr-2 shrink-0 text-muted-foreground"
118
+ aria-expanded={expanded}
119
+ onclick={() => (expanded = !expanded)}
120
+ >
121
+ {expanded ? t.less : t.more}
122
+ </Button>
123
+ {/if}
124
+ </section>
125
+ {/if}
@@ -35,7 +35,7 @@
35
35
 
36
36
  // WHY: every height is authored for a mouse. Under a thumb, Apple's floor is 44px, so each size grows wherever a coarse pointer exists.
37
37
 
38
- // WHY sm carries a hit area: 28px is under the 32px desktop floor and the 40px coarse height is under 44. The ::after box stays the same 32px (44 on coarse) square whatever the visual size, so a dense toolbar keeps its look.
38
+ // WHY sm carries a hit area: 28px is under the 32px desktop floor, so an ::after box keeps it 32px (44 on coarse) without changing its look.
39
39
 
40
40
  // Icon buttons grow both axes because they have no label to widen them. any-pointer, not pointer: see the note in tokens/base.css.
41
41
  size: {
@@ -2,11 +2,7 @@
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
3
  import { cn, type WithElementRef } from "../../lib/utils.js";
4
4
 
5
- // WHY: card titles render as a div by default for backwards compatibility,
6
-
7
- // but a page whose sections are all cards ends up with no headings below the h1, so screen readers cannot navigate it by heading (WCAG 2.4.6).
8
-
9
- // Pass `as="h2"` on real section titles.
5
+ // WHY: card titles default to a div, so pass `as="h2"` on real section titles or a page of cards has no headings for screen readers (WCAG 2.4.6).
10
6
  let {
11
7
  ref = $bindable(null),
12
8
  class: className,
@@ -6,19 +6,8 @@ import {
6
6
  import type { GrammarState } from "shiki/core";
7
7
 
8
8
  /**
9
- * Highlights `code` in `language`. `current` holds one entry per line: a plain
10
- * string until the highlighter loads, then the line's tokens. Pass each one to
11
- * the `code` prop of a `CodeBlock.Line`.
12
- *
13
- * ```svelte
14
- * <script lang="ts">
15
- * const lines = useHighlightedCode(() => code, () => "json");
16
- * </script>
17
- *
18
- * {#each lines.current as line, index (index)}
19
- * <CodeBlock.Line code={line} />
20
- * {/each}
21
- * ```
9
+ * Highlights `code`. Each `current` entry is a plain string until the highlighter loads, then
10
+ * tokens, so pass it to `CodeBlock.Line`'s `code` prop either way.
22
11
  */
23
12
  export function useHighlightedCode(value: () => string, language: () => string) {
24
13
  let loaded = $state.raw<{
@@ -29,10 +29,8 @@
29
29
  children: Snippet;
30
30
  } = $props();
31
31
 
32
- // Side classes are plain utilities, not `data-[side=x]:` variants. An attribute
33
- // variant outranks a plain class and tailwind-merge won't dedupe across
34
- // modifier sets, so a caller's `sm:max-w-xl` lost to the default 384px.
35
- // Plain utilities let `cn` drop the default when a caller passes its own.
32
+ // Plain side utilities, not `data-[side=x]:` variants, because the variant outranks a
33
+ // caller's `sm:max-w-xl` and tailwind-merge can't dedupe across modifier sets.
36
34
  const SIDE_CLASS: Record<Side, string> = {
37
35
  // h-dvh, not h-full: on iOS Safari a fixed panel sizes against the layout
38
36
  // viewport, which is taller than the visible area, so h-full hides the footer.
@@ -19,9 +19,8 @@
19
19
  {@render children?.()}
20
20
  </td>
21
21
 
22
- <!-- WHY here: a link in a cell is 20px tall, and a cell is the only place that knows it sits in a
23
- row of them. On a coarse pointer the link grows a 44px box centred on it, wider when the link
24
- is. A link that positions itself (a stretched row link) keeps its own box. -->
22
+ <!-- A cell's link is 20px tall, so on a coarse pointer it grows a 44px hit box here, unless it
23
+ positions itself, like a stretched row link. -->
25
24
  <style>
26
25
  @media (any-pointer: coarse) {
27
26
  td :global(a[href]:not([class*="absolute"], [class*="fixed"], [class*="sticky"])) {
@@ -1,11 +1,6 @@
1
- /* Tailwind utilities the shadcn Attachment and Message components rely on:
2
- scroll-fade-x and scrollbar-none for Attachment.Group's snap-scroller,
3
- shimmer for a processing/uploading Attachment.Title. Vendored from
4
- shadcn-svelte's own tailwind.css bundle (MIT) so this package stays
5
- dependency-free (docs/decisions.md, "The package stays dependency-free").
6
- `no-scrollbar` is deliberately left out: consumers already define it, and
7
- a duplicate @utility breaks their build. Import this file once, after
8
- Tailwind, alongside `@heyramzi/ui/design`. */
1
+ /* Utilities the shadcn Attachment and Message parts need, vendored from
2
+ shadcn-svelte (MIT); `no-scrollbar` stays out because consumers define it
3
+ and a duplicate @utility breaks their build. */
9
4
 
10
5
  @utility scrollbar-none {
11
6
  -ms-overflow-style: none;