@heyramzi/ui 1.54.2 → 2.0.1

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 (363) hide show
  1. package/COMPONENTS.md +3 -2
  2. package/README.md +34 -9
  3. package/package.json +205 -13
  4. package/scripts/sync-shadcn.mjs +2 -0
  5. package/src/ai/generation-progress/GenerationProgress.svelte +12 -12
  6. package/src/ambient.d.ts +8 -0
  7. package/src/brand/brand.test.ts +67 -0
  8. package/src/brand/index.ts +137 -0
  9. package/src/canvas/README.md +236 -0
  10. package/src/canvas/card/card-frame.svelte +61 -0
  11. package/src/canvas/card/card.css +39 -0
  12. package/src/canvas/card/resize-handles.svelte +119 -0
  13. package/src/canvas/card/resize.ts +33 -0
  14. package/src/canvas/chrome/chrome-zoom.svelte +120 -0
  15. package/src/canvas/chrome/chrome.css +231 -0
  16. package/src/canvas/chrome/lock-badge.svelte +18 -0
  17. package/src/canvas/chrome/side-handles.svelte +30 -0
  18. package/src/canvas/chrome/size-chip.svelte +45 -0
  19. package/src/canvas/chrome/snap-guides.svelte +69 -0
  20. package/src/canvas/components/icons/ToolIcon.svelte +30 -0
  21. package/src/canvas/components/shared/svg/PreviewSvg.svelte +67 -0
  22. package/src/canvas/components/shared/svg/ShapeOutline.svelte +76 -0
  23. package/src/canvas/components/shared/svg/StrokeNode.svelte +69 -0
  24. package/src/canvas/components/shared/svg/TextRule.svelte +37 -0
  25. package/src/canvas/edges/bend-handle.svelte +76 -0
  26. package/src/canvas/edges/edge-gradient.svelte +30 -0
  27. package/src/canvas/edges/edge-path.test.ts +58 -0
  28. package/src/canvas/edges/edge-path.ts +293 -0
  29. package/src/canvas/edges/gradient-edge.svelte +43 -0
  30. package/src/canvas/editor/context-menu.test.ts +65 -0
  31. package/src/canvas/editor/context-menu.ts +48 -0
  32. package/src/canvas/editor/dispatch.svelte.test.ts +243 -0
  33. package/src/canvas/editor/dispatch.svelte.ts +205 -0
  34. package/src/canvas/editor/editor-context.ts +62 -0
  35. package/src/canvas/editor/editor-mode.ts +67 -0
  36. package/src/canvas/editor/frame.ts +98 -0
  37. package/src/canvas/editor/hand-context.ts +15 -0
  38. package/src/canvas/editor/history-core.test.ts +239 -0
  39. package/src/canvas/editor/history-core.ts +247 -0
  40. package/src/canvas/editor/keymap.test.ts +161 -0
  41. package/src/canvas/editor/keymap.ts +151 -0
  42. package/src/canvas/editor/layout-actions.test.ts +156 -0
  43. package/src/canvas/editor/layout-actions.ts +219 -0
  44. package/src/canvas/editor/lock.test.ts +68 -0
  45. package/src/canvas/editor/lock.ts +87 -0
  46. package/src/canvas/editor/rebase.test.ts +208 -0
  47. package/src/canvas/editor/rebase.ts +272 -0
  48. package/src/canvas/editor/style.ts +184 -0
  49. package/src/canvas/editor/text-field-context.ts +41 -0
  50. package/src/canvas/editor/z-order.test.ts +75 -0
  51. package/src/canvas/editor/z-order.ts +87 -0
  52. package/src/canvas/headless/alignment.test.ts +59 -0
  53. package/src/canvas/headless/alignment.ts +23 -0
  54. package/src/canvas/headless/break-apart-find.ts +418 -0
  55. package/src/canvas/headless/break-apart-pieces.ts +104 -0
  56. package/src/canvas/headless/break-apart-plan.ts +439 -0
  57. package/src/canvas/headless/break-apart-style.ts +106 -0
  58. package/src/canvas/headless/break-apart.ts +244 -0
  59. package/src/canvas/headless/canvas-cleanup.test.ts +181 -0
  60. package/src/canvas/headless/canvas-cleanup.ts +102 -0
  61. package/src/canvas/headless/edge-anchors.test.ts +70 -0
  62. package/src/canvas/headless/edge-anchors.ts +143 -0
  63. package/src/canvas/headless/edge-memo.test.ts +58 -0
  64. package/src/canvas/headless/edge-memo.ts +32 -0
  65. package/src/canvas/headless/edge-serialization.test.ts +55 -0
  66. package/src/canvas/headless/edge-serialization.ts +32 -0
  67. package/src/canvas/headless/figure-geometry.ts +293 -0
  68. package/src/canvas/headless/fit-viewport.test.ts +85 -0
  69. package/src/canvas/headless/fit-viewport.ts +137 -0
  70. package/src/canvas/headless/grid.test.ts +34 -0
  71. package/src/canvas/headless/grid.ts +94 -0
  72. package/src/canvas/headless/icon-node.test.ts +23 -0
  73. package/src/canvas/headless/icon-node.ts +66 -0
  74. package/src/canvas/headless/index.ts +9 -0
  75. package/src/canvas/headless/linkify.test.ts +23 -0
  76. package/src/canvas/headless/linkify.ts +25 -0
  77. package/src/canvas/headless/pen-marks.ts +133 -0
  78. package/src/canvas/headless/sketch.test.ts +99 -0
  79. package/src/canvas/headless/sketch.ts +302 -0
  80. package/src/canvas/headless/snap-guides.test.ts +61 -0
  81. package/src/canvas/headless/snap-guides.ts +146 -0
  82. package/src/canvas/headless/svg-figure.test.ts +49 -0
  83. package/src/canvas/headless/svg-figure.ts +176 -0
  84. package/src/canvas/headless/table-layout.test.ts +158 -0
  85. package/src/canvas/headless/table-layout.ts +309 -0
  86. package/src/canvas/index.ts +35 -0
  87. package/src/canvas/lod/lod-mode.svelte.ts +30 -0
  88. package/src/canvas/lod/lod-switch.svelte +140 -0
  89. package/src/canvas/lod/render-all.svelte.ts +52 -0
  90. package/src/canvas/ops/arrange.ts +108 -0
  91. package/src/canvas/ops/ops.test.ts +98 -0
  92. package/src/canvas/ops/ops.ts +347 -0
  93. package/src/canvas/pen/PenField.svelte +128 -0
  94. package/src/canvas/pen/PenField.test.ts +87 -0
  95. package/src/canvas/pen/PenOverlay.svelte +172 -0
  96. package/src/canvas/pen/ShapeNode.svelte +64 -0
  97. package/src/canvas/pen/TextNode.svelte +97 -0
  98. package/src/canvas/pen/colors.ts +28 -0
  99. package/src/canvas/pen/eraser.ts +46 -0
  100. package/src/canvas/pen/geometry.ts +148 -0
  101. package/src/canvas/pen/host-field.test.svelte +13 -0
  102. package/src/canvas/pen/index.ts +40 -0
  103. package/src/canvas/pen/ops.ts +16 -0
  104. package/src/canvas/pen/pen-session.ts +177 -0
  105. package/src/canvas/pen/pen.css +149 -0
  106. package/src/canvas/pen/plugin.ts +139 -0
  107. package/src/canvas/pen/shapes.ts +104 -0
  108. package/src/canvas/pen/stroke-path.ts +54 -0
  109. package/src/canvas/pen/types.ts +96 -0
  110. package/src/canvas/pen/ui.ts +26 -0
  111. package/src/canvas/plugin.test.ts +45 -0
  112. package/src/canvas/plugin.ts +159 -0
  113. package/src/canvas/screen/GlyphIcon.svelte +25 -0
  114. package/src/canvas/screen/PartView.svelte +176 -0
  115. package/src/canvas/screen/ScreenBody.svelte +142 -0
  116. package/src/canvas/screen/ScreenCard.svelte +32 -0
  117. package/src/canvas/screen/glyphs/button.svg +1 -0
  118. package/src/canvas/screen/glyphs/close.svg +1 -0
  119. package/src/canvas/screen/glyphs/down.svg +1 -0
  120. package/src/canvas/screen/glyphs/header.svg +1 -0
  121. package/src/canvas/screen/glyphs/hills.svg +1 -0
  122. package/src/canvas/screen/glyphs/image.svg +1 -0
  123. package/src/canvas/screen/glyphs/input.svg +1 -0
  124. package/src/canvas/screen/glyphs/list.svg +1 -0
  125. package/src/canvas/screen/glyphs/modal.svg +1 -0
  126. package/src/canvas/screen/glyphs/plus.svg +1 -0
  127. package/src/canvas/screen/glyphs/up.svg +1 -0
  128. package/src/canvas/screen/glyphs.ts +40 -0
  129. package/src/canvas/screen/labels.ts +41 -0
  130. package/src/canvas/screen/parts.ts +164 -0
  131. package/src/canvas/screen/plugin.ts +67 -0
  132. package/src/canvas/screen/screen.css +593 -0
  133. package/src/canvas/screen/types.ts +47 -0
  134. package/src/canvas/screen/ui.ts +13 -0
  135. package/src/canvas/test-utils.ts +18 -0
  136. package/src/canvas/types.ts +53 -0
  137. package/src/canvas/ui/CanvasToolbar.svelte +169 -0
  138. package/src/canvas/ui/FrameLayer.svelte +116 -0
  139. package/src/canvas/ui/PenLayer.svelte +39 -0
  140. package/src/canvas/ui/StylePanel.svelte +268 -0
  141. package/src/canvas/ui/canvas-grid.svelte +156 -0
  142. package/src/canvas/ui/canvas-ui.css +350 -0
  143. package/src/canvas/ui/forward-wheel.ts +29 -0
  144. package/src/canvas/ui/icons.ts +124 -0
  145. package/src/canvas/ui/place-panel.ts +80 -0
  146. package/src/canvas/ui/shortcut-tip.test.ts +16 -0
  147. package/src/canvas/ui/shortcut-tip.ts +17 -0
  148. package/src/canvas/ui/tools.ts +146 -0
  149. package/src/charts/README.md +104 -0
  150. package/src/charts/ambient.d.ts +4 -0
  151. package/src/charts/animated-value.css +7 -0
  152. package/src/charts/animated-value.svelte +110 -0
  153. package/src/charts/area-chart.css +29 -0
  154. package/src/charts/area-chart.svelte +204 -0
  155. package/src/charts/bar-chart.css +119 -0
  156. package/src/charts/bar-chart.svelte +275 -0
  157. package/src/charts/charts.css +201 -0
  158. package/src/charts/crosshair.svelte +35 -0
  159. package/src/charts/dashboard/chart.ts +5 -0
  160. package/src/charts/dashboard/dashboard-empty.svelte +55 -0
  161. package/src/charts/dashboard/dashboard-section.svelte +58 -0
  162. package/src/charts/dashboard/data-panel.svelte +81 -0
  163. package/src/charts/dashboard/data-sort-head.svelte +102 -0
  164. package/src/charts/dashboard/data-table.svelte +278 -0
  165. package/src/charts/dashboard/data-table.ts +26 -0
  166. package/src/charts/dashboard/index.ts +25 -0
  167. package/src/charts/dashboard/panel-grid.svelte +43 -0
  168. package/src/charts/dashboard/period-filter.svelte +53 -0
  169. package/src/charts/dashboard/rating-chip.svelte +35 -0
  170. package/src/charts/dashboard/stat-band.svelte +47 -0
  171. package/src/charts/dashboard/stat-tile.svelte +176 -0
  172. package/src/charts/dashboard/windows.ts +8 -0
  173. package/src/charts/donut-chart.css +56 -0
  174. package/src/charts/donut-chart.svelte +158 -0
  175. package/src/charts/edge-fade.svelte +43 -0
  176. package/src/charts/edge-fade.ts +16 -0
  177. package/src/charts/figure.css +100 -0
  178. package/src/charts/figure.svelte +122 -0
  179. package/src/charts/gauge-chart.css +94 -0
  180. package/src/charts/gauge-chart.svelte +302 -0
  181. package/src/charts/hover-band.test.ts +30 -0
  182. package/src/charts/hover-band.ts +21 -0
  183. package/src/charts/hover-focus.svelte +85 -0
  184. package/src/charts/index.ts +31 -0
  185. package/src/charts/legend.css +136 -0
  186. package/src/charts/legend.svelte +112 -0
  187. package/src/charts/line-chart.css +26 -0
  188. package/src/charts/line-chart.svelte +256 -0
  189. package/src/charts/motion.ts +19 -0
  190. package/src/charts/number-chart.css +179 -0
  191. package/src/charts/number-chart.svelte +187 -0
  192. package/src/charts/primitives/chart-container.svelte +57 -0
  193. package/src/charts/primitives/chart-style.svelte +36 -0
  194. package/src/charts/primitives/chart-tooltip.svelte +161 -0
  195. package/src/charts/primitives/chart-utils.ts +54 -0
  196. package/src/charts/primitives/index.ts +11 -0
  197. package/src/charts/reference-line.css +18 -0
  198. package/src/charts/reference-line.svelte +31 -0
  199. package/src/charts/reveal.svelte +61 -0
  200. package/src/charts/ring-chart.css +104 -0
  201. package/src/charts/ring-chart.svelte +166 -0
  202. package/src/charts/sparkline.css +45 -0
  203. package/src/charts/sparkline.svelte +141 -0
  204. package/src/charts/stat-card.css +105 -0
  205. package/src/charts/stat-card.svelte +267 -0
  206. package/src/charts/time-series-chart.css +28 -0
  207. package/src/charts/time-series-chart.svelte +179 -0
  208. package/src/charts/trend-pill.css +38 -0
  209. package/src/charts/trend-pill.svelte +48 -0
  210. package/src/components/copy/copy-button.svelte +1 -1
  211. package/src/components/docs/doc-page.svelte +1 -1
  212. package/src/components/docs/on-this-page.svelte +2 -2
  213. package/src/components/list-page/empty-state.svelte +1 -1
  214. package/src/components/list-page/filter-popover.svelte +1 -1
  215. package/src/components/list-page/section-label.svelte +1 -1
  216. package/src/components/segmented/segment.svelte +4 -4
  217. package/src/components/theme/theme-provider.svelte +6 -4
  218. package/src/components/toolbar/index.ts +5 -0
  219. package/src/components/toolbar/toolbar.svelte +168 -0
  220. package/src/components/toolbar/toolbar.test.ts +66 -0
  221. package/src/editor/README.md +58 -0
  222. package/src/editor/ambient.d.ts +1 -0
  223. package/src/editor/block-id.ts +77 -0
  224. package/src/editor/block-ops.test.ts +237 -0
  225. package/src/editor/block-ops.ts +157 -0
  226. package/src/editor/content.css +418 -0
  227. package/src/editor/editor-toolbar.svelte +303 -0
  228. package/src/editor/editor.svelte +469 -0
  229. package/src/editor/extensions.ts +145 -0
  230. package/src/editor/image-toolbar.svelte +123 -0
  231. package/src/editor/index.ts +17 -0
  232. package/src/editor/inline-editor.svelte +261 -0
  233. package/src/editor/markdown-escaping.test.ts +158 -0
  234. package/src/editor/markdown-marks.test.ts +89 -0
  235. package/src/editor/markdown-test-helpers.ts +13 -0
  236. package/src/editor/markdown-youtube.test.ts +93 -0
  237. package/src/editor/markdown.test.ts +250 -0
  238. package/src/editor/markdown.ts +511 -0
  239. package/src/editor/renderer.svelte +92 -0
  240. package/src/editor/serializer.test.ts +133 -0
  241. package/src/editor/serializer.ts +370 -0
  242. package/src/editor/slash-items.ts +115 -0
  243. package/src/editor/slash-menu.svelte +159 -0
  244. package/src/editor/youtube-url.ts +35 -0
  245. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +13 -13
  246. package/src/gallery.test.ts +20 -1
  247. package/src/lib/result.ts +19 -0
  248. package/src/onboarding/README.md +120 -0
  249. package/src/onboarding/define-tours.test.ts +50 -0
  250. package/src/onboarding/define-tours.ts +31 -0
  251. package/src/onboarding/demo.svelte +86 -0
  252. package/src/onboarding/index.ts +12 -0
  253. package/src/onboarding/onboarding-provider.svelte +120 -0
  254. package/src/onboarding/onboarding.svelte.ts +24 -0
  255. package/src/onboarding/pick.test.ts +79 -0
  256. package/src/onboarding/pick.ts +36 -0
  257. package/src/onboarding/stores.test.ts +103 -0
  258. package/src/onboarding/stores.ts +58 -0
  259. package/src/onboarding/tour-card.svelte +70 -0
  260. package/src/onboarding/tour-runner.svelte +197 -0
  261. package/src/onboarding/types.ts +34 -0
  262. package/src/onboarding/wait.ts +29 -0
  263. package/src/shadcn/command/command-input.svelte +1 -1
  264. package/src/slides/Controls.svelte +138 -0
  265. package/src/slides/Deck.svelte +271 -0
  266. package/src/slides/Fragment.svelte +71 -0
  267. package/src/slides/Overview.svelte +113 -0
  268. package/src/slides/Presenter.svelte +292 -0
  269. package/src/slides/README.md +89 -0
  270. package/src/slides/Slide.svelte +179 -0
  271. package/src/slides/SlideList.svelte +19 -0
  272. package/src/slides/ambient.d.ts +1 -0
  273. package/src/slides/bridges.ts +69 -0
  274. package/src/slides/deck-controller.svelte.ts +292 -0
  275. package/src/slides/demo-slides.svelte +358 -0
  276. package/src/slides/demo.svelte +176 -0
  277. package/src/slides/export/capture.ts +113 -0
  278. package/src/slides/export/ffmpeg.ts +120 -0
  279. package/src/slides/export/index.ts +9 -0
  280. package/src/slides/export/launch.ts +10 -0
  281. package/src/slides/export/pdf.ts +152 -0
  282. package/src/slides/export/types.ts +48 -0
  283. package/src/slides/index.ts +19 -0
  284. package/src/slides/labels.ts +48 -0
  285. package/src/slides/layout.ts +51 -0
  286. package/src/slides/model.ts +47 -0
  287. package/src/slides/navigation.ts +100 -0
  288. package/src/slides/theme.css +205 -0
  289. package/src/slides/types.ts +86 -0
  290. package/src/tasks/README.md +312 -0
  291. package/src/tasks/ambient.d.ts +3 -0
  292. package/src/tasks/board-order.ts +137 -0
  293. package/src/tasks/dates.ts +31 -0
  294. package/src/tasks/define-statuses.ts +32 -0
  295. package/src/tasks/demo.svelte +389 -0
  296. package/src/tasks/detail/detail-demo.svelte +186 -0
  297. package/src/tasks/detail/detail-types.ts +54 -0
  298. package/src/tasks/detail/task-chip.svelte +28 -0
  299. package/src/tasks/detail/task-detail.svelte +315 -0
  300. package/src/tasks/detail/task-property.svelte +17 -0
  301. package/src/tasks/detail/task-thread.svelte +258 -0
  302. package/src/tasks/gantt/gantt-bar.svelte +243 -0
  303. package/src/tasks/gantt/gantt-grid.svelte +94 -0
  304. package/src/tasks/gantt/gantt-group-row.svelte +55 -0
  305. package/src/tasks/gantt/gantt-header.svelte +71 -0
  306. package/src/tasks/gantt/gantt-task-row.svelte +186 -0
  307. package/src/tasks/gantt/gantt.css +22 -0
  308. package/src/tasks/gantt/gantt.test.ts +77 -0
  309. package/src/tasks/gantt/logic.test.ts +163 -0
  310. package/src/tasks/gantt/logic.ts +248 -0
  311. package/src/tasks/inbox/inbox-card.svelte +283 -0
  312. package/src/tasks/inbox/inbox-draft.svelte +84 -0
  313. package/src/tasks/inbox/inbox-logic.ts +55 -0
  314. package/src/tasks/inbox/inbox-statuses.ts +48 -0
  315. package/src/tasks/inbox/inbox-trace.svelte +45 -0
  316. package/src/tasks/inbox/inbox-types.ts +108 -0
  317. package/src/tasks/inbox/inbox.svelte +203 -0
  318. package/src/tasks/inbox/index.ts +23 -0
  319. package/src/tasks/index.ts +85 -0
  320. package/src/tasks/kanban-board.svelte +561 -0
  321. package/src/tasks/kanban-column.svelte +117 -0
  322. package/src/tasks/kanban-drag.ts +38 -0
  323. package/src/tasks/kanban-types.ts +65 -0
  324. package/src/tasks/presets/sales.ts +16 -0
  325. package/src/tasks/record/demo-data.ts +286 -0
  326. package/src/tasks/record/field-section.svelte +53 -0
  327. package/src/tasks/record/fields.ts +142 -0
  328. package/src/tasks/record/format.ts +87 -0
  329. package/src/tasks/record/gutter.ts +2 -0
  330. package/src/tasks/record/index.ts +54 -0
  331. package/src/tasks/record/info-list.svelte +316 -0
  332. package/src/tasks/record/kinds.ts +42 -0
  333. package/src/tasks/record/logic.ts +324 -0
  334. package/src/tasks/record/next-up.svelte +70 -0
  335. package/src/tasks/record/project-list.svelte +133 -0
  336. package/src/tasks/record/record-demo.svelte +324 -0
  337. package/src/tasks/record/record-header.svelte +70 -0
  338. package/src/tasks/record/record-shell.svelte +138 -0
  339. package/src/tasks/record/record-stats.svelte +48 -0
  340. package/src/tasks/record/record-timeline.svelte +231 -0
  341. package/src/tasks/record/record.test.ts +350 -0
  342. package/src/tasks/record/steps-rail.svelte +474 -0
  343. package/src/tasks/record/types.ts +280 -0
  344. package/src/tasks/record/where-we-are.svelte +121 -0
  345. package/src/tasks/reorder.ts +19 -0
  346. package/src/tasks/side-panel-types.ts +16 -0
  347. package/src/tasks/side-panel.svelte +193 -0
  348. package/src/tasks/status-badge.svelte +63 -0
  349. package/src/tasks/status-dot.svelte +228 -0
  350. package/src/tasks/status-icon.svelte +30 -0
  351. package/src/tasks/status-types.ts +40 -0
  352. package/src/tasks/task-gantt.svelte +230 -0
  353. package/src/tasks/task-list.svelte +470 -0
  354. package/src/tasks/task-types.ts +33 -0
  355. package/src/tasks/time/duration.ts +69 -0
  356. package/src/tasks/time/time-chip.svelte +62 -0
  357. package/src/tasks/time/time-dot.svelte +58 -0
  358. package/src/tasks/time/time-ring.svelte +74 -0
  359. package/src/tasks/time/time-tracker.svelte +565 -0
  360. package/src/tasks/time/time-types.ts +74 -0
  361. package/src/tasks/use-kanban-dnd.svelte.ts +173 -0
  362. package/src/tokens/base.css +5 -5
  363. package/src/tokens/palette.css +115 -120
