@tomkapa/tayto 0.1.2 → 0.3.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 +252 -33
- package/dist/index.js.map +1 -1
- package/dist/migrations/004_remove_check_constraints.sql +63 -0
- package/dist/{tui-JNZRBEIQ.js → tui-5JJH67YY.js} +888 -294
- package/dist/tui-5JJH67YY.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
|
-
import { useReducer, useEffect, useCallback as useCallback2, useMemo } from "react";
|
|
16
|
-
import { Box as
|
|
16
|
+
import { useReducer, useEffect as useEffect2, useCallback as useCallback2, useMemo as useMemo2, useState as useState6 } from "react";
|
|
17
|
+
import { Box as Box16, Text as Text16, useInput as useInput6, useApp, useStdout as useStdout4 } from "ink";
|
|
17
18
|
|
|
18
19
|
// src/tui/types.ts
|
|
19
20
|
var ViewType = {
|
|
@@ -24,9 +25,144 @@ 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
|
|
|
32
|
+
// src/tui/theme.ts
|
|
33
|
+
var theme = {
|
|
34
|
+
// Body
|
|
35
|
+
fg: "#1E90FF",
|
|
36
|
+
// dodgerblue
|
|
37
|
+
bg: "black",
|
|
38
|
+
logo: "#FFA500",
|
|
39
|
+
// orange
|
|
40
|
+
// Table
|
|
41
|
+
table: {
|
|
42
|
+
fg: "#00FFFF",
|
|
43
|
+
// aqua
|
|
44
|
+
cursorFg: "black",
|
|
45
|
+
cursorBg: "#00FFFF",
|
|
46
|
+
// aqua
|
|
47
|
+
headerFg: "white",
|
|
48
|
+
markColor: "#98FB98",
|
|
49
|
+
// palegreen
|
|
50
|
+
depHighlightBg: "#1A5276",
|
|
51
|
+
// dark steel blue – non-terminal dep rows
|
|
52
|
+
blockedCursorBg: "#C0392B"
|
|
53
|
+
// strong red – selected row blocked by non-terminal dep
|
|
54
|
+
},
|
|
55
|
+
// Status colors (row-level)
|
|
56
|
+
status: {
|
|
57
|
+
new: "#87CEFA",
|
|
58
|
+
// lightskyblue
|
|
59
|
+
modified: "#ADFF2F",
|
|
60
|
+
// greenyellow
|
|
61
|
+
added: "#1E90FF",
|
|
62
|
+
// dodgerblue
|
|
63
|
+
error: "#FF4500",
|
|
64
|
+
// orangered
|
|
65
|
+
pending: "#FF8C00",
|
|
66
|
+
// darkorange
|
|
67
|
+
highlight: "#00FFFF",
|
|
68
|
+
// aqua
|
|
69
|
+
kill: "#9370DB",
|
|
70
|
+
// mediumpurple
|
|
71
|
+
completed: "#778899"
|
|
72
|
+
// lightslategray
|
|
73
|
+
},
|
|
74
|
+
// Frame / borders
|
|
75
|
+
border: "#1E90FF",
|
|
76
|
+
// dodgerblue
|
|
77
|
+
borderFocus: "#00FFFF",
|
|
78
|
+
// aqua
|
|
79
|
+
// Title
|
|
80
|
+
title: "#00FFFF",
|
|
81
|
+
// aqua
|
|
82
|
+
titleHighlight: "#FF00FF",
|
|
83
|
+
// fuchsia
|
|
84
|
+
titleCounter: "#FFEFD5",
|
|
85
|
+
// papayawhip
|
|
86
|
+
titleFilter: "#2E8B57",
|
|
87
|
+
// seagreen
|
|
88
|
+
// Prompt
|
|
89
|
+
prompt: "#5F9EA0",
|
|
90
|
+
// cadetblue
|
|
91
|
+
promptSuggest: "#1E90FF",
|
|
92
|
+
// dodgerblue
|
|
93
|
+
// Dialog
|
|
94
|
+
dialog: {
|
|
95
|
+
fg: "#1E90FF",
|
|
96
|
+
// dodgerblue
|
|
97
|
+
buttonFg: "black",
|
|
98
|
+
buttonBg: "#1E90FF",
|
|
99
|
+
buttonFocusFg: "white",
|
|
100
|
+
buttonFocusBg: "#FF00FF",
|
|
101
|
+
// fuchsia
|
|
102
|
+
label: "#FF00FF",
|
|
103
|
+
// fuchsia
|
|
104
|
+
field: "#1E90FF"
|
|
105
|
+
},
|
|
106
|
+
// Detail/YAML
|
|
107
|
+
yaml: {
|
|
108
|
+
key: "#4682B4",
|
|
109
|
+
// steelblue
|
|
110
|
+
colon: "white",
|
|
111
|
+
value: "#FFEFD5"
|
|
112
|
+
// papayawhip
|
|
113
|
+
},
|
|
114
|
+
// Crumbs
|
|
115
|
+
crumb: {
|
|
116
|
+
fg: "black",
|
|
117
|
+
bg: "#4682B4",
|
|
118
|
+
// steelblue
|
|
119
|
+
activeBg: "#FFA500"
|
|
120
|
+
// orange
|
|
121
|
+
},
|
|
122
|
+
// Flash
|
|
123
|
+
flash: {
|
|
124
|
+
info: "#FFDEAD",
|
|
125
|
+
// navajowhite
|
|
126
|
+
warn: "#FFA500",
|
|
127
|
+
// orange
|
|
128
|
+
error: "#FF4500"
|
|
129
|
+
// orangered
|
|
130
|
+
},
|
|
131
|
+
// Menu / key hints
|
|
132
|
+
menu: {
|
|
133
|
+
key: "#1E90FF",
|
|
134
|
+
// dodgerblue
|
|
135
|
+
numKey: "#FF00FF",
|
|
136
|
+
// fuchsia
|
|
137
|
+
desc: "white"
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/tui/constants.ts
|
|
142
|
+
var STATUS_VALUES = Object.values(TaskStatus);
|
|
143
|
+
var TYPE_VALUES = Object.values(TaskType);
|
|
144
|
+
var PAGE_SIZE = 20;
|
|
145
|
+
var STATUS_COLOR = {
|
|
146
|
+
[TaskStatus.Backlog]: theme.status.completed,
|
|
147
|
+
[TaskStatus.Todo]: theme.status.new,
|
|
148
|
+
[TaskStatus.InProgress]: theme.status.pending,
|
|
149
|
+
[TaskStatus.Review]: theme.status.modified,
|
|
150
|
+
[TaskStatus.Done]: theme.status.added,
|
|
151
|
+
[TaskStatus.Cancelled]: theme.status.kill
|
|
152
|
+
};
|
|
153
|
+
var TYPE_COLOR = {
|
|
154
|
+
[TaskType.Epic]: theme.status.modified,
|
|
155
|
+
[TaskType.Story]: theme.status.highlight,
|
|
156
|
+
[TaskType.TechDebt]: theme.status.pending,
|
|
157
|
+
[TaskType.Bug]: theme.status.error
|
|
158
|
+
};
|
|
159
|
+
var DEP_TYPE_LABEL = {
|
|
160
|
+
[DependencyType.Blocks]: "blocks",
|
|
161
|
+
[DependencyType.RelatesTo]: "relates-to",
|
|
162
|
+
[DependencyType.Duplicates]: "duplicates",
|
|
163
|
+
[UIDependencyType.BlockedBy]: "blocked-by"
|
|
164
|
+
};
|
|
165
|
+
|
|
30
166
|
// src/tui/state.ts
|
|
31
167
|
var initialState = {
|
|
32
168
|
activeView: ViewType.TaskList,
|
|
@@ -51,7 +187,13 @@ var initialState = {
|
|
|
51
187
|
depSelectedIndex: 0,
|
|
52
188
|
isAddingDep: false,
|
|
53
189
|
addDepInput: "",
|
|
54
|
-
focusedPanel: "list"
|
|
190
|
+
focusedPanel: "list",
|
|
191
|
+
detailScrollOffset: 0,
|
|
192
|
+
epics: [],
|
|
193
|
+
epicSelectedIndex: 0,
|
|
194
|
+
selectedEpicIds: /* @__PURE__ */ new Set(),
|
|
195
|
+
isEpicReordering: false,
|
|
196
|
+
epicReorderSnapshot: null
|
|
55
197
|
};
|
|
56
198
|
function appReducer(state, action) {
|
|
57
199
|
switch (action.type) {
|
|
@@ -111,14 +253,19 @@ function appReducer(state, action) {
|
|
|
111
253
|
case "MOVE_CURSOR": {
|
|
112
254
|
const maxIndex = Math.max(0, state.tasks.length - 1);
|
|
113
255
|
const newIndex = action.direction === "up" ? Math.max(0, state.selectedIndex - 1) : Math.min(maxIndex, state.selectedIndex + 1);
|
|
114
|
-
return { ...state, selectedIndex: newIndex };
|
|
256
|
+
return { ...state, selectedIndex: newIndex, detailScrollOffset: 0 };
|
|
257
|
+
}
|
|
258
|
+
case "PAGE_CURSOR": {
|
|
259
|
+
const maxIndex = Math.max(0, state.tasks.length - 1);
|
|
260
|
+
const newIndex = action.direction === "up" ? Math.max(0, state.selectedIndex - PAGE_SIZE) : Math.min(maxIndex, state.selectedIndex + PAGE_SIZE);
|
|
261
|
+
return { ...state, selectedIndex: newIndex, detailScrollOffset: 0 };
|
|
115
262
|
}
|
|
116
263
|
case "SET_CURSOR": {
|
|
117
264
|
const maxIndex = Math.max(0, state.tasks.length - 1);
|
|
118
265
|
return { ...state, selectedIndex: Math.max(0, Math.min(action.index, maxIndex)) };
|
|
119
266
|
}
|
|
120
267
|
case "SELECT_TASK":
|
|
121
|
-
return { ...state, selectedTask: action.task };
|
|
268
|
+
return { ...state, selectedTask: action.task, detailScrollOffset: 0 };
|
|
122
269
|
case "SET_FORM_DATA":
|
|
123
270
|
return { ...state, formData: action.data };
|
|
124
271
|
case "ENTER_REORDER":
|
|
@@ -181,151 +328,98 @@ function appReducer(state, action) {
|
|
|
181
328
|
return { ...state, addDepInput: action.input };
|
|
182
329
|
case "SET_PANEL_FOCUS":
|
|
183
330
|
return { ...state, focusedPanel: action.panel };
|
|
331
|
+
case "DETAIL_SCROLL":
|
|
332
|
+
return {
|
|
333
|
+
...state,
|
|
334
|
+
detailScrollOffset: action.direction === "up" ? Math.max(0, state.detailScrollOffset - 1) : state.detailScrollOffset + 1
|
|
335
|
+
};
|
|
336
|
+
case "DETAIL_RESET_SCROLL":
|
|
337
|
+
return { ...state, detailScrollOffset: 0 };
|
|
338
|
+
case "SET_EPICS":
|
|
339
|
+
return {
|
|
340
|
+
...state,
|
|
341
|
+
epics: action.epics,
|
|
342
|
+
epicSelectedIndex: Math.min(state.epicSelectedIndex, Math.max(0, action.epics.length - 1))
|
|
343
|
+
};
|
|
344
|
+
case "EPIC_MOVE_CURSOR": {
|
|
345
|
+
if (state.epics.length === 0) return state;
|
|
346
|
+
const maxIdx = Math.max(0, state.epics.length - 1);
|
|
347
|
+
const newIdx = action.direction === "up" ? Math.max(0, state.epicSelectedIndex - 1) : Math.min(maxIdx, state.epicSelectedIndex + 1);
|
|
348
|
+
return { ...state, epicSelectedIndex: newIdx };
|
|
349
|
+
}
|
|
350
|
+
case "TOGGLE_EPIC": {
|
|
351
|
+
const next = new Set(state.selectedEpicIds);
|
|
352
|
+
if (next.has(action.epicId)) {
|
|
353
|
+
next.delete(action.epicId);
|
|
354
|
+
} else {
|
|
355
|
+
next.add(action.epicId);
|
|
356
|
+
}
|
|
357
|
+
return { ...state, selectedEpicIds: next, selectedIndex: 0 };
|
|
358
|
+
}
|
|
359
|
+
case "CLEAR_EPIC_SELECTION":
|
|
360
|
+
return { ...state, selectedEpicIds: /* @__PURE__ */ new Set(), selectedIndex: 0 };
|
|
361
|
+
case "ENTER_EPIC_REORDER":
|
|
362
|
+
return {
|
|
363
|
+
...state,
|
|
364
|
+
isEpicReordering: true,
|
|
365
|
+
epicReorderSnapshot: [...state.epics]
|
|
366
|
+
};
|
|
367
|
+
case "EPIC_REORDER_MOVE": {
|
|
368
|
+
if (!state.isEpicReordering) return state;
|
|
369
|
+
const idx = state.epicSelectedIndex;
|
|
370
|
+
const epics = [...state.epics];
|
|
371
|
+
const swapIdx = action.direction === "up" ? idx - 1 : idx + 1;
|
|
372
|
+
if (swapIdx < 0 || swapIdx >= epics.length) return state;
|
|
373
|
+
const current = epics[idx];
|
|
374
|
+
const swap = epics[swapIdx];
|
|
375
|
+
if (!current || !swap) return state;
|
|
376
|
+
epics[idx] = swap;
|
|
377
|
+
epics[swapIdx] = current;
|
|
378
|
+
return {
|
|
379
|
+
...state,
|
|
380
|
+
epics,
|
|
381
|
+
epicSelectedIndex: swapIdx
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
case "EXIT_EPIC_REORDER": {
|
|
385
|
+
if (!action.save && state.epicReorderSnapshot) {
|
|
386
|
+
return {
|
|
387
|
+
...state,
|
|
388
|
+
isEpicReordering: false,
|
|
389
|
+
epics: state.epicReorderSnapshot,
|
|
390
|
+
epicReorderSnapshot: null
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
...state,
|
|
395
|
+
isEpicReordering: false,
|
|
396
|
+
epicReorderSnapshot: null
|
|
397
|
+
};
|
|
398
|
+
}
|
|
184
399
|
}
|
|
185
400
|
}
|
|
186
401
|
|
|
187
|
-
// src/tui/theme.ts
|
|
188
|
-
var theme = {
|
|
189
|
-
// Body
|
|
190
|
-
fg: "#1E90FF",
|
|
191
|
-
// dodgerblue
|
|
192
|
-
bg: "black",
|
|
193
|
-
logo: "#FFA500",
|
|
194
|
-
// orange
|
|
195
|
-
// Table
|
|
196
|
-
table: {
|
|
197
|
-
fg: "#00FFFF",
|
|
198
|
-
// aqua
|
|
199
|
-
cursorFg: "black",
|
|
200
|
-
cursorBg: "#00FFFF",
|
|
201
|
-
// aqua
|
|
202
|
-
headerFg: "white",
|
|
203
|
-
markColor: "#98FB98",
|
|
204
|
-
// palegreen
|
|
205
|
-
depHighlightBg: "#1A5276",
|
|
206
|
-
// dark steel blue – non-terminal dep rows
|
|
207
|
-
blockedCursorBg: "#C0392B"
|
|
208
|
-
// strong red – selected row blocked by non-terminal dep
|
|
209
|
-
},
|
|
210
|
-
// Status colors (row-level)
|
|
211
|
-
status: {
|
|
212
|
-
new: "#87CEFA",
|
|
213
|
-
// lightskyblue
|
|
214
|
-
modified: "#ADFF2F",
|
|
215
|
-
// greenyellow
|
|
216
|
-
added: "#1E90FF",
|
|
217
|
-
// dodgerblue
|
|
218
|
-
error: "#FF4500",
|
|
219
|
-
// orangered
|
|
220
|
-
pending: "#FF8C00",
|
|
221
|
-
// darkorange
|
|
222
|
-
highlight: "#00FFFF",
|
|
223
|
-
// aqua
|
|
224
|
-
kill: "#9370DB",
|
|
225
|
-
// mediumpurple
|
|
226
|
-
completed: "#778899"
|
|
227
|
-
// lightslategray
|
|
228
|
-
},
|
|
229
|
-
// Frame / borders
|
|
230
|
-
border: "#1E90FF",
|
|
231
|
-
// dodgerblue
|
|
232
|
-
borderFocus: "#00FFFF",
|
|
233
|
-
// aqua
|
|
234
|
-
// Title
|
|
235
|
-
title: "#00FFFF",
|
|
236
|
-
// aqua
|
|
237
|
-
titleHighlight: "#FF00FF",
|
|
238
|
-
// fuchsia
|
|
239
|
-
titleCounter: "#FFEFD5",
|
|
240
|
-
// papayawhip
|
|
241
|
-
titleFilter: "#2E8B57",
|
|
242
|
-
// seagreen
|
|
243
|
-
// Prompt
|
|
244
|
-
prompt: "#5F9EA0",
|
|
245
|
-
// cadetblue
|
|
246
|
-
promptSuggest: "#1E90FF",
|
|
247
|
-
// dodgerblue
|
|
248
|
-
// Dialog
|
|
249
|
-
dialog: {
|
|
250
|
-
fg: "#1E90FF",
|
|
251
|
-
// dodgerblue
|
|
252
|
-
buttonFg: "black",
|
|
253
|
-
buttonBg: "#1E90FF",
|
|
254
|
-
buttonFocusFg: "white",
|
|
255
|
-
buttonFocusBg: "#FF00FF",
|
|
256
|
-
// fuchsia
|
|
257
|
-
label: "#FF00FF",
|
|
258
|
-
// fuchsia
|
|
259
|
-
field: "#1E90FF"
|
|
260
|
-
},
|
|
261
|
-
// Detail/YAML
|
|
262
|
-
yaml: {
|
|
263
|
-
key: "#4682B4",
|
|
264
|
-
// steelblue
|
|
265
|
-
colon: "white",
|
|
266
|
-
value: "#FFEFD5"
|
|
267
|
-
// papayawhip
|
|
268
|
-
},
|
|
269
|
-
// Crumbs
|
|
270
|
-
crumb: {
|
|
271
|
-
fg: "black",
|
|
272
|
-
bg: "#4682B4",
|
|
273
|
-
// steelblue
|
|
274
|
-
activeBg: "#FFA500"
|
|
275
|
-
// orange
|
|
276
|
-
},
|
|
277
|
-
// Flash
|
|
278
|
-
flash: {
|
|
279
|
-
info: "#FFDEAD",
|
|
280
|
-
// navajowhite
|
|
281
|
-
warn: "#FFA500",
|
|
282
|
-
// orange
|
|
283
|
-
error: "#FF4500"
|
|
284
|
-
// orangered
|
|
285
|
-
},
|
|
286
|
-
// Menu / key hints
|
|
287
|
-
menu: {
|
|
288
|
-
key: "#1E90FF",
|
|
289
|
-
// dodgerblue
|
|
290
|
-
numKey: "#FF00FF",
|
|
291
|
-
// fuchsia
|
|
292
|
-
desc: "white"
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
// src/tui/constants.ts
|
|
297
|
-
var STATUS_VALUES = Object.values(TaskStatus);
|
|
298
|
-
var TYPE_VALUES = Object.values(TaskType);
|
|
299
|
-
var STATUS_COLOR = {
|
|
300
|
-
[TaskStatus.Backlog]: theme.status.completed,
|
|
301
|
-
[TaskStatus.Todo]: theme.status.new,
|
|
302
|
-
[TaskStatus.InProgress]: theme.status.pending,
|
|
303
|
-
[TaskStatus.Review]: theme.status.modified,
|
|
304
|
-
[TaskStatus.Done]: theme.status.added,
|
|
305
|
-
[TaskStatus.Cancelled]: theme.status.kill
|
|
306
|
-
};
|
|
307
|
-
var TYPE_COLOR = {
|
|
308
|
-
[TaskType.Story]: theme.status.highlight,
|
|
309
|
-
[TaskType.TechDebt]: theme.status.pending,
|
|
310
|
-
[TaskType.Bug]: theme.status.error
|
|
311
|
-
};
|
|
312
|
-
var DEP_TYPE_LABEL = {
|
|
313
|
-
[DependencyType.Blocks]: "blocks",
|
|
314
|
-
[DependencyType.RelatesTo]: "relates-to",
|
|
315
|
-
[DependencyType.Duplicates]: "duplicates",
|
|
316
|
-
[UIDependencyType.BlockedBy]: "blocked-by"
|
|
317
|
-
};
|
|
318
|
-
|
|
319
402
|
// src/tui/components/Header.tsx
|
|
320
403
|
import { Box, Text } from "ink";
|
|
321
404
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
322
|
-
function getKeyHints(view, isSearchActive) {
|
|
405
|
+
function getKeyHints(view, isSearchActive, focusedPanel) {
|
|
323
406
|
if (isSearchActive) {
|
|
324
407
|
return [
|
|
325
408
|
{ key: "enter", desc: "apply" },
|
|
326
409
|
{ key: "esc", desc: "cancel" }
|
|
327
410
|
];
|
|
328
411
|
}
|
|
412
|
+
if (view === "task-list" && focusedPanel === "epic") {
|
|
413
|
+
return [
|
|
414
|
+
{ key: "j/k", desc: "nav" },
|
|
415
|
+
{ key: "space", desc: "toggle" },
|
|
416
|
+
{ key: "\u2190", desc: "reorder" },
|
|
417
|
+
{ key: "0", desc: "clear" },
|
|
418
|
+
{ key: "tab", desc: "tasks" },
|
|
419
|
+
{ key: "?", desc: "help" },
|
|
420
|
+
{ key: "q", desc: "quit" }
|
|
421
|
+
];
|
|
422
|
+
}
|
|
329
423
|
if (view === "task-list") {
|
|
330
424
|
return [
|
|
331
425
|
{ key: "enter", desc: "view" },
|
|
@@ -333,11 +427,15 @@ function getKeyHints(view, isSearchActive) {
|
|
|
333
427
|
{ key: "e", desc: "edit" },
|
|
334
428
|
{ key: "d", desc: "del" },
|
|
335
429
|
{ key: "s", desc: "status" },
|
|
430
|
+
{ key: "a", desc: "assign" },
|
|
431
|
+
{ key: "A", desc: "unassign" },
|
|
336
432
|
{ key: "\u2190", desc: "reorder" },
|
|
337
433
|
{ key: "/", desc: "search" },
|
|
338
434
|
{ key: "p", desc: "project" },
|
|
339
435
|
{ key: "f", desc: "status-f" },
|
|
340
436
|
{ key: "t", desc: "type-f" },
|
|
437
|
+
{ key: "PgDn/Up", desc: "page" },
|
|
438
|
+
{ key: "tab", desc: "panel" },
|
|
341
439
|
{ key: "?", desc: "help" },
|
|
342
440
|
{ key: "q", desc: "quit" }
|
|
343
441
|
];
|
|
@@ -362,10 +460,10 @@ function getKeyHints(view, isSearchActive) {
|
|
|
362
460
|
function Header({ state }) {
|
|
363
461
|
const projectName = state.activeProject?.name ?? "none";
|
|
364
462
|
const taskCount = state.tasks.length;
|
|
365
|
-
const hints = getKeyHints(state.activeView, state.isSearchActive);
|
|
463
|
+
const hints = getKeyHints(state.activeView, state.isSearchActive, state.focusedPanel);
|
|
366
464
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
367
465
|
/* @__PURE__ */ jsxs(Box, { gap: 2, children: [
|
|
368
|
-
/* @__PURE__ */ jsx(Text, { color: theme.logo, bold: true, children: "
|
|
466
|
+
/* @__PURE__ */ jsx(Text, { color: theme.logo, bold: true, children: "Tayto" }),
|
|
369
467
|
/* @__PURE__ */ jsx(Text, { color: theme.logo, children: "Project:" }),
|
|
370
468
|
/* @__PURE__ */ jsx(Text, { color: "white", bold: true, children: projectName }),
|
|
371
469
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
|
|
@@ -426,7 +524,6 @@ function FlashMessage({ message, level }) {
|
|
|
426
524
|
// src/tui/components/TaskList.tsx
|
|
427
525
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
428
526
|
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
429
|
-
var PAGE_SIZE = 20;
|
|
430
527
|
var COL = {
|
|
431
528
|
rank: 5,
|
|
432
529
|
type: 12,
|
|
@@ -443,7 +540,8 @@ function TaskList({
|
|
|
443
540
|
nonTerminalBlockerIds,
|
|
444
541
|
nonTerminalDependentIds,
|
|
445
542
|
isSelectedBlocked,
|
|
446
|
-
isFocused = true
|
|
543
|
+
isFocused = true,
|
|
544
|
+
epicFilterActive = false
|
|
447
545
|
}) {
|
|
448
546
|
const currentPage = Math.floor(selectedIndex / PAGE_SIZE);
|
|
449
547
|
const viewStart = currentPage * PAGE_SIZE;
|
|
@@ -478,6 +576,7 @@ function TaskList({
|
|
|
478
576
|
" ",
|
|
479
577
|
"REORDER"
|
|
480
578
|
] }),
|
|
579
|
+
epicFilterActive && /* @__PURE__ */ jsx4(Text4, { color: theme.titleHighlight, children: " [epic]" }),
|
|
481
580
|
filterText && /* @__PURE__ */ jsxs2(Text4, { color: theme.titleFilter, children: [
|
|
482
581
|
" /",
|
|
483
582
|
filterText
|
|
@@ -495,7 +594,7 @@ function TaskList({
|
|
|
495
594
|
/* @__PURE__ */ jsx4(Text4, { color: theme.table.headerFg, bold: true, children: "STATUS".padEnd(COL.status) }),
|
|
496
595
|
/* @__PURE__ */ jsx4(Text4, { color: theme.table.headerFg, bold: true, children: "NAME" })
|
|
497
596
|
] }),
|
|
498
|
-
tasks.length === 0 ? /* @__PURE__ */ jsx4(Box4, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.fg, children: "No tasks found. Press 'c' to create one." }) }) : visibleTasks.map((task, i) => {
|
|
597
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: tasks.length === 0 ? /* @__PURE__ */ jsx4(Box4, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.fg, children: "No tasks found. Press 'c' to create one." }) }) : visibleTasks.map((task, i) => {
|
|
499
598
|
const actualIndex = viewStart + i;
|
|
500
599
|
const isSelected = actualIndex === selectedIndex;
|
|
501
600
|
const isNonTerminalBlocker = nonTerminalBlockerIds.has(task.id);
|
|
@@ -529,8 +628,7 @@ function TaskList({
|
|
|
529
628
|
/* @__PURE__ */ jsx4(Text4, { color: STATUS_COLOR[task.status] ?? rowColor, children: task.status.padEnd(COL.status) }),
|
|
530
629
|
/* @__PURE__ */ jsx4(Text4, { color: rowColor, children: task.name })
|
|
531
630
|
] }, task.id);
|
|
532
|
-
}),
|
|
533
|
-
/* @__PURE__ */ jsx4(Box4, { flexGrow: 1 }),
|
|
631
|
+
}) }),
|
|
534
632
|
tasks.length > PAGE_SIZE && /* @__PURE__ */ jsx4(Box4, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs2(Text4, { dimColor: true, children: [
|
|
535
633
|
"[",
|
|
536
634
|
viewStart + 1,
|
|
@@ -546,7 +644,9 @@ function TaskList({
|
|
|
546
644
|
}
|
|
547
645
|
|
|
548
646
|
// src/tui/components/TaskDetail.tsx
|
|
549
|
-
import {
|
|
647
|
+
import { useMemo } from "react";
|
|
648
|
+
import { Box as Box6, Text as Text6, useStdout } from "ink";
|
|
649
|
+
import chalk2 from "chalk";
|
|
550
650
|
|
|
551
651
|
// src/tui/components/Markdown.tsx
|
|
552
652
|
import { Text as Text5, Box as Box5 } from "ink";
|
|
@@ -638,15 +738,10 @@ function createRenderer() {
|
|
|
638
738
|
);
|
|
639
739
|
}
|
|
640
740
|
var marked = createRenderer();
|
|
641
|
-
function
|
|
642
|
-
if (!content.trim())
|
|
643
|
-
return /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No content" });
|
|
644
|
-
}
|
|
741
|
+
function renderMarkdown(content) {
|
|
742
|
+
if (!content.trim()) return "";
|
|
645
743
|
const rendered = marked.parse(content);
|
|
646
|
-
|
|
647
|
-
return /* @__PURE__ */ jsx5(Text5, { children: content });
|
|
648
|
-
}
|
|
649
|
-
return /* @__PURE__ */ jsx5(Text5, { children: rendered.trimEnd() });
|
|
744
|
+
return typeof rendered === "string" ? rendered.trimEnd() : content;
|
|
650
745
|
}
|
|
651
746
|
function MermaidHint({ content }) {
|
|
652
747
|
const blocks = extractMermaidBlocks(content);
|
|
@@ -675,93 +770,109 @@ function openAllMermaidDiagrams(content) {
|
|
|
675
770
|
|
|
676
771
|
// src/tui/components/TaskDetail.tsx
|
|
677
772
|
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
773
|
+
var PAD = " ";
|
|
774
|
+
function field(label, value) {
|
|
775
|
+
return PAD + chalk2.hex(theme.yaml.key).bold(label) + chalk2.hex(theme.yaml.colon)(": ") + chalk2.hex(theme.yaml.value)(value);
|
|
776
|
+
}
|
|
777
|
+
function sectionHeader(title) {
|
|
778
|
+
return PAD + chalk2.hex(theme.title).bold(`--- ${title} ---`);
|
|
779
|
+
}
|
|
780
|
+
function markdownLines(content) {
|
|
781
|
+
const rendered = renderMarkdown(content);
|
|
782
|
+
if (!rendered) return [];
|
|
783
|
+
return rendered.split("\n").map((line) => PAD + line);
|
|
684
784
|
}
|
|
785
|
+
function buildContentLines(task, blockers, dependents, related, duplicates) {
|
|
786
|
+
const lines = [];
|
|
787
|
+
lines.push(field("id", task.id));
|
|
788
|
+
lines.push(field("type", task.type));
|
|
789
|
+
lines.push(field("status", task.status));
|
|
790
|
+
lines.push(field("created", new Date(task.createdAt).toLocaleString()));
|
|
791
|
+
lines.push(field("updated", new Date(task.updatedAt).toLocaleString()));
|
|
792
|
+
if (task.parentId) lines.push(field("parent", task.parentId));
|
|
793
|
+
const hasDeps = blockers.length > 0 || dependents.length > 0 || related.length > 0 || duplicates.length > 0;
|
|
794
|
+
if (hasDeps) {
|
|
795
|
+
lines.push(sectionHeader("dependencies"));
|
|
796
|
+
if (blockers.length > 0) {
|
|
797
|
+
lines.push(
|
|
798
|
+
PAD + chalk2.hex(theme.status.error).bold("blocked by: ") + chalk2.hex(theme.yaml.value)(blockers.map((t) => t.id).join(", "))
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
if (dependents.length > 0) {
|
|
802
|
+
lines.push(
|
|
803
|
+
PAD + chalk2.hex(theme.status.added).bold("blocks: ") + chalk2.hex(theme.yaml.value)(dependents.map((t) => t.id).join(", "))
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
if (related.length > 0) {
|
|
807
|
+
lines.push(
|
|
808
|
+
PAD + chalk2.hex(theme.status.new).bold("relates to: ") + chalk2.hex(theme.yaml.value)(related.map((t) => t.id).join(", "))
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
if (duplicates.length > 0) {
|
|
812
|
+
lines.push(
|
|
813
|
+
PAD + chalk2.hex(theme.status.pending).bold("duplicates: ") + chalk2.hex(theme.yaml.value)(duplicates.map((t) => t.id).join(", "))
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
lines.push(PAD + chalk2.dim("press D to manage dependencies"));
|
|
817
|
+
}
|
|
818
|
+
lines.push(sectionHeader("description"));
|
|
819
|
+
if (task.description.trim()) {
|
|
820
|
+
lines.push(...markdownLines(task.description));
|
|
821
|
+
} else {
|
|
822
|
+
lines.push(PAD + chalk2.dim("No description"));
|
|
823
|
+
}
|
|
824
|
+
if (task.technicalNotes.trim()) {
|
|
825
|
+
lines.push(sectionHeader("technical notes"));
|
|
826
|
+
lines.push(...markdownLines(task.technicalNotes));
|
|
827
|
+
}
|
|
828
|
+
if (task.additionalRequirements.trim()) {
|
|
829
|
+
lines.push(sectionHeader("requirements"));
|
|
830
|
+
lines.push(...markdownLines(task.additionalRequirements));
|
|
831
|
+
}
|
|
832
|
+
return lines;
|
|
833
|
+
}
|
|
834
|
+
var CHROME_LINES = 8;
|
|
685
835
|
function TaskDetail({
|
|
686
836
|
task,
|
|
687
|
-
blockers,
|
|
688
|
-
dependents,
|
|
689
|
-
related,
|
|
690
|
-
duplicates,
|
|
691
|
-
isFocused = true
|
|
837
|
+
blockers = [],
|
|
838
|
+
dependents = [],
|
|
839
|
+
related = [],
|
|
840
|
+
duplicates = [],
|
|
841
|
+
isFocused = true,
|
|
842
|
+
scrollOffset = 0
|
|
692
843
|
}) {
|
|
844
|
+
const { stdout } = useStdout();
|
|
693
845
|
const allText = `${task.description}
|
|
694
846
|
${task.technicalNotes}
|
|
695
847
|
${task.additionalRequirements}`;
|
|
848
|
+
const contentLines = useMemo(
|
|
849
|
+
() => buildContentLines(task, blockers, dependents, related, duplicates),
|
|
850
|
+
[task, blockers, dependents, related, duplicates]
|
|
851
|
+
);
|
|
852
|
+
const viewportHeight = Math.max(1, (stdout.rows > 0 ? stdout.rows : 24) - CHROME_LINES);
|
|
853
|
+
const visibleLines = contentLines.slice(scrollOffset, scrollOffset + viewportHeight);
|
|
696
854
|
return /* @__PURE__ */ jsxs4(
|
|
697
855
|
Box6,
|
|
698
856
|
{
|
|
699
857
|
flexDirection: "column",
|
|
700
858
|
flexGrow: 1,
|
|
701
859
|
borderStyle: "bold",
|
|
702
|
-
borderColor: isFocused ? theme.borderFocus : theme.border,
|
|
703
|
-
children: [
|
|
704
|
-
/* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
705
|
-
/* @__PURE__ */ jsxs4(Text6, { color: theme.title, bold: true, children: [
|
|
706
|
-
" ",
|
|
707
|
-
"detail"
|
|
708
|
-
] }),
|
|
709
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.fg, children: "(" }),
|
|
710
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.titleHighlight, bold: true, children: task.name }),
|
|
711
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.fg, children: ")" })
|
|
712
|
-
] }),
|
|
713
|
-
/* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", paddingX: 1, paddingY: 0, children: [
|
|
714
|
-
/* @__PURE__ */ jsx6(Field, { label: "id", value: task.id }),
|
|
715
|
-
/* @__PURE__ */ jsx6(Field, { label: "type", value: task.type }),
|
|
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 })
|
|
720
|
-
] }),
|
|
721
|
-
(blockers && blockers.length > 0 || dependents && dependents.length > 0 || related && related.length > 0 || duplicates && duplicates.length > 0) && /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
722
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.title, bold: true, children: "--- dependencies ---" }),
|
|
723
|
-
blockers && blockers.length > 0 && /* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
724
|
-
/* @__PURE__ */ jsxs4(Text6, { color: theme.status.error, bold: true, children: [
|
|
725
|
-
"blocked by:",
|
|
726
|
-
" "
|
|
727
|
-
] }),
|
|
728
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.yaml.value, children: blockers.map((t) => t.id).join(", ") })
|
|
729
|
-
] }),
|
|
730
|
-
dependents && dependents.length > 0 && /* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
731
|
-
/* @__PURE__ */ jsxs4(Text6, { color: theme.status.added, bold: true, children: [
|
|
732
|
-
"blocks:",
|
|
733
|
-
" "
|
|
734
|
-
] }),
|
|
735
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.yaml.value, children: dependents.map((t) => t.id).join(", ") })
|
|
736
|
-
] }),
|
|
737
|
-
related && related.length > 0 && /* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
738
|
-
/* @__PURE__ */ jsxs4(Text6, { color: theme.status.new, bold: true, children: [
|
|
739
|
-
"relates to:",
|
|
740
|
-
" "
|
|
741
|
-
] }),
|
|
742
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.yaml.value, children: related.map((t) => t.id).join(", ") })
|
|
743
|
-
] }),
|
|
744
|
-
duplicates && duplicates.length > 0 && /* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
745
|
-
/* @__PURE__ */ jsxs4(Text6, { color: theme.status.pending, bold: true, children: [
|
|
746
|
-
"duplicates:",
|
|
747
|
-
" "
|
|
748
|
-
] }),
|
|
749
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.yaml.value, children: duplicates.map((t) => t.id).join(", ") })
|
|
860
|
+
borderColor: isFocused ? theme.borderFocus : theme.border,
|
|
861
|
+
children: [
|
|
862
|
+
/* @__PURE__ */ jsxs4(Box6, { gap: 0, children: [
|
|
863
|
+
/* @__PURE__ */ jsxs4(Text6, { color: theme.title, bold: true, children: [
|
|
864
|
+
" ",
|
|
865
|
+
"detail"
|
|
750
866
|
] }),
|
|
751
|
-
/* @__PURE__ */ jsx6(Text6, {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
/* @__PURE__ */
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.title, bold: true, children: "--- technical notes ---" }),
|
|
759
|
-
/* @__PURE__ */ jsx6(Markdown, { content: task.technicalNotes })
|
|
760
|
-
] }),
|
|
761
|
-
task.additionalRequirements.trim() && /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
762
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.title, bold: true, children: "--- requirements ---" }),
|
|
763
|
-
/* @__PURE__ */ jsx6(Markdown, { content: task.additionalRequirements })
|
|
867
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.fg, children: "(" }),
|
|
868
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.titleHighlight, bold: true, children: task.name }),
|
|
869
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.fg, children: ")" }),
|
|
870
|
+
scrollOffset > 0 && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
|
|
871
|
+
" \u2191",
|
|
872
|
+
scrollOffset
|
|
873
|
+
] })
|
|
764
874
|
] }),
|
|
875
|
+
/* @__PURE__ */ jsx6(Text6, { children: visibleLines.join("\n") }),
|
|
765
876
|
/* @__PURE__ */ jsx6(Box6, { flexGrow: 1 }),
|
|
766
877
|
/* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(MermaidHint, { content: allText }) })
|
|
767
878
|
]
|
|
@@ -808,7 +919,7 @@ function openInEditor(content, filename) {
|
|
|
808
919
|
|
|
809
920
|
// src/tui/components/TaskPicker.tsx
|
|
810
921
|
import { useState } from "react";
|
|
811
|
-
import { Box as Box7, Text as Text7, useInput, useStdout } from "ink";
|
|
922
|
+
import { Box as Box7, Text as Text7, useInput, useStdout as useStdout2 } from "ink";
|
|
812
923
|
import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
813
924
|
var DEP_TYPE_VALUES = Object.values(UIDependencyType);
|
|
814
925
|
var DEP_TYPE_COLOR = {
|
|
@@ -818,7 +929,7 @@ var DEP_TYPE_COLOR = {
|
|
|
818
929
|
[UIDependencyType.BlockedBy]: theme.status.modified
|
|
819
930
|
};
|
|
820
931
|
function TaskPicker({ tasks, excludeIds, initialSelection, onConfirm, onCancel }) {
|
|
821
|
-
const { stdout } =
|
|
932
|
+
const { stdout } = useStdout2();
|
|
822
933
|
const termHeight = stdout.rows > 0 ? stdout.rows : 24;
|
|
823
934
|
const maxVisible = Math.max(3, termHeight - 12);
|
|
824
935
|
const [searchQuery, setSearchQuery] = useState("");
|
|
@@ -1041,13 +1152,13 @@ function TaskForm({ editingTask, allTasks, initialDeps, onSave, onCancel }) {
|
|
|
1041
1152
|
const [pickedDeps, setPickedDeps] = useState2(initialDeps ?? []);
|
|
1042
1153
|
const currentField = FIELDS[focusIndex];
|
|
1043
1154
|
const launchEditor = useCallback(
|
|
1044
|
-
(
|
|
1155
|
+
(field2) => {
|
|
1045
1156
|
setEditorActive(true);
|
|
1046
1157
|
setTimeout(() => {
|
|
1047
|
-
const content = values[
|
|
1048
|
-
const result = openInEditor(content,
|
|
1158
|
+
const content = values[field2.key] ?? "";
|
|
1159
|
+
const result = openInEditor(content, field2.editorFilename ?? `${field2.key}.md`);
|
|
1049
1160
|
if (result !== null) {
|
|
1050
|
-
setValues((v) => ({ ...v, [
|
|
1161
|
+
setValues((v) => ({ ...v, [field2.key]: result }));
|
|
1051
1162
|
}
|
|
1052
1163
|
setEditorActive(false);
|
|
1053
1164
|
}, 50);
|
|
@@ -1155,37 +1266,37 @@ function TaskForm({ editingTask, allTasks, initialDeps, onSave, onCancel }) {
|
|
|
1155
1266
|
" ",
|
|
1156
1267
|
isEdit ? "edit" : "create"
|
|
1157
1268
|
] }) }),
|
|
1158
|
-
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", paddingX: 1, paddingY: 0, children: FIELDS.map((
|
|
1269
|
+
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", paddingX: 1, paddingY: 0, children: FIELDS.map((field2, i) => {
|
|
1159
1270
|
const isFocused = i === focusIndex;
|
|
1160
|
-
const value =
|
|
1271
|
+
const value = field2.key === "dependsOn" ? depSummary : values[field2.key] ?? "";
|
|
1161
1272
|
const displayValue = value;
|
|
1162
1273
|
return /* @__PURE__ */ jsxs6(Box8, { gap: 1, children: [
|
|
1163
1274
|
/* @__PURE__ */ jsxs6(Text8, { color: isFocused ? theme.dialog.label : theme.yaml.key, bold: isFocused, children: [
|
|
1164
1275
|
isFocused ? ">" : " ",
|
|
1165
1276
|
" ",
|
|
1166
|
-
|
|
1277
|
+
field2.label.padEnd(14)
|
|
1167
1278
|
] }),
|
|
1168
|
-
|
|
1279
|
+
field2.type === "inline" && /* @__PURE__ */ jsxs6(Text8, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
|
|
1169
1280
|
displayValue,
|
|
1170
1281
|
isFocused ? /* @__PURE__ */ jsx8(Text8, { color: theme.titleHighlight, children: "_" }) : ""
|
|
1171
1282
|
] }),
|
|
1172
|
-
|
|
1283
|
+
field2.type === "picker" && /* @__PURE__ */ jsxs6(Text8, { children: [
|
|
1173
1284
|
displayValue ? /* @__PURE__ */ jsx8(Text8, { color: theme.status.added, children: displayValue.length > 60 ? displayValue.slice(0, 60) + "..." : displayValue }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: isFocused ? "press enter to select" : "none" }),
|
|
1174
1285
|
isFocused && /* @__PURE__ */ jsx8(Text8, { color: theme.menu.key, children: " [enter: open picker]" })
|
|
1175
1286
|
] }),
|
|
1176
|
-
|
|
1287
|
+
field2.type === "editor" && /* @__PURE__ */ jsxs6(Text8, { children: [
|
|
1177
1288
|
displayValue ? /* @__PURE__ */ jsxs6(Text8, { color: theme.status.added, children: [
|
|
1178
1289
|
displayValue.split("\n")[0]?.slice(0, 50),
|
|
1179
1290
|
displayValue.length > 50 || displayValue.includes("\n") ? "..." : ""
|
|
1180
1291
|
] }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: isFocused ? "press enter" : "empty" }),
|
|
1181
1292
|
isFocused && /* @__PURE__ */ jsx8(Text8, { color: theme.menu.key, children: " [enter: $EDITOR]" })
|
|
1182
1293
|
] }),
|
|
1183
|
-
|
|
1294
|
+
field2.type === "select" && /* @__PURE__ */ jsxs6(Text8, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
|
|
1184
1295
|
isFocused ? "< " : " ",
|
|
1185
1296
|
displayValue,
|
|
1186
1297
|
isFocused ? " >" : ""
|
|
1187
1298
|
] })
|
|
1188
|
-
] },
|
|
1299
|
+
] }, field2.key);
|
|
1189
1300
|
}) }),
|
|
1190
1301
|
/* @__PURE__ */ jsx8(Box8, { flexGrow: 1 }),
|
|
1191
1302
|
/* @__PURE__ */ jsx8(Box8, { paddingX: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "tab: next | shift+tab: prev | ctrl+s: save | esc: cancel" }) }),
|
|
@@ -1353,26 +1464,26 @@ function ProjectForm({ onSave, onCancel }) {
|
|
|
1353
1464
|
" ",
|
|
1354
1465
|
"new project"
|
|
1355
1466
|
] }) }),
|
|
1356
|
-
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", paddingX: 1, paddingY: 0, children: FIELDS2.map((
|
|
1467
|
+
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", paddingX: 1, paddingY: 0, children: FIELDS2.map((field2, i) => {
|
|
1357
1468
|
const isFocused = i === focusIndex;
|
|
1358
|
-
const value = values[
|
|
1469
|
+
const value = values[field2.key] ?? "";
|
|
1359
1470
|
return /* @__PURE__ */ jsxs8(Box10, { gap: 1, children: [
|
|
1360
1471
|
/* @__PURE__ */ jsxs8(Text10, { color: isFocused ? theme.dialog.label : theme.yaml.key, bold: isFocused, children: [
|
|
1361
1472
|
isFocused ? ">" : " ",
|
|
1362
1473
|
" ",
|
|
1363
|
-
|
|
1474
|
+
field2.label.padEnd(14)
|
|
1364
1475
|
] }),
|
|
1365
|
-
|
|
1476
|
+
field2.type === "inline" && /* @__PURE__ */ jsxs8(Text10, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
|
|
1366
1477
|
value,
|
|
1367
1478
|
isFocused ? /* @__PURE__ */ jsx10(Text10, { color: theme.titleHighlight, children: "_" }) : "",
|
|
1368
|
-
|
|
1479
|
+
field2.key === "key" && !value && /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: isFocused ? " (auto from name)" : "" })
|
|
1369
1480
|
] }),
|
|
1370
|
-
|
|
1481
|
+
field2.type === "toggle" && /* @__PURE__ */ jsxs8(Text10, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
|
|
1371
1482
|
isFocused ? "< " : " ",
|
|
1372
1483
|
value,
|
|
1373
1484
|
isFocused ? " >" : ""
|
|
1374
1485
|
] })
|
|
1375
|
-
] },
|
|
1486
|
+
] }, field2.key);
|
|
1376
1487
|
}) }),
|
|
1377
1488
|
/* @__PURE__ */ jsx10(Box10, { flexGrow: 1 }),
|
|
1378
1489
|
/* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "tab: next | shift+tab: prev | ctrl+s: save | esc: cancel" }) })
|
|
@@ -1388,6 +1499,8 @@ var SECTIONS = [
|
|
|
1388
1499
|
keys: [
|
|
1389
1500
|
["j/k", "Up/Down"],
|
|
1390
1501
|
["g/G", "Top/Bottom"],
|
|
1502
|
+
["PgDn", "Page down"],
|
|
1503
|
+
["PgUp", "Page up"],
|
|
1391
1504
|
["enter", "View"],
|
|
1392
1505
|
["esc", "Back"]
|
|
1393
1506
|
]
|
|
@@ -1634,8 +1747,256 @@ function DependencyList({
|
|
|
1634
1747
|
] });
|
|
1635
1748
|
}
|
|
1636
1749
|
|
|
1637
|
-
// src/tui/components/
|
|
1750
|
+
// src/tui/components/EpicPanel.tsx
|
|
1751
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
1638
1752
|
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1753
|
+
var PAGE_SIZE2 = 20;
|
|
1754
|
+
function EpicPanel({
|
|
1755
|
+
epics,
|
|
1756
|
+
selectedIndex,
|
|
1757
|
+
selectedEpicIds,
|
|
1758
|
+
isFocused,
|
|
1759
|
+
isReordering = false
|
|
1760
|
+
}) {
|
|
1761
|
+
const filterActive = selectedEpicIds.size > 0;
|
|
1762
|
+
const currentPage = Math.floor(selectedIndex / PAGE_SIZE2);
|
|
1763
|
+
const viewStart = currentPage * PAGE_SIZE2;
|
|
1764
|
+
const visibleEpics = epics.slice(viewStart, viewStart + PAGE_SIZE2);
|
|
1765
|
+
return /* @__PURE__ */ jsxs12(
|
|
1766
|
+
Box14,
|
|
1767
|
+
{
|
|
1768
|
+
flexDirection: "column",
|
|
1769
|
+
width: 48,
|
|
1770
|
+
borderStyle: "bold",
|
|
1771
|
+
borderColor: isFocused ? theme.borderFocus : theme.border,
|
|
1772
|
+
children: [
|
|
1773
|
+
/* @__PURE__ */ jsxs12(Box14, { children: [
|
|
1774
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.title, bold: true, children: [
|
|
1775
|
+
" ",
|
|
1776
|
+
"epics"
|
|
1777
|
+
] }),
|
|
1778
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.titleCounter, bold: true, children: [
|
|
1779
|
+
"[",
|
|
1780
|
+
epics.length,
|
|
1781
|
+
"]"
|
|
1782
|
+
] }),
|
|
1783
|
+
isReordering && /* @__PURE__ */ jsxs12(Text14, { color: theme.flash.warn, bold: true, children: [
|
|
1784
|
+
" ",
|
|
1785
|
+
"REORDER"
|
|
1786
|
+
] }),
|
|
1787
|
+
filterActive && /* @__PURE__ */ jsxs12(Text14, { color: theme.titleFilter, children: [
|
|
1788
|
+
" *",
|
|
1789
|
+
selectedEpicIds.size
|
|
1790
|
+
] })
|
|
1791
|
+
] }),
|
|
1792
|
+
/* @__PURE__ */ jsx14(Box14, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: epics.length === 0 ? /* @__PURE__ */ jsx14(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "No epics" }) }) : visibleEpics.map((epic, i) => {
|
|
1793
|
+
const actualIndex = viewStart + i;
|
|
1794
|
+
const isSelected = actualIndex === selectedIndex && isFocused;
|
|
1795
|
+
const isChecked = selectedEpicIds.has(epic.id);
|
|
1796
|
+
const marker = isChecked ? "[x]" : "[ ]";
|
|
1797
|
+
const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
|
|
1798
|
+
if (isSelected) {
|
|
1799
|
+
const cursorBg = isReordering ? theme.flash.warn : theme.table.cursorBg;
|
|
1800
|
+
return /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs12(Text14, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: [
|
|
1801
|
+
isReordering ? "~ " : " ",
|
|
1802
|
+
marker,
|
|
1803
|
+
" ",
|
|
1804
|
+
epic.name
|
|
1805
|
+
] }) }, epic.id);
|
|
1806
|
+
}
|
|
1807
|
+
return /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs12(Text14, { color: isChecked ? theme.titleHighlight : statusColor, children: [
|
|
1808
|
+
" ",
|
|
1809
|
+
marker,
|
|
1810
|
+
" ",
|
|
1811
|
+
epic.name
|
|
1812
|
+
] }) }, epic.id);
|
|
1813
|
+
}) }),
|
|
1814
|
+
epics.length > PAGE_SIZE2 && /* @__PURE__ */ jsx14(Box14, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs12(Text14, { dimColor: true, children: [
|
|
1815
|
+
"[",
|
|
1816
|
+
viewStart + 1,
|
|
1817
|
+
"-",
|
|
1818
|
+
Math.min(viewStart + PAGE_SIZE2, epics.length),
|
|
1819
|
+
"/",
|
|
1820
|
+
epics.length,
|
|
1821
|
+
"]"
|
|
1822
|
+
] }) })
|
|
1823
|
+
]
|
|
1824
|
+
}
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// src/tui/components/EpicPicker.tsx
|
|
1829
|
+
import { useState as useState5 } from "react";
|
|
1830
|
+
import { Box as Box15, Text as Text15, useInput as useInput5, useStdout as useStdout3 } from "ink";
|
|
1831
|
+
import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1832
|
+
function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
|
|
1833
|
+
const { stdout } = useStdout3();
|
|
1834
|
+
const termHeight = stdout.rows > 0 ? stdout.rows : 24;
|
|
1835
|
+
const maxVisible = Math.max(3, termHeight - 10);
|
|
1836
|
+
const [searchQuery, setSearchQuery] = useState5("");
|
|
1837
|
+
const [isSearching, setIsSearching] = useState5(false);
|
|
1838
|
+
const [cursorIndex, setCursorIndex] = useState5(0);
|
|
1839
|
+
const filtered = epics.filter((e) => {
|
|
1840
|
+
if (!searchQuery.trim()) return true;
|
|
1841
|
+
const q = searchQuery.toLowerCase();
|
|
1842
|
+
return e.id.toLowerCase().includes(q) || e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q);
|
|
1843
|
+
});
|
|
1844
|
+
let viewStart = 0;
|
|
1845
|
+
if (cursorIndex >= viewStart + maxVisible) {
|
|
1846
|
+
viewStart = cursorIndex - maxVisible + 1;
|
|
1847
|
+
}
|
|
1848
|
+
if (cursorIndex < viewStart) {
|
|
1849
|
+
viewStart = cursorIndex;
|
|
1850
|
+
}
|
|
1851
|
+
const visible = filtered.slice(viewStart, viewStart + maxVisible);
|
|
1852
|
+
useInput5((input, key) => {
|
|
1853
|
+
if (isSearching) {
|
|
1854
|
+
if (key.escape) {
|
|
1855
|
+
setIsSearching(false);
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
if (key.return) {
|
|
1859
|
+
setIsSearching(false);
|
|
1860
|
+
setCursorIndex(0);
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
if (key.backspace || key.delete) {
|
|
1864
|
+
setSearchQuery((q) => q.slice(0, -1));
|
|
1865
|
+
setCursorIndex(0);
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
if (input && !key.ctrl && !key.meta) {
|
|
1869
|
+
setSearchQuery((q) => q + input);
|
|
1870
|
+
setCursorIndex(0);
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
if (key.upArrow || input === "k") {
|
|
1876
|
+
setCursorIndex((i) => Math.max(0, i - 1));
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
if (key.downArrow || input === "j") {
|
|
1880
|
+
setCursorIndex((i) => Math.min(filtered.length - 1, i + 1));
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
if (key.return) {
|
|
1884
|
+
const epic = filtered[cursorIndex];
|
|
1885
|
+
if (epic) {
|
|
1886
|
+
onSelect(epic.id);
|
|
1887
|
+
}
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
if (input === "x") {
|
|
1891
|
+
onSelect(null);
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
if (input === "/") {
|
|
1895
|
+
setIsSearching(true);
|
|
1896
|
+
setSearchQuery("");
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
if (key.escape || input === "q") {
|
|
1900
|
+
onCancel();
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", borderStyle: "bold", borderColor: theme.borderFocus, flexGrow: 1, children: [
|
|
1905
|
+
/* @__PURE__ */ jsxs13(Box15, { gap: 0, children: [
|
|
1906
|
+
/* @__PURE__ */ jsxs13(Text15, { color: theme.title, bold: true, children: [
|
|
1907
|
+
" ",
|
|
1908
|
+
"assign to epic"
|
|
1909
|
+
] }),
|
|
1910
|
+
/* @__PURE__ */ jsxs13(Text15, { color: theme.titleCounter, bold: true, children: [
|
|
1911
|
+
" ",
|
|
1912
|
+
"[",
|
|
1913
|
+
epics.length,
|
|
1914
|
+
"]"
|
|
1915
|
+
] })
|
|
1916
|
+
] }),
|
|
1917
|
+
isSearching ? /* @__PURE__ */ jsxs13(Box15, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
|
|
1918
|
+
/* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: "/" }),
|
|
1919
|
+
/* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: searchQuery }),
|
|
1920
|
+
/* @__PURE__ */ jsx15(Text15, { color: theme.promptSuggest, children: "_" })
|
|
1921
|
+
] }) : searchQuery ? /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsxs13(Text15, { color: theme.titleFilter, children: [
|
|
1922
|
+
"/",
|
|
1923
|
+
searchQuery
|
|
1924
|
+
] }) }) : null,
|
|
1925
|
+
/* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsxs13(Text15, { color: theme.table.headerFg, bold: true, children: [
|
|
1926
|
+
" ",
|
|
1927
|
+
"ID".padEnd(14),
|
|
1928
|
+
"STATUS".padEnd(14),
|
|
1929
|
+
"NAME"
|
|
1930
|
+
] }) }),
|
|
1931
|
+
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) => {
|
|
1932
|
+
const actualIndex = viewStart + i;
|
|
1933
|
+
const isCursor = actualIndex === cursorIndex;
|
|
1934
|
+
const isCurrent = epic.id === currentEpicId;
|
|
1935
|
+
const marker = isCurrent ? "* " : " ";
|
|
1936
|
+
const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
|
|
1937
|
+
return /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: isCursor ? /* @__PURE__ */ jsxs13(Text15, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
|
|
1938
|
+
"> ",
|
|
1939
|
+
epic.id.padEnd(14),
|
|
1940
|
+
epic.status.padEnd(14),
|
|
1941
|
+
epic.name
|
|
1942
|
+
] }) : /* @__PURE__ */ jsxs13(Fragment3, { children: [
|
|
1943
|
+
/* @__PURE__ */ jsx15(Text15, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: marker }),
|
|
1944
|
+
/* @__PURE__ */ jsx15(Text15, { color: theme.yaml.value, children: epic.id.padEnd(14) }),
|
|
1945
|
+
/* @__PURE__ */ jsx15(Text15, { color: statusColor, children: epic.status.padEnd(14) }),
|
|
1946
|
+
/* @__PURE__ */ jsx15(Text15, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: epic.name })
|
|
1947
|
+
] }) }, epic.id);
|
|
1948
|
+
}),
|
|
1949
|
+
/* @__PURE__ */ jsx15(Box15, { flexGrow: 1 }),
|
|
1950
|
+
filtered.length > maxVisible && /* @__PURE__ */ jsx15(Box15, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs13(Text15, { dimColor: true, children: [
|
|
1951
|
+
"[",
|
|
1952
|
+
viewStart + 1,
|
|
1953
|
+
"-",
|
|
1954
|
+
Math.min(viewStart + maxVisible, filtered.length),
|
|
1955
|
+
"/",
|
|
1956
|
+
filtered.length,
|
|
1957
|
+
"]"
|
|
1958
|
+
] }) }),
|
|
1959
|
+
/* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "enter: assign | x: unassign | /: search | esc: cancel" }) })
|
|
1960
|
+
] });
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// src/tui/useAutoRefetch.ts
|
|
1964
|
+
import { useEffect, useRef } from "react";
|
|
1965
|
+
import { watchFile, unwatchFile } from "fs";
|
|
1966
|
+
var POLL_INTERVAL_MS = 1e3;
|
|
1967
|
+
var DEBOUNCE_MS = 200;
|
|
1968
|
+
function useAutoRefetch(dbPath, onRefetch) {
|
|
1969
|
+
const callbackRef = useRef(onRefetch);
|
|
1970
|
+
callbackRef.current = onRefetch;
|
|
1971
|
+
useEffect(() => {
|
|
1972
|
+
let debounceTimer = null;
|
|
1973
|
+
const handleChange = () => {
|
|
1974
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1975
|
+
debounceTimer = setTimeout(() => {
|
|
1976
|
+
logger.info("useAutoRefetch: external db change detected, refetching");
|
|
1977
|
+
callbackRef.current();
|
|
1978
|
+
}, DEBOUNCE_MS);
|
|
1979
|
+
};
|
|
1980
|
+
const walPath = `${dbPath}-wal`;
|
|
1981
|
+
const listener = (curr, prev) => {
|
|
1982
|
+
if (curr.mtimeMs !== prev.mtimeMs) {
|
|
1983
|
+
handleChange();
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
1986
|
+
watchFile(dbPath, { interval: POLL_INTERVAL_MS }, listener);
|
|
1987
|
+
watchFile(walPath, { interval: POLL_INTERVAL_MS }, listener);
|
|
1988
|
+
logger.info(`useAutoRefetch: watching ${dbPath} (poll ${POLL_INTERVAL_MS}ms)`);
|
|
1989
|
+
return () => {
|
|
1990
|
+
unwatchFile(dbPath, listener);
|
|
1991
|
+
unwatchFile(walPath, listener);
|
|
1992
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1993
|
+
logger.info("useAutoRefetch: stopped watching");
|
|
1994
|
+
};
|
|
1995
|
+
}, [dbPath]);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// src/tui/components/App.tsx
|
|
1999
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1639
2000
|
var STATUS_CYCLE = [
|
|
1640
2001
|
TaskStatus.Backlog,
|
|
1641
2002
|
TaskStatus.Todo,
|
|
@@ -1643,9 +2004,25 @@ var STATUS_CYCLE = [
|
|
|
1643
2004
|
TaskStatus.Review,
|
|
1644
2005
|
TaskStatus.Done
|
|
1645
2006
|
];
|
|
2007
|
+
var EPIC_PANEL_WIDTH = 48;
|
|
1646
2008
|
function App({ container, initialProject }) {
|
|
1647
2009
|
const { exit } = useApp();
|
|
2010
|
+
const { stdout } = useStdout4();
|
|
1648
2011
|
const [state, dispatch] = useReducer(appReducer, initialState);
|
|
2012
|
+
const [, setResizeTick] = useState6(0);
|
|
2013
|
+
useEffect2(() => {
|
|
2014
|
+
const onResize = () => {
|
|
2015
|
+
setResizeTick((n) => n + 1);
|
|
2016
|
+
};
|
|
2017
|
+
stdout.on("resize", onResize);
|
|
2018
|
+
return () => {
|
|
2019
|
+
stdout.off("resize", onResize);
|
|
2020
|
+
};
|
|
2021
|
+
}, [stdout]);
|
|
2022
|
+
const termWidth = stdout.columns > 0 ? stdout.columns : 120;
|
|
2023
|
+
const remaining = Math.max(0, termWidth - EPIC_PANEL_WIDTH - 2);
|
|
2024
|
+
const taskListWidth = Math.floor(remaining * 0.6);
|
|
2025
|
+
const taskDetailWidth = remaining - taskListWidth;
|
|
1649
2026
|
const loadProjects = useCallback2(() => {
|
|
1650
2027
|
const result = container.projectService.listProjects();
|
|
1651
2028
|
if (result.ok) {
|
|
@@ -1658,10 +2035,13 @@ function App({ container, initialProject }) {
|
|
|
1658
2035
|
}, [container]);
|
|
1659
2036
|
const loadTasks = useCallback2(() => {
|
|
1660
2037
|
logger.startSpan("TUI.loadTasks", () => {
|
|
1661
|
-
const filter = { ...state.filter };
|
|
2038
|
+
const filter = { ...state.filter, level: TaskLevel.Work };
|
|
1662
2039
|
if (state.activeProject) {
|
|
1663
2040
|
filter.projectId = state.activeProject.id;
|
|
1664
2041
|
}
|
|
2042
|
+
if (state.selectedEpicIds.size > 0) {
|
|
2043
|
+
filter.parentIds = [...state.selectedEpicIds];
|
|
2044
|
+
}
|
|
1665
2045
|
const result = container.taskService.listTasks(filter);
|
|
1666
2046
|
if (result.ok) {
|
|
1667
2047
|
logger.info(`TUI.loadTasks: loaded ${result.value.length} tasks`);
|
|
@@ -1671,7 +2051,19 @@ function App({ container, initialProject }) {
|
|
|
1671
2051
|
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
1672
2052
|
}
|
|
1673
2053
|
});
|
|
1674
|
-
}, [container, state.filter, state.activeProject]);
|
|
2054
|
+
}, [container, state.filter, state.activeProject, state.selectedEpicIds]);
|
|
2055
|
+
const loadEpics = useCallback2(() => {
|
|
2056
|
+
if (!state.activeProject) return;
|
|
2057
|
+
const result = container.taskService.listTasks({
|
|
2058
|
+
projectId: state.activeProject.id,
|
|
2059
|
+
level: TaskLevel.Epic
|
|
2060
|
+
});
|
|
2061
|
+
if (result.ok) {
|
|
2062
|
+
dispatch({ type: "SET_EPICS", epics: result.value });
|
|
2063
|
+
} else {
|
|
2064
|
+
logger.error("TUI.loadEpics: failed", result.error);
|
|
2065
|
+
}
|
|
2066
|
+
}, [container, state.activeProject]);
|
|
1675
2067
|
const loadDeps = useCallback2(
|
|
1676
2068
|
(taskId) => {
|
|
1677
2069
|
const blockersResult = container.dependencyService.listBlockers(taskId);
|
|
@@ -1700,11 +2092,12 @@ function App({ container, initialProject }) {
|
|
|
1700
2092
|
dispatch({ type: "SELECT_TASK", task: result.value });
|
|
1701
2093
|
}
|
|
1702
2094
|
loadTasks();
|
|
2095
|
+
loadEpics();
|
|
1703
2096
|
} else {
|
|
1704
2097
|
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
1705
2098
|
}
|
|
1706
2099
|
},
|
|
1707
|
-
[container, state.selectedTask, loadTasks]
|
|
2100
|
+
[container, state.selectedTask, loadTasks, loadEpics]
|
|
1708
2101
|
);
|
|
1709
2102
|
const saveReorder = useCallback2(() => {
|
|
1710
2103
|
const tasks = state.tasks;
|
|
@@ -1722,10 +2115,32 @@ function App({ container, initialProject }) {
|
|
|
1722
2115
|
});
|
|
1723
2116
|
loadTasks();
|
|
1724
2117
|
}, [container, state.tasks, state.selectedIndex, loadTasks]);
|
|
1725
|
-
|
|
2118
|
+
const saveEpicReorder = useCallback2(() => {
|
|
2119
|
+
const epics = state.epics;
|
|
2120
|
+
const idx = state.epicSelectedIndex;
|
|
2121
|
+
const epic = epics[idx];
|
|
2122
|
+
if (!epic) return;
|
|
2123
|
+
const prev = epics[idx - 1];
|
|
2124
|
+
const next = epics[idx + 1];
|
|
2125
|
+
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 });
|
|
2126
|
+
dispatch({ type: "EXIT_EPIC_REORDER", save: result.ok });
|
|
2127
|
+
dispatch({
|
|
2128
|
+
type: "FLASH",
|
|
2129
|
+
message: result.ok ? "Epic rank saved" : result.error.message,
|
|
2130
|
+
level: result.ok ? "info" : "error"
|
|
2131
|
+
});
|
|
2132
|
+
loadEpics();
|
|
2133
|
+
}, [container, state.epics, state.epicSelectedIndex, loadEpics]);
|
|
2134
|
+
const refetchAll = useCallback2(() => {
|
|
2135
|
+
loadProjects();
|
|
2136
|
+
loadTasks();
|
|
2137
|
+
loadEpics();
|
|
2138
|
+
}, [loadProjects, loadTasks, loadEpics]);
|
|
2139
|
+
useAutoRefetch(container.dbPath, refetchAll);
|
|
2140
|
+
useEffect2(() => {
|
|
1726
2141
|
loadProjects();
|
|
1727
2142
|
}, [loadProjects]);
|
|
1728
|
-
|
|
2143
|
+
useEffect2(() => {
|
|
1729
2144
|
if (state.projects.length > 0 && !state.activeProject) {
|
|
1730
2145
|
logger.info(`TUI.resolveProject: resolving initialProject=${initialProject ?? "(default)"}`);
|
|
1731
2146
|
const result = container.projectService.resolveProject(initialProject);
|
|
@@ -1742,12 +2157,13 @@ function App({ container, initialProject }) {
|
|
|
1742
2157
|
}
|
|
1743
2158
|
}
|
|
1744
2159
|
}, [state.projects, state.activeProject, initialProject, container]);
|
|
1745
|
-
|
|
2160
|
+
useEffect2(() => {
|
|
1746
2161
|
if (state.activeProject) {
|
|
1747
2162
|
loadTasks();
|
|
2163
|
+
loadEpics();
|
|
1748
2164
|
}
|
|
1749
|
-
}, [state.activeProject, state.filter, loadTasks]);
|
|
1750
|
-
|
|
2165
|
+
}, [state.activeProject, state.filter, loadTasks, loadEpics]);
|
|
2166
|
+
useEffect2(() => {
|
|
1751
2167
|
if (state.flash) {
|
|
1752
2168
|
const timer = setTimeout(() => {
|
|
1753
2169
|
dispatch({ type: "CLEAR_FLASH" });
|
|
@@ -1758,7 +2174,7 @@ function App({ container, initialProject }) {
|
|
|
1758
2174
|
}
|
|
1759
2175
|
return void 0;
|
|
1760
2176
|
}, [state.flash]);
|
|
1761
|
-
|
|
2177
|
+
useInput6((input, key) => {
|
|
1762
2178
|
if (state.confirmDelete) {
|
|
1763
2179
|
if (input === "y") {
|
|
1764
2180
|
const result = container.taskService.deleteTask(state.confirmDelete.id);
|
|
@@ -1769,6 +2185,7 @@ function App({ container, initialProject }) {
|
|
|
1769
2185
|
dispatch({ type: "GO_BACK" });
|
|
1770
2186
|
}
|
|
1771
2187
|
loadTasks();
|
|
2188
|
+
loadEpics();
|
|
1772
2189
|
} else {
|
|
1773
2190
|
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
1774
2191
|
dispatch({ type: "CANCEL_DELETE" });
|
|
@@ -1782,7 +2199,7 @@ function App({ container, initialProject }) {
|
|
|
1782
2199
|
dispatch({ type: "GO_BACK" });
|
|
1783
2200
|
return;
|
|
1784
2201
|
}
|
|
1785
|
-
if (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit || state.activeView === ViewType.ProjectSelector || state.activeView === ViewType.ProjectCreate) {
|
|
2202
|
+
if (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit || state.activeView === ViewType.ProjectSelector || state.activeView === ViewType.ProjectCreate || state.activeView === ViewType.EpicPicker) {
|
|
1786
2203
|
return;
|
|
1787
2204
|
}
|
|
1788
2205
|
if (state.activeView === ViewType.DependencyList && state.isAddingDep) {
|
|
@@ -1853,6 +2270,26 @@ function App({ container, initialProject }) {
|
|
|
1853
2270
|
}
|
|
1854
2271
|
return;
|
|
1855
2272
|
}
|
|
2273
|
+
if (state.isEpicReordering) {
|
|
2274
|
+
if (key.upArrow || input === "k") {
|
|
2275
|
+
dispatch({ type: "EPIC_REORDER_MOVE", direction: "up" });
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (key.downArrow || input === "j") {
|
|
2279
|
+
dispatch({ type: "EPIC_REORDER_MOVE", direction: "down" });
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
if (key.rightArrow) {
|
|
2283
|
+
saveEpicReorder();
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
if (key.escape || key.leftArrow) {
|
|
2287
|
+
dispatch({ type: "EXIT_EPIC_REORDER", save: false });
|
|
2288
|
+
dispatch({ type: "FLASH", message: "Epic reorder cancelled", level: "info" });
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
1856
2293
|
if (state.isReordering) {
|
|
1857
2294
|
if (key.upArrow || input === "k") {
|
|
1858
2295
|
dispatch({ type: "REORDER_MOVE", direction: "up" });
|
|
@@ -1889,13 +2326,49 @@ function App({ container, initialProject }) {
|
|
|
1889
2326
|
dispatch({ type: "NAVIGATE_TO", view: ViewType.Help });
|
|
1890
2327
|
return;
|
|
1891
2328
|
}
|
|
1892
|
-
if (key.tab && state.activeView === ViewType.TaskList
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
});
|
|
2329
|
+
if (key.tab && state.activeView === ViewType.TaskList) {
|
|
2330
|
+
const panels = previewTask ? ["epic", "list", "detail"] : ["epic", "list"];
|
|
2331
|
+
const curIdx = panels.indexOf(state.focusedPanel);
|
|
2332
|
+
const nextPanel = panels[(curIdx + 1) % panels.length] ?? "list";
|
|
2333
|
+
dispatch({ type: "SET_PANEL_FOCUS", panel: nextPanel });
|
|
1897
2334
|
return;
|
|
1898
2335
|
}
|
|
2336
|
+
if (state.activeView === ViewType.TaskList && state.focusedPanel === "epic") {
|
|
2337
|
+
if (key.upArrow || input === "k") {
|
|
2338
|
+
dispatch({ type: "EPIC_MOVE_CURSOR", direction: "up" });
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
if (key.downArrow || input === "j") {
|
|
2342
|
+
dispatch({ type: "EPIC_MOVE_CURSOR", direction: "down" });
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
if (input === " " || key.return) {
|
|
2346
|
+
const epic = state.epics[state.epicSelectedIndex];
|
|
2347
|
+
if (epic) {
|
|
2348
|
+
dispatch({ type: "TOGGLE_EPIC", epicId: epic.id });
|
|
2349
|
+
}
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
if (input === "0") {
|
|
2353
|
+
dispatch({ type: "CLEAR_EPIC_SELECTION" });
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
if (key.leftArrow) {
|
|
2357
|
+
if (state.epics.length > 0) {
|
|
2358
|
+
dispatch({ type: "ENTER_EPIC_REORDER" });
|
|
2359
|
+
dispatch({
|
|
2360
|
+
type: "FLASH",
|
|
2361
|
+
message: "Reorder: \u2191\u2193 move, \u2192 save, \u2190 cancel",
|
|
2362
|
+
level: "info"
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
if (input === "q") {
|
|
2368
|
+
exit();
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
1899
2372
|
if (state.activeView === ViewType.TaskList && state.focusedPanel === "list") {
|
|
1900
2373
|
if (key.upArrow || input === "k") {
|
|
1901
2374
|
dispatch({ type: "MOVE_CURSOR", direction: "up" });
|
|
@@ -1913,6 +2386,14 @@ function App({ container, initialProject }) {
|
|
|
1913
2386
|
dispatch({ type: "SET_CURSOR", index: state.tasks.length - 1 });
|
|
1914
2387
|
return;
|
|
1915
2388
|
}
|
|
2389
|
+
if (key.pageDown || key.ctrl && input === "d") {
|
|
2390
|
+
dispatch({ type: "PAGE_CURSOR", direction: "down" });
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
if (key.pageUp || key.ctrl && input === "u") {
|
|
2394
|
+
dispatch({ type: "PAGE_CURSOR", direction: "up" });
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
1916
2397
|
if (key.return) {
|
|
1917
2398
|
const task = state.tasks[state.selectedIndex];
|
|
1918
2399
|
if (task) {
|
|
@@ -1949,6 +2430,30 @@ function App({ container, initialProject }) {
|
|
|
1949
2430
|
}
|
|
1950
2431
|
return;
|
|
1951
2432
|
}
|
|
2433
|
+
if (input === "a") {
|
|
2434
|
+
const task = state.tasks[state.selectedIndex];
|
|
2435
|
+
if (task) {
|
|
2436
|
+
dispatch({ type: "SELECT_TASK", task });
|
|
2437
|
+
dispatch({ type: "NAVIGATE_TO", view: ViewType.EpicPicker });
|
|
2438
|
+
}
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
if (input === "A") {
|
|
2442
|
+
const task = state.tasks[state.selectedIndex];
|
|
2443
|
+
if (task && task.parentId) {
|
|
2444
|
+
const result = container.taskService.updateTask(task.id, { parentId: null });
|
|
2445
|
+
if (result.ok) {
|
|
2446
|
+
dispatch({ type: "FLASH", message: "Unassigned from epic", level: "info" });
|
|
2447
|
+
loadTasks();
|
|
2448
|
+
loadEpics();
|
|
2449
|
+
} else {
|
|
2450
|
+
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
2451
|
+
}
|
|
2452
|
+
} else if (task) {
|
|
2453
|
+
dispatch({ type: "FLASH", message: "Task has no epic", level: "warn" });
|
|
2454
|
+
}
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
1952
2457
|
if (input === "/") {
|
|
1953
2458
|
dispatch({ type: "SET_SEARCH_ACTIVE", active: true });
|
|
1954
2459
|
dispatch({ type: "SET_SEARCH_QUERY", query: "" });
|
|
@@ -1959,6 +2464,22 @@ function App({ container, initialProject }) {
|
|
|
1959
2464
|
return;
|
|
1960
2465
|
}
|
|
1961
2466
|
if (key.leftArrow) {
|
|
2467
|
+
if (state.selectedEpicIds.size > 0) {
|
|
2468
|
+
dispatch({
|
|
2469
|
+
type: "FLASH",
|
|
2470
|
+
message: "Clear epic filter (0) before reordering",
|
|
2471
|
+
level: "warn"
|
|
2472
|
+
});
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
if (state.filter.status || state.filter.type || state.filter.search) {
|
|
2476
|
+
dispatch({
|
|
2477
|
+
type: "FLASH",
|
|
2478
|
+
message: "Clear filters (0) before reordering",
|
|
2479
|
+
level: "warn"
|
|
2480
|
+
});
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
1962
2483
|
if (state.tasks.length > 0) {
|
|
1963
2484
|
dispatch({ type: "ENTER_REORDER" });
|
|
1964
2485
|
dispatch({ type: "FLASH", message: "Reorder: \u2191\u2193 move, \u2192 save, \u2190 cancel", level: "info" });
|
|
@@ -1987,6 +2508,14 @@ function App({ container, initialProject }) {
|
|
|
1987
2508
|
}
|
|
1988
2509
|
}
|
|
1989
2510
|
if (state.activeView === ViewType.TaskList && state.focusedPanel === "detail" && previewTask) {
|
|
2511
|
+
if (key.upArrow || input === "k") {
|
|
2512
|
+
dispatch({ type: "DETAIL_SCROLL", direction: "up" });
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
if (key.downArrow || input === "j") {
|
|
2516
|
+
dispatch({ type: "DETAIL_SCROLL", direction: "down" });
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
1990
2519
|
if (input === "e") {
|
|
1991
2520
|
dispatch({ type: "SELECT_TASK", task: previewTask });
|
|
1992
2521
|
dispatch({ type: "NAVIGATE_TO", view: ViewType.TaskEdit });
|
|
@@ -2022,6 +2551,14 @@ ${previewTask.additionalRequirements}`;
|
|
|
2022
2551
|
}
|
|
2023
2552
|
}
|
|
2024
2553
|
if (state.activeView === ViewType.TaskDetail) {
|
|
2554
|
+
if (key.upArrow || input === "k") {
|
|
2555
|
+
dispatch({ type: "DETAIL_SCROLL", direction: "up" });
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (key.downArrow || input === "j") {
|
|
2559
|
+
dispatch({ type: "DETAIL_SCROLL", direction: "down" });
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2025
2562
|
if (input === "e" && state.selectedTask) {
|
|
2026
2563
|
dispatch({ type: "NAVIGATE_TO", view: ViewType.TaskEdit });
|
|
2027
2564
|
return;
|
|
@@ -2141,22 +2678,53 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2141
2678
|
loadDeps(taskId);
|
|
2142
2679
|
dispatch({ type: "GO_BACK" });
|
|
2143
2680
|
loadTasks();
|
|
2681
|
+
loadEpics();
|
|
2144
2682
|
} else {
|
|
2145
2683
|
const result = container.taskService.createTask(data, state.activeProject?.id);
|
|
2146
2684
|
if (result.ok) {
|
|
2147
2685
|
dispatch({ type: "FLASH", message: "Task created", level: "info" });
|
|
2148
2686
|
dispatch({ type: "GO_BACK" });
|
|
2149
2687
|
loadTasks();
|
|
2688
|
+
loadEpics();
|
|
2150
2689
|
} else {
|
|
2151
2690
|
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
2152
2691
|
}
|
|
2153
2692
|
}
|
|
2154
2693
|
},
|
|
2155
|
-
[
|
|
2694
|
+
[
|
|
2695
|
+
container,
|
|
2696
|
+
state.activeView,
|
|
2697
|
+
state.selectedTask,
|
|
2698
|
+
state.activeProject,
|
|
2699
|
+
loadTasks,
|
|
2700
|
+
loadDeps,
|
|
2701
|
+
loadEpics
|
|
2702
|
+
]
|
|
2156
2703
|
);
|
|
2157
2704
|
const handleFormCancel = useCallback2(() => {
|
|
2158
2705
|
dispatch({ type: "GO_BACK" });
|
|
2159
2706
|
}, []);
|
|
2707
|
+
const handleEpicPickerSelect = useCallback2(
|
|
2708
|
+
(epicId) => {
|
|
2709
|
+
if (!state.selectedTask) return;
|
|
2710
|
+
const result = container.taskService.updateTask(state.selectedTask.id, {
|
|
2711
|
+
parentId: epicId
|
|
2712
|
+
});
|
|
2713
|
+
if (result.ok) {
|
|
2714
|
+
const msg = epicId ? `Assigned to ${epicId}` : "Unassigned from epic";
|
|
2715
|
+
dispatch({ type: "FLASH", message: msg, level: "info" });
|
|
2716
|
+
loadTasks();
|
|
2717
|
+
loadEpics();
|
|
2718
|
+
} else {
|
|
2719
|
+
dispatch({ type: "FLASH", message: result.error.message, level: "error" });
|
|
2720
|
+
}
|
|
2721
|
+
dispatch({ type: "GO_BACK" });
|
|
2722
|
+
},
|
|
2723
|
+
[container, state.selectedTask, loadTasks, loadEpics]
|
|
2724
|
+
);
|
|
2725
|
+
const handleEpicPickerCancel = useCallback2(() => {
|
|
2726
|
+
dispatch({ type: "GO_BACK" });
|
|
2727
|
+
}, []);
|
|
2160
2728
|
const handleProjectSelect = useCallback2((project) => {
|
|
2161
2729
|
dispatch({ type: "SET_ACTIVE_PROJECT", project });
|
|
2162
2730
|
dispatch({ type: "GO_BACK" });
|
|
@@ -2167,7 +2735,11 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2167
2735
|
const result = container.projectService.updateProject(project.id, { isDefault: true });
|
|
2168
2736
|
if (result.ok) {
|
|
2169
2737
|
logger.info(`TUI.setDefault: set key=${result.value.key} as default`);
|
|
2170
|
-
dispatch({
|
|
2738
|
+
dispatch({
|
|
2739
|
+
type: "FLASH",
|
|
2740
|
+
message: `Default project: ${result.value.name}`,
|
|
2741
|
+
level: "info"
|
|
2742
|
+
});
|
|
2171
2743
|
loadProjects();
|
|
2172
2744
|
} else {
|
|
2173
2745
|
logger.error("TUI.setDefault: failed", result.error);
|
|
@@ -2212,7 +2784,7 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2212
2784
|
dispatch({ type: "GO_BACK" });
|
|
2213
2785
|
}, []);
|
|
2214
2786
|
const previewTask = state.tasks[state.selectedIndex] ?? null;
|
|
2215
|
-
const initialDepsForEdit =
|
|
2787
|
+
const initialDepsForEdit = useMemo2(() => {
|
|
2216
2788
|
return [
|
|
2217
2789
|
...state.depBlockers.map((t) => ({ id: t.id, name: t.name, type: DependencyType.Blocks })),
|
|
2218
2790
|
...state.depRelated.map((t) => ({ id: t.id, name: t.name, type: DependencyType.RelatesTo })),
|
|
@@ -2223,23 +2795,33 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2223
2795
|
}))
|
|
2224
2796
|
];
|
|
2225
2797
|
}, [state.depBlockers, state.depRelated, state.depDuplicates]);
|
|
2226
|
-
const allProjectTasks =
|
|
2798
|
+
const allProjectTasks = useMemo2(() => {
|
|
2227
2799
|
if (!state.activeProject) return [];
|
|
2228
2800
|
const result = container.taskService.listTasks({ projectId: state.activeProject.id });
|
|
2229
2801
|
return result.ok ? result.value : [];
|
|
2230
2802
|
}, [container, state.activeProject, state.tasks]);
|
|
2231
2803
|
const previewTaskId = previewTask?.id ?? null;
|
|
2232
|
-
|
|
2804
|
+
useEffect2(() => {
|
|
2233
2805
|
if (state.activeView === ViewType.TaskList && previewTaskId) {
|
|
2234
2806
|
loadDeps(previewTaskId);
|
|
2235
2807
|
}
|
|
2236
2808
|
}, [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__ */
|
|
2809
|
+
return /* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", height: stdout.rows, children: [
|
|
2810
|
+
/* @__PURE__ */ jsx16(Header, { state }),
|
|
2811
|
+
/* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: [
|
|
2812
|
+
state.confirmDelete && /* @__PURE__ */ jsx16(ConfirmDialog, { task: state.confirmDelete }),
|
|
2813
|
+
!state.confirmDelete && state.activeView === ViewType.TaskList && /* @__PURE__ */ jsxs14(Box16, { flexDirection: "row", flexGrow: 1, children: [
|
|
2814
|
+
/* @__PURE__ */ jsx16(
|
|
2815
|
+
EpicPanel,
|
|
2816
|
+
{
|
|
2817
|
+
epics: state.epics,
|
|
2818
|
+
selectedIndex: state.epicSelectedIndex,
|
|
2819
|
+
selectedEpicIds: state.selectedEpicIds,
|
|
2820
|
+
isFocused: state.focusedPanel === "epic",
|
|
2821
|
+
isReordering: state.isEpicReordering
|
|
2822
|
+
}
|
|
2823
|
+
),
|
|
2824
|
+
/* @__PURE__ */ jsx16(Box16, { width: taskListWidth, children: /* @__PURE__ */ jsx16(
|
|
2243
2825
|
TaskList,
|
|
2244
2826
|
{
|
|
2245
2827
|
tasks: state.tasks,
|
|
@@ -2256,10 +2838,11 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2256
2838
|
state.depDependents.filter((t) => !isTerminalStatus(t.status)).map((t) => t.id)
|
|
2257
2839
|
),
|
|
2258
2840
|
isSelectedBlocked: state.depBlockers.some((t) => !isTerminalStatus(t.status)),
|
|
2259
|
-
isFocused: state.focusedPanel === "list"
|
|
2841
|
+
isFocused: state.focusedPanel === "list",
|
|
2842
|
+
epicFilterActive: state.selectedEpicIds.size > 0
|
|
2260
2843
|
}
|
|
2261
2844
|
) }),
|
|
2262
|
-
/* @__PURE__ */
|
|
2845
|
+
/* @__PURE__ */ jsx16(Box16, { width: taskDetailWidth, children: previewTask ? /* @__PURE__ */ jsx16(
|
|
2263
2846
|
TaskDetail,
|
|
2264
2847
|
{
|
|
2265
2848
|
task: previewTask,
|
|
@@ -2267,36 +2850,38 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2267
2850
|
dependents: state.depDependents,
|
|
2268
2851
|
related: state.depRelated,
|
|
2269
2852
|
duplicates: state.depDuplicates,
|
|
2270
|
-
isFocused: state.focusedPanel === "detail"
|
|
2853
|
+
isFocused: state.focusedPanel === "detail",
|
|
2854
|
+
scrollOffset: state.detailScrollOffset
|
|
2271
2855
|
}
|
|
2272
|
-
) : /* @__PURE__ */
|
|
2273
|
-
|
|
2856
|
+
) : /* @__PURE__ */ jsxs14(
|
|
2857
|
+
Box16,
|
|
2274
2858
|
{
|
|
2275
2859
|
flexDirection: "column",
|
|
2276
2860
|
flexGrow: 1,
|
|
2277
2861
|
borderStyle: "bold",
|
|
2278
2862
|
borderColor: theme.border,
|
|
2279
2863
|
children: [
|
|
2280
|
-
/* @__PURE__ */
|
|
2864
|
+
/* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs14(Text16, { color: theme.title, bold: true, children: [
|
|
2281
2865
|
" ",
|
|
2282
2866
|
"detail"
|
|
2283
2867
|
] }) }),
|
|
2284
|
-
/* @__PURE__ */
|
|
2868
|
+
/* @__PURE__ */ jsx16(Box16, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "No task selected" }) })
|
|
2285
2869
|
]
|
|
2286
2870
|
}
|
|
2287
2871
|
) })
|
|
2288
2872
|
] }),
|
|
2289
|
-
!state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */
|
|
2873
|
+
!state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx16(
|
|
2290
2874
|
TaskDetail,
|
|
2291
2875
|
{
|
|
2292
2876
|
task: state.selectedTask,
|
|
2293
2877
|
blockers: state.depBlockers,
|
|
2294
2878
|
dependents: state.depDependents,
|
|
2295
2879
|
related: state.depRelated,
|
|
2296
|
-
duplicates: state.depDuplicates
|
|
2880
|
+
duplicates: state.depDuplicates,
|
|
2881
|
+
scrollOffset: state.detailScrollOffset
|
|
2297
2882
|
}
|
|
2298
2883
|
),
|
|
2299
|
-
!state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */
|
|
2884
|
+
!state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx16(
|
|
2300
2885
|
DependencyList,
|
|
2301
2886
|
{
|
|
2302
2887
|
task: state.selectedTask,
|
|
@@ -2309,7 +2894,7 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2309
2894
|
addDepInput: state.addDepInput
|
|
2310
2895
|
}
|
|
2311
2896
|
),
|
|
2312
|
-
!state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */
|
|
2897
|
+
!state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */ jsx16(
|
|
2313
2898
|
TaskForm,
|
|
2314
2899
|
{
|
|
2315
2900
|
editingTask: state.activeView === ViewType.TaskEdit ? state.selectedTask : null,
|
|
@@ -2319,7 +2904,16 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2319
2904
|
onCancel: handleFormCancel
|
|
2320
2905
|
}
|
|
2321
2906
|
),
|
|
2322
|
-
!state.confirmDelete && state.activeView === ViewType.
|
|
2907
|
+
!state.confirmDelete && state.activeView === ViewType.EpicPicker && state.selectedTask && /* @__PURE__ */ jsx16(
|
|
2908
|
+
EpicPicker,
|
|
2909
|
+
{
|
|
2910
|
+
epics: state.epics,
|
|
2911
|
+
currentEpicId: state.selectedTask.parentId,
|
|
2912
|
+
onSelect: handleEpicPickerSelect,
|
|
2913
|
+
onCancel: handleEpicPickerCancel
|
|
2914
|
+
}
|
|
2915
|
+
),
|
|
2916
|
+
!state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx16(
|
|
2323
2917
|
ProjectSelector,
|
|
2324
2918
|
{
|
|
2325
2919
|
projects: state.projects,
|
|
@@ -2330,18 +2924,18 @@ ${state.selectedTask.additionalRequirements}`;
|
|
|
2330
2924
|
onCancel: handleProjectCancel
|
|
2331
2925
|
}
|
|
2332
2926
|
),
|
|
2333
|
-
!state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */
|
|
2334
|
-
!state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */
|
|
2927
|
+
!state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */ jsx16(ProjectForm, { onSave: handleProjectFormSave, onCancel: handleProjectFormCancel }),
|
|
2928
|
+
!state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx16(HelpOverlay, {})
|
|
2335
2929
|
] }),
|
|
2336
|
-
/* @__PURE__ */
|
|
2337
|
-
state.flash && /* @__PURE__ */
|
|
2930
|
+
/* @__PURE__ */ jsx16(Crumbs, { breadcrumbs: state.breadcrumbs }),
|
|
2931
|
+
state.flash && /* @__PURE__ */ jsx16(FlashMessage, { message: state.flash.message, level: state.flash.level })
|
|
2338
2932
|
] });
|
|
2339
2933
|
}
|
|
2340
2934
|
|
|
2341
2935
|
// src/tui/index.tsx
|
|
2342
|
-
import { jsx as
|
|
2936
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2343
2937
|
async function launchTUI(container, initialProject) {
|
|
2344
|
-
const instance = render(/* @__PURE__ */
|
|
2938
|
+
const instance = render(/* @__PURE__ */ jsx17(App, { container, initialProject }), {
|
|
2345
2939
|
exitOnCtrlC: true
|
|
2346
2940
|
});
|
|
2347
2941
|
await instance.waitUntilExit();
|
|
@@ -2349,4 +2943,4 @@ async function launchTUI(container, initialProject) {
|
|
|
2349
2943
|
export {
|
|
2350
2944
|
launchTUI
|
|
2351
2945
|
};
|
|
2352
|
-
//# sourceMappingURL=tui-
|
|
2946
|
+
//# sourceMappingURL=tui-5JJH67YY.js.map
|