@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
@@ -13,7 +13,12 @@
13
13
  personColor,
14
14
  } from "./duration.js";
15
15
  import { DEFAULT_TIME_LABELS } from "./time-types.js";
16
- import type { TimeLabels, TimeLogInput, TimeRunning, TimeStopInfo } from "./time-types.js";
16
+ import type {
17
+ TimeLabels,
18
+ TimeLogInput,
19
+ TimeRunning,
20
+ TimeStopInfo,
21
+ } from "./time-types.js";
17
22
  import TimeDot from "./time-dot.svelte";
18
23
  import { CaretDown, Play, Stop, X } from "phosphor-svelte";
19
24
 
@@ -135,7 +140,9 @@
135
140
  let invalid = $state(false);
136
141
  let durationInput = $state<HTMLInputElement>();
137
142
  let parsed = $derived(parseMinutes(duration));
138
- let preview = $derived(parsed !== null && parsed >= 1 && parsed <= 1440 ? formatMinutes(parsed) : "");
143
+ let preview = $derived(
144
+ parsed !== null && parsed >= 1 && parsed <= 1440 ? formatMinutes(parsed) : "",
145
+ );
139
146
 
140
147
  async function submit(event: SubmitEvent): Promise<void> {
141
148
  event.preventDefault();
@@ -228,9 +235,15 @@
228
235
  <Popover.Trigger class="time-open" aria-label={labels.title}>
229
236
  {#if running}
230
237
  <TimeDot running size={6} />
231
- <span role="timer" aria-label={labels.elapsed}>{clock.hours}:{clock.minutes}:{clock.seconds}</span>
238
+ <span role="timer" aria-label={labels.elapsed}
239
+ >{clock.hours}:{clock.minutes}:{clock.seconds}</span
240
+ >
232
241
  {:else if total > 0}
233
- <span>{formatMinutes(total)}{#if estimate}<span class="ms-1 font-normal text-muted-foreground">/ {formatMinutes(estimate)}</span>{/if}</span>
242
+ <span
243
+ >{formatMinutes(total)}{#if estimate}<span
244
+ class="ms-1 font-normal text-muted-foreground">/ {formatMinutes(estimate)}</span
245
+ >{/if}</span
246
+ >
234
247
  {:else}
235
248
  <span class="text-muted-foreground">{labels.timer}</span>
236
249
  {/if}
@@ -254,12 +267,20 @@
254
267
  class={cn("time-field w-full tabular-nums", preview && "pr-12")}
255
268
  />
256
269
  {#if preview && preview !== duration.trim()}
257
- <span class="time-preview pointer-events-none absolute inset-y-0 right-2 flex items-center text-xs text-muted-foreground tabular-nums" aria-hidden="true">
270
+ <span
271
+ class="time-preview pointer-events-none absolute inset-y-0 right-2 flex items-center text-xs text-muted-foreground tabular-nums"
272
+ aria-hidden="true"
273
+ >
258
274
  {preview}
259
275
  </span>
260
276
  {/if}
261
277
  </div>
262
- <input type="date" bind:value={day} aria-label={labels.date} class="time-field w-full tabular-nums" />
278
+ <input
279
+ type="date"
280
+ bind:value={day}
281
+ aria-label={labels.date}
282
+ class="time-field w-full tabular-nums"
283
+ />
263
284
  <input
264
285
  bind:value={note}
265
286
  placeholder={labels.notePlaceholder}
@@ -288,11 +309,18 @@
288
309
  <div class="h-1 overflow-hidden rounded-full bg-muted" aria-hidden="true">
289
310
  <div
290
311
  class="time-segment h-full rounded-full"
291
- style:background={tone === "neutral" ? "var(--color-foreground)" : TONE_COLOR[tone]}
312
+ style:background={tone === "neutral"
313
+ ? "var(--color-foreground)"
314
+ : TONE_COLOR[tone]}
292
315
  style:width="{Math.min(100, percent)}%"
293
316
  ></div>
294
317
  </div>
295
- <p class={cn("text-xs tabular-nums", tone === "neutral" ? "text-muted-foreground" : TONE_TEXT[tone])}>
318
+ <p
319
+ class={cn(
320
+ "text-xs tabular-nums",
321
+ tone === "neutral" ? "text-muted-foreground" : TONE_TEXT[tone],
322
+ )}
323
+ >
296
324
  {liveTotal > estimate
297
325
  ? labels.over(formatMinutes(liveTotal - estimate), formatMinutes(estimate))
298
326
  : labels.remaining(formatMinutes(estimate - liveTotal), formatMinutes(estimate))}
@@ -303,7 +331,11 @@
303
331
  <ul class="flex flex-wrap gap-x-3 gap-y-1">
304
332
  {#each people as person (person.key)}
305
333
  <li class="flex items-center gap-1.5 text-xs">
306
- <span class="size-2 rounded-full" style:background={personColor(person.key)} aria-hidden="true"></span>
334
+ <span
335
+ class="size-2 rounded-full"
336
+ style:background={personColor(person.key)}
337
+ aria-hidden="true"
338
+ ></span>
307
339
  <span class="text-muted-foreground">{person.name}</span>
308
340
  <span class="font-medium tabular-nums">{formatMinutes(person.minutes)}</span>
309
341
  </li>
@@ -318,8 +350,12 @@
318
350
  {#each groups as group (group.day)}
319
351
  <div>
320
352
  <div class="flex items-baseline justify-between border-b border-border/60 pb-1">
321
- <h3 class="text-xs font-medium text-muted-foreground">{dayLabel(group.day, locale)}</h3>
322
- <span class="text-xs font-medium text-muted-foreground tabular-nums">{formatMinutes(group.minutes)}</span>
353
+ <h3 class="text-xs font-medium text-muted-foreground">
354
+ {dayLabel(group.day, locale)}
355
+ </h3>
356
+ <span class="text-xs font-medium text-muted-foreground tabular-nums"
357
+ >{formatMinutes(group.minutes)}</span
358
+ >
323
359
  </div>
324
360
  <ul>
325
361
  {#each group.items as entry (getId(entry))}
@@ -341,9 +377,13 @@
341
377
  <span class="min-w-0 flex-1 truncate">
342
378
  <span class="sr-only">{name},</span>
343
379
  {#if text}{text}{:else}<span class="text-muted-foreground">{name}</span>{/if}
344
- {#if renderMeta}<span class="ms-1.5 text-xs text-muted-foreground">{@render renderMeta(entry)}</span>{/if}
380
+ {#if renderMeta}<span class="ms-1.5 text-xs text-muted-foreground"
381
+ >{@render renderMeta(entry)}</span
382
+ >{/if}
345
383
  </span>
346
- <span class="shrink-0 font-medium tabular-nums">{formatMinutes(getMinutes(entry))}</span>
384
+ <span class="shrink-0 font-medium tabular-nums"
385
+ >{formatMinutes(getMinutes(entry))}</span
386
+ >
347
387
  {#if onDelete && (canDelete?.(entry) ?? true)}
348
388
  <button
349
389
  type="button"
@@ -498,7 +538,8 @@
498
538
  }
499
539
 
500
540
  .time-segment {
501
- transition: width var(--duration-slow, 400ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
541
+ transition: width var(--duration-slow, 400ms)
542
+ var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
502
543
  }
503
544
 
504
545
  .time-delete {
@@ -527,7 +568,8 @@
527
568
  }
528
569
 
529
570
  .time-row.is-new {
530
- animation: time-row-in var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
571
+ animation: time-row-in var(--duration-fast, 250ms)
572
+ var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
531
573
  }
532
574
 
533
575
  @keyframes time-pop {
@@ -1,74 +1,74 @@
1
1
  /** A running timer. The host persists it, the tracker only reads it. */
2
2
  export type TimeRunning = {
3
- startedAt: Date | string | number;
3
+ startedAt: Date | string | number;
4
4
  };
5
5
 
6
6
  /** What the tracker hands back when a manual entry is submitted. */
7
7
  export type TimeLogInput = {
8
- minutes: number;
9
- /** The day worked, `YYYY-MM-DD`. */
10
- day: string;
11
- note?: string;
8
+ minutes: number;
9
+ /** The day worked, `YYYY-MM-DD`. */
10
+ day: string;
11
+ note?: string;
12
12
  };
13
13
 
14
14
  /** What the tracker hands back when the timer stops. */
15
15
  export type TimeStopInfo = {
16
- /** Elapsed whole minutes, never below 1. */
17
- minutes: number;
18
- startedAt: Date;
19
- stoppedAt: Date;
20
- /** The day it started, `YYYY-MM-DD`, in the reader's calendar. */
21
- day: string;
16
+ /** Elapsed whole minutes, never below 1. */
17
+ minutes: number;
18
+ startedAt: Date;
19
+ stoppedAt: Date;
20
+ /** The day it started, `YYYY-MM-DD`, in the reader's calendar. */
21
+ day: string;
22
22
  };
23
23
 
24
24
  export type TimeLabels = {
25
- title: string;
26
- timer: string;
27
- start: string;
28
- stop: string;
29
- running: string;
30
- since: (time: string) => string;
31
- started: string;
32
- runningFor: (duration: string) => string;
33
- stopped: (duration: string) => string;
34
- elapsed: string;
35
- logged: string;
36
- duration: string;
37
- durationPlaceholder: string;
38
- date: string;
39
- note: string;
40
- notePlaceholder: string;
41
- log: string;
42
- saving: string;
43
- invalid: string;
44
- empty: string;
45
- remaining: (left: string, estimate: string) => string;
46
- over: (over: string, estimate: string) => string;
47
- deleteEntry: (duration: string) => string;
25
+ title: string;
26
+ timer: string;
27
+ start: string;
28
+ stop: string;
29
+ running: string;
30
+ since: (time: string) => string;
31
+ started: string;
32
+ runningFor: (duration: string) => string;
33
+ stopped: (duration: string) => string;
34
+ elapsed: string;
35
+ logged: string;
36
+ duration: string;
37
+ durationPlaceholder: string;
38
+ date: string;
39
+ note: string;
40
+ notePlaceholder: string;
41
+ log: string;
42
+ saving: string;
43
+ invalid: string;
44
+ empty: string;
45
+ remaining: (left: string, estimate: string) => string;
46
+ over: (over: string, estimate: string) => string;
47
+ deleteEntry: (duration: string) => string;
48
48
  };
49
49
 
50
50
  export const DEFAULT_TIME_LABELS: TimeLabels = {
51
- title: "Time",
52
- timer: "Timer",
53
- start: "Start timer",
54
- stop: "Stop timer",
55
- running: "Running",
56
- since: (time) => `Since ${time}`,
57
- started: "Timer started",
58
- runningFor: (duration) => `Timer running, ${duration}`,
59
- stopped: (duration) => `Timer stopped, ${duration} to log`,
60
- elapsed: "Elapsed time",
61
- logged: "Logged",
62
- duration: "Time spent",
63
- durationPlaceholder: "1h30",
64
- date: "Date worked",
65
- note: "Note",
66
- notePlaceholder: "What was done",
67
- log: "Log",
68
- saving: "Saving",
69
- invalid: "Try 45m, 1h30 or 1.5h",
70
- empty: "Log your first hour",
71
- remaining: (left, estimate) => `${left} left of ${estimate}`,
72
- over: (over, estimate) => `${over} over ${estimate}`,
73
- deleteEntry: (duration) => `Delete ${duration} entry`,
51
+ title: "Time",
52
+ timer: "Timer",
53
+ start: "Start timer",
54
+ stop: "Stop timer",
55
+ running: "Running",
56
+ since: (time) => `Since ${time}`,
57
+ started: "Timer started",
58
+ runningFor: (duration) => `Timer running, ${duration}`,
59
+ stopped: (duration) => `Timer stopped, ${duration} to log`,
60
+ elapsed: "Elapsed time",
61
+ logged: "Logged",
62
+ duration: "Time spent",
63
+ durationPlaceholder: "1h30",
64
+ date: "Date worked",
65
+ note: "Note",
66
+ notePlaceholder: "What was done",
67
+ log: "Log",
68
+ saving: "Saving",
69
+ invalid: "Try 45m, 1h30 or 1.5h",
70
+ empty: "Log your first hour",
71
+ remaining: (left, estimate) => `${left} left of ${estimate}`,
72
+ over: (over, estimate) => `${over} over ${estimate}`,
73
+ deleteEntry: (duration) => `Delete ${duration} entry`,
74
74
  };
@@ -22,9 +22,8 @@ export type KanbanDndConfig<T> = {
22
22
  };
23
23
 
24
24
  /**
25
- * The drag state and handlers for a board. Native HTML5 drag is deliberate:
26
- * it reuses the browser's own drop targets, so a card can leave the board and
27
- * land on any other element a caller wired up.
25
+ * A board's drag state and handlers, on native HTML5 drag so a card can leave the board for
26
+ * any drop target a caller wired up.
28
27
  */
29
28
  export function createKanbanDnd<T>(config: KanbanDndConfig<T>) {
30
29
  let draggedId = $state<string | null>(null);
@@ -2,11 +2,11 @@ export { default as Thread } from "./thread.svelte";
2
2
  export { buildThread, fillCount, resolveThreadLabels, resolvedCount } from "./logic.js";
3
3
  export { DEFAULT_THREAD_LABELS } from "./types.js";
4
4
  export type {
5
- ThreadAnchor,
6
- ThreadAuthor,
7
- ThreadAuthorKind,
8
- ThreadComment,
9
- ThreadLabels,
10
- ThreadNode,
11
- ThreadPost,
5
+ ThreadAnchor,
6
+ ThreadAuthor,
7
+ ThreadAuthorKind,
8
+ ThreadComment,
9
+ ThreadLabels,
10
+ ThreadNode,
11
+ ThreadPost,
12
12
  } from "./types.js";
@@ -2,57 +2,60 @@ import type { ThreadComment, ThreadLabels, ThreadNode } from "./types.js";
2
2
  import { DEFAULT_THREAD_LABELS } from "./types.js";
3
3
 
4
4
  function time(comment: ThreadComment): number {
5
- const ms = Date.parse(comment.createdAt);
6
- return Number.isNaN(ms) ? Number.POSITIVE_INFINITY : ms;
5
+ const ms = Date.parse(comment.createdAt);
6
+ return Number.isNaN(ms) ? Number.POSITIVE_INFINITY : ms;
7
7
  }
8
8
 
9
9
  /** Oldest first, a malformed timestamp last, ties in the order they came. */
10
10
  function byTime(comments: ThreadComment[]): ThreadComment[] {
11
- return comments
12
- .map((comment, index) => ({ comment, index }))
13
- .toSorted((a, b) => time(a.comment) - time(b.comment) || a.index - b.index)
14
- .map((row) => row.comment);
11
+ return comments
12
+ .map((comment, index) => ({ comment, index }))
13
+ .toSorted((a, b) => time(a.comment) - time(b.comment) || a.index - b.index)
14
+ .map((row) => row.comment);
15
15
  }
16
16
 
17
17
  /**
18
- * Roots in time order, each with its replies. A reply to a reply joins the root's list, a reply whose
19
- * parent is gone becomes a root, and a loop of parents can't hang the walk.
18
+ * Roots in time order with their replies: a reply to a reply joins the root, an orphan becomes a
19
+ * root, and a parent loop can't hang the walk.
20
20
  */
21
21
  export function buildThread(comments: ThreadComment[]): ThreadNode[] {
22
- const byId = new Map(comments.map((comment) => [comment.id, comment]));
23
-
24
- function rootOf(comment: ThreadComment): ThreadComment {
25
- const seen = new Set<string>([comment.id]);
26
- let current = comment;
27
- for (;;) {
28
- const parent = current.parentId ? byId.get(current.parentId) : undefined;
29
- if (!parent) return current;
30
- if (seen.has(parent.id)) return comment;
31
- seen.add(parent.id);
32
- current = parent;
33
- }
34
- }
35
-
36
- const replies = new Map<string, ThreadComment[]>();
37
- const roots: ThreadComment[] = [];
38
- for (const comment of comments) {
39
- const root = rootOf(comment);
40
- if (root.id === comment.id) roots.push(comment);
41
- else replies.set(root.id, [...(replies.get(root.id) ?? []), comment]);
42
- }
43
- return byTime(roots).map((comment) => ({ comment, replies: byTime(replies.get(comment.id) ?? []) }));
22
+ const byId = new Map(comments.map((comment) => [comment.id, comment]));
23
+
24
+ function rootOf(comment: ThreadComment): ThreadComment {
25
+ const seen = new Set<string>([comment.id]);
26
+ let current = comment;
27
+ for (;;) {
28
+ const parent = current.parentId ? byId.get(current.parentId) : undefined;
29
+ if (!parent) return current;
30
+ if (seen.has(parent.id)) return comment;
31
+ seen.add(parent.id);
32
+ current = parent;
33
+ }
34
+ }
35
+
36
+ const replies = new Map<string, ThreadComment[]>();
37
+ const roots: ThreadComment[] = [];
38
+ for (const comment of comments) {
39
+ const root = rootOf(comment);
40
+ if (root.id === comment.id) roots.push(comment);
41
+ else replies.set(root.id, [...(replies.get(root.id) ?? []), comment]);
42
+ }
43
+ return byTime(roots).map((comment) => ({
44
+ comment,
45
+ replies: byTime(replies.get(comment.id) ?? []),
46
+ }));
44
47
  }
45
48
 
46
49
  /** How many conversations are resolved. */
47
50
  export function resolvedCount(nodes: ThreadNode[]): number {
48
- return nodes.filter((node) => node.comment.resolved).length;
51
+ return nodes.filter((node) => node.comment.resolved).length;
49
52
  }
50
53
 
51
54
  export function resolveThreadLabels(overrides?: Partial<ThreadLabels>): ThreadLabels {
52
- return { ...DEFAULT_THREAD_LABELS, ...overrides };
55
+ return { ...DEFAULT_THREAD_LABELS, ...overrides };
53
56
  }
54
57
 
55
58
  /** Fills `{count}` in a label, so a French host writes its own sentence. */
56
59
  export function fillCount(label: string, count: number): string {
57
- return label.replaceAll("{count}", String(count));
60
+ return label.replaceAll("{count}", String(count));
58
61
  }
@@ -42,7 +42,8 @@
42
42
  avatar,
43
43
  attachments,
44
44
  anchorChip,
45
- when = (comment) => new Date(comment.createdAt).toLocaleDateString(undefined, { month: "short", day: "numeric" }),
45
+ when = (comment) =>
46
+ new Date(comment.createdAt).toLocaleDateString(undefined, { month: "short", day: "numeric" }),
46
47
  onPost,
47
48
  onEdit,
48
49
  onDelete,
@@ -106,8 +107,16 @@
106
107
  }
107
108
  </script>
108
109
 
109
- {#snippet composer(value: string, placeholder: string, onSend: () => Promise<void>, set: (next: string) => void, isReply: boolean)}
110
- <div class="rounded-lg border border-input transition-shadow focus-within:ring-3 focus-within:ring-ring/50">
110
+ {#snippet composer(
111
+ value: string,
112
+ placeholder: string,
113
+ onSend: () => Promise<void>,
114
+ set: (next: string) => void,
115
+ isReply: boolean,
116
+ )}
117
+ <div
118
+ class="rounded-lg border border-input transition-shadow focus-within:ring-3 focus-within:ring-ring/50"
119
+ >
111
120
  <!-- svelte-ignore a11y_autofocus -->
112
121
  <textarea
113
122
  {value}
@@ -120,7 +129,12 @@
120
129
  class="block max-h-60 min-h-16 w-full resize-none border-0 bg-transparent px-3 py-2 text-sm leading-relaxed outline-none [field-sizing:content] placeholder:text-muted-foreground"
121
130
  ></textarea>
122
131
  <div class="flex justify-end px-2 pb-2">
123
- <button type="button" class="thread-button thread-primary" onclick={onSend} disabled={!value.trim() || sending}>
132
+ <button
133
+ type="button"
134
+ class="thread-button thread-primary"
135
+ onclick={onSend}
136
+ disabled={!value.trim() || sending}
137
+ >
124
138
  {sending ? labels.sending : isReply ? labels.reply : labels.send}
125
139
  </button>
126
140
  </div>
@@ -137,7 +151,10 @@
137
151
  {:else if comment.author.avatar}
138
152
  <img src={comment.author.avatar} alt="" class="size-6 rounded-full object-cover" />
139
153
  {:else}
140
- <span class="grid size-6 place-items-center rounded-full bg-muted text-xs font-medium text-muted-foreground" aria-hidden="true">
154
+ <span
155
+ class="grid size-6 place-items-center rounded-full bg-muted text-xs font-medium text-muted-foreground"
156
+ aria-hidden="true"
157
+ >
141
158
  {initial(comment.author.name)}
142
159
  </span>
143
160
  {/if}
@@ -147,7 +164,12 @@
147
164
  <div class="flex min-h-6 flex-wrap items-center gap-x-2">
148
165
  <span class="truncate text-[13px] font-medium">{comment.author.name}</span>
149
166
  {#if comment.author.kind === "agent"}
150
- <span class={cn("inline-flex h-5 items-center rounded-sm border px-1.5 text-xs font-medium", TONE_CLASS.accent)}>
167
+ <span
168
+ class={cn(
169
+ "inline-flex h-5 items-center rounded-sm border px-1.5 text-xs font-medium",
170
+ TONE_CLASS.accent,
171
+ )}
172
+ >
151
173
  {labels.agent}
152
174
  </span>
153
175
  {/if}
@@ -163,11 +185,15 @@
163
185
  <MapPin class="size-3" aria-hidden="true" />{labels.pinned}
164
186
  </button>
165
187
  {:else}
166
- <span class="thread-chip"><MapPin class="size-3" aria-hidden="true" />{labels.pinned}</span>
188
+ <span class="thread-chip"
189
+ ><MapPin class="size-3" aria-hidden="true" />{labels.pinned}</span
190
+ >
167
191
  {/if}
168
192
  {/if}
169
193
  {#if editingId !== comment.id}
170
- <span class="ms-auto flex gap-0.5 opacity-0 transition-opacity group-hover/row:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100">
194
+ <span
195
+ class="ms-auto flex gap-0.5 opacity-0 transition-opacity group-hover/row:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100"
196
+ >
171
197
  {#if isRoot && onResolve}
172
198
  <button
173
199
  type="button"
@@ -181,12 +207,25 @@
181
207
  {/if}
182
208
  {#if canEdit(comment)}
183
209
  {#if onEdit && !comment.content}
184
- <button type="button" class="thread-icon" aria-label={labels.edit} onclick={() => { editingId = comment.id; editDraft = comment.body; }}>
210
+ <button
211
+ type="button"
212
+ class="thread-icon"
213
+ aria-label={labels.edit}
214
+ onclick={() => {
215
+ editingId = comment.id;
216
+ editDraft = comment.body;
217
+ }}
218
+ >
185
219
  <PencilSimple class="size-3.5" aria-hidden="true" />
186
220
  </button>
187
221
  {/if}
188
222
  {#if onDelete}
189
- <button type="button" class="thread-icon" aria-label={labels.delete} onclick={() => onDelete?.(comment)}>
223
+ <button
224
+ type="button"
225
+ class="thread-icon"
226
+ aria-label={labels.delete}
227
+ onclick={() => onDelete?.(comment)}
228
+ >
190
229
  <Trash class="size-3.5" aria-hidden="true" />
191
230
  </button>
192
231
  {/if}
@@ -203,8 +242,14 @@
203
242
  class="mt-1 w-full resize-y rounded-md border border-input bg-transparent px-2.5 py-2 text-sm leading-relaxed outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50"
204
243
  ></textarea>
205
244
  <div class="mt-2 flex justify-end gap-1.5">
206
- <button type="button" class="thread-button" onclick={() => (editingId = null)}>{labels.cancel}</button>
207
- <button type="button" class="thread-button thread-primary" onclick={() => saveEdit(comment)}>{labels.save}</button>
245
+ <button type="button" class="thread-button" onclick={() => (editingId = null)}
246
+ >{labels.cancel}</button
247
+ >
248
+ <button
249
+ type="button"
250
+ class="thread-button thread-primary"
251
+ onclick={() => saveEdit(comment)}>{labels.save}</button
252
+ >
208
253
  </div>
209
254
  {:else if comment.content}
210
255
  <div class="text-sm leading-relaxed"><RichRenderer content={comment.content} /></div>
@@ -221,7 +266,12 @@
221
266
  <div class="flex h-7 items-center justify-between">
222
267
  <h2 class="text-xs font-medium text-muted-foreground">{labels.title}</h2>
223
268
  {#if resolved > 0}
224
- <button type="button" class="thread-button" aria-expanded={showResolved} onclick={() => (showResolved = !showResolved)}>
269
+ <button
270
+ type="button"
271
+ class="thread-button"
272
+ aria-expanded={showResolved}
273
+ onclick={() => (showResolved = !showResolved)}
274
+ >
225
275
  {showResolved ? labels.hideResolved : fillCount(labels.showResolved, resolved)}
226
276
  </button>
227
277
  {/if}
@@ -242,7 +292,10 @@
242
292
  {:else if shown.length > 0}
243
293
  <ol class="flex flex-col gap-4">
244
294
  {#each shown as node (node.comment.id)}
245
- <li class={cn("flex flex-col gap-3", node.comment.resolved && "opacity-60")} data-resolved={node.comment.resolved || undefined}>
295
+ <li
296
+ class={cn("flex flex-col gap-3", node.comment.resolved && "opacity-60")}
297
+ data-resolved={node.comment.resolved || undefined}
298
+ >
246
299
  {@render row(node.comment, true)}
247
300
  {#if node.comment.resolved}
248
301
  <span class="ms-9 text-xs text-muted-foreground">{labels.resolved}</span>
@@ -257,10 +310,23 @@
257
310
  {#if onPost && !node.comment.resolved}
258
311
  {#if replyTo === node.comment.id}
259
312
  <div class="ms-9">
260
- {@render composer(replyDraft, labels.replyPlaceholder, sendReply, (next) => (replyDraft = next), true)}
313
+ {@render composer(
314
+ replyDraft,
315
+ labels.replyPlaceholder,
316
+ sendReply,
317
+ (next) => (replyDraft = next),
318
+ true,
319
+ )}
261
320
  </div>
262
321
  {:else}
263
- <button type="button" class="thread-button ms-9 flex w-fit items-center gap-1.5" onclick={() => { replyTo = node.comment.id; replyDraft = ""; }}>
322
+ <button
323
+ type="button"
324
+ class="thread-button ms-9 flex w-fit items-center gap-1.5"
325
+ onclick={() => {
326
+ replyTo = node.comment.id;
327
+ replyDraft = "";
328
+ }}
329
+ >
264
330
  <ArrowBendDownRight class="size-3.5" aria-hidden="true" />{labels.reply}
265
331
  </button>
266
332
  {/if}