@heyramzi/ui 2.1.0 → 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 (312) hide show
  1. package/package.json +7 -8
  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/card/card-frame.svelte +43 -39
  31. package/src/canvas/card/resize-handles.svelte +89 -80
  32. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  33. package/src/canvas/chrome/lock-badge.svelte +8 -8
  34. package/src/canvas/chrome/side-handles.svelte +24 -19
  35. package/src/canvas/chrome/size-chip.svelte +30 -30
  36. package/src/canvas/chrome/snap-guides.svelte +20 -20
  37. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  38. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  39. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  40. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  41. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  42. package/src/canvas/edges/bend-handle.svelte +46 -46
  43. package/src/canvas/edges/edge-gradient.svelte +10 -10
  44. package/src/canvas/edges/gradient-edge.svelte +45 -29
  45. package/src/canvas/editor/lock.test.ts +6 -1
  46. package/src/canvas/editor/style.ts +7 -1
  47. package/src/canvas/headless/svg-figure.test.ts +5 -1
  48. package/src/canvas/lod/lod-switch.svelte +111 -106
  49. package/src/canvas/pen/PenField.svelte +87 -81
  50. package/src/canvas/pen/PenOverlay.svelte +127 -127
  51. package/src/canvas/pen/ShapeNode.svelte +31 -32
  52. package/src/canvas/pen/TextNode.svelte +54 -53
  53. package/src/canvas/pen/host-field.test.svelte +7 -7
  54. package/src/canvas/pen/shapes.ts +7 -1
  55. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  56. package/src/canvas/screen/PartView.svelte +138 -72
  57. package/src/canvas/screen/ScreenBody.svelte +81 -60
  58. package/src/canvas/screen/ScreenCard.svelte +20 -20
  59. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  60. package/src/canvas/ui/FrameLayer.svelte +59 -59
  61. package/src/canvas/ui/PenLayer.svelte +39 -30
  62. package/src/canvas/ui/StylePanel.svelte +119 -102
  63. package/src/canvas/ui/canvas-grid.svelte +137 -129
  64. package/src/charts/animated-value.svelte +1 -1
  65. package/src/charts/area-chart.css +2 -5
  66. package/src/charts/area-chart.svelte +1 -1
  67. package/src/charts/bar-chart.css +2 -3
  68. package/src/charts/bar-chart.svelte +1 -1
  69. package/src/charts/charts.css +5 -17
  70. package/src/charts/crosshair.svelte +1 -1
  71. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  72. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  73. package/src/charts/dashboard/data-panel.svelte +50 -50
  74. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  75. package/src/charts/dashboard/data-table.svelte +138 -138
  76. package/src/charts/dashboard/panel-grid.svelte +30 -30
  77. package/src/charts/dashboard/period-filter.svelte +25 -25
  78. package/src/charts/dashboard/rating-chip.svelte +19 -19
  79. package/src/charts/dashboard/stat-band.svelte +32 -32
  80. package/src/charts/dashboard/stat-tile.svelte +106 -99
  81. package/src/charts/donut-chart.svelte +1 -1
  82. package/src/charts/edge-fade.svelte +1 -1
  83. package/src/charts/figure.svelte +1 -1
  84. package/src/charts/gauge-chart.css +4 -7
  85. package/src/charts/gauge-chart.svelte +1 -1
  86. package/src/charts/hover-focus.svelte +1 -1
  87. package/src/charts/legend.css +1 -3
  88. package/src/charts/legend.svelte +1 -1
  89. package/src/charts/line-chart.svelte +1 -1
  90. package/src/charts/number-chart.css +6 -9
  91. package/src/charts/number-chart.svelte +1 -1
  92. package/src/charts/reference-line.svelte +19 -19
  93. package/src/charts/reveal.svelte +1 -1
  94. package/src/charts/ring-chart.css +2 -4
  95. package/src/charts/ring-chart.svelte +1 -1
  96. package/src/charts/sparkline.svelte +5 -2
  97. package/src/charts/stat-card.svelte +1 -1
  98. package/src/charts/time-series-chart.css +2 -5
  99. package/src/charts/trend-pill.css +2 -4
  100. package/src/charts/trend-pill.svelte +1 -1
  101. package/src/components/copy/copy-button.svelte +93 -98
  102. package/src/components/copy/copy-button.test.ts +10 -2
  103. package/src/components/data-table/infinite-scroll.svelte +42 -42
  104. package/src/components/data-table/pagination.svelte +34 -22
  105. package/src/components/data-table/row-arrow.svelte +19 -16
  106. package/src/components/data-table/row-context-menu.svelte +52 -52
  107. package/src/components/data-table/sortable-head.svelte +25 -25
  108. package/src/components/data-table/table-skeleton.svelte +41 -36
  109. package/src/components/docs/doc-page.svelte +18 -9
  110. package/src/components/docs/on-this-page.svelte +16 -3
  111. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  112. package/src/components/field-row/field-row.svelte +10 -8
  113. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  114. package/src/components/icon/icon.svelte +14 -14
  115. package/src/components/language-flag/language-flag.svelte +3 -1
  116. package/src/components/list-page/detail-empty.svelte +34 -34
  117. package/src/components/list-page/detail-header.svelte +55 -47
  118. package/src/components/list-page/detail-panel.svelte +51 -59
  119. package/src/components/list-page/detail-row.svelte +19 -19
  120. package/src/components/list-page/detail-stack.svelte +21 -21
  121. package/src/components/list-page/empty-state.svelte +30 -30
  122. package/src/components/list-page/filter-item.svelte +20 -20
  123. package/src/components/list-page/filter-popover.svelte +60 -55
  124. package/src/components/list-page/grouped-list.svelte +4 -2
  125. package/src/components/list-page/index.ts +5 -1
  126. package/src/components/list-page/list-page-shell.svelte +2 -5
  127. package/src/components/list-page/master-detail.svelte +87 -88
  128. package/src/components/list-page/notice-bar.svelte +38 -38
  129. package/src/components/list-page/prev-next.svelte +63 -63
  130. package/src/components/list-page/rail-body.svelte +18 -18
  131. package/src/components/list-page/rail-empty.svelte +23 -30
  132. package/src/components/list-page/rail-head.svelte +24 -24
  133. package/src/components/list-page/rail-row.svelte +77 -73
  134. package/src/components/list-page/section-host.ts +2 -3
  135. package/src/components/locale-switch/locale-switch.svelte +41 -41
  136. package/src/components/mark/mark.svelte +34 -38
  137. package/src/components/open-in/index.ts +1 -5
  138. package/src/components/open-in/open-in.svelte +40 -40
  139. package/src/components/picker/mark-picker.svelte +49 -49
  140. package/src/components/picker/picker.svelte +5 -3
  141. package/src/components/section-header/section-header.test.ts +7 -1
  142. package/src/components/section-header/section-label.svelte +20 -20
  143. package/src/components/segmented/animated-tabs.svelte +31 -2
  144. package/src/components/segmented/collapse.test.ts +2 -1
  145. package/src/components/segmented/index.ts +2 -0
  146. package/src/components/segmented/segment.svelte +52 -49
  147. package/src/components/segmented/segmented-filter.svelte +79 -80
  148. package/src/components/segmented/segmented-nav.svelte +34 -40
  149. package/src/components/segmented/segmented-overflow.svelte +53 -50
  150. package/src/components/segmented/segmented-track.svelte +62 -46
  151. package/src/components/segmented/segmented.ts +2 -3
  152. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  153. package/src/components/svg/svg.svelte +4 -4
  154. package/src/components/table-view/table-view.svelte +17 -2
  155. package/src/components/table-view/table-view.test.ts +7 -1
  156. package/src/components/table-view/types.ts +11 -11
  157. package/src/components/tags-input/index.ts +13 -0
  158. package/src/components/tags-input/tags-input.svelte +305 -0
  159. package/src/components/tags-input/tags.test.ts +64 -0
  160. package/src/components/tags-input/tags.ts +142 -0
  161. package/src/components/theme/index.ts +2 -4
  162. package/src/components/theme/theme-provider.svelte +1 -3
  163. package/src/components/toolbar/toolbar-search.svelte +25 -25
  164. package/src/components/toolbar/toolbar.svelte +11 -7
  165. package/src/components/toolbar/toolbar.test.ts +10 -1
  166. package/src/design/index.css +21 -78
  167. package/src/design/polish.css +26 -35
  168. package/src/editor/editor-toolbar.svelte +104 -99
  169. package/src/editor/editor.svelte +296 -266
  170. package/src/editor/image-toolbar.svelte +20 -22
  171. package/src/editor/index.ts +1 -4
  172. package/src/editor/inline-editor.svelte +280 -238
  173. package/src/editor/markdown.ts +15 -15
  174. package/src/editor/renderer.svelte +71 -71
  175. package/src/editor/slash-menu.svelte +24 -27
  176. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  177. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  178. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  179. package/src/effects/magic-card/MagicCard.css +14 -7
  180. package/src/effects/marquee/Marquee.svelte +3 -5
  181. package/src/effects/reveal/observe.ts +0 -3
  182. package/src/effects/spotlight/Spotlight.svelte +1 -1
  183. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  184. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  185. package/src/gallery.test.ts +14 -4
  186. package/src/inbox/README.md +1 -3
  187. package/src/inbox/inbox.test.ts +181 -138
  188. package/src/inbox/index.ts +20 -20
  189. package/src/inbox/logic.ts +114 -111
  190. package/src/inbox/thread-inbox.svelte +227 -151
  191. package/src/inbox/types.ts +130 -108
  192. package/src/lib/theme-transition.ts +2 -4
  193. package/src/lib/tone.ts +2 -4
  194. package/src/lib/utils.ts +2 -2
  195. package/src/modules.test.ts +79 -69
  196. package/src/onboarding/define-tours.test.ts +55 -40
  197. package/src/onboarding/define-tours.ts +23 -23
  198. package/src/onboarding/demo.svelte +24 -5
  199. package/src/onboarding/index.ts +1 -8
  200. package/src/onboarding/onboarding-provider.svelte +19 -7
  201. package/src/onboarding/onboarding.svelte.ts +16 -16
  202. package/src/onboarding/pick.test.ts +61 -52
  203. package/src/onboarding/pick.ts +16 -13
  204. package/src/onboarding/stores.test.ts +80 -80
  205. package/src/onboarding/stores.ts +41 -39
  206. package/src/onboarding/tour-card.svelte +30 -5
  207. package/src/onboarding/tour-runner.svelte +12 -3
  208. package/src/onboarding/types.ts +20 -12
  209. package/src/onboarding/wait.ts +22 -22
  210. package/src/panel/side-panel.svelte +94 -93
  211. package/src/record/demo-data.ts +291 -225
  212. package/src/record/field-section.svelte +38 -34
  213. package/src/record/fields.ts +148 -96
  214. package/src/record/format.ts +63 -56
  215. package/src/record/index.ts +49 -35
  216. package/src/record/info-list.svelte +145 -114
  217. package/src/record/kinds.ts +31 -31
  218. package/src/record/logic.ts +216 -214
  219. package/src/record/next-up.svelte +45 -34
  220. package/src/record/project-list.svelte +56 -50
  221. package/src/record/record-demo.svelte +130 -110
  222. package/src/record/record-header.svelte +37 -28
  223. package/src/record/record-shell.svelte +54 -54
  224. package/src/record/record-stats.svelte +25 -25
  225. package/src/record/record-timeline.svelte +134 -114
  226. package/src/record/record.test.ts +430 -309
  227. package/src/record/steps-rail.svelte +225 -216
  228. package/src/record/types.ts +234 -215
  229. package/src/record/where-we-are.svelte +62 -58
  230. package/src/shadcn/button/button.svelte +1 -1
  231. package/src/shadcn/card/card-title.svelte +1 -5
  232. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  233. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  234. package/src/shadcn/table/table-cell.svelte +2 -3
  235. package/src/shadcn/utilities.css +3 -8
  236. package/src/slides/Controls.svelte +150 -124
  237. package/src/slides/Deck.svelte +243 -238
  238. package/src/slides/Fragment.svelte +64 -57
  239. package/src/slides/Overview.svelte +106 -101
  240. package/src/slides/Presenter.svelte +300 -266
  241. package/src/slides/Slide.svelte +185 -170
  242. package/src/slides/SlideList.svelte +16 -12
  243. package/src/slides/ambient.d.ts +1 -1
  244. package/src/slides/bridges.ts +42 -23
  245. package/src/slides/deck-controller.svelte.ts +47 -35
  246. package/src/slides/demo-slides.svelte +330 -306
  247. package/src/slides/demo.svelte +164 -160
  248. package/src/slides/export/capture.ts +20 -16
  249. package/src/slides/export/ffmpeg.ts +43 -38
  250. package/src/slides/export/index.ts +5 -5
  251. package/src/slides/export/launch.ts +3 -1
  252. package/src/slides/export/pdf.ts +30 -27
  253. package/src/slides/export/types.ts +10 -8
  254. package/src/slides/index.ts +11 -11
  255. package/src/slides/labels.ts +22 -19
  256. package/src/slides/layout.ts +9 -2
  257. package/src/slides/model.ts +4 -4
  258. package/src/slides/navigation.ts +43 -31
  259. package/src/slides/theme.css +5 -7
  260. package/src/slides/types.ts +9 -10
  261. package/src/status/define-statuses.ts +17 -24
  262. package/src/status/index.ts +5 -5
  263. package/src/status/status-badge.svelte +3 -1
  264. package/src/status/status-dot.svelte +1 -3
  265. package/src/status/status-icon.svelte +1 -1
  266. package/src/status/status-types.ts +21 -21
  267. package/src/tasks/board-order.ts +2 -4
  268. package/src/tasks/demo.svelte +189 -31
  269. package/src/tasks/detail/detail-demo.svelte +73 -13
  270. package/src/tasks/detail/detail-types.ts +45 -45
  271. package/src/tasks/detail/task-detail.svelte +40 -10
  272. package/src/tasks/detail/task-property.svelte +8 -2
  273. package/src/tasks/detail/task-thread.svelte +53 -12
  274. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  275. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  276. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  277. package/src/tasks/gantt/gantt-header.svelte +9 -3
  278. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  279. package/src/tasks/gantt/gantt.css +2 -1
  280. package/src/tasks/gantt/gantt.test.ts +3 -1
  281. package/src/tasks/gantt/logic.test.ts +110 -20
  282. package/src/tasks/gantt/logic.ts +42 -6
  283. package/src/tasks/inbox/inbox-card.svelte +13 -5
  284. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  285. package/src/tasks/inbox/inbox-logic.ts +6 -40
  286. package/src/tasks/inbox/inbox-statuses.ts +23 -23
  287. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  288. package/src/tasks/inbox/inbox-types.ts +54 -58
  289. package/src/tasks/inbox/index.ts +22 -21
  290. package/src/tasks/index.ts +49 -52
  291. package/src/tasks/kanban-board.svelte +16 -12
  292. package/src/tasks/kanban-types.ts +2 -3
  293. package/src/tasks/presets/sales.ts +6 -6
  294. package/src/tasks/task-gantt.svelte +28 -8
  295. package/src/tasks/task-list.svelte +41 -17
  296. package/src/tasks/time/duration.ts +40 -40
  297. package/src/tasks/time/time-chip.svelte +9 -2
  298. package/src/tasks/time/time-dot.svelte +1 -1
  299. package/src/tasks/time/time-ring.svelte +11 -3
  300. package/src/tasks/time/time-tracker.svelte +57 -15
  301. package/src/tasks/time/time-types.ts +57 -57
  302. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  303. package/src/thread/index.ts +7 -7
  304. package/src/thread/logic.ts +36 -33
  305. package/src/thread/thread.svelte +82 -16
  306. package/src/thread/thread.test.ts +58 -47
  307. package/src/thread/types.ts +68 -68
  308. package/src/tokens/craft.css +17 -53
  309. package/src/tokens/index.css +3 -13
  310. package/src/tokens/palette.css +13 -11
  311. package/src/tokens/primitives.css +10 -23
  312. package/src/tasks/inbox/inbox.svelte +0 -203
@@ -8,10 +8,10 @@ export type StageCategory = "open" | "proposal" | "ongoing" | "completed" | "los
8
8
 
9
9
  /** One stage an agency declares. The record draws its label and tone. */
10
10
  export type StageDef = {
11
- key: string;
12
- label: string;
13
- tone: Tone;
14
- category: StageCategory;
11
+ key: string;
12
+ label: string;
13
+ tone: Tone;
14
+ category: StageCategory;
15
15
  };
16
16
 
17
17
  /**
@@ -19,125 +19,142 @@ export type StageDef = {
19
19
  * when no project exists yet. `occurredAt` is an ISO timestamp.
20
20
  */
21
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;
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
43
  };
44
44
 
45
45
  /** The numbers and lines one project rolls up to. The host computes them. */
46
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;
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
73
  };
74
74
 
75
75
  /** The next milestone, or the next dated task when there are no milestones. */
76
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;
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
86
  };
87
87
 
88
- export type FieldType =
89
- | "text"
90
- | "longtext"
91
- | "choice"
92
- | "multi"
93
- | "money"
94
- | "date"
95
- | "url"
96
- | "email"
97
- | "timezone";
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
+ };
98
115
 
99
116
  export type FieldOption = {
100
- key: string;
101
- label: string;
102
- tone?: Tone;
103
- /** Still reads on old records, cannot be picked. */
104
- archived?: boolean;
117
+ key: string;
118
+ label: string;
119
+ tone?: Tone;
120
+ /** Still reads on old records, cannot be picked. */
121
+ archived?: boolean;
105
122
  };
106
123
 
107
- /** What a field holds. A choice is its option key, a multi is its keys. */
124
+ /** What a field holds. A choice is its option key, a multi is its keys, tags are the labels. */
108
125
  export type FieldValue = string | number | string[] | null;
109
126
 
110
- /** One attribute a module declares. The record draws it and calls `onSave`. */
111
- export type FieldDef = {
112
- key: string;
113
- label: string;
114
- type: FieldType;
115
- options?: FieldOption[];
116
- section?: string;
117
- required?: boolean;
118
- /** Shown only while another field has this value. Without `equals`, any value. */
119
- showWhen?: { key: string; equals?: string | string[] };
120
- /** For money. Defaults to the record's currency. */
121
- currency?: string;
122
- placeholder?: string;
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;
123
135
  };
124
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
+
125
142
  export type FieldSectionDef = { key: string; label: string };
126
143
 
127
144
  /** One step of the rail. A step is a milestone task. */
128
145
  export type StepDef = {
129
- id: string;
130
- label: string;
131
- done: boolean;
132
- /** Which group header it sits under, such as Sales or Delivery. */
133
- group?: string;
134
- /** ISO dates. Both set draws a range, one draws a day. */
135
- start?: string | null;
136
- end?: string | null;
137
- /** Hidden from the rail, still in the edit menu. */
138
- hidden?: boolean;
139
- /** The label was renamed: the menu offers a reset. */
140
- renamed?: boolean;
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;
141
158
  };
