@tomkapa/tayto 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-6NQOFUIQ.js → chunk-FUNYPBWJ.js} +23 -1
- package/dist/chunk-FUNYPBWJ.js.map +1 -0
- package/dist/index.js +207 -24
- package/dist/index.js.map +1 -1
- package/dist/migrations/004_remove_check_constraints.sql +63 -0
- package/dist/{tui-JNZRBEIQ.js → tui-FTXYP3HM.js} +619 -100
- package/dist/tui-FTXYP3HM.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-6NQOFUIQ.js.map +0 -1
- package/dist/tui-JNZRBEIQ.js.map +0 -1
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
DependencyType,
|
|
4
|
+
TaskLevel,
|
|
4
5
|
TaskStatus,
|
|
5
6
|
TaskType,
|
|
6
7
|
UIDependencyType,
|
|
7
8
|
isTerminalStatus,
|
|
8
9
|
logger
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-FUNYPBWJ.js";
|
|
10
11
|
|
|
11
12
|
// src/tui/index.tsx
|
|
12
13
|
import { render } from "ink";
|
|
13
14
|
|
|
14
15
|
// src/tui/components/App.tsx
|
|
15
16
|
import { useReducer, useEffect, useCallback as useCallback2, useMemo } from "react";
|
|
16
|
-
import { Box as
|
|
17
|
+
import { Box as Box16, Text as Text16, useInput as useInput6, useApp } from "ink";
|
|
17
18
|
|
|
18
19
|
// src/tui/types.ts
|
|
19
20
|
var ViewType = {
|
|
@@ -24,6 +25,7 @@ var ViewType = {
|
|
|
24
25
|
ProjectSelector: "project-selector",
|
|
25
26
|
ProjectCreate: "project-create",
|
|
26
27
|
DependencyList: "dependency-list",
|
|
28
|
+
EpicPicker: "epic-picker",
|
|
27
29
|
Help: "help"
|
|
28
30
|
};
|
|
29
31
|
|
|
@@ -51,7 +53,13 @@ var initialState = {
|
|
|
51
53
|
depSelectedIndex: 0,
|
|
52
54
|
isAddingDep: false,
|
|
53
55
|
addDepInput: "",
|
|
54
|
-
focusedPanel: "list"
|
|
56
|
+
focusedPanel: "list",
|
|
57
|
+
detailScrollOffset: 0,
|
|
58
|
+
epics: [],
|
|
59
|
+
epicSelectedIndex: 0,
|
|
60
|
+
selectedEpicIds: /* @__PURE__ */ new Set(),
|
|
61
|
+
isEpicReordering: false,
|
|
62
|
+
epicReorderSnapshot: null
|
|
55
63
|
};
|
|
56
64
|
function appReducer(state, action) {
|
|
57
65
|
switch (action.type) {
|
|
@@ -111,14 +119,14 @@ function appReducer(state, action) {
|
|
|
111
119
|
case "MOVE_CURSOR": {
|
|
112
120
|
const maxIndex = Math.max(0, state.tasks.length - 1);
|
|
113
121
|
const newIndex = action.direction === "up" ? Math.max(0, state.selectedIndex - 1) : Math.min(maxIndex, state.selectedIndex + 1);
|
|
114
|
-
return { ...state, selectedIndex: newIndex };
|
|
122
|
+
return { ...state, selectedIndex: newIndex, detailScrollOffset: 0 };
|
|
115
123
|
}
|
|
116
124
|
case "SET_CURSOR": {
|
|
117
125
|
const maxIndex = Math.max(0, state.tasks.length - 1);
|
|
118
126
|
return { ...state, selectedIndex: Math.max(0, Math.min(action.index, maxIndex)) };
|
|
119
127
|
}
|
|
120
128
|
case "SELECT_TASK":
|
|
121
|
-
return { ...state, selectedTask: action.task };
|
|
129
|
+
return { ...state, selectedTask: action.task, detailScrollOffset: 0 };
|
|
122
130
|
case "SET_FORM_DATA":
|
|
123
131
|
return { ...state, formData: action.data };
|
|
124
132
|
case "ENTER_REORDER":
|
|
@@ -181,6 +189,74 @@ function appReducer(state, action) {
|
|
|
181
189
|
return { ...state, addDepInput: action.input };
|
|
182
190
|
case "SET_PANEL_FOCUS":
|
|
183
191
|
return { ...state, focusedPanel: action.panel };
|
|
192
|
+
case "DETAIL_SCROLL":
|
|
193
|
+
return {
|
|
194
|
+
...state,
|
|
195
|
+
detailScrollOffset: action.direction === "up" ? Math.max(0, state.detailScrollOffset - 1) : state.detailScrollOffset + 1
|
|
196
|
+
};
|
|
197
|
+
case "DETAIL_RESET_SCROLL":
|
|
198
|
+
return { ...state, detailScrollOffset: 0 };
|
|
199
|
+
case "SET_EPICS":
|
|
200
|
+
return {
|
|
201
|
+
...state,
|
|
202
|
+
epics: action.epics,
|
|
203
|
+
epicSelectedIndex: Math.min(state.epicSelectedIndex, Math.max(0, action.epics.length - 1))
|
|
204
|
+
};
|
|
205
|
+
case "EPIC_MOVE_CURSOR": {
|
|
206
|
+
if (state.epics.length === 0) return state;
|
|
207
|
+
const maxIdx = Math.max(0, state.epics.length - 1);
|
|
208
|
+
const newIdx = action.direction === "up" ? Math.max(0, state.epicSelectedIndex - 1) : Math.min(maxIdx, state.epicSelectedIndex + 1);
|
|
209
|
+
return { ...state, epicSelectedIndex: newIdx };
|
|
210
|
+
}
|
|
211
|
+
case "TOGGLE_EPIC": {
|
|
212
|
+
const next = new Set(state.selectedEpicIds);
|
|
213
|
+
if (next.has(action.epicId)) {
|
|
214
|
+
next.delete(action.epicId);
|
|
215
|
+
} else {
|
|
216
|
+
next.add(action.epicId);
|
|
217
|
+
}
|
|
218
|
+
return { ...state, selectedEpicIds: next, selectedIndex: 0 };
|
|
219
|
+
}
|
|
220
|
+
case "CLEAR_EPIC_SELECTION":
|
|
221
|
+
return { ...state, selectedEpicIds: /* @__PURE__ */ new Set(), selectedIndex: 0 };
|
|
222
|
+
case "ENTER_EPIC_REORDER":
|
|
223
|
+
return {
|
|
224
|
+
...state,
|
|
225
|
+
isEpicReordering: true,
|
|
226
|
+
epicReorderSnapshot: [...state.epics]
|
|
227
|
+
};
|
|
228
|
+
case "EPIC_REORDER_MOVE": {
|
|
229
|
+
if (!state.isEpicReordering) return state;
|
|
230
|
+
const idx = state.epicSelectedIndex;
|
|
231
|
+
const epics = [...state.epics];
|
|
232
|
+
const swapIdx = action.direction === "up" ? idx - 1 : idx + 1;
|
|
233
|
+
if (swapIdx < 0 || swapIdx >= epics.length) return state;
|
|
234
|
+
const current = epics[idx];
|
|
235
|
+
const swap = epics[swapIdx];
|
|
236
|
+
if (!current || !swap) return state;
|
|
237
|
+
epics[idx] = swap;
|
|
238
|
+
epics[swapIdx] = current;
|
|
239
|
+
return {
|
|
240
|
+
...state,
|
|
241
|
+
epics,
|
|
242
|
+
epicSelectedIndex: swapIdx
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
case "EXIT_EPIC_REORDER": {
|
|
246
|
+
if (!action.save && state.epicReorderSnapshot) {
|
|
247
|
+
return {
|
|
248
|
+
...state,
|
|
249
|
+
isEpicReordering: false,
|
|
250
|
+
epics: state.epicReorderSnapshot,
|
|
251
|
+
epicReorderSnapshot: null
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
...state,
|
|
256
|
+
isEpicReordering: false,
|
|
257
|
+
epicReorderSnapshot: null
|
|
258
|
+
};
|
|
259
|
+
}
|
|
184
260
|
}
|
|
185
261
|
}
|
|
186
262
|
|
|
@@ -305,6 +381,7 @@ var STATUS_COLOR = {
|
|
|
305
381
|
[TaskStatus.Cancelled]: theme.status.kill
|
|
306
382
|
};
|
|
307
383
|
var TYPE_COLOR = {
|
|
384
|
+
[TaskType.Epic]: theme.status.modified,
|
|
308
385
|
[TaskType.Story]: theme.status.highlight,
|
|
309
386
|
[TaskType.TechDebt]: theme.status.pending,
|
|
310
387
|
[TaskType.Bug]: theme.status.error
|
|
@@ -319,13 +396,24 @@ var DEP_TYPE_LABEL = {
|
|
|
319
396
|
// src/tui/components/Header.tsx
|
|
320
397
|
import { Box, Text } from "ink";
|
|
321
398
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
322
|
-
function getKeyHints(view, isSearchActive) {
|
|
399
|
+
function getKeyHints(view, isSearchActive, focusedPanel) {
|
|
323
400
|
if (isSearchActive) {
|
|
324
401
|
return [
|
|
325
402
|
{ key: "enter", desc: "apply" },
|
|
326
403
|
{ key: "esc", desc: "cancel" }
|
|
327
404
|
];
|
|
328
405
|
}
|
|
406
|
+
if (view === "task-list" && focusedPanel === "epic") {
|
|
407
|
+
return [
|
|
408
|
+
{ key: "j/k", desc: "nav" },
|
|
409
|
+
{ key: "space", desc: "toggle" },
|
|
410
|
+
{ key: "\u2190", desc: "reorder" },
|
|
411
|
+
{ key: "0", desc: "clear" },
|
|
412
|
+
{ key: "tab", desc: "tasks" },
|
|
413
|
+
{ key: "?", desc: "help" },
|
|
414
|
+
{ key: "q", desc: "quit" }
|
|
415
|
+
];
|
|
416
|
+
}
|
|
329
417
|
if (view === "task-list") {
|
|
330
418
|
return [
|
|
331
419
|
{ key: "enter", desc: "view" },
|
|
@@ -333,11 +421,14 @@ function getKeyHints(view, isSearchActive) {
|
|
|
333
421
|
{ key: "e", desc: "edit" },
|
|
334
422
|
{ key: "d", desc: "del" },
|
|
335
423
|
{ key: "s", desc: "status" },
|
|
424
|
+
{ key: "a", desc: "assign" },
|
|
425
|
+
{ key: "A", desc: "unassign" },
|
|
336
426
|
{ key: "\u2190", desc: "reorder" },
|
|
337
427
|
{ key: "/", desc: "search" },
|
|
338
428
|
{ key: "p", desc: "project" },
|
|
339
429
|
{ key: "f", desc: "status-f" },
|
|
340
430
|
{ key: "t", desc: "type-f" },
|
|
431
|
+
{ key: "tab", desc: "panel" },
|
|
341
432
|
{ key: "?", desc: "help" },
|
|
342
433
|
{ key: "q", desc: "quit" }
|
|
343
434
|
];
|
|
@@ -362,10 +453,10 @@ function getKeyHints(view, isSearchActive) {
|
|
|
362
453
|
function Header({ state }) {
|
|
363
454
|
const projectName = state.activeProject?.name ?? "none";
|
|
364
455
|
const taskCount = state.tasks.length;
|
|
365
|
-
const hints = getKeyHints(state.activeView, state.isSearchActive);
|
|
456
|
+
const hints = getKeyHints(state.activeView, state.isSearchActive, state.focusedPanel);
|
|
366
457
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
367
458
|
/* @__PURE__ */ jsxs(Box, { gap: 2, children: [
|
|
368
|
-
/* @__PURE__ */ jsx(Text, { color: theme.logo, bold: true, children: "
|
|
459
|
+
/* @__PURE__ */ jsx(Text, { color: theme.logo, bold: true, children: "Tayto" }),
|
|
369
460
|
/* @__PURE__ */ jsx(Text, { color: theme.logo, children: "Project:" }),
|
|
370
461
|
/* @__PURE__ */ jsx(Text, { color: "white", bold: true, children: projectName }),
|
|
371
462
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
|
|
@@ -443,7 +534,8 @@ function TaskList({
|
|
|
443
534
|
nonTerminalBlockerIds,
|
|
444
535
|
nonTerminalDependentIds,
|
|
445
536
|
isSelectedBlocked,
|
|
446
|
-
isFocused = true
|
|
537
|
+
isFocused = true,
|
|
538
|
+
epicFilterActive = false
|
|
447
539
|
}) {
|
|
448
540
|
const currentPage = Math.floor(selectedIndex / PAGE_SIZE);
|
|
449
541
|
const viewStart = currentPage * PAGE_SIZE;
|
|
@@ -478,6 +570,7 @@ function TaskList({
|
|
|
478
570
|
" ",
|
|
479
571
|
"REORDER"
|
|
480
572
|
] }),
|
|
573
|
+
epicFilterActive && /* @__PURE__ */ jsx4(Text4, { color: theme.titleHighlight, children: " [epic]" }),
|
|
481
574
|
filterText && /* @__PURE__ */ jsxs2(Text4, { color: theme.titleFilter, children: [
|
|
482
575
|
" /",
|
|
483
576
|
filterText
|
|
@@ -546,7 +639,7 @@ function TaskList({
|
|
|
546
639
|
}
|
|
547
640
|
|
|
548
641
|
// src/tui/components/TaskDetail.tsx
|
|
549
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
642
|
+
import { Box as Box6, Text as Text6, useStdout } from "ink";
|
|
550
643
|
|
|
551
644
|
// src/tui/components/Markdown.tsx
|
|
552
645
|
import { Text as Text5, Box as Box5 } from "ink";
|
|
@@ -688,11 +781,14 @@ function TaskDetail({
|
|
|
688
781
|
dependents,
|
|
689
782
|
related,
|
|
690
783
|
duplicates,
|
|
691
|
-
isFocused = true
|
|
784
|
+
isFocused = true,
|
|
785
|
+
scrollOffset = 0
|
|
692
786
|
}) {
|
|
787
|
+
const { stdout } = useStdout();
|
|
693
788
|
const allText = `${task.description}
|
|
694
789
|
${task.technicalNotes}
|
|
695
790
|
${task.additionalRequirements}`;
|
|
791
|
+
const viewportHeight = Math.max(5, (stdout.rows > 0 ? stdout.rows : 24) - 4);
|
|
696
792
|
return /* @__PURE__ */ jsxs4(
|
|
697
793
|
Box6,
|
|
698
794
|
{
|
|
@@ -708,60 +804,66 @@ ${task.additionalRequirements}`;
|
|
|
708
804
|
] }),
|
|
709
805
|
/* @__PURE__ */ jsx6(Text6, { color: theme.fg, children: "(" }),
|
|
710
806
|
/* @__PURE__ */ jsx6(Text6, { color: theme.titleHighlight, bold: true, children: task.name }),
|
|
711
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.fg, children: ")" })
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
/* @__PURE__ */ jsx6(Field, { label: "status", value: task.status }),
|
|
717
|
-
/* @__PURE__ */ jsx6(Field, { label: "created", value: new Date(task.createdAt).toLocaleString() }),
|
|
718
|
-
/* @__PURE__ */ jsx6(Field, { label: "updated", value: new Date(task.updatedAt).toLocaleString() }),
|
|
719
|
-
task.parentId && /* @__PURE__ */ jsx6(Field, { label: "parent", value: task.parentId })
|
|
807
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.fg, children: ")" }),
|
|
808
|
+
scrollOffset > 0 && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
|
|
809
|
+
" \u2191",
|
|
810
|
+
scrollOffset
|
|
811
|
+
] })
|
|
720
812
|
] }),
|
|
721
|
-
|
|
722
|
-
/* @__PURE__ */
|
|
723
|
-
|
|
724
|
-
/* @__PURE__ */
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
/* @__PURE__ */ jsx6(
|
|
813
|
+
/* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: viewportHeight, overflowY: "hidden", children: /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", marginTop: -scrollOffset, children: [
|
|
814
|
+
/* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", paddingX: 1, paddingY: 0, children: [
|
|
815
|
+
/* @__PURE__ */ jsx6(Field, { label: "id", value: task.id }),
|
|
816
|
+
/* @__PURE__ */ jsx6(Field, { label: "type", value: task.type }),
|
|
817
|
+
/* @__PURE__ */ jsx6(Field, { label: "status", value: task.status }),
|
|
818
|
+
/* @__PURE__ */ jsx6(Field, { label: "created", value: new Date(task.createdAt).toLocaleString() }),
|
|
819
|
+
/* @__PURE__ */ jsx6(Field, { label: "updated", value: new Date(task.updatedAt).toLocaleString() }),
|
|
820
|
+
task.parentId && /* @__PURE__ */ jsx6(Field, { label: "parent", value: task.parentId })
|
|
729
821
|
] }),
|
|
730
|
-
dependents && dependents.length > 0 && /* @__PURE__ */ jsxs4(Box6, {
|
|
731
|
-
/* @__PURE__ */
|
|
732
|
-
|
|
733
|
-
|
|
822
|
+
(blockers && blockers.length > 0 || dependents && dependents.length > 0 || related && related.length > 0 || duplicates && duplicates.length > 0) && /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
823
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.title, bold: true, children: "--- dependencies ---" }),
|
|
824
|
+
blockers && blockers.length > 0 && /* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
825
|
+
/* @__PURE__ */ jsxs4(Text6, { color: theme.status.error, bold: true, children: [
|
|
826
|
+
"blocked by:",
|
|
827
|
+
" "
|
|
828
|
+
] }),
|
|
829
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.yaml.value, children: blockers.map((t) => t.id).join(", ") })
|
|
734
830
|
] }),
|
|
735
|
-
/* @__PURE__ */
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
" "
|
|
831
|
+
dependents && dependents.length > 0 && /* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
832
|
+
/* @__PURE__ */ jsxs4(Text6, { color: theme.status.added, bold: true, children: [
|
|
833
|
+
"blocks:",
|
|
834
|
+
" "
|
|
835
|
+
] }),
|
|
836
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.yaml.value, children: dependents.map((t) => t.id).join(", ") })
|
|
741
837
|
] }),
|
|
742
|
-
/* @__PURE__ */
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
" "
|
|
838
|
+
related && related.length > 0 && /* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
839
|
+
/* @__PURE__ */ jsxs4(Text6, { color: theme.status.new, bold: true, children: [
|
|
840
|
+
"relates to:",
|
|
841
|
+
" "
|
|
842
|
+
] }),
|
|
843
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.yaml.value, children: related.map((t) => t.id).join(", ") })
|
|
844
|
+
] }),
|
|
845
|
+
duplicates && duplicates.length > 0 && /* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
846
|
+
/* @__PURE__ */ jsxs4(Text6, { color: theme.status.pending, bold: true, children: [
|
|
847
|
+
"duplicates:",
|
|
848
|
+
" "
|
|
849
|
+
] }),
|
|
850
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.yaml.value, children: duplicates.map((t) => t.id).join(", ") })
|
|
748
851
|
] }),
|
|
749
|
-
/* @__PURE__ */ jsx6(Text6, {
|
|
852
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "press D to manage dependencies" })
|
|
750
853
|
] }),
|
|
751
|
-
/* @__PURE__ */
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
task.
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
/* @__PURE__ */
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
] }),
|
|
854
|
+
/* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
855
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.title, bold: true, children: "--- description ---" }),
|
|
856
|
+
task.description.trim() ? /* @__PURE__ */ jsx6(Markdown, { content: task.description }) : /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No description" })
|
|
857
|
+
] }),
|
|
858
|
+
task.technicalNotes.trim() && /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
859
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.title, bold: true, children: "--- technical notes ---" }),
|
|
860
|
+
/* @__PURE__ */ jsx6(Markdown, { content: task.technicalNotes })
|
|
861
|
+
] }),
|
|
862
|
+
task.additionalRequirements.trim() && /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
863
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.title, bold: true, children: "--- requirements ---" }),
|
|
864
|
+
/* @__PURE__ */ jsx6(Markdown, { content: task.additionalRequirements })
|
|
865
|
+
] })
|
|
866
|
+
] }) }),
|
|
765
867
|
/* @__PURE__ */ jsx6(Box6, { flexGrow: 1 }),
|
|
766
868
|
/* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(MermaidHint, { content: allText }) })
|
|
767
869
|
]
|
|
@@ -808,7 +910,7 @@ function openInEditor(content, filename) {
|
|
|
808
910
|
|
|
809
911
|
// src/tui/components/TaskPicker.tsx
|
|
810
912
|
import { useState } from "react";
|
|
811
|
-
import { Box as Box7, Text as Text7, useInput, useStdout } from "ink";
|
|
913
|
+
import { Box as Box7, Text as Text7, useInput, useStdout as useStdout2 } from "ink";
|
|
812
914
|
import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
813
915
|
var DEP_TYPE_VALUES = Object.values(UIDependencyType);
|
|
814
916
|
var DEP_TYPE_COLOR = {
|
|
@@ -818,7 +920,7 @@ var DEP_TYPE_COLOR = {
|
|
|
818
920
|
[UIDependencyType.BlockedBy]: theme.status.modified
|
|
819
921
|
};
|
|
820
922
|
function TaskPicker({ tasks, excludeIds, initialSelection, onConfirm, onCancel }) {
|
|
821
|
-
const { stdout } =
|
|
923
|
+
const { stdout } = useStdout2();
|
|
822
924
|
const termHeight = stdout.rows > 0 ? stdout.rows : 24;
|
|
823
925
|
const maxVisible = Math.max(3, termHeight - 12);
|
|
824
926
|
const [searchQuery, setSearchQuery] = useState("");
|
|
@@ -1634,8 +1736,222 @@ function DependencyList({
|
|
|
1634
1736
|
] });
|
|
1635
1737
|
}
|
|
1636
1738
|
|
|
1637
|
-
// src/tui/components/
|
|
1739
|
+
// src/tui/components/EpicPanel.tsx
|
|
1740
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
1638
1741
|
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1742
|
+
var PAGE_SIZE2 = 20;
|
|
1743
|
+
function EpicPanel({
|
|
1744
|
+
epics,
|
|
1745
|
+
selectedIndex,
|
|
1746
|
+
selectedEpicIds,
|
|
1747
|
+
isFocused,
|
|
1748
|
+
isReordering = false
|
|
1749
|
+
}) {
|
|
1750
|
+
const filterActive = selectedEpicIds.size > 0;
|
|
1751
|
+
const currentPage = Math.floor(selectedIndex / PAGE_SIZE2);
|
|
1752
|
+
const viewStart = currentPage * PAGE_SIZE2;
|
|
1753
|
+
const visibleEpics = epics.slice(viewStart, viewStart + PAGE_SIZE2);
|
|
1754
|
+
return /* @__PURE__ */ jsxs12(
|
|
1755
|
+
Box14,
|
|
1756
|
+
{
|
|
1757
|
+
flexDirection: "column",
|
|
1758
|
+
width: 48,
|
|
1759
|
+
borderStyle: "bold",
|
|
1760
|
+
borderColor: isFocused ? theme.borderFocus : theme.border,
|
|
1761
|
+
children: [
|
|
1762
|
+
/* @__PURE__ */ jsxs12(Box14, { children: [
|
|
1763
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.title, bold: true, children: [
|
|
1764
|
+
" ",
|
|
1765
|
+
"epics"
|
|
1766
|
+
] }),
|
|
1767
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.titleCounter, bold: true, children: [
|
|
1768
|
+
"[",
|
|
1769
|
+
epics.length,
|
|
1770
|
+
"]"
|
|
1771
|
+
] }),
|
|
1772
|
+
isReordering && /* @__PURE__ */ jsxs12(Text14, { color: theme.flash.warn, bold: true, children: [
|
|
1773
|
+
" ",
|
|
1774
|
+
"REORDER"
|
|
1775
|
+
] }),
|
|
1776
|
+
filterActive && /* @__PURE__ */ jsxs12(Text14, { color: theme.titleFilter, children: [
|
|
1777
|
+
" *",
|
|
1778
|
+
selectedEpicIds.size
|
|
1779
|
+
] })
|
|
1780
|
+
] }),
|
|
1781
|
+
epics.length === 0 ? /* @__PURE__ */ jsx14(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "No epics" }) }) : visibleEpics.map((epic, i) => {
|
|
1782
|
+
const actualIndex = viewStart + i;
|
|
1783
|
+
const isSelected = actualIndex === selectedIndex && isFocused;
|
|
1784
|
+
const isChecked = selectedEpicIds.has(epic.id);
|
|
1785
|
+
const marker = isChecked ? "[x]" : "[ ]";
|
|
1786
|
+
const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
|
|
1787
|
+
if (isSelected) {
|
|
1788
|
+
const cursorBg = isReordering ? theme.flash.warn : theme.table.cursorBg;
|
|
1789
|
+
return /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs12(Text14, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: [
|
|
1790
|
+
isReordering ? "~ " : " ",
|
|
1791
|
+
marker,
|
|
1792
|
+
" ",
|
|
1793
|
+
epic.name
|
|
1794
|
+
] }) }, epic.id);
|
|
1795
|
+
}
|
|
1796
|
+
return /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs12(Text14, { color: isChecked ? theme.titleHighlight : statusColor, children: [
|
|
1797
|
+
" ",
|
|
1798
|
+
marker,
|
|
1799
|
+
" ",
|
|
1800
|
+
epic.name
|
|
1801
|
+
] }) }, epic.id);
|
|
1802
|
+
}),
|
|
1803
|
+
/* @__PURE__ */ jsx14(Box14, { flexGrow: 1 }),
|
|
1804
|
+
epics.length > PAGE_SIZE2 && /* @__PURE__ */ jsx14(Box14, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs12(Text14, { dimColor: true, children: [
|
|
1805
|
+
"[",
|
|
1806
|
+
viewStart + 1,
|
|
1807
|
+
"-",
|
|
1808
|
+
Math.min(viewStart + PAGE_SIZE2, epics.length),
|
|
1809
|
+
"/",
|
|
1810
|
+
epics.length,
|
|
1811
|
+
"]"
|
|
1812
|
+
] }) })
|
|
1813
|
+
]
|
|
1814
|
+
}
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// src/tui/components/EpicPicker.tsx
|
|
1819
|
+
import { useState as useState5 } from "react";
|
|
1820
|
+
import { Box as Box15, Text as Text15, useInput as useInput5, useStdout as useStdout3 } from "ink";
|
|
1821
|
+
import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1822
|
+
function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
|
|
1823
|
+
const { stdout } = useStdout3();
|
|
1824
|
+
const termHeight = stdout.rows > 0 ? stdout.rows : 24;
|
|
1825
|
+
const maxVisible = Math.max(3, termHeight - 10);
|
|
1826
|
+
const [searchQuery, setSearchQuery] = useState5("");
|
|
1827
|
+
const [isSearching, setIsSearching] = useState5(false);
|
|
1828
|
+
const [cursorIndex, setCursorIndex] = useState5(0);
|
|
1829
|
+
const filtered = epics.filter((e) => {
|
|
1830
|
+
if (!searchQuery.trim()) return true;
|
|
1831
|
+
const q = searchQuery.toLowerCase();
|
|
1832
|
+
return e.id.toLowerCase().includes(q) || e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q);
|
|
1833
|
+
});
|
|
1834
|
+
let viewStart = 0;
|
|
1835
|
+
if (cursorIndex >= viewStart + maxVisible) {
|
|
1836
|
+
viewStart = cursorIndex - maxVisible + 1;
|
|
1837
|
+
}
|
|
1838
|
+
if (cursorIndex < viewStart) {
|
|
1839
|
+
viewStart = cursorIndex;
|
|
1840
|
+
}
|
|
1841
|
+
const visible = filtered.slice(viewStart, viewStart + maxVisible);
|
|
1842
|
+
useInput5((input, key) => {
|
|
1843
|
+
if (isSearching) {
|
|
1844
|
+
if (key.escape) {
|
|
1845
|
+
setIsSearching(false);
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
if (key.return) {
|
|
1849
|
+
setIsSearching(false);
|
|
1850
|
+
setCursorIndex(0);
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
if (key.backspace || key.delete) {
|
|
1854
|
+
setSearchQuery((q) => q.slice(0, -1));
|
|
1855
|
+
setCursorIndex(0);
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
if (input && !key.ctrl && !key.meta) {
|
|
1859
|
+
setSearchQuery((q) => q + input);
|
|
1860
|
+
setCursorIndex(0);
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
if (key.upArrow || input === "k") {
|
|
1866
|
+
setCursorIndex((i) => Math.max(0, i - 1));
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
if (key.downArrow || input === "j") {
|
|
1870
|
+
setCursorIndex((i) => Math.min(filtered.length - 1, i + 1));
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
if (key.return) {
|
|
1874
|
+
const epic = filtered[cursorIndex];
|
|
1875
|
+
if (epic) {
|
|
1876
|
+
onSelect(epic.id);
|
|
1877
|
+
}
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
if (input === "x") {
|
|
1881
|
+
onSelect(null);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
if (input === "/") {
|
|
1885
|
+
setIsSearching(true);
|
|
1886
|
+
setSearchQuery("");
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
if (key.escape || input === "q") {
|
|
1890
|
+
onCancel();
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1894
|
+
return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", borderStyle: "bold", borderColor: theme.borderFocus, flexGrow: 1, children: [
|
|
1895
|
+
/* @__PURE__ */ jsxs13(Box15, { gap: 0, children: [
|
|
1896
|
+
/* @__PURE__ */ jsxs13(Text15, { color: theme.title, bold: true, children: [
|
|
1897
|
+
" ",
|
|
1898
|
+
"assign to epic"
|
|
1899
|
+
] }),
|
|
1900
|
+
/* @__PURE__ */ jsxs13(Text15, { color: theme.titleCounter, bold: true, children: [
|
|
1901
|
+
" ",
|
|
1902
|
+
"[",
|
|
1903
|
+
epics.length,
|
|
1904
|
+
"]"
|
|
1905
|
+
] })
|
|
1906
|
+
] }),
|
|
1907
|
+
isSearching ? /* @__PURE__ */ jsxs13(Box15, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
|
|
1908
|
+
/* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: "/" }),
|
|
1909
|
+
/* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: searchQuery }),
|
|
1910
|
+
/* @__PURE__ */ jsx15(Text15, { color: theme.promptSuggest, children: "_" })
|
|
1911
|
+
] }) : searchQuery ? /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsxs13(Text15, { color: theme.titleFilter, children: [
|
|
1912
|
+
"/",
|
|
1913
|
+
searchQuery
|
|
1914
|
+
] }) }) : null,
|
|
1915
|
+
/* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsxs13(Text15, { color: theme.table.headerFg, bold: true, children: [
|
|
1916
|
+
" ",
|
|
1917
|
+
"ID".padEnd(14),
|
|
1918
|
+
"STATUS".padEnd(14),
|
|
1919
|
+
"NAME"
|
|
1920
|
+
] }) }),
|
|
1921
|
+
filtered.length === 0 ? /* @__PURE__ */ jsx15(Box15, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "No epics match the filter" }) }) : visible.map((epic, i) => {
|
|
1922
|
+
const actualIndex = viewStart + i;
|
|
1923
|
+
const isCursor = actualIndex === cursorIndex;
|
|
1924
|
+
const isCurrent = epic.id === currentEpicId;
|
|
1925
|
+
const marker = isCurrent ? "* " : " ";
|
|
1926
|
+
const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
|
|
1927
|
+
return /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: isCursor ? /* @__PURE__ */ jsxs13(Text15, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
|
|
1928
|
+
"> ",
|
|
1929
|
+
epic.id.padEnd(14),
|
|
1930
|
+
epic.status.padEnd(14),
|
|
1931
|
+
epic.name
|
|
1932
|
+
] }) : /* @__PURE__ */ jsxs13(Fragment3, { children: [
|
|
1933
|
+
/* @__PURE__ */ jsx15(Text15, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: marker }),
|
|
1934
|
+
/* @__PURE__ */ jsx15(Text15, { color: theme.yaml.value, children: epic.id.padEnd(14) }),
|
|
1935
|
+
/* @__PURE__ */ jsx15(Text15, { color: statusColor, children: epic.status.padEnd(14) }),
|
|
1936
|
+
/* @__PURE__ */ jsx15(Text15, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: epic.name })
|
|
1937
|
+
] }) }, epic.id);
|
|
1938
|
+
}),
|
|
1939
|
+
/* @__PURE__ */ jsx15(Box15, { flexGrow: 1 }),
|
|
1940
|
+
filtered.length > maxVisible && /* @__PURE__ */ jsx15(Box15, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs13(Text15, { dimColor: true, children: [
|
|
1941
|
+
"[",
|
|
1942
|
+
viewStart + 1,
|
|
1943
|
+
"-",
|
|
1944
|
+
Math.min(viewStart + maxVisible, filtered.length),
|
|
1945
|
+
"/",
|
|
1946
|
+
filtered.length,
|
|
1947
|
+
"]"
|
|
1948
|
+
] }) }),
|
|
1949
|
+
/* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "enter: assign | x: unassign | /: search | esc: cancel" }) })
|
|
1950
|
+
] });
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// src/tui/components/App.tsx
|
|
1954
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1639
1955
|
var STATUS_CYCLE = [
|
|
1640
1956
|
TaskStatus.Backlog,
|
|
1641
1957
|
TaskStatus.Todo,
|
|
@@ -1658,10 +1974,13 @@ function App({ container, initialProject }) {
|
|
|
1658
1974
|
}, [container]);
|
|
1659
1975
|
const loadTasks = useCallback2(() => {
|
|
1660
1976
|
logger.startSpan("TUI.loadTasks", () => {
|
|
1661
|
-
const filter = { ...state.filter };
|
|
1977
|
+
const filter = { ...state.filter, level: TaskLevel.Work };
|
|
1662
1978
|
if (state.activeProject) {
|
|
1663
1979
|
filter.projectId = state.activeProject.id;
|
|
1664
1980
|
}
|
|
1981
|
+
if (state.selectedEpicIds.size > 0) {
|
|
1982
|
+
filter.parentIds = [...state.selectedEpicIds];
|
|
1983
|
+
}
|
|
1665
1984
|
const result = container.taskService.listTasks(filter);
|
|
1666
1985
|
if (result.ok) {
|
|
1667
1986
|
logger.info(`TUI.loadTasks: loaded ${result.value.length} tasks`);
|
|
@@ -1671,7 +1990,19 @@ function App({ container, initialProject }) {
|
|
|
1671
1990
|
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
1672
1991
|
}
|
|
1673
1992
|
});
|
|
1674
|
-
}, [container, state.filter, state.activeProject]);
|
|
1993
|
+
}, [container, state.filter, state.activeProject, state.selectedEpicIds]);
|
|
1994
|
+
const loadEpics = useCallback2(() => {
|
|
1995
|
+
if (!state.activeProject) return;
|
|
1996
|
+
const result = container.taskService.listTasks({
|
|
1997
|
+
projectId: state.activeProject.id,
|
|
1998
|
+
level: TaskLevel.Epic
|
|
1999
|
+
});
|
|
2000
|
+
if (result.ok) {
|
|
2001
|
+
dispatch({ type: "SET_EPICS", epics: result.value });
|
|
2002
|
+
} else {
|
|
2003
|
+
logger.error("TUI.loadEpics: failed", result.error);
|
|
2004
|
+
}
|
|
2005
|
+
}, [container, state.activeProject]);
|
|
1675
2006
|
const loadDeps = useCallback2(
|
|
1676
2007
|
(taskId) => {
|
|
1677
2008
|
const blockersResult = container.dependencyService.listBlockers(taskId);
|
|
@@ -1700,11 +2031,12 @@ function App({ container, initialProject }) {
|
|
|
1700
2031
|
dispatch({ type: "SELECT_TASK", task: result.value });
|
|
1701
2032
|
}
|
|
1702
2033
|
loadTasks();
|
|
2034
|
+
loadEpics();
|
|
1703
2035
|
} else {
|
|
1704
2036
|
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
1705
2037
|
}
|
|
1706
2038
|
},
|
|
1707
|
-
[container, state.selectedTask, loadTasks]
|
|
2039
|
+
[container, state.selectedTask, loadTasks, loadEpics]
|
|
1708
2040
|
);
|
|
1709
2041
|
const saveReorder = useCallback2(() => {
|
|
1710
2042
|
const tasks = state.tasks;
|
|
@@ -1722,6 +2054,22 @@ function App({ container, initialProject }) {
|
|
|
1722
2054
|
});
|
|
1723
2055
|
loadTasks();
|
|
1724
2056
|
}, [container, state.tasks, state.selectedIndex, loadTasks]);
|
|
2057
|
+
const saveEpicReorder = useCallback2(() => {
|
|
2058
|
+
const epics = state.epics;
|
|
2059
|
+
const idx = state.epicSelectedIndex;
|
|
2060
|
+
const epic = epics[idx];
|
|
2061
|
+
if (!epic) return;
|
|
2062
|
+
const prev = epics[idx - 1];
|
|
2063
|
+
const next = epics[idx + 1];
|
|
2064
|
+
const result = prev ? container.taskService.rerankTask({ taskId: epic.id, afterId: prev.id }) : next ? container.taskService.rerankTask({ taskId: epic.id, beforeId: next.id }) : container.taskService.rerankTask({ taskId: epic.id, position: 1 });
|
|
2065
|
+
dispatch({ type: "EXIT_EPIC_REORDER", save: result.ok });
|
|
2066
|
+
dispatch({
|
|
2067
|
+
type: "FLASH",
|
|
2068
|
+
message: result.ok ? "Epic rank saved" : result.error.message,
|
|
2069
|
+
level: result.ok ? "info" : "error"
|
|
2070
|
+
});
|
|
2071
|
+
loadEpics();
|
|
2072
|
+
}, [container, state.epics, state.epicSelectedIndex, loadEpics]);
|
|
1725
2073
|
useEffect(() => {
|
|
1726
2074
|
loadProjects();
|
|
1727
2075
|
}, [loadProjects]);
|
|
@@ -1745,8 +2093,9 @@ function App({ container, initialProject }) {
|
|
|
1745
2093
|
useEffect(() => {
|
|
1746
2094
|
if (state.activeProject) {
|
|
1747
2095
|
loadTasks();
|
|
2096
|
+
loadEpics();
|
|
1748
2097
|
}
|
|
1749
|
-
}, [state.activeProject, state.filter, loadTasks]);
|
|
2098
|
+
}, [state.activeProject, state.filter, loadTasks, loadEpics]);
|
|
1750
2099
|
useEffect(() => {
|
|
1751
2100
|
if (state.flash) {
|
|
1752
2101
|
const timer = setTimeout(() => {
|
|
@@ -1758,7 +2107,7 @@ function App({ container, initialProject }) {
|
|
|
1758
2107
|
}
|
|
1759
2108
|
return void 0;
|
|
1760
2109
|
}, [state.flash]);
|
|
1761
|
-
|
|
2110
|
+
useInput6((input, key) => {
|
|
1762
2111
|
if (state.confirmDelete) {
|
|
1763
2112
|
if (input === "y") {
|
|
1764
2113
|
const result = container.taskService.deleteTask(state.confirmDelete.id);
|
|
@@ -1769,6 +2118,7 @@ function App({ container, initialProject }) {
|
|
|
1769
2118
|
dispatch({ type: "GO_BACK" });
|
|
1770
2119
|
}
|
|
1771
2120
|
loadTasks();
|
|
2121
|
+
loadEpics();
|
|
1772
2122
|
} else {
|
|
1773
2123
|
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
1774
2124
|
dispatch({ type: "CANCEL_DELETE" });
|
|
@@ -1782,7 +2132,7 @@ function App({ container, initialProject }) {
|
|
|
1782
2132
|
dispatch({ type: "GO_BACK" });
|
|
1783
2133
|
return;
|
|
1784
2134
|
}
|
|
1785
|
-
if (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit || state.activeView === ViewType.ProjectSelector || state.activeView === ViewType.ProjectCreate) {
|
|
2135
|
+
if (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit || state.activeView === ViewType.ProjectSelector || state.activeView === ViewType.ProjectCreate || state.activeView === ViewType.EpicPicker) {
|
|
1786
2136
|
return;
|
|
1787
2137
|
}
|
|
1788
2138
|
if (state.activeView === ViewType.DependencyList && state.isAddingDep) {
|
|
@@ -1853,6 +2203,26 @@ function App({ container, initialProject }) {
|
|
|
1853
2203
|
}
|
|
1854
2204
|
return;
|
|
1855
2205
|
}
|
|
2206
|
+
if (state.isEpicReordering) {
|
|
2207
|
+
if (key.upArrow || input === "k") {
|
|
2208
|
+
dispatch({ type: "EPIC_REORDER_MOVE", direction: "up" });
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
if (key.downArrow || input === "j") {
|
|
2212
|
+
dispatch({ type: "EPIC_REORDER_MOVE", direction: "down" });
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
if (key.rightArrow) {
|
|
2216
|
+
saveEpicReorder();
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
if (key.escape || key.leftArrow) {
|
|
2220
|
+
dispatch({ type: "EXIT_EPIC_REORDER", save: false });
|
|
2221
|
+
dispatch({ type: "FLASH", message: "Epic reorder cancelled", level: "info" });
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
1856
2226
|
if (state.isReordering) {
|
|
1857
2227
|
if (key.upArrow || input === "k") {
|
|
1858
2228
|
dispatch({ type: "REORDER_MOVE", direction: "up" });
|
|
@@ -1889,13 +2259,49 @@ function App({ container, initialProject }) {
|
|
|
1889
2259
|
dispatch({ type: "NAVIGATE_TO", view: ViewType.Help });
|
|
1890
2260
|
return;
|
|
1891
2261
|
}
|
|
1892
|
-
if (key.tab && state.activeView === ViewType.TaskList
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
});
|
|
2262
|
+
if (key.tab && state.activeView === ViewType.TaskList) {
|
|
2263
|
+
const panels = previewTask ? ["epic", "list", "detail"] : ["epic", "list"];
|
|
2264
|
+
const curIdx = panels.indexOf(state.focusedPanel);
|
|
2265
|
+
const nextPanel = panels[(curIdx + 1) % panels.length] ?? "list";
|
|
2266
|
+
dispatch({ type: "SET_PANEL_FOCUS", panel: nextPanel });
|
|
1897
2267
|
return;
|
|
1898
2268
|
}
|
|
2269
|
+
if (state.activeView === ViewType.TaskList && state.focusedPanel === "epic") {
|
|
2270
|
+
if (key.upArrow || input === "k") {
|
|
2271
|
+
dispatch({ type: "EPIC_MOVE_CURSOR", direction: "up" });
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
if (key.downArrow || input === "j") {
|
|
2275
|
+
dispatch({ type: "EPIC_MOVE_CURSOR", direction: "down" });
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (input === " " || key.return) {
|
|
2279
|
+
const epic = state.epics[state.epicSelectedIndex];
|
|
2280
|
+
if (epic) {
|
|
2281
|
+
dispatch({ type: "TOGGLE_EPIC", epicId: epic.id });
|
|
2282
|
+
}
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
if (input === "0") {
|
|
2286
|
+
dispatch({ type: "CLEAR_EPIC_SELECTION" });
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
if (key.leftArrow) {
|
|
2290
|
+
if (state.epics.length > 0) {
|
|
2291
|
+
dispatch({ type: "ENTER_EPIC_REORDER" });
|
|
2292
|
+
dispatch({
|
|
2293
|
+
type: "FLASH",
|
|
2294
|
+
message: "Reorder: \u2191\u2193 move, \u2192 save, \u2190 cancel",
|
|
2295
|
+
level: "info"
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
if (input === "q") {
|
|
2301
|
+
exit();
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
1899
2305
|
if (state.activeView === ViewType.TaskList && state.focusedPanel === "list") {
|
|
1900
2306
|
if (key.upArrow || input === "k") {
|
|
1901
2307
|
dispatch({ type: "MOVE_CURSOR", direction: "up" });
|
|
@@ -1949,6 +2355,30 @@ function App({ container, initialProject }) {
|
|
|
1949
2355
|
}
|
|
1950
2356
|
return;
|
|
1951
2357
|
}
|
|
2358
|
+
if (input === "a") {
|
|
2359
|
+
const task = state.tasks[state.selectedIndex];
|
|
2360
|
+
if (task) {
|
|
2361
|
+
dispatch({ type: "SELECT_TASK", task });
|
|
2362
|
+
dispatch({ type: "NAVIGATE_TO", view: ViewType.EpicPicker });
|
|
2363
|
+
}
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
if (input === "A") {
|
|
2367
|
+
const task = state.tasks[state.selectedIndex];
|
|
2368
|
+
if (task && task.parentId) {
|
|
2369
|
+
const result = container.taskService.updateTask(task.id, { parentId: null });
|
|
2370
|
+
if (result.ok) {
|
|
2371
|
+
dispatch({ type: "FLASH", message: "Unassigned from epic", level: "info" });
|
|
2372
|
+
loadTasks();
|
|
2373
|
+
loadEpics();
|
|
2374
|
+
} else {
|
|
2375
|
+
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
2376
|
+
}
|
|
2377
|
+
} else if (task) {
|
|
2378
|
+
dispatch({ type: "FLASH", message: "Task has no epic", level: "warn" });
|
|
2379
|
+
}
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
1952
2382
|
if (input === "/") {
|
|
1953
2383
|
dispatch({ type: "SET_SEARCH_ACTIVE", active: true });
|
|
1954
2384
|
dispatch({ type: "SET_SEARCH_QUERY", query: "" });
|
|
@@ -1959,6 +2389,22 @@ function App({ container, initialProject }) {
|
|
|
1959
2389
|
return;
|
|
1960
2390
|
}
|
|
1961
2391
|
if (key.leftArrow) {
|
|
2392
|
+
if (state.selectedEpicIds.size > 0) {
|
|
2393
|
+
dispatch({
|
|
2394
|
+
type: "FLASH",
|
|
2395
|
+
message: "Clear epic filter (0) before reordering",
|
|
2396
|
+
level: "warn"
|
|
2397
|
+
});
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
if (state.filter.status || state.filter.type || state.filter.search) {
|
|
2401
|
+
dispatch({
|
|
2402
|
+
type: "FLASH",
|
|
2403
|
+
message: "Clear filters (0) before reordering",
|
|
2404
|
+
level: "warn"
|
|
2405
|
+
});
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
1962
2408
|
if (state.tasks.length > 0) {
|
|
1963
2409
|
dispatch({ type: "ENTER_REORDER" });
|
|
1964
2410
|
dispatch({ type: "FLASH", message: "Reorder: \u2191\u2193 move, \u2192 save, \u2190 cancel", level: "info" });
|
|
@@ -1987,6 +2433,14 @@ function App({ container, initialProject }) {
|
|
|
1987
2433
|
}
|
|
1988
2434
|
}
|
|
1989
2435
|
if (state.activeView === ViewType.TaskList && state.focusedPanel === "detail" && previewTask) {
|
|
2436
|
+
if (key.upArrow || input === "k") {
|
|
2437
|
+
dispatch({ type: "DETAIL_SCROLL", direction: "up" });
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
if (key.downArrow || input === "j") {
|
|
2441
|
+
dispatch({ type: "DETAIL_SCROLL", direction: "down" });
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
1990
2444
|
if (input === "e") {
|
|
1991
2445
|
dispatch({ type: "SELECT_TASK", task: previewTask });
|
|
1992
2446
|
dispatch({ type: "NAVIGATE_TO", view: ViewType.TaskEdit });
|
|
@@ -2022,6 +2476,14 @@ ${previewTask.additionalRequirements}`;
|
|
|
2022
2476
|
}
|
|
2023
2477
|
}
|
|
2024
2478
|
if (state.activeView === ViewType.TaskDetail) {
|
|
2479
|
+
if (key.upArrow || input === "k") {
|
|
2480
|
+
dispatch({ type: "DETAIL_SCROLL", direction: "up" });
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
if (key.downArrow || input === "j") {
|
|
2484
|
+
dispatch({ type: "DETAIL_SCROLL", direction: "down" });
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2025
2487
|
if (input === "e" && state.selectedTask) {
|
|
2026
2488
|
dispatch({ type: "NAVIGATE_TO", view: ViewType.TaskEdit });
|
|
2027
2489
|
return;
|
|
@@ -2141,22 +2603,53 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2141
2603
|
loadDeps(taskId);
|
|
2142
2604
|
dispatch({ type: "GO_BACK" });
|
|
2143
2605
|
loadTasks();
|
|
2606
|
+
loadEpics();
|
|
2144
2607
|
} else {
|
|
2145
2608
|
const result = container.taskService.createTask(data, state.activeProject?.id);
|
|
2146
2609
|
if (result.ok) {
|
|
2147
2610
|
dispatch({ type: "FLASH", message: "Task created", level: "info" });
|
|
2148
2611
|
dispatch({ type: "GO_BACK" });
|
|
2149
2612
|
loadTasks();
|
|
2613
|
+
loadEpics();
|
|
2150
2614
|
} else {
|
|
2151
2615
|
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
2152
2616
|
}
|
|
2153
2617
|
}
|
|
2154
2618
|
},
|
|
2155
|
-
[
|
|
2619
|
+
[
|
|
2620
|
+
container,
|
|
2621
|
+
state.activeView,
|
|
2622
|
+
state.selectedTask,
|
|
2623
|
+
state.activeProject,
|
|
2624
|
+
loadTasks,
|
|
2625
|
+
loadDeps,
|
|
2626
|
+
loadEpics
|
|
2627
|
+
]
|
|
2156
2628
|
);
|
|
2157
2629
|
const handleFormCancel = useCallback2(() => {
|
|
2158
2630
|
dispatch({ type: "GO_BACK" });
|
|
2159
2631
|
}, []);
|
|
2632
|
+
const handleEpicPickerSelect = useCallback2(
|
|
2633
|
+
(epicId) => {
|
|
2634
|
+
if (!state.selectedTask) return;
|
|
2635
|
+
const result = container.taskService.updateTask(state.selectedTask.id, {
|
|
2636
|
+
parentId: epicId
|
|
2637
|
+
});
|
|
2638
|
+
if (result.ok) {
|
|
2639
|
+
const msg = epicId ? `Assigned to ${epicId}` : "Unassigned from epic";
|
|
2640
|
+
dispatch({ type: "FLASH", message: msg, level: "info" });
|
|
2641
|
+
loadTasks();
|
|
2642
|
+
loadEpics();
|
|
2643
|
+
} else {
|
|
2644
|
+
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
2645
|
+
}
|
|
2646
|
+
dispatch({ type: "GO_BACK" });
|
|
2647
|
+
},
|
|
2648
|
+
[container, state.selectedTask, loadTasks, loadEpics]
|
|
2649
|
+
);
|
|
2650
|
+
const handleEpicPickerCancel = useCallback2(() => {
|
|
2651
|
+
dispatch({ type: "GO_BACK" });
|
|
2652
|
+
}, []);
|
|
2160
2653
|
const handleProjectSelect = useCallback2((project) => {
|
|
2161
2654
|
dispatch({ type: "SET_ACTIVE_PROJECT", project });
|
|
2162
2655
|
dispatch({ type: "GO_BACK" });
|
|
@@ -2167,7 +2660,11 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2167
2660
|
const result = container.projectService.updateProject(project.id, { isDefault: true });
|
|
2168
2661
|
if (result.ok) {
|
|
2169
2662
|
logger.info(`TUI.setDefault: set key=${result.value.key} as default`);
|
|
2170
|
-
dispatch({
|
|
2663
|
+
dispatch({
|
|
2664
|
+
type: "FLASH",
|
|
2665
|
+
message: `Default project: ${result.value.name}`,
|
|
2666
|
+
level: "info"
|
|
2667
|
+
});
|
|
2171
2668
|
loadProjects();
|
|
2172
2669
|
} else {
|
|
2173
2670
|
logger.error("TUI.setDefault: failed", result.error);
|
|
@@ -2234,12 +2731,22 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2234
2731
|
loadDeps(previewTaskId);
|
|
2235
2732
|
}
|
|
2236
2733
|
}, [state.activeView, previewTaskId, loadDeps]);
|
|
2237
|
-
return /* @__PURE__ */
|
|
2238
|
-
/* @__PURE__ */
|
|
2239
|
-
/* @__PURE__ */
|
|
2240
|
-
state.confirmDelete && /* @__PURE__ */
|
|
2241
|
-
!state.confirmDelete && state.activeView === ViewType.TaskList && /* @__PURE__ */
|
|
2242
|
-
/* @__PURE__ */
|
|
2734
|
+
return /* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", height: "100%", children: [
|
|
2735
|
+
/* @__PURE__ */ jsx16(Header, { state }),
|
|
2736
|
+
/* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", flexGrow: 1, children: [
|
|
2737
|
+
state.confirmDelete && /* @__PURE__ */ jsx16(ConfirmDialog, { task: state.confirmDelete }),
|
|
2738
|
+
!state.confirmDelete && state.activeView === ViewType.TaskList && /* @__PURE__ */ jsxs14(Box16, { flexDirection: "row", flexGrow: 1, children: [
|
|
2739
|
+
/* @__PURE__ */ jsx16(
|
|
2740
|
+
EpicPanel,
|
|
2741
|
+
{
|
|
2742
|
+
epics: state.epics,
|
|
2743
|
+
selectedIndex: state.epicSelectedIndex,
|
|
2744
|
+
selectedEpicIds: state.selectedEpicIds,
|
|
2745
|
+
isFocused: state.focusedPanel === "epic",
|
|
2746
|
+
isReordering: state.isEpicReordering
|
|
2747
|
+
}
|
|
2748
|
+
),
|
|
2749
|
+
/* @__PURE__ */ jsx16(Box16, { flexGrow: 2, children: /* @__PURE__ */ jsx16(
|
|
2243
2750
|
TaskList,
|
|
2244
2751
|
{
|
|
2245
2752
|
tasks: state.tasks,
|
|
@@ -2256,10 +2763,11 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2256
2763
|
state.depDependents.filter((t) => !isTerminalStatus(t.status)).map((t) => t.id)
|
|
2257
2764
|
),
|
|
2258
2765
|
isSelectedBlocked: state.depBlockers.some((t) => !isTerminalStatus(t.status)),
|
|
2259
|
-
isFocused: state.focusedPanel === "list"
|
|
2766
|
+
isFocused: state.focusedPanel === "list",
|
|
2767
|
+
epicFilterActive: state.selectedEpicIds.size > 0
|
|
2260
2768
|
}
|
|
2261
2769
|
) }),
|
|
2262
|
-
/* @__PURE__ */
|
|
2770
|
+
/* @__PURE__ */ jsx16(Box16, { flexGrow: 1, children: previewTask ? /* @__PURE__ */ jsx16(
|
|
2263
2771
|
TaskDetail,
|
|
2264
2772
|
{
|
|
2265
2773
|
task: previewTask,
|
|
@@ -2267,36 +2775,38 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2267
2775
|
dependents: state.depDependents,
|
|
2268
2776
|
related: state.depRelated,
|
|
2269
2777
|
duplicates: state.depDuplicates,
|
|
2270
|
-
isFocused: state.focusedPanel === "detail"
|
|
2778
|
+
isFocused: state.focusedPanel === "detail",
|
|
2779
|
+
scrollOffset: state.detailScrollOffset
|
|
2271
2780
|
}
|
|
2272
|
-
) : /* @__PURE__ */
|
|
2273
|
-
|
|
2781
|
+
) : /* @__PURE__ */ jsxs14(
|
|
2782
|
+
Box16,
|
|
2274
2783
|
{
|
|
2275
2784
|
flexDirection: "column",
|
|
2276
2785
|
flexGrow: 1,
|
|
2277
2786
|
borderStyle: "bold",
|
|
2278
2787
|
borderColor: theme.border,
|
|
2279
2788
|
children: [
|
|
2280
|
-
/* @__PURE__ */
|
|
2789
|
+
/* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs14(Text16, { color: theme.title, bold: true, children: [
|
|
2281
2790
|
" ",
|
|
2282
2791
|
"detail"
|
|
2283
2792
|
] }) }),
|
|
2284
|
-
/* @__PURE__ */
|
|
2793
|
+
/* @__PURE__ */ jsx16(Box16, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "No task selected" }) })
|
|
2285
2794
|
]
|
|
2286
2795
|
}
|
|
2287
2796
|
) })
|
|
2288
2797
|
] }),
|
|
2289
|
-
!state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */
|
|
2798
|
+
!state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx16(
|
|
2290
2799
|
TaskDetail,
|
|
2291
2800
|
{
|
|
2292
2801
|
task: state.selectedTask,
|
|
2293
2802
|
blockers: state.depBlockers,
|
|
2294
2803
|
dependents: state.depDependents,
|
|
2295
2804
|
related: state.depRelated,
|
|
2296
|
-
duplicates: state.depDuplicates
|
|
2805
|
+
duplicates: state.depDuplicates,
|
|
2806
|
+
scrollOffset: state.detailScrollOffset
|
|
2297
2807
|
}
|
|
2298
2808
|
),
|
|
2299
|
-
!state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */
|
|
2809
|
+
!state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx16(
|
|
2300
2810
|
DependencyList,
|
|
2301
2811
|
{
|
|
2302
2812
|
task: state.selectedTask,
|
|
@@ -2309,7 +2819,7 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2309
2819
|
addDepInput: state.addDepInput
|
|
2310
2820
|
}
|
|
2311
2821
|
),
|
|
2312
|
-
!state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */
|
|
2822
|
+
!state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */ jsx16(
|
|
2313
2823
|
TaskForm,
|
|
2314
2824
|
{
|
|
2315
2825
|
editingTask: state.activeView === ViewType.TaskEdit ? state.selectedTask : null,
|
|
@@ -2319,7 +2829,16 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2319
2829
|
onCancel: handleFormCancel
|
|
2320
2830
|
}
|
|
2321
2831
|
),
|
|
2322
|
-
!state.confirmDelete && state.activeView === ViewType.
|
|
2832
|
+
!state.confirmDelete && state.activeView === ViewType.EpicPicker && state.selectedTask && /* @__PURE__ */ jsx16(
|
|
2833
|
+
EpicPicker,
|
|
2834
|
+
{
|
|
2835
|
+
epics: state.epics,
|
|
2836
|
+
currentEpicId: state.selectedTask.parentId,
|
|
2837
|
+
onSelect: handleEpicPickerSelect,
|
|
2838
|
+
onCancel: handleEpicPickerCancel
|
|
2839
|
+
}
|
|
2840
|
+
),
|
|
2841
|
+
!state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx16(
|
|
2323
2842
|
ProjectSelector,
|
|
2324
2843
|
{
|
|
2325
2844
|
projects: state.projects,
|
|
@@ -2330,18 +2849,18 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2330
2849
|
onCancel: handleProjectCancel
|
|
2331
2850
|
}
|
|
2332
2851
|
),
|
|
2333
|
-
!state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */
|
|
2334
|
-
!state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */
|
|
2852
|
+
!state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */ jsx16(ProjectForm, { onSave: handleProjectFormSave, onCancel: handleProjectFormCancel }),
|
|
2853
|
+
!state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx16(HelpOverlay, {})
|
|
2335
2854
|
] }),
|
|
2336
|
-
/* @__PURE__ */
|
|
2337
|
-
state.flash && /* @__PURE__ */
|
|
2855
|
+
/* @__PURE__ */ jsx16(Crumbs, { breadcrumbs: state.breadcrumbs }),
|
|
2856
|
+
state.flash && /* @__PURE__ */ jsx16(FlashMessage, { message: state.flash.message, level: state.flash.level })
|
|
2338
2857
|
] });
|
|
2339
2858
|
}
|
|
2340
2859
|
|
|
2341
2860
|
// src/tui/index.tsx
|
|
2342
|
-
import { jsx as
|
|
2861
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2343
2862
|
async function launchTUI(container, initialProject) {
|
|
2344
|
-
const instance = render(/* @__PURE__ */
|
|
2863
|
+
const instance = render(/* @__PURE__ */ jsx17(App, { container, initialProject }), {
|
|
2345
2864
|
exitOnCtrlC: true
|
|
2346
2865
|
});
|
|
2347
2866
|
await instance.waitUntilExit();
|
|
@@ -2349,4 +2868,4 @@ async function launchTUI(container, initialProject) {
|
|
|
2349
2868
|
export {
|
|
2350
2869
|
launchTUI
|
|
2351
2870
|
};
|
|
2352
|
-
//# sourceMappingURL=tui-
|
|
2871
|
+
//# sourceMappingURL=tui-FTXYP3HM.js.map
|