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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loop-task",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Loop engineering toolkit. Run any command on a cadence, in the background, managed from a terminal board. Schedule tests, builds, syncs, or agent prompts.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -72,7 +72,7 @@
72
72
  "vitest": "^3.1.0"
73
73
  },
74
74
  "scripts": {
75
- "build": "tsc -p tsconfig.build.json && node -e \"require('fs').copyFileSync('src/entry.js','dist/entry.js'); require('fs').copyFileSync('src/esm-loader.js','dist/esm-loader.js')\"",
75
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && node -e \"require('fs').copyFileSync('src/entry.js','dist/entry.js'); require('fs').copyFileSync('src/esm-loader.js','dist/esm-loader.js')\"",
76
76
  "start": "node dist/entry.js",
77
77
  "dev": "tsx src/cli.ts",
78
78
  "dev:watch": "tsx --watch src/cli.ts",
package/dist/board/App.js DELETED
@@ -1,405 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useRef, useState } from "react";
3
- import { applyLoopFilters, cycleSortMode, cycleStatusFilter, defaultFilters, } from "./state.js";
4
- import { ToastStack, useToasts } from "./toast.js";
5
- import { t } from "../i18n/index.js";
6
- import { useLoopPolling } from "./hooks/useLoopPolling.js";
7
- import { useLogStream } from "./hooks/useLogStream.js";
8
- import { useBoardKeybindings } from "./hooks/useBoardKeybindings.js";
9
- import { useTaskKeybindings } from "./hooks/useTaskKeybindings.js";
10
- import { Header } from "./components/Header.js";
11
- import { FilterBar } from "./components/FilterBar.js";
12
- import { Navigator } from "./components/Navigator.js";
13
- import { Inspector } from "./components/Inspector.js";
14
- import { RunHistory } from "./components/RunHistory.js";
15
- import { ActionButtons, getActionKeys } from "./components/ActionButtons.js";
16
- import { HelpModal } from "./components/HelpModal.js";
17
- import { ContextHelpModal } from "./components/ContextHelpModal.js";
18
- import { Footer } from "./components/Footer.js";
19
- import { ConfirmModal } from "./components/ConfirmModal.js";
20
- import { CreateView, createInitialValues } from "./components/CreateForm.js";
21
- import { TaskForm } from "./components/TaskForm.js";
22
- import { TaskNavigator, TaskInspector, TaskActionButtons } from "./components/TaskBrowser.js";
23
- import { TaskFilterBar } from "./components/TaskFilterBar.js";
24
- import { LogModal } from "./components/LogModal.js";
25
- import { ProjectsModal } from "./components/ProjectsModal.js";
26
- import { ProjectsPage } from "./components/ProjectsPage.js";
27
- import { fetchRunLog, deleteLoop, pauseLoop, resumeLoop, stopLoop, playLoop, triggerLoop, listTasks, deleteTask, listProjects } from "./daemon.js";
28
- import { useBreakpoint } from "./hooks/useBreakpoint.js";
29
- import { useRouter } from "./router.js";
30
- import { POLL_MS } from "../config/constants.js";
31
- import { useTabNav } from "./hooks/useTabNav.js";
32
- const VIEW_TO_MODE = {
33
- create: "create",
34
- "task-create": "task",
35
- "task-edit": "task",
36
- "task-list": "task",
37
- projects: "projects",
38
- };
39
- function resolveMode(confirm, searchActive, helpOpen, view) {
40
- if (confirm)
41
- return "confirm";
42
- if (searchActive)
43
- return "search";
44
- if (helpOpen)
45
- return "help";
46
- return VIEW_TO_MODE[view] ?? "normal";
47
- }
48
- function viewKey(view, editTarget, editTask) {
49
- if (view === "create")
50
- return `${view}:${editTarget?.id ?? "new"}`;
51
- if (view === "task-edit")
52
- return `${view}:${editTask?.id ?? "new"}`;
53
- return view;
54
- }
55
- export function App(props) {
56
- const { loops, daemonStatus, refresh } = useLoopPolling();
57
- const { view, stack, push, replace, pop } = useRouter("board");
58
- const [filters, setFilters] = useState(defaultFilters);
59
- const [sort, setSort] = useState("description");
60
- const [selectedIndex, setSelectedIndex] = useState(0);
61
- const [searchActive, setSearchActive] = useState(false);
62
- const [helpOpen, setHelpOpen] = useState(false);
63
- const [contextHelpOpen, setContextHelpOpen] = useState(false);
64
- const [confirm, setConfirm] = useState(null);
65
- const [confirmChoice, setConfirmChoice] = useState(0);
66
- const [editTarget, setEditTarget] = useState(null);
67
- const [cloneMode, setCloneMode] = useState(false);
68
- const [editTask, setEditTask] = useState(null);
69
- const [pendingTaskSelection, setPendingTaskSelection] = useState(null);
70
- const [selectedRunIndex, setSelectedRunIndex] = useState(0);
71
- const [selectedAction, setSelectedAction] = useState(0);
72
- const [logModalRun, setLogModalRun] = useState(null);
73
- const [logModalLines, setLogModalLines] = useState([]);
74
- const [logModalLoading, setLogModalLoading] = useState(false);
75
- const [tasks, setTasks] = useState([]);
76
- const [taskSelectedIndex, setTaskSelectedIndex] = useState(0);
77
- const [taskSelectedAction, setTaskSelectedAction] = useState(0);
78
- const [taskSearchActive, setTaskSearchActive] = useState(false);
79
- const [taskQuery, setTaskQuery] = useState("");
80
- const [projects, setProjects] = useState([]);
81
- const createProjectTriggerRef = useRef(null);
82
- const [currentProjectId, setCurrentProjectId] = useState(() => {
83
- try {
84
- return localStorage.getItem("loop-current-project") ?? "all";
85
- }
86
- catch {
87
- return "all";
88
- }
89
- });
90
- const [projectsModalOpen, setProjectsModalOpen] = useState(false);
91
- const { toasts, push: pushToast } = useToasts();
92
- const breakpoint = useBreakpoint();
93
- const visible = useMemo(() => applyLoopFilters(currentProjectId === "all" ? loops : loops.filter((l) => (l.projectId ?? "default") === currentProjectId), filters, sort), [loops, filters, sort, currentProjectId]);
94
- const clampedIndex = Math.min(selectedIndex, Math.max(0, visible.length - 1));
95
- const selected = visible[clampedIndex] ?? null;
96
- const selectedId = selected?.id ?? null;
97
- const selectedDesc = selected?.description ?? "";
98
- const prevSelectedId = useRef(null);
99
- if (selectedId !== prevSelectedId.current) {
100
- prevSelectedId.current = selectedId;
101
- setSelectedRunIndex(0);
102
- }
103
- useEffect(() => {
104
- setSelectedAction(0);
105
- }, [selected?.id]);
106
- const { destroy: destroyLogSocket } = useLogStream(selectedId, view, (error) => pushToast("error", t("board.logStreamError", { message: error.message })));
107
- const filteredTasks = useMemo(() => {
108
- if (!taskQuery)
109
- return tasks;
110
- const q = taskQuery.toLowerCase();
111
- return tasks.filter((t) => `${t.id} ${t.name} ${t.command}`.toLowerCase().includes(q));
112
- }, [tasks, taskQuery]);
113
- const taskClampedIndex = Math.min(taskSelectedIndex, Math.max(0, filteredTasks.length - 1));
114
- const selectedTask = filteredTasks[taskClampedIndex] ?? null;
115
- async function refreshTasks() {
116
- try {
117
- setTasks(await listTasks());
118
- }
119
- catch { /* ignore */ }
120
- }
121
- async function refreshProjects() {
122
- try {
123
- setProjects(await listProjects());
124
- }
125
- catch { /* ignore */ }
126
- }
127
- useEffect(() => { void refreshTasks(); void refreshProjects(); }, []);
128
- useEffect(() => {
129
- const timer = setInterval(() => {
130
- void refreshTasks();
131
- void refreshProjects();
132
- }, POLL_MS);
133
- return () => clearInterval(timer);
134
- }, []);
135
- useEffect(() => {
136
- try {
137
- localStorage.setItem("loop-current-project", currentProjectId);
138
- }
139
- catch { }
140
- }, [currentProjectId]);
141
- function runAction(label, action) {
142
- return async () => {
143
- try {
144
- await action();
145
- void refresh();
146
- pushToast("success", label);
147
- }
148
- catch (error) {
149
- pushToast("error", error instanceof Error ? error.message : String(error));
150
- }
151
- };
152
- }
153
- function handleOpenRunLog(run) {
154
- if (!selectedId)
155
- return;
156
- setLogModalRun(run);
157
- if (run.status === "running") {
158
- setLogModalLoading(false);
159
- setLogModalLines([]);
160
- return;
161
- }
162
- setLogModalLoading(true);
163
- setLogModalLines([]);
164
- fetchRunLog(selectedId, run.runNumber)
165
- .then((log) => {
166
- setLogModalLines(log ? log.split("\n") : []);
167
- setLogModalLoading(false);
168
- })
169
- .catch(() => {
170
- setLogModalLines([]);
171
- setLogModalLoading(false);
172
- });
173
- }
174
- function confirmAction(msg, toast, fn) {
175
- setConfirmChoice(0);
176
- setConfirm({ message: msg, action: runAction(toast, fn) });
177
- }
178
- function handleAction(action) {
179
- if (!selected || !selectedId)
180
- return;
181
- if (action === "edit") {
182
- setEditTarget(selected);
183
- push("create");
184
- return;
185
- }
186
- if (action === "clone") {
187
- const copy = {
188
- ...selected,
189
- id: "",
190
- description: `${selected.description} - Copy`,
191
- };
192
- setEditTarget(copy);
193
- setCloneMode(true);
194
- push("create");
195
- return;
196
- }
197
- if (action === "delete") {
198
- confirmAction(t("board.confirmDelete", { desc: selectedDesc }), t("board.toastDeleted", { desc: selectedDesc }), () => deleteLoop(selectedId));
199
- return;
200
- }
201
- if (action === "pause-or-play") {
202
- if (selected.status === "waiting") {
203
- confirmAction(t("board.confirmPause", { desc: selectedDesc }), t("board.toastPaused", { desc: selectedDesc }), () => pauseLoop(selectedId));
204
- }
205
- else if (selected.status === "paused") {
206
- confirmAction(t("board.confirmResume", { desc: selectedDesc }), t("board.toastResumed", { desc: selectedDesc }), () => resumeLoop(selectedId));
207
- }
208
- else {
209
- confirmAction(t("board.confirmPlay", { desc: selectedDesc }), t("board.toastPlayed", { desc: selectedDesc }), () => playLoop(selectedId));
210
- }
211
- return;
212
- }
213
- if (action === "stop") {
214
- confirmAction(t("board.confirmStop", { desc: selectedDesc }), t("board.toastStopped", { desc: selectedDesc }), () => stopLoop(selectedId));
215
- return;
216
- }
217
- if (action === "play") {
218
- const isPaused = selected.status === "paused";
219
- const msgKey = isPaused ? "board.confirmResume" : "board.confirmPlay";
220
- const toastKey = isPaused ? "board.toastResumed" : "board.toastPlayed";
221
- const fn = isPaused ? () => resumeLoop(selectedId) : () => playLoop(selectedId);
222
- confirmAction(t(msgKey, { desc: selectedDesc }), t(toastKey, { desc: selectedDesc }), fn);
223
- return;
224
- }
225
- if (action === "trigger") {
226
- confirmAction(t("board.confirmTrigger", { desc: selectedDesc }), t("board.toastTriggered", { desc: selectedDesc }), () => triggerLoop(selectedId));
227
- return;
228
- }
229
- }
230
- function handleTaskAction(action) {
231
- if (!selectedTask)
232
- return;
233
- if (action === "select") {
234
- if (!stack.includes("create") && !stack.includes("task-edit"))
235
- return;
236
- setPendingTaskSelection({ id: selectedTask.id, name: selectedTask.name });
237
- pop();
238
- pushToast("success", t("board.toastTaskSelected", { desc: selectedTask.name }));
239
- return;
240
- }
241
- if (action === "edit") {
242
- setEditTask(selectedTask);
243
- push("task-edit");
244
- return;
245
- }
246
- if (action === "delete") {
247
- setConfirmChoice(0);
248
- setConfirm({
249
- message: t("board.confirmDeleteTask", { desc: selectedTask.name }),
250
- action: async () => {
251
- await deleteTask(selectedTask.id);
252
- await refreshTasks();
253
- setTaskSelectedIndex((i) => Math.max(0, i - 1));
254
- pushToast("success", t("board.toastTaskDeleted", { desc: selectedTask.name }));
255
- },
256
- });
257
- }
258
- }
259
- const cancelCreate = () => { setEditTarget(null); setCloneMode(false); setPendingTaskSelection(null); pop(); };
260
- const cancelTask = () => { setEditTask(null); pop(); };
261
- const cancelTaskList = () => pop();
262
- function handleChooseTask() {
263
- void refreshTasks();
264
- push("task-list");
265
- }
266
- function handleCreateTask() {
267
- setEditTask(null);
268
- push("task-create");
269
- }
270
- const onCreateDone = (updated, _id, desc) => {
271
- setEditTarget(null);
272
- setCloneMode(false);
273
- setPendingTaskSelection(null);
274
- pop();
275
- pushToast("success", updated ? t("board.toastUpdated", { desc }) : t("board.toastStarted", { desc }));
276
- void refresh();
277
- };
278
- const onTaskDone = (updated, id) => {
279
- setEditTask(null);
280
- void refreshTasks();
281
- pop();
282
- pushToast("success", updated ? t("board.toastTaskUpdated", { id }) : t("board.toastTaskCreated", { id }));
283
- };
284
- const FORM_VIEWS = ["create", "task-create", "task-edit"];
285
- const isFormView = FORM_VIEWS.includes(view);
286
- const actionKeys = selected ? getActionKeys(selected.status) : [];
287
- const actionItems = actionKeys.map((k) => `action-${k}`);
288
- const boardItems = ["search", "project-filter", "status", "sort", "header-1", "header-2", "header-3", "navigator", "run-history", ...actionItems];
289
- const taskItems = ["task-search", "header-1", "header-2", "header-3", "task-list", ...(["select", "edit", "delete"].map((k) => `task-action-${k}`))];
290
- const projectItems = ["header-1", "header-2", "header-3"];
291
- const navItems = isFormView ? [] : view === "task-list" ? taskItems : view === "projects" ? projectItems : boardItems;
292
- const { focusedItem: navFocused, setFocusIndex: setNavIndex, enabled: navEnabled } = useTabNav(navItems);
293
- const HEADER_MAP = { "header-1": "header-tasks", "header-2": "header-projects", "header-3": "header-new" };
294
- const isHeaderFocused = navFocused === "header-1" || navFocused === "header-2" || navFocused === "header-3";
295
- const derivedFocusedPanel = isHeaderFocused ? HEADER_MAP[navFocused] : navFocused ?? "loops";
296
- const actionIdx = navFocused?.startsWith("action-") ? actionKeys.indexOf(navFocused.slice("action-".length)) : -1;
297
- const derivedSelectedAction = actionIdx >= 0 ? actionIdx : selectedAction;
298
- const taskActionIdx = navFocused?.startsWith("task-action-") ? ["select", "edit", "delete"].indexOf(navFocused.slice("task-action-".length)) : -1;
299
- const derivedTaskAction = taskActionIdx >= 0 ? taskActionIdx : taskSelectedAction;
300
- const taskPanelMap = {
301
- "task-search": "search",
302
- "task-list": "tasks",
303
- "task-action-select": "actions",
304
- "task-action-edit": "actions",
305
- "task-action-delete": "actions",
306
- };
307
- const derivedTaskPanel = (navFocused ? taskPanelMap[navFocused] : undefined) ?? "tasks";
308
- useBoardKeybindings({
309
- confirm,
310
- confirmChoice,
311
- setConfirm,
312
- setConfirmChoice,
313
- helpOpen,
314
- setHelpOpen,
315
- searchActive,
316
- setSearchActive,
317
- view,
318
- push,
319
- pop,
320
- setEditTarget,
321
- setEditTask,
322
- selected,
323
- visibleCount: visible.length,
324
- setSelectedIndex,
325
- setFilters,
326
- setSort,
327
- onQuit: props.onQuit,
328
- destroyLogSocket,
329
- logModalRun,
330
- setLogModalRun,
331
- logModalLines,
332
- selectedRunIndex,
333
- setSelectedRunIndex,
334
- selectedRunCount: selected?.runHistory?.length ?? 0,
335
- focusedPanel: derivedFocusedPanel,
336
- setFocusedPanel: ((p) => {
337
- const navIdx = navItems.indexOf(p);
338
- if (navIdx >= 0)
339
- setNavIndex(navIdx);
340
- }),
341
- selectedAction: derivedSelectedAction,
342
- setSelectedAction,
343
- onAction: handleAction,
344
- onOpenRunLog: handleOpenRunLog,
345
- refreshTasks,
346
- onViewTasks: () => { void refreshTasks(); push("task-list"); },
347
- onViewProjects: () => { void refreshProjects(); push("projects"); },
348
- onViewLoops: () => replace("board"),
349
- onAddLoop: () => { setEditTarget(null); push("create"); },
350
- onAddTask: () => { setEditTask(null); push("task-create"); },
351
- onSelectProject: () => setProjectsModalOpen(true),
352
- });
353
- useTaskKeybindings({
354
- confirm,
355
- view,
356
- tasks: filteredTasks,
357
- taskSelectedIndex,
358
- setTaskSelectedIndex,
359
- taskSelectedAction: derivedTaskAction,
360
- setTaskSelectedAction: ((updater) => {
361
- const nextIdx = typeof updater === "function" ? updater(derivedTaskAction) : updater;
362
- const taskActions = ["select", "edit", "delete"];
363
- const actionKey = taskActions[nextIdx];
364
- if (actionKey) {
365
- const navIdx = navItems.indexOf(`task-action-${actionKey}`);
366
- if (navIdx >= 0)
367
- setNavIndex(navIdx);
368
- }
369
- }),
370
- taskFocusedPanel: derivedTaskPanel,
371
- setTaskFocusedPanel: ((p) => {
372
- const navIdx = navItems.indexOf(p === "search" ? "task-search" : p === "actions" ? "task-actions" : "task-list");
373
- if (navIdx >= 0)
374
- setNavIndex(navIdx);
375
- }),
376
- taskSearchActive,
377
- setTaskSearchActive,
378
- taskQuery,
379
- setTaskQuery,
380
- onTaskAction: handleTaskAction,
381
- onCancel: cancelTaskList,
382
- onCreateTask: handleCreateTask,
383
- onToggleContextHelp: () => setContextHelpOpen((v) => !v),
384
- headerFocused: isHeaderFocused,
385
- selectable: stack.includes("create") || stack.includes("task-edit"),
386
- });
387
- const counts = {
388
- total: loops.length,
389
- running: loops.filter((l) => l.status === "running").length,
390
- waiting: loops.filter((l) => l.status === "waiting").length,
391
- paused: loops.filter((l) => l.status === "paused").length,
392
- idle: loops.filter((l) => l.status === "idle").length,
393
- };
394
- const mode = resolveMode(confirm, searchActive || taskSearchActive, helpOpen, view);
395
- return (_jsxs("box", { style: { flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#0b0b0b" }, children: [_jsx(Header, { daemonStatus: daemonStatus, counts: counts, view: view, focusedPanel: derivedFocusedPanel, onViewLoops: () => replace("board"), onViewTasks: () => { void refreshTasks(); push("task-list"); }, onViewProjects: () => push("projects"), onAddLoop: () => { setEditTarget(null); push("create"); }, onAddTask: () => { setEditTask(null); push("task-create"); }, onAddProject: () => { if (view === "projects") {
396
- createProjectTriggerRef.current?.();
397
- }
398
- else {
399
- push("projects");
400
- } } }), view === "board" ? (_jsx(FilterBar, { filters: filters, sort: sort, searchActive: searchActive, focusedPanel: derivedFocusedPanel, onStatusCycle: () => setFilters((prev) => ({ ...prev, status: cycleStatusFilter(prev.status) })), onSortCycle: () => setSort(cycleSortMode(sort)), onSelectProject: () => setProjectsModalOpen(true), currentProjectName: currentProjectId === "all" ? t("project.showAll") : (projects.find(p => p.id === currentProjectId)?.name ?? "Default"), onQueryChange: (value) => setFilters((prev) => ({ ...prev, query: value })), onSearchActivate: () => setSearchActive(true), onSearchDismiss: () => { setSearchActive(false); } })) : view === "task-list" ? (_jsx(TaskFilterBar, { query: taskQuery, searchActive: taskSearchActive, focusedPanel: derivedTaskPanel, onQueryChange: setTaskQuery, onSearchActivate: () => setTaskSearchActive(true), onSearchDismiss: () => { setTaskSearchActive(false); } })) : null, _jsx("box", { style: { flexGrow: 1, backgroundColor: "#0b0b0b" }, children: view === "create" ? (_jsx(CreateView, { mode: editTarget && !cloneMode ? "edit" : "create", editId: editTarget && !cloneMode ? editTarget.id : null, initial: createInitialValues(editTarget, currentProjectId), selectedTaskId: pendingTaskSelection?.id ?? null, selectedTaskName: pendingTaskSelection?.name ?? null, projects: projects, currentProjectId: currentProjectId, onCancel: cancelCreate, onDone: onCreateDone, onChooseTask: handleChooseTask })) : TASK_FORM_VIEWS.has(view) ? (_jsx(TaskForm, { mode: view === "task-edit" ? "edit" : "create", editTask: editTask, onCancel: cancelTask, onDone: onTaskDone, onCopy: () => pushToast("success", t("board.toastCopied")) })) : view === "task-list" ? (_jsxs("box", { style: { flexDirection: breakpoint === "narrow" ? "column" : "row", flexGrow: 1, backgroundColor: "#0b0b0b" }, children: [_jsx(TaskNavigator, { visible: filteredTasks, total: tasks.length, selectedIndex: taskClampedIndex, focused: derivedTaskPanel === "tasks", query: taskQuery, onSelect: (index) => { setTaskSelectedIndex(index); }, onActivate: (index) => { setTaskSelectedIndex(index); setEditTask(filteredTasks[index] ?? null); push("task-edit"); } }), _jsxs("box", { style: { flexDirection: "column", flexGrow: 1, backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx(TaskInspector, { task: selectedTask }, `ti-${selectedTask?.id}`), _jsx(TaskActionButtons, { task: selectedTask, focused: taskActionIdx >= 0, selectedAction: derivedTaskAction, selectable: stack.includes("create") || stack.includes("task-edit"), onAction: handleTaskAction }, `tab-${selectedTask?.id}`)] })] })) : view === "projects" ? (_jsx(ProjectsPage, { projects: projects, loops: loops, headerFocused: isHeaderFocused, onClose: () => pop(), onRefresh: refreshProjects, onOpenCreate: (trigger) => { createProjectTriggerRef.current = trigger; }, onEnterHeader: () => { } })) : (_jsxs("box", { style: { flexDirection: breakpoint === "narrow" ? "column" : "row", flexGrow: 1, backgroundColor: "#0b0b0b" }, children: [_jsx(Navigator, { visible: visible, total: loops.length, selectedIndex: clampedIndex, filters: filters, sort: sort, breakpoint: breakpoint, focused: derivedFocusedPanel === "loops" || derivedFocusedPanel === "navigator", projects: projects, onSelect: (index) => setSelectedIndex(index), onActivate: (index) => { setSelectedIndex(index); const loop = visible[index]; if (loop) {
401
- setEditTarget(loop);
402
- push("create");
403
- } } }), _jsxs("box", { style: { flexDirection: "column", flexGrow: 1, backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx(Inspector, { loop: selected }), _jsx(RunHistory, { loop: selected, selectedRunIndex: selectedRunIndex, focused: derivedFocusedPanel === "runs" || derivedFocusedPanel === "run-history", onSelectRun: (index) => setSelectedRunIndex(index), onOpenRun: handleOpenRunLog }), _jsx(ActionButtons, { loop: selected, focused: actionIdx >= 0, selectedAction: derivedSelectedAction, onAction: handleAction })] })] })) }, viewKey(view, editTarget, editTask)), _jsx(Footer, { mode: mode }), confirm ? (_jsx(ConfirmModal, { message: confirm.message, choice: confirmChoice, onYes: () => { const action = confirm.action; setConfirm(null); void action(); }, onNo: () => setConfirm(null) })) : null, helpOpen ? _jsx(HelpModal, { view: view }) : null, contextHelpOpen ? _jsx(ContextHelpModal, { onClose: () => setContextHelpOpen(false) }) : null, logModalRun ? (_jsx(LogModal, { loopId: selectedId, run: logModalRun, logLines: logModalLines, loading: logModalLoading, onClose: () => setLogModalRun(null) })) : null, projectsModalOpen ? (_jsx(ProjectsModal, { projects: projects, loops: loops, currentProjectId: currentProjectId, onSelect: (id) => { setCurrentProjectId(id); setProjectsModalOpen(false); }, onClose: () => setProjectsModalOpen(false) })) : null, _jsx(ToastStack, { toasts: toasts })] }));
404
- }
405
- const TASK_FORM_VIEWS = new Set(["task-create", "task-edit"]);
@@ -1,18 +0,0 @@
1
- import fs from "node:fs";
2
- import { getBoardLogFile, getDataDir } from "../config/paths.js";
3
- function formatError(error) {
4
- if (error instanceof Error) {
5
- return error.stack ?? `${error.name}: ${error.message}`;
6
- }
7
- return typeof error === "string" ? error : JSON.stringify(error);
8
- }
9
- export function boardLog(message, error) {
10
- try {
11
- fs.mkdirSync(getDataDir(), { recursive: true });
12
- const suffix = error === undefined ? "" : `\n${formatError(error)}\n`;
13
- fs.appendFileSync(getBoardLogFile(), `${new Date().toISOString()} ${message}${suffix}`);
14
- }
15
- catch {
16
- // best effort crash logging only
17
- }
18
- }
@@ -1,46 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { t } from "../../i18n/index.js";
3
- import { useHoverState } from "../hooks/useHoverState.js";
4
- import { HOVER_BG } from "../../config/constants.js";
5
- const STOP_ACTION = { key: "stop", label: t("board.actionStop") };
6
- const PAUSE_ACTION = { key: "pause", label: t("board.actionPause") };
7
- const PLAY_ACTION = { key: "play", label: t("board.actionPlay") };
8
- const STATUS_ACTIONS = {
9
- running: [STOP_ACTION],
10
- waiting: [PAUSE_ACTION, STOP_ACTION],
11
- paused: [STOP_ACTION, PLAY_ACTION],
12
- idle: [PLAY_ACTION],
13
- stopped: [PLAY_ACTION],
14
- };
15
- function getActions(status) {
16
- const actions = [
17
- { key: "edit", label: t("board.actionEdit") },
18
- { key: "delete", label: t("board.actionDelete") },
19
- ];
20
- actions.push(...STATUS_ACTIONS[status]);
21
- if (status !== "running") {
22
- actions.push({ key: "clone", label: t("board.actionClone") });
23
- actions.push({ key: "trigger", label: t("board.actionTrigger") });
24
- }
25
- return actions;
26
- }
27
- export function getActionKeys(status) {
28
- return getActions(status).map((a) => a.key);
29
- }
30
- export function getActionCount(status) {
31
- return getActions(status).length;
32
- }
33
- export function ActionButtons(props) {
34
- const { loop, focused, selectedAction, onAction } = props;
35
- if (!loop) {
36
- return (_jsx("box", { border: true, borderColor: focused ? "#38bdf8" : undefined, style: { flexDirection: "row", height: 3, flexShrink: 0, backgroundColor: "#0b0b0b", justifyContent: "center", alignItems: "center" }, children: _jsx("text", { fg: "#9ca3af", children: t("board.noActions") }) }));
37
- }
38
- const actions = getActions(loop.status);
39
- return (_jsx("box", { border: true, borderColor: focused ? "#38bdf8" : undefined, style: { flexDirection: "row", height: 3, flexShrink: 0, paddingLeft: 1, backgroundColor: "#0b0b0b", alignItems: "center" }, children: actions.map((action, i) => (_jsx(ActionButton, { label: action.label, selected: focused && selectedAction === i, onMouseDown: () => onAction(action.key) }, action.key))) }));
40
- }
41
- function ActionButton(props) {
42
- const { isHovered, hoverProps } = useHoverState();
43
- const bg = props.selected ? "#1e3a8a" : isHovered ? HOVER_BG : undefined;
44
- const fg = props.selected ? "#ffffff" : isHovered ? "#e5e7eb" : "#9ca3af";
45
- return (_jsx("box", { onMouseDown: props.onMouseDown, style: { backgroundColor: bg, paddingLeft: 1, paddingRight: 1, marginRight: 1 }, ...hoverProps, children: _jsx("text", { fg: fg, children: _jsx("strong", { children: props.label }) }) }));
46
- }
@@ -1,58 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useHoverState } from "../hooks/useHoverState.js";
3
- const PRIMARY_REST = "#1e3a5f";
4
- const PRIMARY_HOVER = "#2a4f7a";
5
- const PRIMARY_FOCUSED = "#1e3a8a";
6
- const DEFAULT_HOVER = "#334155";
7
- const DEFAULT_FOCUSED = "#374151";
8
- export function BoardButton(props) {
9
- const { label, onMouseDown, focused, variant = "default", marginRight } = props;
10
- const { isHovered, hoverProps } = useHoverState();
11
- let bg;
12
- const fg = "#e5e7eb";
13
- let border;
14
- if (variant === "primary") {
15
- if (focused) {
16
- bg = PRIMARY_FOCUSED;
17
- border = "#38bdf8";
18
- }
19
- else if (isHovered) {
20
- bg = PRIMARY_HOVER;
21
- }
22
- else {
23
- bg = PRIMARY_REST;
24
- }
25
- }
26
- else if (variant === "danger") {
27
- if (focused) {
28
- bg = "#7f1d1d";
29
- border = "#f87171";
30
- }
31
- else if (isHovered) {
32
- bg = "#7f1d1d";
33
- }
34
- else {
35
- bg = "#0b0b0b";
36
- }
37
- }
38
- else {
39
- if (focused) {
40
- bg = DEFAULT_FOCUSED;
41
- border = "#38bdf8";
42
- }
43
- else if (isHovered) {
44
- bg = DEFAULT_HOVER;
45
- }
46
- else {
47
- bg = "#0b0b0b";
48
- }
49
- }
50
- return (_jsx("box", { border: true, onMouseDown: onMouseDown, borderColor: border, style: {
51
- paddingLeft: 2,
52
- paddingRight: 2,
53
- marginRight,
54
- backgroundColor: bg,
55
- justifyContent: "center",
56
- alignItems: "center",
57
- }, ...hoverProps, children: _jsx("text", { fg: fg, children: _jsx("strong", { children: label }) }) }));
58
- }
@@ -1,36 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useTerminalDimensions } from "@opentui/react";
3
- import { t } from "../../i18n/index.js";
4
- import { useHoverState } from "../hooks/useHoverState.js";
5
- import { HOVER_BG } from "../../config/constants.js";
6
- export function ConfirmModal(props) {
7
- const { message, choice, onYes, onNo } = props;
8
- const { width } = useTerminalDimensions();
9
- return (_jsx("box", { style: {
10
- position: "absolute",
11
- top: 0,
12
- left: 0,
13
- width: "100%",
14
- height: "100%",
15
- justifyContent: "center",
16
- alignItems: "center",
17
- zIndex: 100,
18
- }, children: _jsxs("box", { title: t("board.confirmTitle"), border: true, style: {
19
- flexDirection: "column",
20
- padding: 1,
21
- minWidth: Math.min(44, width - 4),
22
- backgroundColor: "#111827",
23
- }, children: [_jsx("text", { children: message }), _jsx("text", { children: " " }), _jsxs("box", { style: { flexDirection: "row", justifyContent: "center" }, children: [_jsx(ConfirmButton, { label: t("board.yes"), onMouseDown: onYes, selected: choice === 1, width: 12, marginRight: 1 }), _jsx(ConfirmButton, { label: t("board.no"), onMouseDown: onNo, selected: choice === 0, width: 12 })] })] }) }));
24
- }
25
- function ConfirmButton(props) {
26
- const { isHovered, hoverProps } = useHoverState();
27
- const bg = props.selected ? "#1e3a8a" : isHovered ? HOVER_BG : undefined;
28
- const borderColor = props.selected ? "#38bdf8" : undefined;
29
- return (_jsx("box", { border: true, onMouseDown: props.onMouseDown, borderColor: borderColor, style: {
30
- width: props.width,
31
- backgroundColor: bg,
32
- justifyContent: "center",
33
- alignItems: "center",
34
- marginRight: props.marginRight,
35
- }, ...hoverProps, children: _jsx("text", { fg: "#e5e7eb", children: _jsx("strong", { children: props.label }) }) }));
36
- }
@@ -1,22 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useTerminalDimensions } from "@opentui/react";
3
- import { t } from "../../i18n/index.js";
4
- export function ContextHelpModal(props) {
5
- const { onClose } = props;
6
- const { width } = useTerminalDimensions();
7
- return (_jsx("box", { onMouseDown: onClose, style: {
8
- position: "absolute",
9
- top: 0,
10
- left: 0,
11
- width: "100%",
12
- height: "100%",
13
- justifyContent: "center",
14
- alignItems: "center",
15
- zIndex: 90,
16
- }, children: _jsxs("box", { title: t("context.helpTitle"), border: true, style: {
17
- flexDirection: "column",
18
- padding: 1,
19
- minWidth: Math.min(72, width - 4),
20
- backgroundColor: "#111827",
21
- }, children: [_jsx("text", { fg: "#e5e7eb", children: t("context.helpRules") }), _jsx("text", { children: " " }), _jsx("text", { fg: "#38bdf8", children: "{{key}} Templates" }), _jsx("text", { fg: "#e5e7eb", children: t("context.helpTemplates") }), _jsx("text", { children: " " }), _jsx("text", { fg: "#f59e0b", children: "Caveat" }), _jsx("text", { fg: "#e5e7eb", children: t("context.helpCaveat") }), _jsx("text", { children: " " }), _jsx("text", { fg: "#34d399", children: "jq Tip" }), _jsx("text", { fg: "#e5e7eb", children: t("context.helpJqTip") }), _jsx("text", { children: " " }), _jsx("text", { fg: "#9ca3af", children: "Press any key or click to close" })] }) }));
22
- }