loop-task 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/config/constants.js +10 -0
  2. package/dist/i18n/en.json +12 -0
  3. package/dist/loop-config.js +59 -0
  4. package/dist/shared/useUndoRedo.js +79 -0
  5. package/dist/tui/components/CodeEditorModal.js +245 -0
  6. package/dist/tui/components/CodeEditorPreview.js +14 -0
  7. package/dist/tui/components/CreateForm.js +12 -10
  8. package/dist/tui/components/TaskForm.js +12 -12
  9. package/dist/tui/utils/syntax.js +128 -0
  10. package/package.json +2 -2
  11. package/dist/board/App.js +0 -405
  12. package/dist/board/board-log.js +0 -18
  13. package/dist/board/components/ActionButtons.js +0 -46
  14. package/dist/board/components/BoardButton.js +0 -58
  15. package/dist/board/components/ConfirmModal.js +0 -36
  16. package/dist/board/components/ContextHelpModal.js +0 -22
  17. package/dist/board/components/CreateForm.js +0 -271
  18. package/dist/board/components/CreateProjectModal.js +0 -97
  19. package/dist/board/components/DeleteProjectConfirm.js +0 -53
  20. package/dist/board/components/DetailView.js +0 -25
  21. package/dist/board/components/EditProjectModal.js +0 -99
  22. package/dist/board/components/FilterBar.js +0 -17
  23. package/dist/board/components/Footer.js +0 -52
  24. package/dist/board/components/Header.js +0 -46
  25. package/dist/board/components/HelpModal.js +0 -65
  26. package/dist/board/components/Inspector.js +0 -12
  27. package/dist/board/components/LogModal.js +0 -82
  28. package/dist/board/components/Navigator.js +0 -84
  29. package/dist/board/components/ProjectsModal.js +0 -70
  30. package/dist/board/components/ProjectsPage.js +0 -134
  31. package/dist/board/components/RunHistory.js +0 -83
  32. package/dist/board/components/SearchBox.js +0 -10
  33. package/dist/board/components/SearchSelect.js +0 -132
  34. package/dist/board/components/TaskBrowser.js +0 -95
  35. package/dist/board/components/TaskFilterBar.js +0 -6
  36. package/dist/board/components/TaskForm.js +0 -155
  37. package/dist/board/components/Timeline.js +0 -5
  38. package/dist/board/daemon.js +0 -117
  39. package/dist/board/focus-context.js +0 -98
  40. package/dist/board/hooks/useBoardKeybindings.js +0 -344
  41. package/dist/board/hooks/useBreakpoint.js +0 -6
  42. package/dist/board/hooks/useHoverState.js +0 -11
  43. package/dist/board/hooks/useInputShortcuts.js +0 -61
  44. package/dist/board/hooks/useLogStream.js +0 -32
  45. package/dist/board/hooks/useLoopPolling.js +0 -28
  46. package/dist/board/hooks/useTabNav.js +0 -60
  47. package/dist/board/hooks/useTaskKeybindings.js +0 -120
  48. package/dist/board/index.js +0 -32
  49. package/dist/board/router.js +0 -16
  50. package/dist/board/state.js +0 -99
  51. package/dist/board/toast.js +0 -65
  52. package/dist/board/types.js +0 -1
  53. package/dist/cli.d.ts +0 -3
  54. package/dist/cli.d.ts.map +0 -1
  55. package/dist/cli.js.map +0 -1
  56. package/dist/client/commands.d.ts +0 -11
  57. package/dist/client/commands.d.ts.map +0 -1
  58. package/dist/client/commands.js.map +0 -1
  59. package/dist/client/ipc.d.ts +0 -5
  60. package/dist/client/ipc.d.ts.map +0 -1
  61. package/dist/client/ipc.js.map +0 -1
  62. package/dist/daemon/index.d.ts +0 -2
  63. package/dist/daemon/index.d.ts.map +0 -1
  64. package/dist/daemon/index.js.map +0 -1
  65. package/dist/daemon/manager.d.ts +0 -17
  66. package/dist/daemon/manager.d.ts.map +0 -1
  67. package/dist/daemon/manager.js.map +0 -1
  68. package/dist/daemon/server.d.ts +0 -15
  69. package/dist/daemon/server.d.ts.map +0 -1
  70. package/dist/daemon/server.js.map +0 -1
  71. package/dist/daemon/spawner.d.ts +0 -3
  72. package/dist/daemon/spawner.d.ts.map +0 -1
  73. package/dist/daemon/spawner.js.map +0 -1
  74. package/dist/daemon/state.d.ts +0 -15
  75. package/dist/daemon/state.d.ts.map +0 -1
  76. package/dist/daemon/state.js.map +0 -1
  77. package/dist/duration.d.ts +0 -3
  78. package/dist/duration.d.ts.map +0 -1
  79. package/dist/duration.js.map +0 -1
  80. package/dist/logger.d.ts +0 -11
  81. package/dist/logger.d.ts.map +0 -1
  82. package/dist/logger.js.map +0 -1
  83. package/dist/loop-config.d.ts +0 -13
  84. package/dist/loop-config.d.ts.map +0 -1
  85. package/dist/loop-config.js.map +0 -1
  86. package/dist/loop.d.ts +0 -46
  87. package/dist/loop.d.ts.map +0 -1
  88. package/dist/loop.js +0 -363
  89. package/dist/loop.js.map +0 -1
  90. package/dist/open-board.d.ts +0 -2
  91. package/dist/open-board.d.ts.map +0 -1
  92. package/dist/open-board.js +0 -19
  93. package/dist/open-board.js.map +0 -1
  94. package/dist/tui/app.d.ts +0 -57
  95. package/dist/tui/app.d.ts.map +0 -1
  96. package/dist/tui/app.js.map +0 -1
  97. package/dist/tui/components/ActionButtons.js +0 -49
  98. package/dist/tui/components/CommandBuilderField.js +0 -143
  99. package/dist/tui/components/CommandEditorModal.js +0 -159
  100. package/dist/tui/components/ConfirmModal.js +0 -9
  101. package/dist/tui/components/FilterBar.js +0 -13
  102. package/dist/tui/components/FocusableSearchSelect.js +0 -82
  103. package/dist/tui/components/Footer.js +0 -42
  104. package/dist/tui/components/InlineCommandEditor.js +0 -143
  105. package/dist/tui/components/PatchEditForm.js +0 -78
  106. package/dist/tui/components/SearchSelect.js +0 -67
  107. package/dist/tui/components/TaskFilterBar.js +0 -9
  108. package/dist/tui/create-loop.d.ts +0 -3
  109. package/dist/tui/create-loop.d.ts.map +0 -1
  110. package/dist/tui/create-loop.js +0 -90
  111. package/dist/tui/create-loop.js.map +0 -1
  112. package/dist/tui/dashboard.d.ts +0 -2
  113. package/dist/tui/dashboard.d.ts.map +0 -1
  114. package/dist/tui/dashboard.js +0 -45
  115. package/dist/tui/dashboard.js.map +0 -1
  116. package/dist/tui/events.d.ts +0 -3
  117. package/dist/tui/events.d.ts.map +0 -1
  118. package/dist/tui/events.js +0 -244
  119. package/dist/tui/events.js.map +0 -1
  120. package/dist/tui/popup.d.ts +0 -3
  121. package/dist/tui/popup.d.ts.map +0 -1
  122. package/dist/tui/popup.js +0 -58
  123. package/dist/tui/popup.js.map +0 -1
  124. package/dist/tui/render.d.ts +0 -9
  125. package/dist/tui/render.d.ts.map +0 -1
  126. package/dist/tui/render.js +0 -305
  127. package/dist/tui/render.js.map +0 -1
  128. package/dist/types.d.ts +0 -94
  129. package/dist/types.d.ts.map +0 -1
  130. package/dist/types.js.map +0 -1
  131. /package/dist/tui/{app.js → App.js} +0 -0
