@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.
- package/LICENSE +21 -0
- package/README.md +41 -52
- package/dist/assets/architecture-YZFGNWBL-DoE0KxgG.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-DeuBhy7X.js +36 -0
- package/dist/assets/{blockDiagram-DXYQGD6D-DfOGNphI.js → blockDiagram-DXYQGD6D-BDBy9ns9.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-B2Yfcwbo.js → c4Diagram-AHTNJAMY-CpqJj_8a.js} +1 -1
- package/dist/assets/channel-PHRyjspt.js +1 -0
- package/dist/assets/{chunk-2KRD3SAO-9yt00aGC.js → chunk-2KRD3SAO-DEpUsxdZ.js} +1 -1
- package/dist/assets/chunk-336JU56O-BGQvSwLk.js +2 -0
- package/dist/assets/chunk-426QAEUC-Cl9nQN9c.js +1 -0
- package/dist/assets/{chunk-4TB4RGXK-DF8yJBFl.js → chunk-4TB4RGXK-Dq7aiIrZ.js} +2 -2
- package/dist/assets/{chunk-5FUZZQ4R-XEga0hMC.js → chunk-5FUZZQ4R-B_HuuUjf.js} +1 -1
- package/dist/assets/{chunk-5PVQY5BW-BrmXs2Gs.js → chunk-5PVQY5BW-cGfZCZGU.js} +2 -2
- package/dist/assets/{chunk-67CJDMHE-5wFKo04G.js → chunk-67CJDMHE-BMYAVZfw.js} +1 -1
- package/dist/assets/{chunk-7N4EOEYR-BRRGX_NC.js → chunk-7N4EOEYR-Ct-EY7Nc.js} +1 -1
- package/dist/assets/{chunk-AA7GKIK3-DUZv_pNI.js → chunk-AA7GKIK3-Bd4HFpeo.js} +1 -1
- package/dist/assets/{chunk-CIAEETIT-mA5aM_d7.js → chunk-CIAEETIT-CrFUkPMT.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-DxUqDyxy.js → chunk-EDXVE4YY-DMDyt0NF.js} +1 -1
- package/dist/assets/{chunk-ENJZ2VHE-BgZKYo1l.js → chunk-ENJZ2VHE-DrWzOrpd.js} +1 -1
- package/dist/assets/{chunk-FOC6F5B3-B-cqGCPC.js → chunk-FOC6F5B3-Bemzq96j.js} +1 -1
- package/dist/assets/{chunk-ICPOFSXX-BNR1V8rT.js → chunk-ICPOFSXX-DkUVjrLw.js} +5 -5
- package/dist/assets/{chunk-K5T4RW27-BLRDzioh.js → chunk-K5T4RW27-ALKIf000.js} +5 -5
- package/dist/assets/{chunk-KGLVRYIC-CTkQSeKy.js → chunk-KGLVRYIC-Bg6HNTZ-.js} +1 -1
- package/dist/assets/{chunk-LIHQZDEY-Cf34Nu3J.js → chunk-LIHQZDEY-DeyGongE.js} +1 -1
- package/dist/assets/{chunk-ORNJ4GCN-D3uXgbay.js → chunk-ORNJ4GCN-Bx83s1bJ.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-syQho5jf.js → chunk-OYMX7WX6-BqRUtRpL.js} +1 -1
- package/dist/assets/{chunk-U2HBQHQK-DTJPeU7W.js → chunk-U2HBQHQK-DogcerR6.js} +1 -1
- package/dist/assets/{chunk-X2U36JSP-CrTnmMqG.js → chunk-X2U36JSP-CwVWdmZV.js} +1 -1
- package/dist/assets/chunk-XPW4576I-DQpNCogT.js +32 -0
- package/dist/assets/{chunk-YZCP3GAM-9wq0QKUn.js → chunk-YZCP3GAM-crQSbji9.js} +1 -1
- package/dist/assets/{chunk-ZZ45TVLE-D3I1kLlo.js → chunk-ZZ45TVLE-Bk1S1YtS.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-B_TabGaU.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CGnZkUWw.js +1 -0
- package/dist/assets/clone-D4ka472w.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-BygGvZGW.js → cose-bilkent-S5V4N54A-RBTHUit8.js} +1 -1
- package/dist/assets/cytoscape.esm-BGJwlmkf.js +321 -0
- package/dist/assets/dagre-B32eYLtm.js +1 -0
- package/dist/assets/{dagre-KV5264BT-BBqulDtd.js → dagre-KV5264BT-nX7tuXXn.js} +1 -1
- package/dist/assets/diagram-5BDNPKRD-DRxMXlQr.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-CoojevGm.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-CWtJyfVW.js +43 -0
- package/dist/assets/diagram-TYMM5635-CsDJC4Hq.js +24 -0
- package/dist/assets/{erDiagram-SMLLAGMA-BN5eJerP.js → erDiagram-SMLLAGMA-Cf7Xtd9A.js} +2 -2
- package/dist/assets/{flatten-C5NL-f24.js → flatten-CYX_pHZ7.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-CbFskc8S.js → flowDiagram-DWJPFMVM-DQaeR16a.js} +3 -3
- package/dist/assets/{ganttDiagram-T4ZO3ILL-OCTvbRxF.js → ganttDiagram-T4ZO3ILL-8EIcztcH.js} +1 -1
- package/dist/assets/gitGraph-7Q5UKJZL-BH9A1SAZ.js +1 -0
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-wpqI2kyI.js → gitGraphDiagram-UUTBAWPF-DO9ODqYw.js} +1 -1
- package/dist/assets/graphlib-bPBqlJKT.js +1 -0
- package/dist/assets/identity-Me9aart9.js +1 -0
- package/dist/assets/index-oKG1C41_.js +273 -0
- package/dist/assets/info-OMHHGYJF-BvKR-zWh.js +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-pRTXCm5C.js +2 -0
- package/dist/assets/isEmpty-Cu0k-j1j.js +1 -0
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-Epc23N_0.js → ishikawaDiagram-UXIWVN3A-BP2YE5QI.js} +2 -2
- package/dist/assets/{journeyDiagram-VCZTEJTY-BkMxoaPq.js → journeyDiagram-VCZTEJTY-B3l2juoL.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-C8dW_26n.js → kanban-definition-6JOO6SKY-BpIpEOZZ.js} +4 -4
- package/dist/assets/{line-DNzQATGr.js → line-otOkzGl8.js} +1 -1
- package/dist/assets/mermaid-parser.core-xWsW24Gq.js +4 -0
- package/dist/assets/{mindmap-definition-QFDTVHPH-CvpNtrKT.js → mindmap-definition-QFDTVHPH-B9khyC7X.js} +3 -3
- package/dist/assets/packet-4T2RLAQJ-D8Dw3nmf.js +1 -0
- package/dist/assets/pie-ZZUOXDRM-ZghowlAE.js +1 -0
- package/dist/assets/{pieDiagram-DEJITSTG-eENymoXZ.js → pieDiagram-DEJITSTG-v32hL3i7.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-c0iZxo2I.js → quadrantDiagram-34T5L4WZ-DIL3GDFt.js} +1 -1
- package/dist/assets/radar-PYXPWWZC-D-PK3JOd.js +1 -0
- package/dist/assets/reduce-CImcgAcU.js +1 -0
- package/dist/assets/{requirementDiagram-MS252O5E-CmRO3hLp.js → requirementDiagram-MS252O5E-D8os2-4y.js} +2 -2
- package/dist/assets/{sankeyDiagram-XADWPNL6-woJZoQ58.js → sankeyDiagram-XADWPNL6-BV70D4l5.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-B7qNcwNo.js → sequenceDiagram-FGHM5R23-Cwu8hQW1.js} +1 -1
- package/dist/assets/stateDiagram-FHFEXIEX-oYUWv7Fb.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-CFUTpFu-.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-CQWqDPGG.js → timeline-definition-GMOUNBTQ-CxSdKxpL.js} +1 -1
- package/dist/assets/treeView-SZITEDCU-uVgaJQzG.js +1 -0
- package/dist/assets/treemap-W4RFUUIX-Dcad_9AN.js +1 -0
- package/dist/assets/vennDiagram-DHZGUBPP-D4wgD7QI.js +34 -0
- package/dist/assets/wardley-RL74JXVD-CFXrK8mx.js +1 -0
- package/dist/assets/{wardleyDiagram-NUSXRM2D-DNhPIFCg.js → wardleyDiagram-NUSXRM2D-5Q201ea3.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-BDblAZ11.js → xychartDiagram-5P7HB3ND-BPZv_axd.js} +3 -3
- package/dist/index.html +16 -12
- package/package.json +99 -92
- package/scripts/stubs/node-domexception/index.cjs +18 -0
- package/scripts/stubs/node-domexception/package.json +7 -0
- package/skills/hiro-task-manager-cli/SKILL.md +97 -0
- package/skills/hiro-task-manager-cli/reference/boards.md +143 -0
- package/skills/hiro-task-manager-cli/reference/cli-access-policy.md +72 -0
- package/skills/hiro-task-manager-cli/reference/errors.md +85 -0
- package/skills/hiro-task-manager-cli/reference/lists.md +106 -0
- package/skills/hiro-task-manager-cli/reference/releases.md +87 -0
- package/skills/hiro-task-manager-cli/reference/search.md +38 -0
- package/skills/hiro-task-manager-cli/reference/statuses.md +25 -0
- package/skills/hiro-task-manager-cli/reference/tasks.md +144 -0
- package/skills/hiro-task-manager-cli/reference/trash.md +50 -0
- package/src/cli/bootstrap/launcher.test.ts +66 -0
- package/src/cli/bootstrap/launcher.ts +389 -35
- package/src/cli/bootstrap/program.test.ts +46 -0
- package/src/cli/bootstrap/program.ts +54 -1
- package/src/cli/bootstrap/runtime.test.ts +15 -0
- package/src/cli/bootstrap/runtime.ts +27 -1
- package/src/cli/commands/query.ts +56 -56
- package/src/cli/commands/server.ts +27 -19
- package/src/cli/handlers/boards.test.ts +669 -669
- package/src/cli/handlers/cli-wiring.test.ts +1 -1
- package/src/cli/handlers/search.test.ts +374 -374
- package/src/cli/handlers/search.ts +17 -17
- package/src/cli/handlers/server.test.ts +55 -13
- package/src/cli/handlers/server.ts +16 -3
- package/src/cli/lib/api-client.test.ts +35 -2
- package/src/cli/lib/api-client.ts +43 -10
- package/src/cli/lib/cli-http-errors.test.ts +85 -85
- package/src/cli/lib/command-helpers.ts +161 -154
- package/src/cli/lib/config.ts +4 -0
- package/src/cli/lib/launcherUi.test.ts +74 -0
- package/src/cli/lib/launcherUi.ts +213 -0
- package/src/cli/lib/process.test.ts +24 -5
- package/src/cli/lib/process.ts +86 -55
- package/src/cli/ports/process.ts +8 -2
- package/src/cli/subprocess.real-stack.test.ts +611 -598
- package/src/cli/subprocess.smoke.test.ts +954 -969
- package/src/cli/types/config.ts +2 -6
- package/src/client/components/auth/AuthScreen.tsx +3 -3
- package/src/client/components/board/BoardStatsChips.tsx +233 -233
- package/src/client/components/board/BoardStatsContext.tsx +41 -41
- package/src/client/components/board/boardHeaderButtonStyles.ts +38 -38
- package/src/client/components/board/shortcuts/useBoardShortcutKeydown.ts +49 -49
- package/src/client/components/board/useBoardCanvasPanScroll.ts +108 -108
- package/src/client/components/board/useBoardTaskContainerDroppableReact.ts +33 -33
- package/src/client/components/board/useBoardTaskSortableReact.ts +26 -26
- package/src/client/components/multi-select.tsx +1206 -1206
- package/src/client/components/routing/BoardPage.tsx +20 -20
- package/src/client/components/routing/NavigationRegistrar.tsx +13 -13
- package/src/client/components/settings/SettingsPage.tsx +1 -1
- package/src/client/components/task/TaskCard.tsx +643 -643
- package/src/client/components/ui/badge.tsx +49 -49
- package/src/client/components/ui/button.tsx +65 -65
- package/src/client/components/ui/command.tsx +193 -193
- package/src/client/components/ui/dialog.tsx +163 -163
- package/src/client/components/ui/input-group.tsx +155 -155
- package/src/client/components/ui/input.tsx +19 -19
- package/src/client/components/ui/popover.tsx +87 -87
- package/src/client/components/ui/separator.tsx +28 -28
- package/src/client/components/ui/textarea.tsx +18 -18
- package/src/client/index.css +248 -248
- package/src/client/lib/appNavigate.ts +16 -16
- package/src/client/lib/taskCardDate.ts +111 -111
- package/src/client/lib/utils.ts +6 -6
- package/src/server/auth.ts +351 -302
- package/src/server/bootstrapDev.ts +11 -2
- package/src/server/bootstrapInstalled.ts +6 -1
- package/src/server/index.ts +33 -7
- package/src/server/migrations/013_cli_policy_and_provenance.ts +2 -2
- package/src/server/migrations/019_cli_global_create_board_default_on.ts +14 -0
- package/src/server/migrations/registry.ts +43 -41
- package/src/server/parseBootstrapProfile.ts +42 -0
- package/src/server/storage/cliPolicy.ts +2 -1
- package/src/shared/runtimeConfig.ts +256 -237
- package/src/shared/runtimeIdentity.test.ts +47 -0
- package/src/shared/runtimeIdentity.ts +35 -0
- package/src/shared/serverStatus.ts +21 -0
- package/src/shared/skillsInstall.ts +70 -0
- package/src/shared/terminalColors.ts +24 -0
- package/dist/assets/architecture-YZFGNWBL-3h1eIYfB.js +0 -1
- package/dist/assets/architectureDiagram-Q4EWVU46-DSQ1_74_.js +0 -36
- package/dist/assets/channel-yBmN_ln0.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CotFZI8-.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-DAPzeDGn.js +0 -1
- package/dist/assets/clone-BRQpYu_n.js +0 -1
- package/dist/assets/cytoscape.esm-BIYWHPG0.js +0 -321
- package/dist/assets/dagre-rhyPjnsQ.js +0 -1
- package/dist/assets/diagram-5BDNPKRD-Ky3EXXj0.js +0 -10
- package/dist/assets/diagram-G4DWMVQ6-t7LbT0Uz.js +0 -24
- package/dist/assets/diagram-MMDJMWI5-CdnLXEMx.js +0 -43
- package/dist/assets/diagram-TYMM5635-CnzTqJBM.js +0 -24
- package/dist/assets/gitGraph-7Q5UKJZL-CG8f8JF7.js +0 -1
- package/dist/assets/graphlib-COiJG5Qv.js +0 -1
- package/dist/assets/identity-D4WOnl_h.js +0 -1
- package/dist/assets/index-lyyIVcc_.js +0 -304
- package/dist/assets/info-OMHHGYJF-C8_SHoRO.js +0 -1
- package/dist/assets/infoDiagram-42DDH7IO-BbvTdpSV.js +0 -2
- package/dist/assets/mermaid-parser.core-6Tn8epr_.js +0 -4
- package/dist/assets/packet-4T2RLAQJ-BvpAX0kJ.js +0 -1
- package/dist/assets/pie-ZZUOXDRM-Ow26Yf-E.js +0 -1
- package/dist/assets/radar-PYXPWWZC-e_ron5jQ.js +0 -1
- package/dist/assets/reduce-BDOBPIXr.js +0 -1
- package/dist/assets/stateDiagram-FHFEXIEX-CYfGMoR8.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-CO1W_n55.js +0 -1
- package/dist/assets/treeView-SZITEDCU-DsEr3xeq.js +0 -1
- package/dist/assets/treemap-W4RFUUIX-DV7nk2AB.js +0 -1
- package/dist/assets/vennDiagram-DHZGUBPP-BjTbuhcb.js +0 -34
- package/dist/assets/wardley-RL74JXVD-CrrFU9AE.js +0 -1
- /package/dist/assets/{chunk-4BX2VUAB-ean5NKtU.js → chunk-4BX2VUAB-C70mcfQR.js} +0 -0
- /package/dist/assets/{chunk-55IACEB6-CvSRyJqy.js → chunk-55IACEB6-CWfnqcLM.js} +0 -0
- /package/dist/assets/{chunk-BSJP7CBP-D8kBlJsf.js → chunk-BSJP7CBP-B0LrXV9y.js} +0 -0
- /package/dist/assets/{chunk-FMBD7UC4-DrNhFt1N.js → chunk-FMBD7UC4-_mV71Mwu.js} +0 -0
- /package/dist/assets/{chunk-QZHKN3VN-Csp3OYJY.js → chunk-QZHKN3VN-t2nrsegL.js} +0 -0
- /package/dist/assets/{katex-8mXVa4k3.js → katex-B2dtGfSp.js} +0 -0
- /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
|
+
}
|
package/src/client/lib/utils.ts
CHANGED
|
@@ -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
|
+
}
|