142
159
 
143
160
  export type StepGroupDef = { key: string; label: string };
@@ -146,135 +163,137 @@ export type StepMarker = "complete" | "current" | "late" | "pending";
146
163
 
147
164
  /** One row of an `InfoList`. The value is already formatted for display. */
148
165
  export type InfoRow = {
149
- /** Names the row for a host to find it. Falls back to the label. */
150
- id?: string;
151
- label: string;
152
- icon?: PhosphorIcon;
153
- value?: string | null;
154
- /** A coloured chip in place of plain text. */
155
- tone?: Tone;
156
- /** Chips for a multi value. Wins over `value`. */
157
- chips?: { label: string; tone?: Tone }[];
158
- /** What the editor starts from, when it differs from what is drawn. */
159
- editValue?: string | string[] | null;
160
- href?: string;
161
- muted?: boolean;
162
- /** `select` becomes a searchable list past seven options. */
163
- input?: "text" | "longtext" | "select" | "multi" | "date" | "number" | "url" | "email";
164
- options?: { value: string; label: string; tone?: Tone }[];
165
- placeholder?: string;
166
- required?: boolean;
167
- /** Without it the row is read-only. A multi row passes the picked keys. */
168
- onSave?: (value: string | string[]) => void | Promise<void>;
169
- divider?: boolean;
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;
170
189
  };
171
190
 
172
191
  /** A kind of event: how its filter chip and its default row read. */
173
192
  export type RecordKindDef = {
174
- key: string;
175
- label: string;
176
- icon?: PhosphorIcon;
177
- tone?: Tone;
193
+ key: string;
194
+ label: string;
195
+ icon?: PhosphorIcon;
196
+ tone?: Tone;
178
197
  };
179
198
 
180
199
  /** A filter chip: a label over the kinds it shows. */
181
200
  export type RecordFilterDef = { key: string; label: string; kinds: string[] };
182
201
 
183
202
  export type RecordLabels = {
184
- whereWeAre: string;
185
- more: string;
186
- less: string;
187
- next: string;
188
- late: string;
189
- steps: string;
190
- stepsCount: (done: number, total: number) => string;
191
- editSteps: string;
192
- rename: string;
193
- resetName: string;
194
- hide: string;
195
- show: string;
196
- moveUp: string;
197
- moveDown: string;
198
- shownCount: (shown: number, total: number) => string;
199
- openSteps: string;
200
- activity: string;
201
- all: string;
202
- noEvents: string;
203
- older: (count: number) => string;
204
- today: string;
205
- yesterday: string;
206
- by: (name: string) => string;
207
- projects: string;
208
- projectCount: (count: number) => string;
209
- noProjects: string;
210
- details: string;
211
- edit: (field: string) => string;
212
- add: (field: string) => string;
213
- save: string;
214
- cancel: string;
215
- search: string;
216
- noMatch: string;
217
- pipeline: string;
218
- won: string;
219
- paid: string;
220
- lastCall: string;
221
- lastTouch: string;
222
- noCall: string;
223
- open: string;
224
- edited: string;
225
- noSteps: string;
226
- saveFailed: string;
227
- required: string;
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;
228
247
  };