@@ -1,117 +0,0 @@
1
- import { sendRequest, streamRequest } from "../client/ipc.js";
2
- import { t } from "../i18n/index.js";
3
- import { LOG_TAIL_DEFAULT } from "../config/constants.js";
4
- function expectOk(message, type) {
5
- if (type !== "ok") {
6
- throw new Error(message);
7
- }
8
- }
9
- export async function listLoops() {
10
- const response = await sendRequest({ type: "list" });
11
- if (response.type !== "ok") {
12
- throw new Error(response.message);
13
- }
14
- return response.data;
15
- }
16
- export async function pauseLoop(id) {
17
- const response = await sendRequest({ type: "pause", payload: { id } });
18
- expectOk(response.message ?? t("errors.pauseFailed"), response.type);
19
- }
20
- export async function resumeLoop(id) {
21
- const response = await sendRequest({ type: "resume", payload: { id } });
22
- expectOk(response.message ?? t("errors.resumeFailed"), response.type);
23
- }
24
- export async function stopLoop(id) {
25
- const response = await sendRequest({ type: "stop-loop", payload: { id } });
26
- expectOk(response.message ?? "Stop failed", response.type);
27
- }
28
- export async function playLoop(id) {
29
- const response = await sendRequest({ type: "play-loop", payload: { id } });
30
- expectOk(response.message ?? "Play failed", response.type);
31
- }
32
- export async function triggerLoop(id) {
33
- const response = await sendRequest({ type: "trigger", payload: { id } });
34
- expectOk(response.message ?? t("errors.forceRunFailed"), response.type);
35
- }
36
- export async function deleteLoop(id) {
37
- const response = await sendRequest({ type: "delete", payload: { id } });
38
- expectOk(response.message ?? t("errors.deleteFailed"), response.type);
39
- }
40
- export async function createLoop(options, intervalHuman) {
41
- const response = await sendRequest({
42
- type: "start",
43
- payload: { ...options, intervalHuman },
44
- });
45
- if (response.type !== "ok") {
46
- throw new Error(response.message);
47
- }
48
- return response.data.id;
49
- }
50
- export async function updateLoop(id, options, intervalHuman) {
51
- const response = await sendRequest({
52
- type: "update",
53
- payload: { id, ...options, intervalHuman },
54
- });
55
- if (response.type !== "ok") {
56
- throw new Error(response.message);
57
- }
58
- return response.data.id;
59
- }
60
- export function streamLogs(id, onLine, onError) {
61
- return streamRequest({ type: "logs", payload: { id, follow: true, tail: LOG_TAIL_DEFAULT } }, onLine, () => { }, onError);
62
- }
63
- export async function fetchRunLog(id, runNumber) {
64
- const response = await sendRequest({ type: "run-log", payload: { id, runNumber } });
65
- if (response.type !== "ok") {
66
- throw new Error(response.message ?? "Failed to fetch run log");
67
- }
68
- return response.data ?? "";
69
- }
70
- export function streamRunLog(id, runNumber, onLine, onEnd, onError) {
71
- return streamRequest({ type: "run-log-stream", payload: { id, runNumber } }, onLine, onEnd, onError);
72
- }
73
- export async function listTasks() {
74
- const response = await sendRequest({ type: "task-list" });
75
- if (response.type !== "ok") {
76
- throw new Error(response.message);
77
- }
78
- return response.data;
79
- }
80
- export async function createTask(payload) {
81
- const response = await sendRequest({ type: "task-create", payload });
82
- if (response.type !== "ok") {
83
- throw new Error(response.message);
84
- }
85
- return response.data;
86
- }
87
- export async function updateTask(id, payload) {
88
- const response = await sendRequest({ type: "task-update", payload: { id, ...payload } });
89
- if (response.type !== "ok") {
90
- throw new Error(response.message);
91
- }
92
- return response.data;
93
- }
94
- export async function deleteTask(id) {
95
- const response = await sendRequest({ type: "task-delete", payload: { id } });
96
- expectOk(response.message ?? "Task delete failed", response.type);
97
- }
98
- export async function listProjects() {
99
- const response = await sendRequest({ type: "project-list" });
100
- if (response.type !== "ok")
101
- throw new Error(response.message);
102
- return response.data;
103
- }
104
- export async function createProject(name, color) {
105
- const response = await sendRequest({ type: "project-create", payload: { name, color } });
106
- if (response.type !== "ok")
107
- throw new Error(response.message);
108
- return response.data;
109
- }
110
- export async function updateProject(id, name, color) {
111
- const response = await sendRequest({ type: "project-update", payload: { id, name, color } });
112
- expectOk(response.message ?? t("project.error.updateFailed"), response.type);
113
- }
114
- export async function deleteProject(id) {
115
- const response = await sendRequest({ type: "project-delete", payload: { id } });
116
- expectOk(response.message ?? t("project.error.deleteFailed"), response.type);
117
- }
@@ -1,98 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useState, useCallback, useRef, useEffect } from "react";
3
- import { useKeyboard } from "@opentui/react";
4
- const FocusContext = createContext(null);
5
- export function useFocusContext() {
6
- return useContext(FocusContext);
7
- }
8
- export function FocusProvider(props) {
9
- const itemsRef = useRef(new Map());
10
- const [focusedOrder, setFocusedOrderState] = useState(null);
11
- const [active, setActive] = useState(true);
12
- useEffect(() => {
13
- if (focusedOrder === null && itemsRef.current.size > 0) {
14
- const minOrder = Math.min(...itemsRef.current.keys());
15
- setFocusedOrderState(minOrder);
16
- }
17
- }, [itemsRef.current.size]);
18
- const registerItem = useCallback((order, id) => {
19
- itemsRef.current.set(order, { order, id });
20
- if (focusedOrder === null) {
21
- setFocusedOrderState(order);
22
- }
23
- }, [focusedOrder]);
24
- const unregisterItem = useCallback((id) => {
25
- for (const [order, entry] of itemsRef.current) {
26
- if (entry.id === id) {
27
- itemsRef.current.delete(order);
28
- break;
29
- }
30
- }
31
- }, []);
32
- const setFocusedOrder = useCallback((order) => {
33
- setFocusedOrderState(order);
34
- }, []);
35
- const isFocused = useCallback((order) => {
36
- return focusedOrder === order;
37
- }, [focusedOrder]);
38
- const focusNext = useCallback(() => {
39
- const orders = Array.from(itemsRef.current.keys()).sort((a, b) => a - b);
40
- if (orders.length === 0)
41
- return;
42
- const currentIdx = orders.indexOf(focusedOrder ?? orders[0]);
43
- const nextIdx = (currentIdx + 1) % orders.length;
44
- setFocusedOrderState(orders[nextIdx]);
45
- }, [focusedOrder]);
46
- const focusPrev = useCallback(() => {
47
- const orders = Array.from(itemsRef.current.keys()).sort((a, b) => a - b);
48
- if (orders.length === 0)
49
- return;
50
- const currentIdx = orders.indexOf(focusedOrder ?? orders[0]);
51
- const prevIdx = (currentIdx - 1 + orders.length) % orders.length;
52
- setFocusedOrderState(orders[prevIdx]);
53
- }, [focusedOrder]);
54
- useKeyboard((key) => {
55
- if (!active)
56
- return;
57
- if (key.name === "tab") {
58
- if (key.shift) {
59
- focusPrev();
60
- }
61
- else {
62
- focusNext();
63
- }
64
- key.preventDefault();
65
- }
66
- });
67
- const value = {
68
- registerItem,
69
- unregisterItem,
70
- focusedOrder,
71
- setFocusedOrder,
72
- isFocused,
73
- focusNext,
74
- focusPrev,
75
- active,
76
- };
77
- return _jsx(FocusContext.Provider, { value: value, children: props.children });
78
- }
79
- export function useFocusable(order, id) {
80
- const ctx = useContext(FocusContext);
81
- const registered = useRef(false);
82
- useEffect(() => {
83
- if (!ctx)
84
- return;
85
- ctx.registerItem(order, id);
86
- registered.current = true;
87
- return () => {
88
- ctx.unregisterItem(id);
89
- };
90
- }, [ctx, order, id]);
91
- if (!ctx) {
92
- return { isFocused: false, setFocused: () => { } };
93
- }
94
- return {
95
- isFocused: ctx.isFocused(order),
96
- setFocused: () => ctx.setFocusedOrder(order),
97
- };
98
- }
@@ -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
- }