@@ -0,0 +1,315 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { Circle, CheckCircle, PencilSimple } from "phosphor-svelte";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { DEFAULT_TASK_DETAIL_LABELS, type TaskComment, type TaskDetailLabels } from "./detail-types.js";
6
+ import TaskThread from "./task-thread.svelte";
7
+
8
+ interface Props {
9
+ title: string;
10
+ /** Without it the title is read-only. */
11
+ onTitleChange?: (next: string) => void | Promise<void>;
12
+ done?: boolean;
13
+ /** Closed covers done and dropped: the title mutes, never strikes through. */
14
+ closed?: boolean;
15
+ onToggleDone?: () => void;
16
+ /** One 12px line under the title: origin, dates. */
17
+ meta?: Snippet;
18
+ /** The rail. A row of `TaskProperty` with a `TaskChip` or any picker trigger in each. */
19
+ properties?: Snippet;
20
+ /** Under the rail's rows: ids, shortcuts, anything quiet. */
21
+ railFooter?: Snippet;
22
+ description?: string | null;
23
+ onDescriptionChange?: (next: string | null) => void | Promise<void>;
24
+ comments?: TaskComment[];
25
+ loadingComments?: boolean;
26
+ agentAvatar?: Snippet<[TaskComment]>;
27
+ avatar?: Snippet<[TaskComment]>;
28
+ when?: (comment: TaskComment) => string;
29
+ onSend?: (body: string) => void | boolean | Promise<void | boolean>;
30
+ onEditComment?: (comment: TaskComment, body: string) => void | Promise<void>;
31
+ onDeleteComment?: (comment: TaskComment) => void | Promise<void>;
32
+ canEditComment?: (comment: TaskComment) => boolean;
33
+ labels?: Partial<TaskDetailLabels>;
34
+ class?: string;
35
+ }
36
+
37
+ let {
38
+ title,
39
+ onTitleChange,
40
+ done = false,
41
+ closed = false,
42
+ onToggleDone,
43
+ meta,
44
+ properties,
45
+ railFooter,
46
+ description = null,
47
+ onDescriptionChange,
48
+ comments = [],
49
+ loadingComments = false,
50
+ agentAvatar,
51
+ avatar,
52
+ when,
53
+ onSend,
54
+ onEditComment,
55
+ onDeleteComment,
56
+ canEditComment,
57
+ labels: labelOverrides,
58
+ class: className,
59
+ }: Props = $props();
60
+
61
+ let labels = $derived({ ...DEFAULT_TASK_DETAIL_LABELS, ...labelOverrides });
62
+ let editingTitle = $state(false);
63
+ let titleDraft = $state("");
64
+ let titleField = $state<HTMLTextAreaElement | null>(null);
65
+ let editingDescription = $state(false);
66
+ let descriptionDraft = $state("");
67
+ let savingDescription = $state(false);
68
+
69
+ function startTitle(): void {
70
+ if (!onTitleChange) return;
71
+ titleDraft = title;
72
+ editingTitle = true;
73
+ queueMicrotask(() => titleField?.focus());
74
+ }
75
+
76
+ function commitTitle(): void {
77
+ const next = titleDraft.trim();
78
+ editingTitle = false;
79
+ if (next && next !== title) void onTitleChange?.(next);
80
+ }
81
+
82
+ function onTitleKeydown(event: KeyboardEvent): void {
83
+ if (event.key === "Enter") {
84
+ event.preventDefault();
85
+ commitTitle();
86
+ }
87
+ if (event.key === "Escape") {
88
+ event.preventDefault();
89
+ event.stopPropagation();
90
+ editingTitle = false;
91
+ }
92
+ }
93
+
94
+ function startDescription(): void {
95
+ if (!onDescriptionChange) return;
96
+ descriptionDraft = description ?? "";
97
+ editingDescription = true;
98
+ }
99
+
100
+ async function saveDescription(): Promise<void> {
101
+ savingDescription = true;
102
+ try {
103
+ await onDescriptionChange?.(descriptionDraft.trim() || null);
104
+ } finally {
105
+ savingDescription = false;
106
+ editingDescription = false;
107
+ }
108
+ }
109
+ </script>
110
+
111
+ <!-- A container query, not a viewport one, so the rail also collapses inside a pop-up. -->
112
+ <div class={cn("@container flex-1", className)}>
113
+ <div class="grid grid-cols-1 @3xl:grid-cols-[minmax(0,1fr)_18rem] @3xl:grid-rows-[auto_1fr]">
114
+ <header class="min-w-0 px-4 pt-6 @3xl:col-start-1 @3xl:px-8">
115
+ <div class="mx-auto w-full max-w-184">
116
+ <div class="flex items-start gap-2">
117
+ <button
118
+ type="button"
119
+ onclick={onToggleDone}
120
+ disabled={!onToggleDone}
121
+ aria-label={done ? labels.markIncomplete : labels.markComplete}
122
+ class={cn("tk-icon mt-px", done ? "text-success" : "text-muted-foreground/60 hover:text-foreground")}
123
+ >
124
+ {#if done}
125
+ <CheckCircle class="size-5" weight="fill" aria-hidden="true" />
126
+ {:else}
127
+ <Circle class="size-5" aria-hidden="true" />
128
+ {/if}
129
+ </button>
130
+
131
+ {#if editingTitle}
132
+ <textarea
133
+ bind:this={titleField}
134
+ bind:value={titleDraft}
135
+ onkeydown={onTitleKeydown}
136
+ onblur={commitTitle}
137
+ rows={2}
138
+ aria-label={labels.editTitle}
139
+ class="-mx-2 -my-1 min-w-0 flex-1 resize-none rounded-md border border-input bg-transparent px-2 py-1 text-2xl leading-tight font-semibold tracking-tight outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
140
+ ></textarea>
141
+ {:else}
142
+ <h1 class="min-w-0 flex-1 text-2xl leading-tight font-semibold tracking-tight text-balance break-words">
143
+ <button
144
+ type="button"
145
+ onclick={startTitle}
146
+ disabled={!onTitleChange}
147
+ aria-label={labels.editTitle}
148
+ class={cn(
149
+ "group -mx-2 -my-1 rounded-md px-2 py-1 text-start outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
150
+ onTitleChange && "hover:bg-muted/60",
151
+ closed && "text-muted-foreground",
152
+ )}
153
+ >
154
+ {title}
155
+ {#if onTitleChange}
156
+ <PencilSimple class="ms-1.5 inline size-3.5 align-middle text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100" aria-hidden="true" />
157
+ {/if}
158
+ </button>
159
+ </h1>
160
+ {/if}
161
+ </div>
162
+ {#if meta}
163
+ <div class="mt-2 flex min-h-5 flex-wrap items-center gap-x-3 gap-y-1 ps-9 text-xs text-muted-foreground">
164
+ {@render meta()}
165
+ </div>
166
+ {/if}
167
+ </div>
168
+ </header>
169
+
170
+ <aside
171
+ class="px-4 pt-3 @3xl:col-start-2 @3xl:row-span-2 @3xl:row-start-1 @3xl:border-s @3xl:border-border @3xl:px-5 @3xl:pt-6"
172
+ aria-label={labels.properties}
173
+ >
174
+ <div class="flex flex-wrap items-center gap-1 @3xl:sticky @3xl:top-0 @3xl:flex-col @3xl:items-stretch @3xl:gap-0.5">
175
+ {@render properties?.()}
176
+ {#if railFooter}
177
+ <div class="mt-2 hidden w-full border-t border-border/60 pt-3 @3xl:block">{@render railFooter()}</div>
178
+ {/if}
179
+ </div>
180
+ </aside>
181
+
182
+ <section class="flex min-w-0 flex-col gap-8 px-4 pt-6 pb-16 @3xl:col-start-1 @3xl:px-8">
183
+ <div class="mx-auto flex w-full max-w-184 flex-col gap-8 ps-0 @3xl:ps-9">
184
+ <div class="flex flex-col gap-1">
185
+ <div class="flex h-7 items-center justify-between">
186
+ <h2 class="text-xs font-medium text-muted-foreground">{labels.description}</h2>
187
+ {#if !editingDescription && description && onDescriptionChange}
188
+ <button type="button" class="tk-icon" onclick={startDescription} aria-label={labels.editDescription}>
189
+ <PencilSimple class="size-3.5" aria-hidden="true" />
190
+ </button>
191
+ {/if}
192
+ </div>
193
+
194
+ {#if editingDescription}
195
+ <textarea
196
+ bind:value={descriptionDraft}
197
+ rows={8}
198
+ placeholder={labels.addDescription}
199
+ aria-label={labels.description}
200
+ class="w-full resize-y rounded-md border border-input bg-transparent px-3 py-2 text-sm leading-relaxed outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50"
201
+ ></textarea>
202
+ <div class="mt-1 flex justify-end gap-1.5">
203
+ <button type="button" class="tk-button" onclick={() => (editingDescription = false)}>{labels.cancel}</button>
204
+ <button type="button" class="tk-button tk-primary" onclick={saveDescription} disabled={savingDescription}>
205
+ {savingDescription ? labels.saving : labels.save}
206
+ </button>
207
+ </div>
208
+ {:else if description}
209
+ <button
210
+ type="button"
211
+ onclick={startDescription}
212
+ disabled={!onDescriptionChange}
213
+ class={cn(
214
+ "-mx-3 block w-[calc(100%+1.5rem)] rounded-md px-3 py-1.5 text-start text-sm leading-relaxed whitespace-pre-wrap text-pretty outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
215
+ onDescriptionChange && "hover:bg-muted/50",
216
+ )}
217
+ >
218
+ {description}
219
+ </button>
220
+ {:else if onDescriptionChange}
221
+ <button
222
+ type="button"
223
+ onclick={startDescription}
224
+ class="flex h-7 w-full items-center rounded-md border border-dashed border-border px-2.5 text-[13px] text-muted-foreground transition-colors outline-none hover:border-muted-foreground/40 hover:bg-muted/40 hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
225
+ >
226
+ {labels.addDescription}
227
+ </button>
228
+ {/if}
229
+ </div>
230
+
231
+ <TaskThread
232
+ {comments}
233
+ loading={loadingComments}
234
+ labels={labelOverrides}
235
+ {agentAvatar}
236
+ {avatar}
237
+ {when}
238
+ {onSend}
239
+ onEdit={onEditComment}
240
+ onDelete={onDeleteComment}
241
+ canEdit={canEditComment}
242
+ />
243
+ </div>
244
+ </section>
245
+ </div>
246
+ </div>
247
+
248
+ <style>
249
+ .tk-icon {
250
+ display: grid;
251
+ place-items: center;
252
+ width: 1.75rem;
253
+ height: 1.75rem;
254
+ flex: none;
255
+ border-radius: var(--radius-md, 0.5rem);
256
+ outline: none;
257
+ transition:
258
+ background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out),
259
+ color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
260
+ }
261
+
262
+ @media (hover: hover) and (pointer: fine) {
263
+ .tk-icon:hover:not(:disabled) {
264
+ background: var(--color-muted);
265
+ }
266
+ }
267
+
268
+ .tk-icon:focus-visible,
269
+ .tk-button:focus-visible {
270
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-ring) 50%, transparent);
271
+ }
272
+
273
+ .tk-button {
274
+ height: 1.75rem;
275
+ padding-inline: 0.75rem;
276
+ border-radius: var(--radius-md, 0.5rem);
277
+ font-size: 0.8125rem;
278
+ font-weight: 500;
279
+ outline: none;
280
+ transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
281
+ }
282
+
283
+ @media (hover: hover) and (pointer: fine) {
284
+ .tk-button:hover:not(:disabled) {
285
+ background: var(--color-muted);
286
+ }
287
+ }
288
+
289
+ .tk-button:disabled {
290
+ opacity: 0.5;
291
+ pointer-events: none;
292
+ }
293
+
294
+ .tk-primary {
295
+ background: var(--color-primary);
296
+ color: var(--color-primary-foreground);
297
+ }
298
+
299
+ @media (hover: hover) and (pointer: fine) {
300
+ .tk-primary:hover:not(:disabled) {
301
+ background: color-mix(in oklab, var(--color-primary) 90%, transparent);
302
+ }
303
+ }
304
+
305
+ @media (any-pointer: coarse) {
306
+ .tk-icon {
307
+ width: 2.5rem;
308
+ height: 2.5rem;
309
+ }
310
+
311
+ .tk-button {
312
+ height: 2.5rem;
313
+ }
314
+ }
315
+ </style>
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ interface Props {
5
+ label: string;
6
+ children: Snippet;
7
+ }
8
+
9
+ let { label, children }: Props = $props();
10
+ </script>
11
+
12
+ <!-- A labelled row in the rail. In the narrow chip row the label hides and
13
+ names the group for assistive tech. -->
14
+ <div class="flex min-h-7 items-center @3xl:grid @3xl:grid-cols-[6rem_minmax(0,1fr)]" role="group" aria-label={label}>
15
+ <span class="hidden text-xs font-medium text-muted-foreground @3xl:block" aria-hidden="true">{label}</span>
16
+ <div class="flex min-w-0 items-center">{@render children()}</div>
17
+ </div>
@@ -0,0 +1,258 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { PencilSimple, Trash } from "phosphor-svelte";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { AgentAvatar } from "../../agents/index.js";
6
+ import { TONE_CLASS } from "../../lib/tone.js";
7
+ import { Skeleton } from "../../shadcn/skeleton/index.js";
8
+ import { DEFAULT_TASK_DETAIL_LABELS, type TaskComment, type TaskDetailLabels } from "./detail-types.js";
9
+
10
+ interface Props {
11
+ comments: TaskComment[];
12
+ /** Rows still on their way. Draws skeleton rows in place of the thread. */
13
+ loading?: boolean;
14
+ labels?: Partial<TaskDetailLabels>;
15
+ /** Replaces the default agent avatar, drawn in a 24px slot. */
16
+ agentAvatar?: Snippet<[TaskComment]>;
17
+ /** Replaces the default initial for a person, drawn in a 24px slot. */
18
+ avatar?: Snippet<[TaskComment]>;
19
+ /** How a timestamp reads. Defaults to a short date. */
20
+ when?: (comment: TaskComment) => string;
21
+ /** Returns false to keep the draft. Without it the composer is hidden. */
22
+ onSend?: (body: string) => void | boolean | Promise<void | boolean>;
23
+ onEdit?: (comment: TaskComment, body: string) => void | Promise<void>;
24
+ onDelete?: (comment: TaskComment) => void | Promise<void>;
25
+ canEdit?: (comment: TaskComment) => boolean;
26
+ }
27
+
28
+ let {
29
+ comments,
30
+ loading = false,
31
+ labels: labelOverrides,
32
+ agentAvatar,
33
+ avatar,
34
+ when = (comment) => new Date(comment.createdAt).toLocaleDateString(undefined, { month: "short", day: "numeric" }),
35
+ onSend,
36
+ onEdit,
37
+ onDelete,
38
+ canEdit = () => false,
39
+ }: Props = $props();
40
+
41
+ let labels = $derived({ ...DEFAULT_TASK_DETAIL_LABELS, ...labelOverrides });
42
+ let draft = $state("");
43
+ let sending = $state(false);
44
+ let editingId = $state<string | null>(null);
45
+ let editDraft = $state("");
46
+ let canSend = $derived(draft.trim().length > 0 && !sending);
47
+
48
+ async function send(): Promise<void> {
49
+ if (!canSend || !onSend) return;
50
+ sending = true;
51
+ try {
52
+ const result = await onSend(draft.trim());
53
+ if (result !== false) draft = "";
54
+ } finally {
55
+ sending = false;
56
+ }
57
+ }
58
+
59
+ function onComposerKeydown(event: KeyboardEvent): void {
60
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
61
+ event.preventDefault();
62
+ void send();
63
+ }
64
+ }
65
+
66
+ function startEdit(comment: TaskComment): void {
67
+ editingId = comment.id;
68
+ editDraft = comment.body;
69
+ }
70
+
71
+ async function saveEdit(comment: TaskComment): Promise<void> {
72
+ const body = editDraft.trim();
73
+ editingId = null;
74
+ if (!body || body === comment.body) return;
75
+ await onEdit?.(comment, body);
76
+ }
77
+
78
+ function initial(name: string): string {
79
+ return name.trim().charAt(0).toLocaleUpperCase() || "?";
80
+ }
81
+ </script>
82
+
83
+ <section aria-label={labels.comments} class="flex flex-col gap-3">
84
+ <h2 class="flex h-7 items-center text-xs font-medium text-muted-foreground">{labels.comments}</h2>
85
+
86
+ {#if loading}
87
+ <ol class="flex flex-col gap-4" aria-busy="true">
88
+ {#each [0, 1] as row (row)}
89
+ <li class="flex gap-3">
90
+ <Skeleton class="size-6 shrink-0 rounded-full" />
91
+ <div class="flex flex-1 flex-col gap-2">
92
+ <Skeleton class="h-3.5 w-28" />
93
+ <Skeleton class="h-3.5 w-full" />
94
+ </div>
95
+ </li>
96
+ {/each}
97
+ </ol>
98
+ {:else if comments.length > 0}
99
+ <ol class="flex flex-col gap-4">
100
+ {#each comments as comment (comment.id)}
101
+ <li class="group relative flex gap-3">
102
+ <!-- The rail that ties one avatar to the next, as in an activity feed. -->
103
+ <span class="absolute start-[11.5px] top-8 -bottom-3 w-px bg-border group-last:hidden" aria-hidden="true"></span>
104
+ <span class="relative size-6 shrink-0">
105
+ {#if comment.agent}
106
+ {#if agentAvatar}
107
+ {@render agentAvatar(comment)}
108
+ {:else}
109
+ <AgentAvatar name={comment.author} size={24} paused interactive={false} />
110
+ {/if}
111
+ {:else if avatar}
112
+ {@render avatar(comment)}
113
+ {:else}
114
+ <span class="grid size-6 place-items-center rounded-full bg-muted text-xs font-medium text-muted-foreground" aria-hidden="true">
115
+ {initial(comment.author)}
116
+ </span>
117
+ {/if}
118
+ </span>
119
+
120
+ <div class="min-w-0 flex-1">
121
+ <div class="flex h-6 items-center gap-2">
122
+ <span class="truncate text-[13px] font-medium">{comment.author}</span>
123
+ {#if comment.agent}
124
+ <span class={cn("inline-flex h-5 items-center rounded-sm border px-1.5 text-xs font-medium", TONE_CLASS.accent)}>
125
+ {labels.ai}
126
+ </span>
127
+ {/if}
128
+ <span class="text-xs text-muted-foreground tabular-nums">{when(comment)}</span>
129
+ {#if comment.edited}
130
+ <span class="text-xs text-muted-foreground">{labels.commentEdited}</span>
131
+ {/if}
132
+ {#if canEdit(comment) && editingId !== comment.id}
133
+ <span class="ms-auto flex gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100">
134
+ {#if onEdit}
135
+ <button type="button" class="thread-icon" aria-label={labels.commentEdit} onclick={() => startEdit(comment)}>
136
+ <PencilSimple class="size-3.5" aria-hidden="true" />
137
+ </button>
138
+ {/if}
139
+ {#if onDelete}
140
+ <button type="button" class="thread-icon" aria-label={labels.commentDelete} onclick={() => onDelete?.(comment)}>
141
+ <Trash class="size-3.5" aria-hidden="true" />
142
+ </button>
143
+ {/if}
144
+ </span>
145
+ {/if}
146
+ </div>
147
+
148
+ {#if editingId === comment.id}
149
+ <textarea
150
+ bind:value={editDraft}
151
+ rows={3}
152
+ aria-label={labels.comments}
153
+ 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"
154
+ ></textarea>
155
+ <div class="mt-2 flex justify-end gap-1.5">
156
+ <button type="button" class="thread-button" onclick={() => (editingId = null)}>{labels.cancel}</button>
157
+ <button type="button" class="thread-button thread-primary" onclick={() => saveEdit(comment)}>{labels.save}</button>
158
+ </div>
159
+ {:else}
160
+ <p class="text-sm leading-relaxed whitespace-pre-wrap text-pretty">{comment.body}</p>
161
+ {/if}
162
+ </div>
163
+ </li>
164
+ {/each}
165
+ </ol>
166
+ {:else if !onSend}
167
+ <p class="text-[13px] text-muted-foreground">{labels.commentsEmpty}</p>
168
+ {/if}
169
+
170
+ {#if onSend}
171
+ <div class="rounded-lg border border-input transition-shadow focus-within:ring-3 focus-within:ring-ring/50">
172
+ <textarea
173
+ bind:value={draft}
174
+ onkeydown={onComposerKeydown}
175
+ rows={2}
176
+ placeholder={labels.commentPlaceholder}
177
+ aria-label={labels.commentPlaceholder}
178
+ 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"
179
+ ></textarea>
180
+ <div class="flex justify-end px-2 pb-2">
181
+ <button type="button" class="thread-button thread-primary" onclick={send} disabled={!canSend}>
182
+ {sending ? labels.saving : labels.commentSend}
183
+ </button>
184
+ </div>
185
+ </div>
186
+ {/if}
187
+ </section>
188
+
189
+ <style>
190
+ .thread-icon {
191
+ display: grid;
192
+ place-items: center;
193
+ width: 1.75rem;
194
+ height: 1.75rem;
195
+ border-radius: var(--radius-md, 0.5rem);
196
+ color: var(--color-muted-foreground);
197
+ outline: none;
198
+ transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
199
+ }
200
+
201
+ @media (hover: hover) and (pointer: fine) {
202
+ .thread-icon:hover {
203
+ background: var(--color-muted);
204
+ color: var(--color-foreground);
205
+ }
206
+ }
207
+
208
+ .thread-icon:focus-visible,
209
+ .thread-button:focus-visible {
210
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-ring) 50%, transparent);
211
+ }
212
+
213
+ .thread-button {
214
+ height: 1.75rem;
215
+ padding-inline: 0.75rem;
216
+ border-radius: var(--radius-md, 0.5rem);
217
+ font-size: 0.8125rem;
218
+ font-weight: 500;
219
+ outline: none;
220
+ transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
221
+ }
222
+
223
+ @media (hover: hover) and (pointer: fine) {
224
+ .thread-button:hover:not(:disabled) {
225
+ background: var(--color-muted);
226
+ }
227
+ }
228
+
229
+ .thread-button:disabled {
230
+ opacity: 0.5;
231
+ pointer-events: none;
232
+ }
233
+
234
+ .thread-primary {
235
+ background: var(--color-primary);
236
+ color: var(--color-primary-foreground);
237
+ }
238
+
239
+ /* An empty draft reads as an outlined button, not a faded fill. */
240
+ .thread-primary:disabled {
241
+ opacity: 1;
242
+ background: transparent;
243
+ box-shadow: inset 0 0 0 1px var(--color-border);
244
+ color: var(--color-muted-foreground);
245
+ }
246
+
247
+ @media (hover: hover) and (pointer: fine) {
248
+ .thread-primary:hover:not(:disabled) {
249
+ background: color-mix(in oklab, var(--color-primary) 90%, transparent);
250
+ }
251
+ }
252
+
253
+ @media (any-pointer: coarse) {
254
+ .thread-button {
255
+ height: 2.5rem;
256
+ }
257
+ }
258
+ </style>