229
248
 
230
249
  export const DEFAULT_RECORD_LABELS: RecordLabels = {
231
- whereWeAre: "Where we are",
232
- more: "More",
233
- less: "Less",
234
- next: "Next",
235
- late: "Late",
236
- steps: "Steps",
237
- stepsCount: (done, total) => `${done}/${total}`,
238
- editSteps: "Choose visible steps",
239
- rename: "Rename",
240
- resetName: "Reset name",
241
- hide: "Hide",
242
- show: "Show",
243
- moveUp: "Move up",
244
- moveDown: "Move down",
245
- shownCount: (shown, total) => `${shown} of ${total} shown`,
246
- openSteps: "Open steps",
247
- activity: "Activity",
248
- all: "All",
249
- noEvents: "Nothing logged yet",
250
- older: (count) => `Show ${count} older`,
251
- today: "Today",
252
- yesterday: "Yesterday",
253
- by: (name) => `by ${name}`,
254
- projects: "Projects",
255
- projectCount: (count) => (count === 1 ? "1 project" : `${count} projects`),
256
- noProjects: "No projects",
257
- details: "Details",
258
- edit: (field) => `Edit ${field}`,
259
- add: (field) => `Add ${field.toLowerCase()}`,
260
- save: "Save",
261
- cancel: "Cancel",
262
- search: "Search",
263
- noMatch: "No match",
264
- pipeline: "Pipeline",
265
- won: "Won",
266
- paid: "Paid",
267
- lastCall: "Last call",
268
- lastTouch: "Last touch",
269
- noCall: "None yet",
270
- open: "Open",
271
- edited: "edited",
272
- noSteps: "No steps yet",
273
- saveFailed: "Couldn't save",
274
- required: "Required",
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",
275
294
  };
276
295
 
277
296
  /** Merge a partial label set over the defaults. */
278
297
  export function resolveLabels(labels?: Partial<RecordLabels>): RecordLabels {
279
- return labels ? { ...DEFAULT_RECORD_LABELS, ...labels } : DEFAULT_RECORD_LABELS;
298
+ return labels ? { ...DEFAULT_RECORD_LABELS, ...labels } : DEFAULT_RECORD_LABELS;
280
299
  }
@@ -1,65 +1,65 @@
1
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";
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
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();
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
32
 
33
- // =========================
34
- // STATE
35
- // =========================
36
- let expanded = $state(false);
37
- let clamped = $state(false);
38
- let text = $state<HTMLElement | null>(null);
33
+ // =========================
34
+ // STATE
35
+ // =========================
36
+ let expanded = $state(false);
37
+ let clamped = $state(false);
38
+ let text = $state<HTMLElement | null>(null);
39
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));
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
46
 
47
- // =========================
48
- // EFFECTS
49
- // =========================
50
- // WHY measure instead of guessing from length: the clamp is two lines, so the
51
- // same note fits at 1280px and overflows at 390px. "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
- });
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
63
  </script>
64
64
 
65
65
  <!-- =========================== -->
@@ -68,7 +68,11 @@ $effect(() => {
68
68
  {#if note}
69
69
  <section
70
70
  aria-label={t.whereWeAre}
71
- class={cn("flex items-start gap-2.5 border-b border-border bg-muted/40 py-2.5", gutterClass, className)}
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
+ )}
72
76
  >
73
77
  <NavigationArrow
74
78
  class="mt-0.5 size-3.5 shrink-0 text-[color:var(--tone-accent,var(--color-primary))]"
@@ -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: {