loop-task 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/package.json +2 -2
  2. package/dist/board/App.js +0 -405
  3. package/dist/board/board-log.js +0 -18
  4. package/dist/board/components/ActionButtons.js +0 -46
  5. package/dist/board/components/BoardButton.js +0 -58
  6. package/dist/board/components/ConfirmModal.js +0 -36
  7. package/dist/board/components/ContextHelpModal.js +0 -22
  8. package/dist/board/components/CreateForm.js +0 -271
  9. package/dist/board/components/CreateProjectModal.js +0 -97
  10. package/dist/board/components/DeleteProjectConfirm.js +0 -53
  11. package/dist/board/components/DetailView.js +0 -25
  12. package/dist/board/components/EditProjectModal.js +0 -99
  13. package/dist/board/components/FilterBar.js +0 -17
  14. package/dist/board/components/Footer.js +0 -52
  15. package/dist/board/components/Header.js +0 -46
  16. package/dist/board/components/HelpModal.js +0 -65
  17. package/dist/board/components/Inspector.js +0 -12
  18. package/dist/board/components/LogModal.js +0 -82
  19. package/dist/board/components/Navigator.js +0 -84
  20. package/dist/board/components/ProjectsModal.js +0 -70
  21. package/dist/board/components/ProjectsPage.js +0 -134
  22. package/dist/board/components/RunHistory.js +0 -83
  23. package/dist/board/components/SearchBox.js +0 -10
  24. package/dist/board/components/SearchSelect.js +0 -132
  25. package/dist/board/components/TaskBrowser.js +0 -95
  26. package/dist/board/components/TaskFilterBar.js +0 -6
  27. package/dist/board/components/TaskForm.js +0 -155
  28. package/dist/board/components/Timeline.js +0 -5
  29. package/dist/board/daemon.js +0 -117
  30. package/dist/board/focus-context.js +0 -98
  31. package/dist/board/hooks/useBoardKeybindings.js +0 -344
  32. package/dist/board/hooks/useBreakpoint.js +0 -6
  33. package/dist/board/hooks/useHoverState.js +0 -11
  34. package/dist/board/hooks/useInputShortcuts.js +0 -61
  35. package/dist/board/hooks/useLogStream.js +0 -32
  36. package/dist/board/hooks/useLoopPolling.js +0 -28
  37. package/dist/board/hooks/useTabNav.js +0 -60
  38. package/dist/board/hooks/useTaskKeybindings.js +0 -120
  39. package/dist/board/index.js +0 -32
  40. package/dist/board/router.js +0 -16
  41. package/dist/board/state.js +0 -99
  42. package/dist/board/toast.js +0 -65
  43. package/dist/board/types.js +0 -1
  44. package/dist/cli.d.ts +0 -3
  45. package/dist/cli.d.ts.map +0 -1
  46. package/dist/cli.js.map +0 -1
  47. package/dist/client/commands.d.ts +0 -11
  48. package/dist/client/commands.d.ts.map +0 -1
  49. package/dist/client/commands.js.map +0 -1
  50. package/dist/client/ipc.d.ts +0 -5
  51. package/dist/client/ipc.d.ts.map +0 -1
  52. package/dist/client/ipc.js.map +0 -1
  53. package/dist/daemon/index.d.ts +0 -2
  54. package/dist/daemon/index.d.ts.map +0 -1
  55. package/dist/daemon/index.js.map +0 -1
  56. package/dist/daemon/manager.d.ts +0 -17
  57. package/dist/daemon/manager.d.ts.map +0 -1
  58. package/dist/daemon/manager.js.map +0 -1
  59. package/dist/daemon/server.d.ts +0 -15
  60. package/dist/daemon/server.d.ts.map +0 -1
  61. package/dist/daemon/server.js.map +0 -1
  62. package/dist/daemon/spawner.d.ts +0 -3
  63. package/dist/daemon/spawner.d.ts.map +0 -1
  64. package/dist/daemon/spawner.js.map +0 -1
  65. package/dist/daemon/state.d.ts +0 -15
  66. package/dist/daemon/state.d.ts.map +0 -1
  67. package/dist/daemon/state.js.map +0 -1
  68. package/dist/duration.d.ts +0 -3
  69. package/dist/duration.d.ts.map +0 -1
  70. package/dist/duration.js.map +0 -1
  71. package/dist/logger.d.ts +0 -11
  72. package/dist/logger.d.ts.map +0 -1
  73. package/dist/logger.js.map +0 -1
  74. package/dist/loop-config.d.ts +0 -13
  75. package/dist/loop-config.d.ts.map +0 -1
  76. package/dist/loop-config.js.map +0 -1
  77. package/dist/loop.d.ts +0 -46
  78. package/dist/loop.d.ts.map +0 -1
  79. package/dist/loop.js +0 -363
  80. package/dist/loop.js.map +0 -1
  81. package/dist/open-board.d.ts +0 -2
  82. package/dist/open-board.d.ts.map +0 -1
  83. package/dist/open-board.js +0 -19
  84. package/dist/open-board.js.map +0 -1
  85. package/dist/tui/app.d.ts +0 -57
  86. package/dist/tui/app.d.ts.map +0 -1
  87. package/dist/tui/app.js.map +0 -1
  88. package/dist/tui/components/ActionButtons.js +0 -49
  89. package/dist/tui/components/CommandBuilderField.js +0 -143
  90. package/dist/tui/components/CommandEditorModal.js +0 -159
  91. package/dist/tui/components/ConfirmModal.js +0 -9
  92. package/dist/tui/components/FilterBar.js +0 -13
  93. package/dist/tui/components/FocusableSearchSelect.js +0 -82
  94. package/dist/tui/components/Footer.js +0 -42
  95. package/dist/tui/components/PatchEditForm.js +0 -78
  96. package/dist/tui/components/SearchSelect.js +0 -67
  97. package/dist/tui/components/TaskFilterBar.js +0 -9
  98. package/dist/tui/create-loop.d.ts +0 -3
  99. package/dist/tui/create-loop.d.ts.map +0 -1
  100. package/dist/tui/create-loop.js +0 -90
  101. package/dist/tui/create-loop.js.map +0 -1
  102. package/dist/tui/dashboard.d.ts +0 -2
  103. package/dist/tui/dashboard.d.ts.map +0 -1
  104. package/dist/tui/dashboard.js +0 -45
  105. package/dist/tui/dashboard.js.map +0 -1
  106. package/dist/tui/events.d.ts +0 -3
  107. package/dist/tui/events.d.ts.map +0 -1
  108. package/dist/tui/events.js +0 -244
  109. package/dist/tui/events.js.map +0 -1
  110. package/dist/tui/popup.d.ts +0 -3
  111. package/dist/tui/popup.d.ts.map +0 -1
  112. package/dist/tui/popup.js +0 -58
  113. package/dist/tui/popup.js.map +0 -1
  114. package/dist/tui/render.d.ts +0 -9
  115. package/dist/tui/render.d.ts.map +0 -1
  116. package/dist/tui/render.js +0 -305
  117. package/dist/tui/render.js.map +0 -1
  118. package/dist/types.d.ts +0 -94
  119. package/dist/types.d.ts.map +0 -1
  120. package/dist/types.js.map +0 -1
  121. /package/dist/tui/{app.js → App.js} +0 -0
@@ -1,344 +0,0 @@
1
- import { useKeyboard } from "@opentui/react";
2
- import { cycleSortMode, cycleStatusFilter } from "../state.js";
3
- import { copyToClipboard } from "../../shared/clipboard.js";
4
- import { getActionCount, getActionKeys } from "../components/ActionButtons.js";
5
- const PANEL_ORDER = ["search", "project-filter", "status", "sort", "header-tasks", "header-projects", "header-new", "loops", "runs", "actions"];
6
- const PANEL_LEFT = {
7
- search: "actions",
8
- "project-filter": "search",
9
- status: "project-filter",
10
- sort: "status",
11
- "header-tasks": "sort",
12
- "header-projects": "header-tasks",
13
- "header-new": "header-projects",
14
- loops: "header-new",
15
- runs: "loops",
16
- actions: "runs",
17
- projects: "header-new",
18
- };
19
- const PANEL_RIGHT = {
20
- search: "project-filter",
21
- "project-filter": "status",
22
- status: "sort",
23
- sort: "header-tasks",
24
- "header-tasks": "header-projects",
25
- "header-projects": "header-new",
26
- "header-new": "loops",
27
- loops: "runs",
28
- runs: "actions",
29
- actions: "search",
30
- projects: "loops",
31
- };
32
- function nextPanel(current, direction) {
33
- return direction === "left" ? PANEL_LEFT[current] : PANEL_RIGHT[current];
34
- }
35
- const CONFIRM_KEYS = {
36
- left: (p) => p.setConfirmChoice((c) => (c === 1 ? 0 : 1)),
37
- right: (p) => p.setConfirmChoice((c) => (c === 1 ? 0 : 1)),
38
- tab: (p) => p.setConfirmChoice((c) => (c === 1 ? 0 : 1)),
39
- y: (p) => { const a = p.confirm.action; p.setConfirm(null); void a(); },
40
- n: (p) => p.setConfirm(null),
41
- escape: (p) => p.setConfirm(null),
42
- return: (p) => {
43
- if (p.confirmChoice === 1) {
44
- const a = p.confirm.action;
45
- p.setConfirm(null);
46
- void a();
47
- }
48
- else
49
- p.setConfirm(null);
50
- },
51
- enter: (p) => {
52
- if (p.confirmChoice === 1) {
53
- const a = p.confirm.action;
54
- p.setConfirm(null);
55
- void a();
56
- }
57
- else
58
- p.setConfirm(null);
59
- },
60
- };
61
- const VIEW_ESCAPE = {
62
- create: (p) => { p.setEditTarget(null); p.onBack(); },
63
- "task-create": (p) => { p.setEditTask(null); p.onBack(); },
64
- "task-edit": (p) => { p.setEditTask(null); p.onBack(); },
65
- "task-list": (p) => p.onBack(),
66
- projects: (p) => p.onBack(),
67
- };
68
- const OVERLAY_DISMISS = {
69
- log: (p) => { p.setLogModalRun(null); return true; },
70
- help: (p) => { p.setHelpOpen(false); return true; },
71
- search: (p, key) => {
72
- p.setSearchActive(false);
73
- p.setFocusedPanel(key === "return" || key === "enter" ? "loops" : "search");
74
- return true;
75
- },
76
- };
77
- const GLOBAL_KEYS = {
78
- escape: (p) => { p.destroyLogSocket(); p.onQuit(); },
79
- h: (p) => p.setHelpOpen(true),
80
- n: (p) => { p.setEditTarget(null); p.push("create"); },
81
- t: (p) => { p.setEditTask(null); p.push("task-create"); },
82
- e: (p) => p.onAction("edit"),
83
- d: (p) => p.onAction("delete"),
84
- delete: (p) => p.onAction("delete"),
85
- c: (p) => p.onAction("clone"),
86
- p: (p) => p.onAction("pause-or-play"),
87
- s: (p) => p.onAction("stop"),
88
- f: (p) => p.onAction("trigger"),
89
- };
90
- const panelHandlers = {
91
- search: (key, p) => {
92
- if (key === "return" || key === "enter") {
93
- p.setSearchActive(true);
94
- return true;
95
- }
96
- return false;
97
- },
98
- "project-filter": (key, p) => {
99
- if (key === "return" || key === "enter") {
100
- p.onSelectProject?.();
101
- return true;
102
- }
103
- return false;
104
- },
105
- status: (key, p) => {
106
- if (key === "return" || key === "enter") {
107
- p.setFilters((prev) => ({ ...prev, status: cycleStatusFilter(prev.status) }));
108
- return true;
109
- }
110
- return false;
111
- },
112
- sort: (key, p) => {
113
- if (key === "return" || key === "enter") {
114
- p.setSort((prev) => cycleSortMode(prev));
115
- return true;
116
- }
117
- return false;
118
- },
119
- "header-tasks": (key, p) => {
120
- if (key === "return" || key === "enter") {
121
- p.onViewProjects?.();
122
- return true;
123
- }
124
- return false;
125
- },
126
- "header-projects": (key, p) => {
127
- if (key === "return" || key === "enter") {
128
- p.onViewTasks?.();
129
- return true;
130
- }
131
- return false;
132
- },
133
- "header-new": (key, p) => {
134
- if (key === "return" || key === "enter") {
135
- p.onAddLoop?.();
136
- return true;
137
- }
138
- return false;
139
- },
140
- loops: (key, p) => {
141
- if (key === "up" || key === "k") {
142
- p.setSelectedIndex((i) => Math.max(0, i - 1));
143
- return true;
144
- }
145
- if (key === "down" || key === "j") {
146
- p.setSelectedIndex((i) => Math.min(p.selected ? p.selected.runHistory.length - 1 : 0, i + 1));
147
- return true;
148
- }
149
- if (key === "return" || key === "enter") {
150
- p.setFocusedPanel("actions");
151
- return true;
152
- }
153
- return false;
154
- },
155
- runs: (key, p) => {
156
- if (key === "up" || key === "k") {
157
- p.setSelectedRunIndex((i) => Math.max(0, i - 1));
158
- return true;
159
- }
160
- if (key === "down" || key === "j") {
161
- p.setSelectedRunIndex((i) => Math.min(p.selectedRunCount - 1, i + 1));
162
- return true;
163
- }
164
- if ((key === "return" || key === "enter") && p.selected && p.selected.runHistory.length > 0) {
165
- const runs = p.selected.runHistory;
166
- const idx = Math.min(p.selectedRunIndex, runs.length - 1);
167
- p.onOpenRunLog(runs[runs.length - 1 - idx]);
168
- return true;
169
- }
170
- return false;
171
- },
172
- actions: (key, p) => {
173
- const count = p.selected ? getActionCount(p.selected.status) : 0;
174
- const keys = p.selected ? getActionKeys(p.selected.status) : [];
175
- if (key === "up" || key === "k") {
176
- p.setSelectedAction((i) => Math.max(0, Math.min(i, count - 1)));
177
- return true;
178
- }
179
- if (key === "down" || key === "j") {
180
- p.setSelectedAction((i) => Math.min(count - 1, i + 1));
181
- return true;
182
- }
183
- if (key === "return" || key === "enter") {
184
- p.onAction(keys[p.selectedAction] ?? "edit");
185
- return true;
186
- }
187
- return false;
188
- },
189
- projects: () => false,
190
- };
191
- const BOARD_SHORTCUTS = {
192
- "/": (p) => p.setSearchActive(true),
193
- o: (p) => p.setSort((prev) => cycleSortMode(prev)),
194
- x: (p) => p.setFilters((prev) => ({ ...prev, status: cycleStatusFilter(prev.status) })),
195
- r: (p) => p.onSelectProject?.(),
196
- };
197
- export function useBoardKeybindings(params) {
198
- const { confirm, confirmChoice, setConfirm, setConfirmChoice, helpOpen, setHelpOpen, searchActive, setSearchActive, view, push, pop, setEditTarget, setEditTask, selected, visibleCount, setSelectedIndex, setFilters, setSort, onQuit, destroyLogSocket, logModalRun, setLogModalRun, logModalLines, selectedRunIndex, setSelectedRunIndex, selectedRunCount, focusedPanel, setFocusedPanel, selectedAction, setSelectedAction, onAction, onOpenRunLog, refreshTasks, onViewTasks, onViewProjects, onViewLoops, onAddLoop, onAddTask, onSelectProject, } = params;
199
- useKeyboard((key) => {
200
- const name = key.name;
201
- if (key.ctrl && name === "c") {
202
- if (!logModalRun && !confirm && !helpOpen) {
203
- onQuit();
204
- key.preventDefault();
205
- key.stopPropagation();
206
- return;
207
- }
208
- }
209
- if (confirm) {
210
- CONFIRM_KEYS[name]?.({ confirm, confirmChoice, setConfirm, setConfirmChoice });
211
- key.preventDefault();
212
- return;
213
- }
214
- if (logModalRun) {
215
- if (name === "escape" || name === "q") {
216
- OVERLAY_DISMISS.log({ setLogModalRun, setHelpOpen, setSearchActive, setFocusedPanel }, name);
217
- key.preventDefault();
218
- return;
219
- }
220
- if (key.ctrl && name === "c") {
221
- copyToClipboard(logModalLines.join("\n"));
222
- key.preventDefault();
223
- return;
224
- }
225
- return;
226
- }
227
- if (helpOpen && (name === "h" || name === "escape")) {
228
- OVERLAY_DISMISS.help({ setLogModalRun, setHelpOpen, setSearchActive, setFocusedPanel }, name);
229
- key.preventDefault();
230
- return;
231
- }
232
- if (helpOpen)
233
- return;
234
- if (searchActive) {
235
- if (name === "escape") {
236
- setSearchActive(false);
237
- setFocusedPanel("loops");
238
- key.preventDefault();
239
- return;
240
- }
241
- if (name === "up" || name === "k" || name === "down" || name === "j") {
242
- const handler = panelHandlers["loops"];
243
- if (handler) {
244
- handler(name, {
245
- setSearchActive, setFilters, setSort, setEditTarget, push,
246
- setSelectedIndex, setSelectedRunIndex, setFocusedPanel, selectedRunCount, selected,
247
- selectedRunIndex, setSelectedAction, selectedAction, onAction, onOpenRunLog,
248
- refreshTasks, onViewTasks, onViewProjects, onAddLoop,
249
- });
250
- key.preventDefault();
251
- }
252
- return;
253
- }
254
- return;
255
- }
256
- if (view !== "board" && name === "escape") {
257
- VIEW_ESCAPE[view]?.({ setEditTarget, setEditTask, onBack: pop });
258
- key.preventDefault();
259
- return;
260
- }
261
- if (view !== "board" && (name === "return" || name === "enter")) {
262
- if (view === "task-list") {
263
- if (focusedPanel === "header-tasks") {
264
- onViewProjects?.();
265
- key.preventDefault();
266
- return;
267
- }
268
- if (focusedPanel === "header-projects") {
269
- onViewLoops?.();
270
- key.preventDefault();
271
- return;
272
- }
273
- if (focusedPanel === "header-new") {
274
- onAddTask?.();
275
- key.preventDefault();
276
- return;
277
- }
278
- }
279
- else if (view === "projects") {
280
- if (focusedPanel === "header-tasks") {
281
- onViewLoops?.();
282
- key.preventDefault();
283
- return;
284
- }
285
- if (focusedPanel === "header-projects") {
286
- onViewTasks?.();
287
- key.preventDefault();
288
- return;
289
- }
290
- if (focusedPanel === "header-new") {
291
- onAddLoop?.();
292
- key.preventDefault();
293
- return;
294
- }
295
- }
296
- else if (view === "create" || view === "task-create" || view === "task-edit") {
297
- if (focusedPanel === "header-tasks") {
298
- onViewProjects?.();
299
- key.preventDefault();
300
- return;
301
- }
302
- if (focusedPanel === "header-projects") {
303
- onViewTasks?.();
304
- key.preventDefault();
305
- return;
306
- }
307
- if (focusedPanel === "header-new") {
308
- onAddLoop?.();
309
- key.preventDefault();
310
- return;
311
- }
312
- }
313
- }
314
- if (view !== "board")
315
- return;
316
- const globalHandler = GLOBAL_KEYS[name];
317
- if (globalHandler) {
318
- globalHandler({ destroyLogSocket, onQuit, setHelpOpen, setEditTarget, setEditTask, push, onAction });
319
- key.preventDefault();
320
- return;
321
- }
322
- const isActionPanel = typeof focusedPanel === "string" && focusedPanel.startsWith("action-");
323
- const panelHandler = isActionPanel ? panelHandlers["actions"] : panelHandlers[focusedPanel];
324
- if (panelHandler?.(name, {
325
- setSearchActive, setFilters, setSort, setEditTarget, push,
326
- setSelectedIndex, setSelectedRunIndex, setFocusedPanel, selectedRunCount, selected,
327
- selectedRunIndex, setSelectedAction, selectedAction, onAction, onOpenRunLog,
328
- refreshTasks, onViewTasks, onViewProjects, onAddLoop,
329
- })) {
330
- key.preventDefault();
331
- return;
332
- }
333
- const shortcut = BOARD_SHORTCUTS[name];
334
- if (shortcut) {
335
- shortcut({
336
- setSearchActive, setFilters, setSort, setEditTarget, push,
337
- setSelectedIndex, setSelectedRunIndex, setFocusedPanel, selectedRunCount, selected,
338
- selectedRunIndex, setSelectedAction, selectedAction, onAction, onOpenRunLog,
339
- onSelectProject,
340
- });
341
- key.preventDefault();
342
- }
343
- });
344
- }
@@ -1,6 +0,0 @@
1
- import { useTerminalDimensions } from "@opentui/react";
2
- import { BOARD_BREAKPOINT_WIDTH } from "../../config/constants.js";
3
- export function useBreakpoint() {
4
- const { width } = useTerminalDimensions();
5
- return width < BOARD_BREAKPOINT_WIDTH ? "narrow" : "wide";
6
- }
@@ -1,11 +0,0 @@
1
- import { useState } from "react";
2
- export function useHoverState() {
3
- const [isHovered, setIsHovered] = useState(false);
4
- return {
5
- isHovered,
6
- hoverProps: {
7
- onMouseOver: () => setIsHovered(true),
8
- onMouseOut: () => setIsHovered(false),
9
- },
10
- };
11
- }
@@ -1,61 +0,0 @@
1
- import { useKeyboard } from "@opentui/react";
2
- import { copyToClipboard, readFromClipboard } from "../../shared/clipboard.js";
3
- export function useInputShortcuts(getActiveInput) {
4
- useKeyboard((key) => {
5
- if (!key.ctrl)
6
- return;
7
- const input = getActiveInput();
8
- if (!input)
9
- return;
10
- const name = key.name;
11
- if (name === "c") {
12
- try {
13
- const text = input.getSelectedText();
14
- if (text) {
15
- copyToClipboard(text);
16
- }
17
- }
18
- catch (e) {
19
- console.error(`[loop-task] copy failed: ${e}`);
20
- }
21
- key.preventDefault();
22
- key.stopPropagation();
23
- return;
24
- }
25
- if (name === "x") {
26
- try {
27
- const text = input.getSelectedText();
28
- if (text) {
29
- copyToClipboard(text);
30
- input.deleteSelection();
31
- }
32
- }
33
- catch (e) {
34
- console.error(`[loop-task] cut failed: ${e}`);
35
- }
36
- key.preventDefault();
37
- key.stopPropagation();
38
- return;
39
- }
40
- if (name === "v") {
41
- try {
42
- const text = readFromClipboard();
43
- if (text) {
44
- input.insertText(text);
45
- }
46
- }
47
- catch (e) {
48
- console.error(`[loop-task] paste failed: ${e}`);
49
- }
50
- key.preventDefault();
51
- key.stopPropagation();
52
- return;
53
- }
54
- if (name === "a") {
55
- input.selectAll();
56
- key.preventDefault();
57
- key.stopPropagation();
58
- return;
59
- }
60
- });
61
- }
@@ -1,32 +0,0 @@
1
- import { useEffect, useRef, useState } from "react";
2
- import { streamLogs } from "../daemon.js";
3
- import { t } from "../../i18n/index.js";
4
- import { LOG_LINES_MAX } from "../../config/constants.js";
5
- export function useLogStream(selectedId, view, onError) {
6
- const [logLines, setLogLines] = useState([]);
7
- const logSocket = useRef(null);
8
- useEffect(() => {
9
- logSocket.current?.destroy();
10
- logSocket.current = null;
11
- setLogLines([]);
12
- if (!selectedId || view !== "board") {
13
- return;
14
- }
15
- setLogLines([t("board.logWaiting")]);
16
- const socket = streamLogs(selectedId, (line) => setLogLines((prev) => {
17
- const next = prev[0] === t("board.logWaiting") ? [] : prev;
18
- return [...next, line].slice(-LOG_LINES_MAX);
19
- }), onError);
20
- logSocket.current = socket;
21
- return () => {
22
- socket.destroy();
23
- if (logSocket.current === socket) {
24
- logSocket.current = null;
25
- }
26
- };
27
- }, [selectedId, view]);
28
- return {
29
- logLines,
30
- destroy: () => logSocket.current?.destroy(),
31
- };
32
- }
@@ -1,28 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import { listLoops } from "../daemon.js";
3
- import { POLL_MS } from "../../config/constants.js";
4
- export function useLoopPolling() {
5
- const [loops, setLoops] = useState([]);
6
- const [daemonStatus, setDaemonStatus] = useState("starting");
7
- const lastSerialized = useRef("");
8
- const refresh = useCallback(async () => {
9
- try {
10
- const next = await listLoops();
11
- const serialized = JSON.stringify(next);
12
- if (serialized !== lastSerialized.current) {
13
- lastSerialized.current = serialized;
14
- setLoops(next);
15
- }
16
- setDaemonStatus("connected");
17
- }
18
- catch {
19
- setDaemonStatus("error");
20
- }
21
- }, []);
22
- useEffect(() => {
23
- void refresh();
24
- const timer = setInterval(() => void refresh(), POLL_MS);
25
- return () => clearInterval(timer);
26
- }, [refresh]);
27
- return { loops, daemonStatus, refresh };
28
- }
@@ -1,60 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from "react";
2
- import { useKeyboard } from "@opentui/react";
3
- export function useTabNav(items, options) {
4
- const initial = options?.initialIndex ?? 0;
5
- const [focusIndex, setFocusIndex] = useState(Math.min(initial, Math.max(0, items.length - 1)));
6
- const [enabled, setEnabled] = useState(true);
7
- const focusIndexRef = useRef(focusIndex);
8
- focusIndexRef.current = focusIndex;
9
- const itemsRef = useRef(items);
10
- itemsRef.current = items;
11
- const onCycleOutRef = useRef(options?.onCycleOut);
12
- onCycleOutRef.current = options?.onCycleOut;
13
- const enabledRef = useRef(enabled);
14
- enabledRef.current = enabled;
15
- useEffect(() => {
16
- setFocusIndex((i) => Math.min(i, Math.max(0, items.length - 1)));
17
- }, [items.length]);
18
- useKeyboard((key) => {
19
- if (!enabledRef.current)
20
- return;
21
- if (key.name !== "tab")
22
- return;
23
- const currentItems = itemsRef.current;
24
- const currentIdx = focusIndexRef.current;
25
- const lastIndex = currentItems.length - 1;
26
- if (lastIndex < 0)
27
- return;
28
- const direction = key.shift ? "left" : "right";
29
- if (direction === "right") {
30
- if (currentIdx >= lastIndex) {
31
- if (onCycleOutRef.current) {
32
- onCycleOutRef.current("right");
33
- }
34
- else {
35
- setFocusIndex(0);
36
- }
37
- }
38
- else {
39
- setFocusIndex(currentIdx + 1);
40
- }
41
- }
42
- else {
43
- if (currentIdx <= 0) {
44
- if (onCycleOutRef.current) {
45
- onCycleOutRef.current("left");
46
- }
47
- else {
48
- setFocusIndex(lastIndex);
49
- }
50
- }
51
- else {
52
- setFocusIndex(currentIdx - 1);
53
- }
54
- }
55
- key.preventDefault();
56
- });
57
- const focusedItem = items[focusIndex];
58
- const isFocused = useCallback((item) => item === focusedItem, [focusedItem]);
59
- return { focusIndex, setFocusIndex, focusedItem, isFocused, enabled, setEnabled };
60
- }
@@ -1,120 +0,0 @@
1
- import { useKeyboard } from "@opentui/react";
2
- export function useTaskKeybindings(params) {
3
- const { confirm, view, tasks, taskSelectedIndex, setTaskSelectedIndex, taskSelectedAction, setTaskSelectedAction, taskFocusedPanel, setTaskFocusedPanel, taskSearchActive, setTaskSearchActive, taskQuery, setTaskQuery, onTaskAction, onCancel, onCreateTask, onToggleContextHelp, headerFocused = false, selectable = true, } = params;
4
- const taskActions = selectable
5
- ? ["select", "edit", "delete"]
6
- : ["edit", "delete"];
7
- const taskActionCount = taskActions.length;
8
- useKeyboard((key) => {
9
- if (view !== "task-list")
10
- return;
11
- if (headerFocused)
12
- return;
13
- const name = key.name;
14
- if (confirm)
15
- return;
16
- if (taskSearchActive) {
17
- if (name === "escape") {
18
- setTaskSearchActive(false);
19
- setTaskFocusedPanel("tasks");
20
- key.preventDefault();
21
- return;
22
- }
23
- if (name === "up" || name === "k") {
24
- setTaskSelectedIndex((i) => Math.max(0, i - 1));
25
- key.preventDefault();
26
- return;
27
- }
28
- if (name === "down" || name === "j") {
29
- setTaskSelectedIndex((i) => Math.min(tasks.length - 1, i + 1));
30
- key.preventDefault();
31
- return;
32
- }
33
- return;
34
- }
35
- if (name === "escape") {
36
- onCancel();
37
- key.preventDefault();
38
- return;
39
- }
40
- if (name === "?") {
41
- onToggleContextHelp();
42
- key.preventDefault();
43
- return;
44
- }
45
- if (name === "n") {
46
- onCreateTask();
47
- key.preventDefault();
48
- return;
49
- }
50
- if (name === "/" && taskFocusedPanel === "tasks") {
51
- setTaskSearchActive(true);
52
- key.preventDefault();
53
- return;
54
- }
55
- if (taskFocusedPanel === "tasks") {
56
- if (name === "up" || name === "k") {
57
- setTaskSelectedIndex((i) => Math.max(0, i - 1));
58
- key.preventDefault();
59
- return;
60
- }
61
- if (name === "down" || name === "j") {
62
- setTaskSelectedIndex((i) => Math.min(tasks.length - 1, i + 1));
63
- key.preventDefault();
64
- return;
65
- }
66
- if (name === "return" || name === "enter") {
67
- setTaskFocusedPanel("actions");
68
- key.preventDefault();
69
- return;
70
- }
71
- if (name === "e" && tasks[taskSelectedIndex]) {
72
- onTaskAction("edit");
73
- key.preventDefault();
74
- return;
75
- }
76
- if (name === "d" && tasks[taskSelectedIndex]) {
77
- onTaskAction("delete");
78
- key.preventDefault();
79
- return;
80
- }
81
- if (name === "delete" && tasks[taskSelectedIndex]) {
82
- onTaskAction("delete");
83
- key.preventDefault();
84
- return;
85
- }
86
- if (selectable && name === "s" && tasks[taskSelectedIndex]) {
87
- onTaskAction("select");
88
- key.preventDefault();
89
- return;
90
- }
91
- }
92
- if (taskFocusedPanel === "actions") {
93
- if (name === "up" || name === "k") {
94
- setTaskSelectedAction((i) => Math.max(0, i - 1));
95
- key.preventDefault();
96
- return;
97
- }
98
- if (name === "down" || name === "j") {
99
- setTaskSelectedAction((i) => Math.min(taskActionCount - 1, i + 1));
100
- key.preventDefault();
101
- return;
102
- }
103
- if (name === "return" || name === "enter") {
104
- onTaskAction(taskActions[taskSelectedAction] ?? "select");
105
- key.preventDefault();
106
- return;
107
- }
108
- if (name === "d") {
109
- onTaskAction("delete");
110
- key.preventDefault();
111
- return;
112
- }
113
- if (selectable && name === "s") {
114
- onTaskAction("select");
115
- key.preventDefault();
116
- return;
117
- }
118
- }
119
- });
120
- }