@hiroleague/taskmanager 0.0.1 → 0.0.3

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 (196) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -52
  3. package/dist/assets/architecture-YZFGNWBL-DoE0KxgG.js +1 -0
  4. package/dist/assets/architectureDiagram-Q4EWVU46-DeuBhy7X.js +36 -0
  5. package/dist/assets/{blockDiagram-DXYQGD6D-DfOGNphI.js → blockDiagram-DXYQGD6D-BDBy9ns9.js} +1 -1
  6. package/dist/assets/{c4Diagram-AHTNJAMY-B2Yfcwbo.js → c4Diagram-AHTNJAMY-CpqJj_8a.js} +1 -1
  7. package/dist/assets/channel-PHRyjspt.js +1 -0
  8. package/dist/assets/{chunk-2KRD3SAO-9yt00aGC.js → chunk-2KRD3SAO-DEpUsxdZ.js} +1 -1
  9. package/dist/assets/chunk-336JU56O-BGQvSwLk.js +2 -0
  10. package/dist/assets/chunk-426QAEUC-Cl9nQN9c.js +1 -0
  11. package/dist/assets/{chunk-4TB4RGXK-DF8yJBFl.js → chunk-4TB4RGXK-Dq7aiIrZ.js} +2 -2
  12. package/dist/assets/{chunk-5FUZZQ4R-XEga0hMC.js → chunk-5FUZZQ4R-B_HuuUjf.js} +1 -1
  13. package/dist/assets/{chunk-5PVQY5BW-BrmXs2Gs.js → chunk-5PVQY5BW-cGfZCZGU.js} +2 -2
  14. package/dist/assets/{chunk-67CJDMHE-5wFKo04G.js → chunk-67CJDMHE-BMYAVZfw.js} +1 -1
  15. package/dist/assets/{chunk-7N4EOEYR-BRRGX_NC.js → chunk-7N4EOEYR-Ct-EY7Nc.js} +1 -1
  16. package/dist/assets/{chunk-AA7GKIK3-DUZv_pNI.js → chunk-AA7GKIK3-Bd4HFpeo.js} +1 -1
  17. package/dist/assets/{chunk-CIAEETIT-mA5aM_d7.js → chunk-CIAEETIT-CrFUkPMT.js} +1 -1
  18. package/dist/assets/{chunk-EDXVE4YY-DxUqDyxy.js → chunk-EDXVE4YY-DMDyt0NF.js} +1 -1
  19. package/dist/assets/{chunk-ENJZ2VHE-BgZKYo1l.js → chunk-ENJZ2VHE-DrWzOrpd.js} +1 -1
  20. package/dist/assets/{chunk-FOC6F5B3-B-cqGCPC.js → chunk-FOC6F5B3-Bemzq96j.js} +1 -1
  21. package/dist/assets/{chunk-ICPOFSXX-BNR1V8rT.js → chunk-ICPOFSXX-DkUVjrLw.js} +5 -5
  22. package/dist/assets/{chunk-K5T4RW27-BLRDzioh.js → chunk-K5T4RW27-ALKIf000.js} +5 -5
  23. package/dist/assets/{chunk-KGLVRYIC-CTkQSeKy.js → chunk-KGLVRYIC-Bg6HNTZ-.js} +1 -1
  24. package/dist/assets/{chunk-LIHQZDEY-Cf34Nu3J.js → chunk-LIHQZDEY-DeyGongE.js} +1 -1
  25. package/dist/assets/{chunk-ORNJ4GCN-D3uXgbay.js → chunk-ORNJ4GCN-Bx83s1bJ.js} +1 -1
  26. package/dist/assets/{chunk-OYMX7WX6-syQho5jf.js → chunk-OYMX7WX6-BqRUtRpL.js} +1 -1
  27. package/dist/assets/{chunk-U2HBQHQK-DTJPeU7W.js → chunk-U2HBQHQK-DogcerR6.js} +1 -1
  28. package/dist/assets/{chunk-X2U36JSP-CrTnmMqG.js → chunk-X2U36JSP-CwVWdmZV.js} +1 -1
  29. package/dist/assets/chunk-XPW4576I-DQpNCogT.js +32 -0
  30. package/dist/assets/{chunk-YZCP3GAM-9wq0QKUn.js → chunk-YZCP3GAM-crQSbji9.js} +1 -1
  31. package/dist/assets/{chunk-ZZ45TVLE-D3I1kLlo.js → chunk-ZZ45TVLE-Bk1S1YtS.js} +1 -1
  32. package/dist/assets/classDiagram-6PBFFD2Q-B_TabGaU.js +1 -0
  33. package/dist/assets/classDiagram-v2-HSJHXN6E-CGnZkUWw.js +1 -0
  34. package/dist/assets/clone-D4ka472w.js +1 -0
  35. package/dist/assets/{cose-bilkent-S5V4N54A-BygGvZGW.js → cose-bilkent-S5V4N54A-RBTHUit8.js} +1 -1
  36. package/dist/assets/cytoscape.esm-BGJwlmkf.js +321 -0
  37. package/dist/assets/dagre-B32eYLtm.js +1 -0
  38. package/dist/assets/{dagre-KV5264BT-BBqulDtd.js → dagre-KV5264BT-nX7tuXXn.js} +1 -1
  39. package/dist/assets/diagram-5BDNPKRD-DRxMXlQr.js +10 -0
  40. package/dist/assets/diagram-G4DWMVQ6-CoojevGm.js +24 -0
  41. package/dist/assets/diagram-MMDJMWI5-CWtJyfVW.js +43 -0
  42. package/dist/assets/diagram-TYMM5635-CsDJC4Hq.js +24 -0
  43. package/dist/assets/{erDiagram-SMLLAGMA-BN5eJerP.js → erDiagram-SMLLAGMA-Cf7Xtd9A.js} +2 -2
  44. package/dist/assets/{flatten-C5NL-f24.js → flatten-CYX_pHZ7.js} +1 -1
  45. package/dist/assets/{flowDiagram-DWJPFMVM-CbFskc8S.js → flowDiagram-DWJPFMVM-DQaeR16a.js} +3 -3
  46. package/dist/assets/{ganttDiagram-T4ZO3ILL-OCTvbRxF.js → ganttDiagram-T4ZO3ILL-8EIcztcH.js} +1 -1
  47. package/dist/assets/gitGraph-7Q5UKJZL-BH9A1SAZ.js +1 -0
  48. package/dist/assets/{gitGraphDiagram-UUTBAWPF-wpqI2kyI.js → gitGraphDiagram-UUTBAWPF-DO9ODqYw.js} +1 -1
  49. package/dist/assets/graphlib-bPBqlJKT.js +1 -0
  50. package/dist/assets/identity-Me9aart9.js +1 -0
  51. package/dist/assets/index-oKG1C41_.js +273 -0
  52. package/dist/assets/info-OMHHGYJF-BvKR-zWh.js +1 -0
  53. package/dist/assets/infoDiagram-42DDH7IO-pRTXCm5C.js +2 -0
  54. package/dist/assets/isEmpty-Cu0k-j1j.js +1 -0
  55. package/dist/assets/{ishikawaDiagram-UXIWVN3A-Epc23N_0.js → ishikawaDiagram-UXIWVN3A-BP2YE5QI.js} +2 -2
  56. package/dist/assets/{journeyDiagram-VCZTEJTY-BkMxoaPq.js → journeyDiagram-VCZTEJTY-B3l2juoL.js} +1 -1
  57. package/dist/assets/{kanban-definition-6JOO6SKY-C8dW_26n.js → kanban-definition-6JOO6SKY-BpIpEOZZ.js} +4 -4
  58. package/dist/assets/{line-DNzQATGr.js → line-otOkzGl8.js} +1 -1
  59. package/dist/assets/mermaid-parser.core-xWsW24Gq.js +4 -0
  60. package/dist/assets/{mindmap-definition-QFDTVHPH-CvpNtrKT.js → mindmap-definition-QFDTVHPH-B9khyC7X.js} +3 -3
  61. package/dist/assets/packet-4T2RLAQJ-D8Dw3nmf.js +1 -0
  62. package/dist/assets/pie-ZZUOXDRM-ZghowlAE.js +1 -0
  63. package/dist/assets/{pieDiagram-DEJITSTG-eENymoXZ.js → pieDiagram-DEJITSTG-v32hL3i7.js} +1 -1
  64. package/dist/assets/{quadrantDiagram-34T5L4WZ-c0iZxo2I.js → quadrantDiagram-34T5L4WZ-DIL3GDFt.js} +1 -1
  65. package/dist/assets/radar-PYXPWWZC-D-PK3JOd.js +1 -0
  66. package/dist/assets/reduce-CImcgAcU.js +1 -0
  67. package/dist/assets/{requirementDiagram-MS252O5E-CmRO3hLp.js → requirementDiagram-MS252O5E-D8os2-4y.js} +2 -2
  68. package/dist/assets/{sankeyDiagram-XADWPNL6-woJZoQ58.js → sankeyDiagram-XADWPNL6-BV70D4l5.js} +1 -1
  69. package/dist/assets/{sequenceDiagram-FGHM5R23-B7qNcwNo.js → sequenceDiagram-FGHM5R23-Cwu8hQW1.js} +1 -1
  70. package/dist/assets/stateDiagram-FHFEXIEX-oYUWv7Fb.js +1 -0
  71. package/dist/assets/stateDiagram-v2-QKLJ7IA2-CFUTpFu-.js +1 -0
  72. package/dist/assets/{timeline-definition-GMOUNBTQ-CQWqDPGG.js → timeline-definition-GMOUNBTQ-CxSdKxpL.js} +1 -1
  73. package/dist/assets/treeView-SZITEDCU-uVgaJQzG.js +1 -0
  74. package/dist/assets/treemap-W4RFUUIX-Dcad_9AN.js +1 -0
  75. package/dist/assets/vennDiagram-DHZGUBPP-D4wgD7QI.js +34 -0
  76. package/dist/assets/wardley-RL74JXVD-CFXrK8mx.js +1 -0
  77. package/dist/assets/{wardleyDiagram-NUSXRM2D-DNhPIFCg.js → wardleyDiagram-NUSXRM2D-5Q201ea3.js} +1 -1
  78. package/dist/assets/{xychartDiagram-5P7HB3ND-BDblAZ11.js → xychartDiagram-5P7HB3ND-BPZv_axd.js} +3 -3
  79. package/dist/index.html +16 -12
  80. package/package.json +99 -92
  81. package/scripts/stubs/node-domexception/index.cjs +18 -0
  82. package/scripts/stubs/node-domexception/package.json +7 -0
  83. package/skills/hiro-task-manager-cli/SKILL.md +97 -0
  84. package/skills/hiro-task-manager-cli/reference/boards.md +143 -0
  85. package/skills/hiro-task-manager-cli/reference/cli-access-policy.md +72 -0
  86. package/skills/hiro-task-manager-cli/reference/errors.md +85 -0
  87. package/skills/hiro-task-manager-cli/reference/lists.md +106 -0
  88. package/skills/hiro-task-manager-cli/reference/releases.md +87 -0
  89. package/skills/hiro-task-manager-cli/reference/search.md +38 -0
  90. package/skills/hiro-task-manager-cli/reference/statuses.md +25 -0
  91. package/skills/hiro-task-manager-cli/reference/tasks.md +144 -0
  92. package/skills/hiro-task-manager-cli/reference/trash.md +50 -0
  93. package/src/cli/bootstrap/launcher.test.ts +66 -0
  94. package/src/cli/bootstrap/launcher.ts +389 -35
  95. package/src/cli/bootstrap/program.test.ts +46 -0
  96. package/src/cli/bootstrap/program.ts +54 -1
  97. package/src/cli/bootstrap/runtime.test.ts +15 -0
  98. package/src/cli/bootstrap/runtime.ts +27 -1
  99. package/src/cli/commands/query.ts +56 -56
  100. package/src/cli/commands/server.ts +27 -19
  101. package/src/cli/handlers/boards.test.ts +669 -669
  102. package/src/cli/handlers/cli-wiring.test.ts +1 -1
  103. package/src/cli/handlers/search.test.ts +374 -374
  104. package/src/cli/handlers/search.ts +17 -17
  105. package/src/cli/handlers/server.test.ts +55 -13
  106. package/src/cli/handlers/server.ts +16 -3
  107. package/src/cli/lib/api-client.test.ts +35 -2
  108. package/src/cli/lib/api-client.ts +43 -10
  109. package/src/cli/lib/cli-http-errors.test.ts +85 -85
  110. package/src/cli/lib/command-helpers.ts +161 -154
  111. package/src/cli/lib/config.ts +4 -0
  112. package/src/cli/lib/launcherUi.test.ts +74 -0
  113. package/src/cli/lib/launcherUi.ts +213 -0
  114. package/src/cli/lib/process.test.ts +24 -5
  115. package/src/cli/lib/process.ts +86 -55
  116. package/src/cli/ports/process.ts +8 -2
  117. package/src/cli/subprocess.real-stack.test.ts +611 -598
  118. package/src/cli/subprocess.smoke.test.ts +954 -969
  119. package/src/cli/types/config.ts +2 -6
  120. package/src/client/components/auth/AuthScreen.tsx +3 -3
  121. package/src/client/components/board/BoardStatsChips.tsx +233 -233
  122. package/src/client/components/board/BoardStatsContext.tsx +41 -41
  123. package/src/client/components/board/boardHeaderButtonStyles.ts +38 -38
  124. package/src/client/components/board/shortcuts/useBoardShortcutKeydown.ts +49 -49
  125. package/src/client/components/board/useBoardCanvasPanScroll.ts +108 -108
  126. package/src/client/components/board/useBoardTaskContainerDroppableReact.ts +33 -33
  127. package/src/client/components/board/useBoardTaskSortableReact.ts +26 -26
  128. package/src/client/components/multi-select.tsx +1206 -1206
  129. package/src/client/components/routing/BoardPage.tsx +20 -20
  130. package/src/client/components/routing/NavigationRegistrar.tsx +13 -13
  131. package/src/client/components/settings/SettingsPage.tsx +1 -1
  132. package/src/client/components/task/TaskCard.tsx +643 -643
  133. package/src/client/components/ui/badge.tsx +49 -49
  134. package/src/client/components/ui/button.tsx +65 -65
  135. package/src/client/components/ui/command.tsx +193 -193
  136. package/src/client/components/ui/dialog.tsx +163 -163
  137. package/src/client/components/ui/input-group.tsx +155 -155
  138. package/src/client/components/ui/input.tsx +19 -19
  139. package/src/client/components/ui/popover.tsx +87 -87
  140. package/src/client/components/ui/separator.tsx +28 -28
  141. package/src/client/components/ui/textarea.tsx +18 -18
  142. package/src/client/index.css +248 -248
  143. package/src/client/lib/appNavigate.ts +16 -16
  144. package/src/client/lib/taskCardDate.ts +111 -111
  145. package/src/client/lib/utils.ts +6 -6
  146. package/src/server/auth.ts +351 -302
  147. package/src/server/bootstrapDev.ts +11 -2
  148. package/src/server/bootstrapInstalled.ts +6 -1
  149. package/src/server/index.ts +33 -7
  150. package/src/server/migrations/013_cli_policy_and_provenance.ts +2 -2
  151. package/src/server/migrations/019_cli_global_create_board_default_on.ts +14 -0
  152. package/src/server/migrations/registry.ts +43 -41
  153. package/src/server/parseBootstrapProfile.ts +42 -0
  154. package/src/server/storage/cliPolicy.ts +2 -1
  155. package/src/shared/runtimeConfig.ts +256 -237
  156. package/src/shared/runtimeIdentity.test.ts +47 -0
  157. package/src/shared/runtimeIdentity.ts +35 -0
  158. package/src/shared/serverStatus.ts +21 -0
  159. package/src/shared/skillsInstall.ts +70 -0
  160. package/src/shared/terminalColors.ts +24 -0
  161. package/dist/assets/architecture-YZFGNWBL-3h1eIYfB.js +0 -1
  162. package/dist/assets/architectureDiagram-Q4EWVU46-DSQ1_74_.js +0 -36
  163. package/dist/assets/channel-yBmN_ln0.js +0 -1
  164. package/dist/assets/classDiagram-6PBFFD2Q-CotFZI8-.js +0 -1
  165. package/dist/assets/classDiagram-v2-HSJHXN6E-DAPzeDGn.js +0 -1
  166. package/dist/assets/clone-BRQpYu_n.js +0 -1
  167. package/dist/assets/cytoscape.esm-BIYWHPG0.js +0 -321
  168. package/dist/assets/dagre-rhyPjnsQ.js +0 -1
  169. package/dist/assets/diagram-5BDNPKRD-Ky3EXXj0.js +0 -10
  170. package/dist/assets/diagram-G4DWMVQ6-t7LbT0Uz.js +0 -24
  171. package/dist/assets/diagram-MMDJMWI5-CdnLXEMx.js +0 -43
  172. package/dist/assets/diagram-TYMM5635-CnzTqJBM.js +0 -24
  173. package/dist/assets/gitGraph-7Q5UKJZL-CG8f8JF7.js +0 -1
  174. package/dist/assets/graphlib-COiJG5Qv.js +0 -1
  175. package/dist/assets/identity-D4WOnl_h.js +0 -1
  176. package/dist/assets/index-lyyIVcc_.js +0 -304
  177. package/dist/assets/info-OMHHGYJF-C8_SHoRO.js +0 -1
  178. package/dist/assets/infoDiagram-42DDH7IO-BbvTdpSV.js +0 -2
  179. package/dist/assets/mermaid-parser.core-6Tn8epr_.js +0 -4
  180. package/dist/assets/packet-4T2RLAQJ-BvpAX0kJ.js +0 -1
  181. package/dist/assets/pie-ZZUOXDRM-Ow26Yf-E.js +0 -1
  182. package/dist/assets/radar-PYXPWWZC-e_ron5jQ.js +0 -1
  183. package/dist/assets/reduce-BDOBPIXr.js +0 -1
  184. package/dist/assets/stateDiagram-FHFEXIEX-CYfGMoR8.js +0 -1
  185. package/dist/assets/stateDiagram-v2-QKLJ7IA2-CO1W_n55.js +0 -1
  186. package/dist/assets/treeView-SZITEDCU-DsEr3xeq.js +0 -1
  187. package/dist/assets/treemap-W4RFUUIX-DV7nk2AB.js +0 -1
  188. package/dist/assets/vennDiagram-DHZGUBPP-BjTbuhcb.js +0 -34
  189. package/dist/assets/wardley-RL74JXVD-CrrFU9AE.js +0 -1
  190. /package/dist/assets/{chunk-4BX2VUAB-ean5NKtU.js → chunk-4BX2VUAB-C70mcfQR.js} +0 -0
  191. /package/dist/assets/{chunk-55IACEB6-CvSRyJqy.js → chunk-55IACEB6-CWfnqcLM.js} +0 -0
  192. /package/dist/assets/{chunk-BSJP7CBP-D8kBlJsf.js → chunk-BSJP7CBP-B0LrXV9y.js} +0 -0
  193. /package/dist/assets/{chunk-FMBD7UC4-DrNhFt1N.js → chunk-FMBD7UC4-_mV71Mwu.js} +0 -0
  194. /package/dist/assets/{chunk-QZHKN3VN-Csp3OYJY.js → chunk-QZHKN3VN-t2nrsegL.js} +0 -0
  195. /package/dist/assets/{katex-8mXVa4k3.js → katex-B2dtGfSp.js} +0 -0
  196. /package/dist/assets/{rough.esm-DtEqI08j.js → rough.esm-DEh6Frf9.js} +0 -0
