@hiroleague/taskmanager 0.0.2 → 0.0.4
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/README.md +1 -1
- package/dist/assets/architecture-YZFGNWBL-DoE0KxgG.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-DeuBhy7X.js +36 -0
- package/dist/assets/{blockDiagram-DXYQGD6D-DQzEOPT2.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-C2e-_49I.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-AZq3s1Dh.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-B1-M78qu.js → chunk-67CJDMHE-BMYAVZfw.js} +1 -1
- package/dist/assets/{chunk-7N4EOEYR-D7mYFpz-.js → chunk-7N4EOEYR-Ct-EY7Nc.js} +1 -1
- package/dist/assets/{chunk-AA7GKIK3-VWI9k39i.js → chunk-AA7GKIK3-Bd4HFpeo.js} +1 -1
- package/dist/assets/{chunk-CIAEETIT-hnu4zamm.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-BJsh9nO9.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-BLIPdXaZ.js → chunk-K5T4RW27-ALKIf000.js} +5 -5
- package/dist/assets/{chunk-KGLVRYIC-DvaW2TkT.js → chunk-KGLVRYIC-Bg6HNTZ-.js} +1 -1
- package/dist/assets/{chunk-LIHQZDEY-CUsM0M11.js → chunk-LIHQZDEY-DeyGongE.js} +1 -1
- package/dist/assets/{chunk-ORNJ4GCN-CfluNV0_.js → chunk-ORNJ4GCN-Bx83s1bJ.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-CkWzw4JX.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-lveZDhBf.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-dx09stuy.js → erDiagram-SMLLAGMA-Cf7Xtd9A.js} +2 -2
- package/dist/assets/{flatten-B2BZ0pzY.js → flatten-CYX_pHZ7.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-CJi2WISS.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-Bjj94M12.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-BpzHnKdP.css +1 -0
- package/dist/assets/index-DmNErTAP.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-Cnc1bwBo.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-CwHbIze0.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-DswAJiEd.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-DgQTCddl.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-D1moa23Z.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-Dvhj7HGn.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-z-IncVmK.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-D-kouujI.js → wardleyDiagram-NUSXRM2D-5Q201ea3.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-D1lnM0pL.js → xychartDiagram-5P7HB3ND-BPZv_axd.js} +3 -3
- package/dist/index.html +17 -13
- package/package.json +2 -4
- package/skills/hiro-task-manager-cli/SKILL.md +6 -4
- package/skills/hiro-task-manager-cli/reference/cli-access-policy.md +1 -0
- package/skills/hiro-task-manager-cli/reference/releases.md +14 -0
- package/src/cli/bootstrap/launcher.ts +19 -5
- package/src/cli/bootstrap/program.test.ts +46 -0
- package/src/cli/bootstrap/program.ts +50 -1
- package/src/cli/commands/query.ts +56 -56
- package/src/cli/commands/releases.ts +22 -0
- package/src/cli/handlers/boards.test.ts +669 -669
- package/src/cli/handlers/cli-wiring.test.ts +38 -1
- package/src/cli/handlers/releases.ts +15 -0
- package/src/cli/handlers/search.test.ts +374 -374
- package/src/cli/handlers/search.ts +17 -17
- package/src/cli/lib/cli-http-errors.test.ts +85 -85
- package/src/cli/lib/launcherUi.test.ts +74 -0
- package/src/cli/lib/launcherUi.ts +47 -0
- package/src/cli/lib/write/releases.ts +64 -1
- package/src/cli/lib/write-result.test.ts +3 -0
- package/src/cli/lib/write-result.ts +3 -0
- package/src/cli/lib/writeCommands.breadth.test.ts +143 -0
- package/src/cli/lib/writeCommands.ts +1 -0
- package/src/cli/subprocess.real-stack.test.ts +625 -611
- package/src/cli/subprocess.smoke.test.ts +954 -954
- package/src/client/api/useBoardChangeStream.ts +421 -168
- package/src/client/api/useBoardIndexStream.ts +35 -0
- 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/layout/AppShell.tsx +5 -2
- package/src/client/components/layout/NotificationToasts.tsx +38 -1
- 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/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/client/store/notificationUi.ts +14 -0
- package/src/server/auth.ts +351 -351
- package/src/server/events.ts +31 -4
- package/src/server/migrations/registry.ts +43 -43
- package/src/server/notificationEvents.ts +8 -1
- package/src/server/routes/boards.ts +15 -1
- package/src/server/routes/trash.ts +6 -1
- package/src/shared/boardEvents.ts +6 -0
- package/src/shared/runtimeConfig.ts +256 -256
- package/src/shared/skillsInstall.ts +2 -3
- package/dist/assets/architecture-YZFGNWBL-C1MoQeSs.js +0 -1
- package/dist/assets/architectureDiagram-Q4EWVU46-DUEfvDBu.js +0 -36
- package/dist/assets/channel-yBmN_ln0.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-Dx_f-9b7.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-CSfvZ-nt.js +0 -1
- package/dist/assets/clone-CXokakwV.js +0 -1
- package/dist/assets/cytoscape.esm-BIYWHPG0.js +0 -321
- package/dist/assets/dagre-Do0eD9eI.js +0 -1
- package/dist/assets/diagram-5BDNPKRD-Dq5yM_uY.js +0 -10
- package/dist/assets/diagram-G4DWMVQ6-D-SYOmKm.js +0 -24
- package/dist/assets/diagram-MMDJMWI5-lU5t9BZA.js +0 -43
- package/dist/assets/diagram-TYMM5635-6tfUbY3R.js +0 -24
- package/dist/assets/gitGraph-7Q5UKJZL-BXTuQaDM.js +0 -1
- package/dist/assets/graphlib-BIlXYGdM.js +0 -1
- package/dist/assets/identity-D4WOnl_h.js +0 -1
- package/dist/assets/index-CZZuue3D.js +0 -304
- package/dist/assets/index-hMFTu7sr.css +0 -1
- package/dist/assets/info-OMHHGYJF-BeeKt8-X.js +0 -1
- package/dist/assets/infoDiagram-42DDH7IO-wq_opQKO.js +0 -2
- package/dist/assets/mermaid-parser.core-DrLhKJ48.js +0 -4
- package/dist/assets/packet-4T2RLAQJ-DQ-H9_jd.js +0 -1
- package/dist/assets/pie-ZZUOXDRM-BSj0Jsyj.js +0 -1
- package/dist/assets/radar-PYXPWWZC-B7-oRPFL.js +0 -1
- package/dist/assets/reduce-Uumu9GdR.js +0 -1
- package/dist/assets/stateDiagram-FHFEXIEX-Dx5CjenB.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-C_PkrTdc.js +0 -1
- package/dist/assets/treeView-SZITEDCU-CFXle9Az.js +0 -1
- package/dist/assets/treemap-W4RFUUIX-CAW3vWh8.js +0 -1
- package/dist/assets/vennDiagram-DHZGUBPP-CT1ehozU.js +0 -34
- package/dist/assets/wardley-RL74JXVD-7q3ju4kc.js +0 -1
- package/scripts/postinstall-message.mjs +0 -160
- /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,49 +1,49 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import type { Board } from "../../../../shared/models";
|
|
3
|
-
import { boardShortcutRegistry } from "./boardShortcutRegistry";
|
|
4
|
-
import type { BoardShortcutActions } from "./boardShortcutTypes";
|
|
5
|
-
import { isEditableKeyboardTarget } from "./isEditableKeyboardTarget";
|
|
6
|
-
import { useShortcutScope } from "./ShortcutScopeContext";
|
|
7
|
-
|
|
8
|
-
interface UseBoardShortcutKeydownOptions {
|
|
9
|
-
board: Board | null;
|
|
10
|
-
actions: BoardShortcutActions;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Registers the board shortcut dispatcher with {@link ShortcutScopeProvider}.
|
|
15
|
-
* The global listener only invokes it when the scope stack is empty (board is active).
|
|
16
|
-
*/
|
|
17
|
-
export function useBoardShortcutKeydown({
|
|
18
|
-
board,
|
|
19
|
-
actions,
|
|
20
|
-
}: UseBoardShortcutKeydownOptions): void {
|
|
21
|
-
const { registerBoardKeyHandler } = useShortcutScope();
|
|
22
|
-
const actionsRef = useRef(actions);
|
|
23
|
-
actionsRef.current = actions;
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (!board) {
|
|
27
|
-
return registerBoardKeyHandler(() => {});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const onKeyDown = (e: KeyboardEvent) => {
|
|
31
|
-
if (!board) return;
|
|
32
|
-
if (isEditableKeyboardTarget(e.target)) return;
|
|
33
|
-
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
34
|
-
// Shift+Tab keeps native reverse tab order; unmodified Tab establishes board highlight.
|
|
35
|
-
if (e.key === "Tab" && e.shiftKey) return;
|
|
36
|
-
|
|
37
|
-
for (const def of boardShortcutRegistry) {
|
|
38
|
-
if (def.helpOnly) continue;
|
|
39
|
-
if (!def.matchKey(e.key)) continue;
|
|
40
|
-
if (def.enabled && !def.enabled(board)) continue;
|
|
41
|
-
if (def.preventDefault) e.preventDefault();
|
|
42
|
-
def.run(board, actionsRef.current);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return registerBoardKeyHandler(onKeyDown);
|
|
48
|
-
}, [board, registerBoardKeyHandler]);
|
|
49
|
-
}
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import type { Board } from "../../../../shared/models";
|
|
3
|
+
import { boardShortcutRegistry } from "./boardShortcutRegistry";
|
|
4
|
+
import type { BoardShortcutActions } from "./boardShortcutTypes";
|
|
5
|
+
import { isEditableKeyboardTarget } from "./isEditableKeyboardTarget";
|
|
6
|
+
import { useShortcutScope } from "./ShortcutScopeContext";
|
|
7
|
+
|
|
8
|
+
interface UseBoardShortcutKeydownOptions {
|
|
9
|
+
board: Board | null;
|
|
10
|
+
actions: BoardShortcutActions;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registers the board shortcut dispatcher with {@link ShortcutScopeProvider}.
|
|
15
|
+
* The global listener only invokes it when the scope stack is empty (board is active).
|
|
16
|
+
*/
|
|
17
|
+
export function useBoardShortcutKeydown({
|
|
18
|
+
board,
|
|
19
|
+
actions,
|
|
20
|
+
}: UseBoardShortcutKeydownOptions): void {
|
|
21
|
+
const { registerBoardKeyHandler } = useShortcutScope();
|
|
22
|
+
const actionsRef = useRef(actions);
|
|
23
|
+
actionsRef.current = actions;
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!board) {
|
|
27
|
+
return registerBoardKeyHandler(() => {});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
31
|
+
if (!board) return;
|
|
32
|
+
if (isEditableKeyboardTarget(e.target)) return;
|
|
33
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
34
|
+
// Shift+Tab keeps native reverse tab order; unmodified Tab establishes board highlight.
|
|
35
|
+
if (e.key === "Tab" && e.shiftKey) return;
|
|
36
|
+
|
|
37
|
+
for (const def of boardShortcutRegistry) {
|
|
38
|
+
if (def.helpOnly) continue;
|
|
39
|
+
if (!def.matchKey(e.key)) continue;
|
|
40
|
+
if (def.enabled && !def.enabled(board)) continue;
|
|
41
|
+
if (def.preventDefault) e.preventDefault();
|
|
42
|
+
def.run(board, actionsRef.current);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return registerBoardKeyHandler(onKeyDown);
|
|
48
|
+
}, [board, registerBoardKeyHandler]);
|
|
49
|
+
}
|
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
import { useCallback, useRef, useState } from "react";
|
|
2
|
-
|
|
3
|
-
const ACTIVATION_PX = 6;
|
|
4
|
-
|
|
5
|
-
/** Selectors for elements that must not start horizontal board pan (columns, chrome, controls). */
|
|
6
|
-
const NO_PAN =
|
|
7
|
-
"[data-board-no-pan],button,a[href],input,textarea,select,option,label,[role='button'],[contenteditable='true']";
|
|
8
|
-
|
|
9
|
-
function targetStartsPan(target: EventTarget | null): boolean {
|
|
10
|
-
if (!(target instanceof Element)) return false;
|
|
11
|
-
return target.closest(NO_PAN) == null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type PanMode = "undecided" | "horizontal" | "none";
|
|
15
|
-
|
|
16
|
-
export function useBoardCanvasPanScroll() {
|
|
17
|
-
const scrollRef = useRef<HTMLDivElement>(null);
|
|
18
|
-
const panRef = useRef<{
|
|
19
|
-
pointerId: number;
|
|
20
|
-
startX: number;
|
|
21
|
-
startY: number;
|
|
22
|
-
startScrollLeft: number;
|
|
23
|
-
mode: PanMode;
|
|
24
|
-
} | null>(null);
|
|
25
|
-
|
|
26
|
-
const [panning, setPanning] = useState(false);
|
|
27
|
-
|
|
28
|
-
const onPointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
29
|
-
if (e.button !== 0) return;
|
|
30
|
-
if (!targetStartsPan(e.target)) return;
|
|
31
|
-
const scroller = scrollRef.current;
|
|
32
|
-
if (!scroller) return;
|
|
33
|
-
|
|
34
|
-
panRef.current = {
|
|
35
|
-
pointerId: e.pointerId,
|
|
36
|
-
startX: e.clientX,
|
|
37
|
-
startY: e.clientY,
|
|
38
|
-
startScrollLeft: scroller.scrollLeft,
|
|
39
|
-
mode: "undecided",
|
|
40
|
-
};
|
|
41
|
-
}, []);
|
|
42
|
-
|
|
43
|
-
const onPointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
44
|
-
const pan = panRef.current;
|
|
45
|
-
const scroller = scrollRef.current;
|
|
46
|
-
if (!pan || !scroller || e.pointerId !== pan.pointerId) return;
|
|
47
|
-
|
|
48
|
-
if (pan.mode === "none") return;
|
|
49
|
-
|
|
50
|
-
if (pan.mode === "undecided") {
|
|
51
|
-
const dx = e.clientX - pan.startX;
|
|
52
|
-
const dy = e.clientY - pan.startY;
|
|
53
|
-
if (
|
|
54
|
-
Math.abs(dx) < ACTIVATION_PX &&
|
|
55
|
-
Math.abs(dy) < ACTIVATION_PX
|
|
56
|
-
) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
60
|
-
pan.mode = "horizontal";
|
|
61
|
-
try {
|
|
62
|
-
scroller.setPointerCapture(e.pointerId);
|
|
63
|
-
} catch {
|
|
64
|
-
/* already captured or unsupported */
|
|
65
|
-
}
|
|
66
|
-
e.preventDefault();
|
|
67
|
-
setPanning(true);
|
|
68
|
-
} else {
|
|
69
|
-
pan.mode = "none";
|
|
70
|
-
panRef.current = null;
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (pan.mode === "horizontal") {
|
|
76
|
-
const dx = e.clientX - pan.startX;
|
|
77
|
-
scroller.scrollLeft = pan.startScrollLeft - dx;
|
|
78
|
-
e.preventDefault();
|
|
79
|
-
}
|
|
80
|
-
}, []);
|
|
81
|
-
|
|
82
|
-
const endPan = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
83
|
-
const pan = panRef.current;
|
|
84
|
-
const scroller = scrollRef.current;
|
|
85
|
-
if (!pan || e.pointerId !== pan.pointerId) return;
|
|
86
|
-
|
|
87
|
-
if (pan.mode === "horizontal" && scroller) {
|
|
88
|
-
try {
|
|
89
|
-
scroller.releasePointerCapture(e.pointerId);
|
|
90
|
-
} catch {
|
|
91
|
-
/* not captured */
|
|
92
|
-
}
|
|
93
|
-
setPanning(false);
|
|
94
|
-
}
|
|
95
|
-
panRef.current = null;
|
|
96
|
-
}, []);
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
scrollRef,
|
|
100
|
-
panning,
|
|
101
|
-
boardCanvasPanHandlers: {
|
|
102
|
-
onPointerDown,
|
|
103
|
-
onPointerMove,
|
|
104
|
-
onPointerUp: endPan,
|
|
105
|
-
onPointerCancel: endPan,
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
}
|
|
1
|
+
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const ACTIVATION_PX = 6;
|
|
4
|
+
|
|
5
|
+
/** Selectors for elements that must not start horizontal board pan (columns, chrome, controls). */
|
|
6
|
+
const NO_PAN =
|
|
7
|
+
"[data-board-no-pan],button,a[href],input,textarea,select,option,label,[role='button'],[contenteditable='true']";
|
|
8
|
+
|
|
9
|
+
function targetStartsPan(target: EventTarget | null): boolean {
|
|
10
|
+
if (!(target instanceof Element)) return false;
|
|
11
|
+
return target.closest(NO_PAN) == null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type PanMode = "undecided" | "horizontal" | "none";
|
|
15
|
+
|
|
16
|
+
export function useBoardCanvasPanScroll() {
|
|
17
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
18
|
+
const panRef = useRef<{
|
|
19
|
+
pointerId: number;
|
|
20
|
+
startX: number;
|
|
21
|
+
startY: number;
|
|
22
|
+
startScrollLeft: number;
|
|
23
|
+
mode: PanMode;
|
|
24
|
+
} | null>(null);
|
|
25
|
+
|
|
26
|
+
const [panning, setPanning] = useState(false);
|
|
27
|
+
|
|
28
|
+
const onPointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
29
|
+
if (e.button !== 0) return;
|
|
30
|
+
if (!targetStartsPan(e.target)) return;
|
|
31
|
+
const scroller = scrollRef.current;
|
|
32
|
+
if (!scroller) return;
|
|
33
|
+
|
|
34
|
+
panRef.current = {
|
|
35
|
+
pointerId: e.pointerId,
|
|
36
|
+
startX: e.clientX,
|
|
37
|
+
startY: e.clientY,
|
|
38
|
+
startScrollLeft: scroller.scrollLeft,
|
|
39
|
+
mode: "undecided",
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const onPointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
44
|
+
const pan = panRef.current;
|
|
45
|
+
const scroller = scrollRef.current;
|
|
46
|
+
if (!pan || !scroller || e.pointerId !== pan.pointerId) return;
|
|
47
|
+
|
|
48
|
+
if (pan.mode === "none") return;
|
|
49
|
+
|
|
50
|
+
if (pan.mode === "undecided") {
|
|
51
|
+
const dx = e.clientX - pan.startX;
|
|
52
|
+
const dy = e.clientY - pan.startY;
|
|
53
|
+
if (
|
|
54
|
+
Math.abs(dx) < ACTIVATION_PX &&
|
|
55
|
+
Math.abs(dy) < ACTIVATION_PX
|
|
56
|
+
) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
60
|
+
pan.mode = "horizontal";
|
|
61
|
+
try {
|
|
62
|
+
scroller.setPointerCapture(e.pointerId);
|
|
63
|
+
} catch {
|
|
64
|
+
/* already captured or unsupported */
|
|
65
|
+
}
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
setPanning(true);
|
|
68
|
+
} else {
|
|
69
|
+
pan.mode = "none";
|
|
70
|
+
panRef.current = null;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (pan.mode === "horizontal") {
|
|
76
|
+
const dx = e.clientX - pan.startX;
|
|
77
|
+
scroller.scrollLeft = pan.startScrollLeft - dx;
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
}
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
const endPan = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
83
|
+
const pan = panRef.current;
|
|
84
|
+
const scroller = scrollRef.current;
|
|
85
|
+
if (!pan || e.pointerId !== pan.pointerId) return;
|
|
86
|
+
|
|
87
|
+
if (pan.mode === "horizontal" && scroller) {
|
|
88
|
+
try {
|
|
89
|
+
scroller.releasePointerCapture(e.pointerId);
|
|
90
|
+
} catch {
|
|
91
|
+
/* not captured */
|
|
92
|
+
}
|
|
93
|
+
setPanning(false);
|
|
94
|
+
}
|
|
95
|
+
panRef.current = null;
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
scrollRef,
|
|
100
|
+
panning,
|
|
101
|
+
boardCanvasPanHandlers: {
|
|
102
|
+
onPointerDown,
|
|
103
|
+
onPointerMove,
|
|
104
|
+
onPointerUp: endPan,
|
|
105
|
+
onPointerCancel: endPan,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { CollisionPriority } from "@dnd-kit/abstract";
|
|
2
|
-
import { useDroppable } from "@dnd-kit/react";
|
|
3
|
-
import {
|
|
4
|
-
BOARD_TASK_CONTAINER_DND_TYPE,
|
|
5
|
-
BOARD_TASK_DND_TYPE,
|
|
6
|
-
type BoardDndLayout,
|
|
7
|
-
boardTaskContainerData,
|
|
8
|
-
} from "./dndReactModel";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Phase 1 React-first wrapper for task drop containers.
|
|
12
|
-
* This encodes the board's container metadata once so stacked lists and lane
|
|
13
|
-
* bands can share the same droppable configuration in later phases.
|
|
14
|
-
*/
|
|
15
|
-
export function useBoardTaskContainerDroppableReact({
|
|
16
|
-
containerId,
|
|
17
|
-
layout,
|
|
18
|
-
listId,
|
|
19
|
-
status,
|
|
20
|
-
}: {
|
|
21
|
-
containerId: string;
|
|
22
|
-
layout: BoardDndLayout;
|
|
23
|
-
listId: number;
|
|
24
|
-
status?: string;
|
|
25
|
-
}) {
|
|
26
|
-
return useDroppable({
|
|
27
|
-
id: containerId,
|
|
28
|
-
type: BOARD_TASK_CONTAINER_DND_TYPE,
|
|
29
|
-
accept: BOARD_TASK_DND_TYPE,
|
|
30
|
-
collisionPriority: CollisionPriority.Low,
|
|
31
|
-
data: boardTaskContainerData(containerId, layout, listId, status),
|
|
32
|
-
});
|
|
33
|
-
}
|
|
1
|
+
import { CollisionPriority } from "@dnd-kit/abstract";
|
|
2
|
+
import { useDroppable } from "@dnd-kit/react";
|
|
3
|
+
import {
|
|
4
|
+
BOARD_TASK_CONTAINER_DND_TYPE,
|
|
5
|
+
BOARD_TASK_DND_TYPE,
|
|
6
|
+
type BoardDndLayout,
|
|
7
|
+
boardTaskContainerData,
|
|
8
|
+
} from "./dndReactModel";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Phase 1 React-first wrapper for task drop containers.
|
|
12
|
+
* This encodes the board's container metadata once so stacked lists and lane
|
|
13
|
+
* bands can share the same droppable configuration in later phases.
|
|
14
|
+
*/
|
|
15
|
+
export function useBoardTaskContainerDroppableReact({
|
|
16
|
+
containerId,
|
|
17
|
+
layout,
|
|
18
|
+
listId,
|
|
19
|
+
status,
|
|
20
|
+
}: {
|
|
21
|
+
containerId: string;
|
|
22
|
+
layout: BoardDndLayout;
|
|
23
|
+
listId: number;
|
|
24
|
+
status?: string;
|
|
25
|
+
}) {
|
|
26
|
+
return useDroppable({
|
|
27
|
+
id: containerId,
|
|
28
|
+
type: BOARD_TASK_CONTAINER_DND_TYPE,
|
|
29
|
+
accept: BOARD_TASK_DND_TYPE,
|
|
30
|
+
collisionPriority: CollisionPriority.Low,
|
|
31
|
+
data: boardTaskContainerData(containerId, layout, listId, status),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { useSortable } from "@dnd-kit/react/sortable";
|
|
2
|
-
import { BOARD_TASK_DND_TYPE, boardTaskDragData } from "./dndReactModel";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Phase 1 React-first wrapper for sortable task rows.
|
|
6
|
-
* Group and index are explicit so grouped multi-list movement can follow the
|
|
7
|
-
* official multiple-sortable-lists approach during later phases.
|
|
8
|
-
*/
|
|
9
|
-
export function useBoardTaskSortableReact(
|
|
10
|
-
taskId: number,
|
|
11
|
-
sortableId: string,
|
|
12
|
-
containerId: string,
|
|
13
|
-
index: number,
|
|
14
|
-
) {
|
|
15
|
-
return useSortable({
|
|
16
|
-
id: sortableId,
|
|
17
|
-
index,
|
|
18
|
-
group: containerId,
|
|
19
|
-
type: BOARD_TASK_DND_TYPE,
|
|
20
|
-
accept: BOARD_TASK_DND_TYPE,
|
|
21
|
-
// Mirror the working board-list route so task drags use the same
|
|
22
|
-
// React-first feedback clone behavior as columns.
|
|
23
|
-
feedback: "clone",
|
|
24
|
-
data: boardTaskDragData(taskId, containerId),
|
|
25
|
-
});
|
|
26
|
-
}
|
|
1
|
+
import { useSortable } from "@dnd-kit/react/sortable";
|
|
2
|
+
import { BOARD_TASK_DND_TYPE, boardTaskDragData } from "./dndReactModel";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Phase 1 React-first wrapper for sortable task rows.
|
|
6
|
+
* Group and index are explicit so grouped multi-list movement can follow the
|
|
7
|
+
* official multiple-sortable-lists approach during later phases.
|
|
8
|
+
*/
|
|
9
|
+
export function useBoardTaskSortableReact(
|
|
10
|
+
taskId: number,
|
|
11
|
+
sortableId: string,
|
|
12
|
+
containerId: string,
|
|
13
|
+
index: number,
|
|
14
|
+
) {
|
|
15
|
+
return useSortable({
|
|
16
|
+
id: sortableId,
|
|
17
|
+
index,
|
|
18
|
+
group: containerId,
|
|
19
|
+
type: BOARD_TASK_DND_TYPE,
|
|
20
|
+
accept: BOARD_TASK_DND_TYPE,
|
|
21
|
+
// Mirror the working board-list route so task drags use the same
|
|
22
|
+
// React-first feedback clone behavior as columns.
|
|
23
|
+
feedback: "clone",
|
|
24
|
+
data: boardTaskDragData(taskId, containerId),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { useBoardChangeStream } from "@/api/useBoardChangeStream";
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
import { usePreferencesStore } from "@/store/preferences";
|
|
5
5
|
import { AppHeader } from "./AppHeader";
|
|
@@ -12,7 +12,10 @@ interface AppShellProps {
|
|
|
12
12
|
|
|
13
13
|
export function AppShell({ sidebar, children }: AppShellProps) {
|
|
14
14
|
const sidebarCollapsed = usePreferencesStore((s) => s.sidebarCollapsed);
|
|
15
|
-
|
|
15
|
+
// Single SSE connection for non-board pages (board-index + notifications).
|
|
16
|
+
// On board pages, BoardView opens its own board-scoped connection that
|
|
17
|
+
// supersedes this one — the effect re-runs with a boardId when navigating.
|
|
18
|
+
useBoardChangeStream(null, null);
|
|
16
19
|
|
|
17
20
|
return (
|
|
18
21
|
<div className="flex h-dvh min-h-0 flex-col bg-background">
|
|
@@ -77,14 +77,51 @@ function ToastCard({ item, onDismiss }: { item: NotificationItem; onDismiss: ()
|
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function SystemToastCard({
|
|
81
|
+
message,
|
|
82
|
+
onDismiss,
|
|
83
|
+
}: {
|
|
84
|
+
message: string;
|
|
85
|
+
onDismiss: () => void;
|
|
86
|
+
}) {
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const timer = window.setTimeout(() => onDismiss(), 12_000);
|
|
89
|
+
return () => window.clearTimeout(timer);
|
|
90
|
+
}, [onDismiss]);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="pointer-events-auto rounded-xl border border-amber-500/40 bg-amber-950/90 p-3 text-sm text-amber-50 shadow-xl backdrop-blur">
|
|
94
|
+
<div className="flex items-start justify-between gap-3">
|
|
95
|
+
<p className="min-w-0 leading-5">{message}</p>
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
onClick={onDismiss}
|
|
99
|
+
className="shrink-0 rounded-md px-2 py-0.5 text-xs font-medium text-amber-200 hover:bg-amber-500/20"
|
|
100
|
+
>
|
|
101
|
+
Dismiss
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
80
108
|
export function NotificationToasts() {
|
|
81
109
|
const toasts = useNotificationUiStore((s) => s.toasts);
|
|
82
110
|
const dismissToast = useNotificationUiStore((s) => s.dismissToast);
|
|
111
|
+
const systemToast = useNotificationUiStore((s) => s.systemToast);
|
|
112
|
+
const dismissSystemToast = useNotificationUiStore((s) => s.dismissSystemToast);
|
|
83
113
|
|
|
84
|
-
if (toasts.length === 0) return null;
|
|
114
|
+
if (toasts.length === 0 && !systemToast) return null;
|
|
85
115
|
|
|
86
116
|
return (
|
|
87
117
|
<div className="pointer-events-none fixed bottom-4 right-4 z-[140] flex w-[min(24rem,calc(100vw-2rem))] flex-col gap-2">
|
|
118
|
+
{systemToast ? (
|
|
119
|
+
<SystemToastCard
|
|
120
|
+
key={systemToast.id}
|
|
121
|
+
message={systemToast.message}
|
|
122
|
+
onDismiss={dismissSystemToast}
|
|
123
|
+
/>
|
|
124
|
+
) : null}
|
|
88
125
|
{toasts.map((toast) => (
|
|
89
126
|
<div key={toast.id} className="pointer-events-auto">
|
|
90
127
|
<ToastCard item={toast.notification} onDismiss={() => dismissToast(toast.id)} />
|