@@ -1,111 +1,111 @@
1
- import type { Task } from "../../shared/models";
2
-
3
- const MONTHS_SHORT = [
4
- "Jan",
5
- "Feb",
6
- "Mar",
7
- "Apr",
8
- "May",
9
- "Jun",
10
- "Jul",
11
- "Aug",
12
- "Sep",
13
- "Oct",
14
- "Nov",
15
- "Dec",
16
- ] as const;
17
-
18
- /** 12-hour time like 11:05PM (no space before AM/PM), per task card spec. */
19
- export function formatTaskCardTime12hCompact(d: Date): string {
20
- const h24 = d.getHours();
21
- const m = d.getMinutes();
22
- const isAm = h24 < 12;
23
- const h12 = h24 % 12 || 12;
24
- const mm = m.toString().padStart(2, "0");
25
- return `${h12}:${mm}${isAm ? "AM" : "PM"}`;
26
- }
27
-
28
- function startOfLocalDay(d: Date): Date {
29
- return new Date(d.getFullYear(), d.getMonth(), d.getDate());
30
- }
31
-
32
- /** Whole calendar days from `date` to `now` (0 = same day; positive = date is in the past). */
33
- function calendarDaysFromDateToNow(date: Date, now: Date): number {
34
- const a = startOfLocalDay(date).getTime();
35
- const b = startOfLocalDay(now).getTime();
36
- return Math.round((b - a) / (24 * 60 * 60 * 1000));
37
- }
38
-
39
- function sixMonthsBefore(now: Date): Date {
40
- const x = new Date(now);
41
- x.setMonth(x.getMonth() - 6);
42
- return x;
43
- }
44
-
45
- /** Same-calendar-day tasks opened/closed within this window show “now” + a dot on the card. */
46
- export const TASK_CARD_RECENT_WINDOW_MS = 5 * 60 * 1000;
47
-
48
- export type TaskCardRelativeDateParts = {
49
- label: string;
50
- /** When true, the card shows a blue dot instead of the clock icon (recent “now” state). */
51
- showRecentDot: boolean;
52
- };
53
-
54
- /**
55
- * Short label for large/larger task cards: today → time only (or “now” if very recent);
56
- * 1–3 days ago; then MMM d; if older than six months and a different calendar year than now → year only.
57
- */
58
- export function getTaskCardRelativeDateParts(
59
- iso: string,
60
- now: Date = new Date(),
61
- ): TaskCardRelativeDateParts {
62
- const date = new Date(iso);
63
- if (Number.isNaN(date.getTime())) return { label: "", showRecentDot: false };
64
-
65
- const deltaDays = calendarDaysFromDateToNow(date, now);
66
-
67
- if (deltaDays === 0) {
68
- const ageMs = now.getTime() - date.getTime();
69
- if (ageMs >= 0 && ageMs <= TASK_CARD_RECENT_WINDOW_MS) {
70
- return { label: "now", showRecentDot: true };
71
- }
72
- return { label: formatTaskCardTime12hCompact(date), showRecentDot: false };
73
- }
74
- if (deltaDays === 1) return { label: "1 day ago", showRecentDot: false };
75
- if (deltaDays === 2) return { label: "2 days ago", showRecentDot: false };
76
- if (deltaDays === 3) return { label: "3 days ago", showRecentDot: false };
77
-
78
- const sixAgo = sixMonthsBefore(now);
79
- if (date < sixAgo && date.getFullYear() !== now.getFullYear()) {
80
- return { label: String(date.getFullYear()), showRecentDot: false };
81
- }
82
-
83
- return { label: `${MONTHS_SHORT[date.getMonth()]} ${date.getDate()}`, showRecentDot: false };
84
- }
85
-
86
- export function formatTaskCardRelativeDate(iso: string, now: Date = new Date()): string {
87
- return getTaskCardRelativeDateParts(iso, now).label;
88
- }
89
-
90
- /** Hover / aria: "Opened on 14 Apr 2026 11:05PM" or "Closed on …". */
91
- export function formatTaskCardDateTooltip(kind: "opened" | "closed", iso: string): string {
92
- const date = new Date(iso);
93
- if (Number.isNaN(date.getTime())) return "";
94
- const prefix = kind === "closed" ? "Closed on" : "Opened on";
95
- const d = date.getDate();
96
- const mon = MONTHS_SHORT[date.getMonth()];
97
- const y = date.getFullYear();
98
- const t = formatTaskCardTime12hCompact(date);
99
- return `${prefix} ${d} ${mon} ${y} ${t}`;
100
- }
101
-
102
- export function getTaskCardTimeline(
103
- task: Task,
104
- ): { iso: string; kind: "opened" | "closed" } | null {
105
- if (task.status === "closed") {
106
- const iso = task.closedAt?.trim() || task.updatedAt;
107
- return iso ? { iso, kind: "closed" } : null;
108
- }
109
- const iso = task.createdAt?.trim();
110
- return iso ? { iso, kind: "opened" } : null;
111
- }
1
+ import type { Task } from "../../shared/models";
2
+
3
+ const MONTHS_SHORT = [
4
+ "Jan",
5
+ "Feb",
6
+ "Mar",
7
+ "Apr",
8
+ "May",
9
+ "Jun",
10
+ "Jul",
11
+ "Aug",
12
+ "Sep",
13
+ "Oct",
14
+ "Nov",
15
+ "Dec",
16
+ ] as const;
17
+
18
+ /** 12-hour time like 11:05PM (no space before AM/PM), per task card spec. */
19
+ export function formatTaskCardTime12hCompact(d: Date): string {
20
+ const h24 = d.getHours();
21
+ const m = d.getMinutes();
22
+ const isAm = h24 < 12;
23
+ const h12 = h24 % 12 || 12;
24
+ const mm = m.toString().padStart(2, "0");
25
+ return `${h12}:${mm}${isAm ? "AM" : "PM"}`;
26
+ }
27
+
28
+ function startOfLocalDay(d: Date): Date {
29
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
30
+ }
31
+
32
+ /** Whole calendar days from `date` to `now` (0 = same day; positive = date is in the past). */
33
+ function calendarDaysFromDateToNow(date: Date, now: Date): number {
34
+ const a = startOfLocalDay(date).getTime();
35
+ const b = startOfLocalDay(now).getTime();
36
+ return Math.round((b - a) / (24 * 60 * 60 * 1000));
37
+ }
38
+
39
+ function sixMonthsBefore(now: Date): Date {
40
+ const x = new Date(now);
41
+ x.setMonth(x.getMonth() - 6);
42
+ return x;
43
+ }
44
+
45
+ /** Same-calendar-day tasks opened/closed within this window show “now” + a dot on the card. */
46
+ export const TASK_CARD_RECENT_WINDOW_MS = 5 * 60 * 1000;
47
+
48
+ export type TaskCardRelativeDateParts = {
49
+ label: string;
50
+ /** When true, the card shows a blue dot instead of the clock icon (recent “now” state). */
51
+ showRecentDot: boolean;
52
+ };
53
+
54
+ /**
55
+ * Short label for large/larger task cards: today → time only (or “now” if very recent);
56
+ * 1–3 days ago; then MMM d; if older than six months and a different calendar year than now → year only.
57
+ */
58
+ export function getTaskCardRelativeDateParts(
59
+ iso: string,
60
+ now: Date = new Date(),
61
+ ): TaskCardRelativeDateParts {
62
+ const date = new Date(iso);
63
+ if (Number.isNaN(date.getTime())) return { label: "", showRecentDot: false };
64
+
65
+ const deltaDays = calendarDaysFromDateToNow(date, now);
66
+
67
+ if (deltaDays === 0) {
68
+ const ageMs = now.getTime() - date.getTime();
69
+ if (ageMs >= 0 && ageMs <= TASK_CARD_RECENT_WINDOW_MS) {
70
+ return { label: "now", showRecentDot: true };
71
+ }
72
+ return { label: formatTaskCardTime12hCompact(date), showRecentDot: false };
73
+ }
74
+ if (deltaDays === 1) return { label: "1 day ago", showRecentDot: false };
75
+ if (deltaDays === 2) return { label: "2 days ago", showRecentDot: false };
76
+ if (deltaDays === 3) return { label: "3 days ago", showRecentDot: false };
77
+
78
+ const sixAgo = sixMonthsBefore(now);
79
+ if (date < sixAgo && date.getFullYear() !== now.getFullYear()) {
80
+ return { label: String(date.getFullYear()), showRecentDot: false };
81
+ }
82
+
83
+ return { label: `${MONTHS_SHORT[date.getMonth()]} ${date.getDate()}`, showRecentDot: false };
84
+ }
85
+
86
+ export function formatTaskCardRelativeDate(iso: string, now: Date = new Date()): string {
87
+ return getTaskCardRelativeDateParts(iso, now).label;
88
+ }
89
+
90
+ /** Hover / aria: "Opened on 14 Apr 2026 11:05PM" or "Closed on …". */
91
+ export function formatTaskCardDateTooltip(kind: "opened" | "closed", iso: string): string {
92
+ const date = new Date(iso);
93
+ if (Number.isNaN(date.getTime())) return "";
94
+ const prefix = kind === "closed" ? "Closed on" : "Opened on";
95
+ const d = date.getDate();
96
+ const mon = MONTHS_SHORT[date.getMonth()];
97
+ const y = date.getFullYear();
98
+ const t = formatTaskCardTime12hCompact(date);
99
+ return `${prefix} ${d} ${mon} ${y} ${t}`;
100
+ }
101
+
102
+ export function getTaskCardTimeline(
103
+ task: Task,
104
+ ): { iso: string; kind: "opened" | "closed" } | null {
105
+ if (task.status === "closed") {
106
+ const iso = task.closedAt?.trim() || task.updatedAt;
107
+ return iso ? { iso, kind: "closed" } : null;
108
+ }
109
+ const iso = task.createdAt?.trim();
110
+ return iso ? { iso, kind: "opened" } : null;
111
+ }
@@ -1,6 +1,6 @@
1
- import { clsx, type ClassValue } from "clsx"
2
- import { twMerge } from "tailwind-merge"
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
6
- }
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }