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,271 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef, useState } from "react";
3
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
4
- import fs from "node:fs";
5
- import { buildLoopOptions, parseCommandLine } from "../../loop-config.js";
6
- import { t } from "../../i18n/index.js";
7
- import { commandLine } from "../format.js";
8
- import { createLoop, updateLoop, listTasks } from "../daemon.js";
9
- import { useHoverState } from "../hooks/useHoverState.js";
10
- import { useInputShortcuts } from "../hooks/useInputShortcuts.js";
11
- import { useTabNav } from "../hooks/useTabNav.js";
12
- import { SearchSelect } from "./SearchSelect.js";
13
- import { HOVER_BG } from "../../config/constants.js";
14
- export const TASK_MODE_INLINE = "inline";
15
- export const TASK_MODE_EXISTING = "existing";
16
- export const createFields = ["interval", "taskMode", "command", "cwd", "taskId", "description", "runNow", "maxRuns", "project"];
17
- export function createInitialValues(loop, currentProjectId) {
18
- if (!loop) {
19
- return {
20
- interval: "30m",
21
- taskMode: TASK_MODE_INLINE,
22
- command: "",
23
- cwd: process.cwd(),
24
- taskId: "",
25
- description: "",
26
- runNow: "n",
27
- maxRuns: "",
28
- project: currentProjectId ?? "default",
29
- };
30
- }
31
- return {
32
- interval: loop.intervalHuman,
33
- taskMode: loop.taskId ? TASK_MODE_EXISTING : TASK_MODE_INLINE,
34
- command: commandLine(loop.command, loop.commandArgs),
35
- cwd: loop.cwd ?? "",
36
- taskId: loop.taskId ?? "",
37
- description: loop.description ?? "",
38
- runNow: loop.immediate ? "y" : "n",
39
- maxRuns: loop.maxRuns !== null ? String(loop.maxRuns) : "",
40
- project: loop.projectId ?? currentProjectId ?? "default",
41
- };
42
- }
43
- export function CreateView(props) {
44
- const [values, setValues] = useState({
45
- ...props.initial,
46
- project: props.initial.project ?? props.currentProjectId ?? "default",
47
- });
48
- const valuesRef = useRef(values);
49
- const [error, setError] = useState("");
50
- const [isSubmitting, setIsSubmitting] = useState(false);
51
- const [selectedTaskName, setSelectedTaskName] = useState(props.selectedTaskName ?? null);
52
- const inputRef = useRef(null);
53
- const { width: termWidth } = useTerminalDimensions();
54
- const btnWidth = Math.max(10, Math.min(14, Math.floor(termWidth / 6)));
55
- const isInline = values.taskMode === TASK_MODE_INLINE;
56
- const visibleFields = props.mode === "edit"
57
- ? createFields.filter((f) => f !== "runNow")
58
- : [...createFields];
59
- const filteredFields = visibleFields.filter((f) => {
60
- if (f === "command")
61
- return isInline;
62
- if (f === "taskId")
63
- return !isInline;
64
- return true;
65
- });
66
- const navItems = [...filteredFields, "save", "cancel"];
67
- const { setFocusIndex, focusedItem, isFocused } = useTabNav(navItems);
68
- const focusedItemRef = useRef(focusedItem);
69
- focusedItemRef.current = focusedItem;
70
- useInputShortcuts(() => {
71
- const fi = focusedItemRef.current;
72
- if (fi === "save" || fi === "cancel" || fi === "project")
73
- return null;
74
- return inputRef.current;
75
- });
76
- useState(() => {
77
- if (values.taskId && !selectedTaskName) {
78
- void listTasks().then((tasks) => {
79
- const found = tasks.find((t) => t.id === values.taskId);
80
- if (found)
81
- setSelectedTaskName(found.name);
82
- }).catch(() => { });
83
- }
84
- });
85
- function updateValues(next) {
86
- valuesRef.current = next;
87
- setValues(next);
88
- }
89
- if (props.selectedTaskId && props.selectedTaskId !== values.taskId) {
90
- const next = { ...valuesRef.current, taskId: props.selectedTaskId, taskMode: TASK_MODE_EXISTING };
91
- valuesRef.current = next;
92
- setValues(next);
93
- setSelectedTaskName(props.selectedTaskName ?? props.selectedTaskId);
94
- }
95
- useKeyboard((key) => {
96
- if (key.name !== "return" && key.name !== "enter" && key.name !== " ")
97
- return;
98
- const fi = focusedItemRef.current;
99
- if (fi === "taskMode") {
100
- const next = valuesRef.current.taskMode === TASK_MODE_INLINE ? TASK_MODE_EXISTING : TASK_MODE_INLINE;
101
- updateValues({ ...valuesRef.current, taskMode: next });
102
- key.preventDefault();
103
- return;
104
- }
105
- if (fi === "runNow") {
106
- const next = valuesRef.current.runNow === "y" ? "n" : "y";
107
- updateValues({ ...valuesRef.current, runNow: next });
108
- key.preventDefault();
109
- return;
110
- }
111
- if (fi === "taskId" && !isInline) {
112
- props.onChooseTask();
113
- key.preventDefault();
114
- return;
115
- }
116
- if (fi === "save") {
117
- void submit(valuesRef.current);
118
- key.preventDefault();
119
- }
120
- else if (fi === "cancel") {
121
- props.onCancel();
122
- key.preventDefault();
123
- }
124
- });
125
- async function submit(current) {
126
- if (isSubmitting)
127
- return;
128
- try {
129
- setIsSubmitting(true);
130
- setError("");
131
- if (current.taskMode === TASK_MODE_INLINE) {
132
- const cwd = current.cwd.trim();
133
- if (cwd && !fs.existsSync(cwd)) {
134
- setError(t("board.cwdMissing", { cwd }));
135
- return;
136
- }
137
- const tokens = parseCommandLine(current.command.trim());
138
- const [command, ...commandArgs] = tokens;
139
- if (!command) {
140
- setError(t("errors.commandEmpty"));
141
- return;
142
- }
143
- if (!current.description.trim()) {
144
- setError(t("errors.descriptionEmpty"));
145
- return;
146
- }
147
- const built = buildLoopOptions(current.interval.trim(), {
148
- now: props.mode === "create" && current.runNow === "y",
149
- maxRuns: current.maxRuns.trim() || null,
150
- verbose: false,
151
- description: current.description.trim(),
152
- command,
153
- commandArgs,
154
- cwd,
155
- });
156
- built.options.projectId = current.project || "default";
157
- const request = props.mode === "edit" && props.editId
158
- ? updateLoop(props.editId, built.options, built.intervalHuman)
159
- : createLoop(built.options, built.intervalHuman);
160
- const id = await request;
161
- props.onDone(props.mode === "edit", id, current.description.trim());
162
- }
163
- else {
164
- if (!current.taskId) {
165
- setError(t("errors.commandEmpty"));
166
- return;
167
- }
168
- if (!current.description.trim()) {
169
- setError(t("errors.descriptionEmpty"));
170
- return;
171
- }
172
- const cwd = current.cwd.trim();
173
- if (cwd && !fs.existsSync(cwd)) {
174
- setError(t("board.cwdMissing", { cwd }));
175
- return;
176
- }
177
- const built = buildLoopOptions(current.interval.trim(), {
178
- now: props.mode === "create" && current.runNow === "y",
179
- maxRuns: current.maxRuns.trim() || null,
180
- verbose: false,
181
- description: current.description.trim(),
182
- taskId: current.taskId,
183
- cwd,
184
- });
185
- built.options.projectId = current.project || "default";
186
- const request = props.mode === "edit" && props.editId
187
- ? updateLoop(props.editId, built.options, built.intervalHuman)
188
- : createLoop(built.options, built.intervalHuman);
189
- const id = await request;
190
- props.onDone(props.mode === "edit", id, current.description.trim());
191
- }
192
- }
193
- catch (e) {
194
- setError(e instanceof Error ? e.message : String(e));
195
- }
196
- finally {
197
- setIsSubmitting(false);
198
- }
199
- }
200
- const labels = {
201
- interval: t("board.labelInterval"),
202
- taskMode: t("board.labelTaskMode"),
203
- command: t("board.labelCommand"),
204
- cwd: t("board.labelCwd"),
205
- taskId: t("board.labelTask"),
206
- description: t("board.labelDescription"),
207
- runNow: t("board.labelRunNow"),
208
- maxRuns: t("board.labelMaxRuns"),
209
- project: t("project.projectsLabel"),
210
- };
211
- const hints = {
212
- interval: t("board.hintInterval"),
213
- taskMode: t("board.hintTaskMode"),
214
- command: t("board.hintCommand"),
215
- cwd: t("board.hintCwd"),
216
- taskId: t("board.hintTask"),
217
- description: t("board.hintDescription"),
218
- runNow: t("board.hintRunNow"),
219
- maxRuns: t("board.hintMaxRuns"),
220
- project: t("project.hintColor"),
221
- };
222
- const examples = {
223
- interval: t("board.exampleInterval"),
224
- taskMode: "",
225
- command: t("board.exampleCommand"),
226
- cwd: "",
227
- taskId: "",
228
- description: t("board.exampleDescription"),
229
- runNow: "",
230
- maxRuns: "",
231
- project: "",
232
- };
233
- const taskModeOptions = [
234
- { name: t("board.taskModeInline"), description: "", value: TASK_MODE_INLINE },
235
- { name: t("board.taskModeExisting"), description: "", value: TASK_MODE_EXISTING },
236
- ];
237
- const runNowOptions = [
238
- { name: t("board.runNowNo"), description: "", value: "n" },
239
- { name: t("board.runNowYes"), description: "", value: "y" },
240
- ];
241
- const projectOptions = (props.projects ?? []).length > 0
242
- ? props.projects.map((p) => ({ name: p.name, value: p.id, color: p.color }))
243
- : [{ name: "Default", value: "default", color: "#ffffff" }];
244
- const title = props.mode === "edit" ? t("board.editTitle") : t("board.createTitle");
245
- return (_jsxs("box", { title: title, border: true, style: { flexDirection: "column", flexGrow: 1, padding: 1, backgroundColor: "#0b0b0b" }, children: [_jsxs("box", { style: { flexDirection: "column", marginBottom: 1 }, children: [_jsx("text", { fg: "#9ca3af", children: t("board.exampleHeading") }), _jsx("text", { fg: "#d1d5db", children: t("board.exampleFull") })] }), _jsx("box", { style: { flexDirection: "column", flexGrow: 1 }, children: Array.from({ length: Math.ceil(filteredFields.length / 2) }, (_, row) => {
246
- const leftField = filteredFields[row * 2];
247
- const rightField = row * 2 + 1 < filteredFields.length ? filteredFields[row * 2 + 1] : null;
248
- return (_jsxs("box", { style: { flexDirection: "row", marginBottom: 1 }, children: [_jsx(FormRow, { field: leftField, index: row * 2, isFocused: isFocused(leftField), values: values, valuesRef: valuesRef, updateValues: updateValues, setFocusIndex: setFocusIndex, submit: submit, labels: labels, hints: hints, examples: examples, taskModeOptions: taskModeOptions, runNowOptions: runNowOptions, projectOptions: projectOptions, selectedTaskName: selectedTaskName, onChooseTask: props.onChooseTask, inputRef: inputRef, style: { width: "50%", paddingRight: 1 } }), rightField ? (_jsx(FormRow, { field: rightField, index: row * 2 + 1, isFocused: isFocused(rightField), values: values, valuesRef: valuesRef, updateValues: updateValues, setFocusIndex: setFocusIndex, submit: submit, labels: labels, hints: hints, examples: examples, taskModeOptions: taskModeOptions, runNowOptions: runNowOptions, projectOptions: projectOptions, selectedTaskName: selectedTaskName, onChooseTask: props.onChooseTask, inputRef: inputRef, style: { width: "50%" } })) : (_jsx("box", { style: { width: "50%" } }))] }, row));
249
- }) }), _jsxs("box", { style: { flexDirection: "row", height: 3, marginBottom: 1, backgroundColor: "#0b0b0b" }, children: [_jsx(HoverButton, { label: isSubmitting ? t("board.saving") : props.mode === "edit" ? t("board.save") : t("board.create"), onMouseDown: () => void submit(valuesRef.current), selected: isFocused("save"), width: btnWidth, marginRight: 1 }), _jsx(HoverButton, { label: t("board.cancel"), onMouseDown: props.onCancel, selected: isFocused("cancel"), width: btnWidth })] }), _jsx("text", { fg: "#9ca3af", children: t("board.formNav") }), error ? _jsx("text", { fg: "#f87171", children: error }) : null] }));
250
- }
251
- function HoverButton(props) {
252
- const { isHovered, hoverProps } = useHoverState();
253
- const bg = props.selected ? "#1e3a8a" : isHovered ? HOVER_BG : undefined;
254
- const borderColor = props.selected ? "#38bdf8" : undefined;
255
- return (_jsx("box", { border: true, onMouseDown: props.onMouseDown, borderColor: borderColor, style: { width: props.width, justifyContent: "center", alignItems: "center", marginRight: props.marginRight, backgroundColor: bg }, ...hoverProps, children: _jsx("text", { fg: "#e5e7eb", children: _jsx("strong", { children: props.label }) }) }));
256
- }
257
- function FormRow(props) {
258
- const { field, index, isFocused, values, valuesRef, updateValues, setFocusIndex, submit, labels, hints, examples, taskModeOptions, runNowOptions, projectOptions, selectedTaskName, onChooseTask, inputRef, style } = props;
259
- const { isHovered, hoverProps } = useHoverState();
260
- const isToggleField = field === "taskMode" || field === "runNow";
261
- const isTaskButton = field === "taskId";
262
- const isProjectField = field === "project";
263
- const toggleOptions = field === "taskMode" ? taskModeOptions : runNowOptions;
264
- const toggleValue = field === "taskMode" ? values.taskMode : values.runNow;
265
- return (_jsxs("box", { style: { flexDirection: "column", ...style }, children: [_jsx("text", { fg: isFocused ? "#38bdf8" : "#e5e7eb", children: labels[field] }), _jsx("text", { fg: "#6b7280", children: hints[field] }), isToggleField ? (_jsx("box", { border: true, borderColor: isFocused ? "#38bdf8" : undefined, style: { height: 3, flexDirection: "row", backgroundColor: "#0b0b0b", alignItems: "center" }, children: toggleOptions.map((opt) => {
266
- const isActive = toggleValue === opt.value;
267
- return (_jsx("box", { onMouseDown: () => updateValues({ ...valuesRef.current, [field]: opt.value }), style: { backgroundColor: isActive ? "#1e3a8a" : undefined, paddingLeft: 1, paddingRight: 1, marginRight: 1 }, ...hoverProps, children: _jsx("text", { fg: isActive ? "#ffffff" : isHovered ? "#e5e7eb" : "#9ca3af", children: _jsx("strong", { children: opt.name }) }) }, opt.value));
268
- }) })) : isTaskButton ? (_jsx("box", { border: true, borderColor: isFocused ? "#38bdf8" : undefined, style: { height: 3, flexDirection: "row", backgroundColor: "#0b0b0b", alignItems: "center", paddingLeft: 1 }, onMouseDown: () => { setFocusIndex(index); onChooseTask(); }, children: _jsx("text", { fg: values.taskId ? "#4ade80" : "#9ca3af", children: values.taskId
269
- ? t("board.selectedTask", { name: selectedTaskName ?? values.taskId })
270
- : t("board.chooseTask") }) })) : isProjectField ? (_jsx(SearchSelect, { options: projectOptions, value: values.project, onChange: (v) => updateValues({ ...valuesRef.current, project: v }), focused: isFocused })) : (_jsx("box", { border: true, borderColor: isFocused ? "#38bdf8" : undefined, style: { height: 3, backgroundColor: "#0b0b0b" }, children: _jsx("input", { ref: inputRef, focused: isFocused, value: values[field], placeholder: examples[field] ? t("board.placeholderExample", { example: examples[field] }) : t("board.placeholderBlank"), onInput: (value) => updateValues({ ...valuesRef.current, [field]: value }), onSubmit: () => { void submit(valuesRef.current); } }) }))] }));
271
- }
@@ -1,97 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useRef } from "react";
3
- import { useTabNav } from "../hooks/useTabNav.js";
4
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
5
- import { t } from "../../i18n/index.js";
6
- import { createProject } from "../daemon.js";
7
- import { PROJECT_COLORS, PROJECT_COLOR_KEYS } from "../../config/constants.js";
8
- export function CreateProjectModal(props) {
9
- const { onDone, onCancel } = props;
10
- const { width } = useTerminalDimensions();
11
- const defaultColorKey = PROJECT_COLOR_KEYS.indexOf("cyan") >= 0 ? "cyan" : PROJECT_COLOR_KEYS[0] ?? "white";
12
- const [name, setName] = useState("");
13
- const [selectedColorKey, setSelectedColorKey] = useState(defaultColorKey);
14
- const [error, setError] = useState("");
15
- const [isSubmitting, setIsSubmitting] = useState(false);
16
- const { focusedItem: focusField, setFocusIndex } = useTabNav(["name", "color", "save", "cancel"]);
17
- const focusFieldRef = useRef(focusField);
18
- focusFieldRef.current = focusField;
19
- useKeyboard((key) => {
20
- if (key.name === "escape") {
21
- onCancel();
22
- return;
23
- }
24
- if (key.name === "return" || key.name === "enter") {
25
- if (focusFieldRef.current === "cancel") {
26
- onCancel();
27
- return;
28
- }
29
- void submit();
30
- return;
31
- }
32
- if (focusFieldRef.current === "color") {
33
- if (key.name === "left" || key.name === "right") {
34
- const idx = PROJECT_COLOR_KEYS.indexOf(selectedColorKey);
35
- const dir = key.name === "left" ? -1 : 1;
36
- setSelectedColorKey(PROJECT_COLOR_KEYS[(idx + dir + PROJECT_COLOR_KEYS.length) % PROJECT_COLOR_KEYS.length] ?? selectedColorKey);
37
- key.preventDefault();
38
- return;
39
- }
40
- }
41
- });
42
- async function submit() {
43
- if (isSubmitting)
44
- return;
45
- if (!name.trim()) {
46
- setError(t("project.error.nameRequired"));
47
- return;
48
- }
49
- try {
50
- setIsSubmitting(true);
51
- setError("");
52
- const color = PROJECT_COLORS[selectedColorKey] ?? "#ffffff";
53
- const project = await createProject(name.trim(), color);
54
- onDone(project);
55
- }
56
- catch (err) {
57
- setError(err instanceof Error ? err.message : String(err));
58
- }
59
- finally {
60
- setIsSubmitting(false);
61
- }
62
- }
63
- return (_jsx("box", { style: {
64
- position: "absolute",
65
- top: 0,
66
- left: 0,
67
- width: "100%",
68
- height: "100%",
69
- justifyContent: "center",
70
- alignItems: "center",
71
- zIndex: 100,
72
- }, children: _jsxs("box", { title: t("project.createTitle"), border: true, style: {
73
- flexDirection: "column",
74
- padding: 1,
75
- minWidth: Math.min(44, width - 4),
76
- backgroundColor: "#111827",
77
- }, children: [_jsx("text", { fg: focusField === "name" ? "#38bdf8" : "#e5e7eb", children: t("project.labelName") }), _jsx("text", { fg: "#6b7280", children: t("project.hintName") }), _jsx("box", { border: true, borderColor: focusField === "name" ? "#38bdf8" : undefined, style: { height: 3, backgroundColor: "#0b0b0b", marginBottom: 1 }, children: _jsx("input", { focused: focusField === "name", value: name, placeholder: "My Project", onInput: (value) => setName(value) }) }), _jsx("text", { fg: focusField === "color" ? "#38bdf8" : "#e5e7eb", children: t("project.labelColor") }), _jsx("text", { fg: "#6b7280", children: t("project.hintColor") }), _jsx("box", { border: true, borderColor: focusField === "color" ? "#38bdf8" : undefined, style: { height: 3, flexDirection: "row", backgroundColor: "#0b0b0b", alignItems: "center", marginBottom: 1 }, children: PROJECT_COLOR_KEYS.map((colorKey) => {
78
- const isActive = selectedColorKey === colorKey;
79
- return (_jsx("box", { onMouseDown: () => { setFocusIndex(1); setSelectedColorKey(colorKey); }, style: {
80
- backgroundColor: isActive ? "#1e3a8a" : undefined,
81
- paddingLeft: 1,
82
- paddingRight: 1,
83
- marginRight: 1,
84
- }, children: _jsxs("text", { children: [_jsx("span", { fg: PROJECT_COLORS[colorKey] ?? "#ffffff", children: "\u25CF" }), ` ${colorKey}`] }) }, colorKey));
85
- }) }), error ? _jsx("text", { fg: "#f87171", children: error }) : null, _jsxs("box", { style: { flexDirection: "row", marginTop: 1 }, children: [_jsx("box", { border: true, borderColor: focusField === "save" ? "#38bdf8" : undefined, onMouseDown: () => void submit(), style: {
86
- width: 12,
87
- justifyContent: "center",
88
- alignItems: "center",
89
- marginRight: 1,
90
- backgroundColor: focusField === "save" ? "#1e3a8a" : "#0b0b0b",
91
- }, children: _jsx("text", { fg: "#e5e7eb", children: _jsx("strong", { children: isSubmitting ? t("board.saving") : t("board.create") }) }) }), _jsx("box", { border: true, borderColor: focusField === "cancel" ? "#38bdf8" : undefined, onMouseDown: onCancel, style: {
92
- width: 12,
93
- justifyContent: "center",
94
- alignItems: "center",
95
- backgroundColor: focusField === "cancel" ? "#1e3a8a" : "#0b0b0b",
96
- }, children: _jsx("text", { fg: "#e5e7eb", children: _jsx("strong", { children: t("board.cancel") }) }) })] })] }) }));
97
- }
@@ -1,53 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
4
- import { t } from "../../i18n/index.js";
5
- import { deleteProject } from "../daemon.js";
6
- export function DeleteProjectConfirm(props) {
7
- const { project, loopCount, onDone, onCancel } = props;
8
- const { width } = useTerminalDimensions();
9
- const [isDeleting, setIsDeleting] = useState(false);
10
- const [error, setError] = useState("");
11
- useKeyboard((key) => {
12
- if (key.name === "escape") {
13
- onCancel();
14
- }
15
- if (key.name === "return" || key.name === "enter") {
16
- void handleDelete();
17
- }
18
- });
19
- async function handleDelete() {
20
- if (isDeleting)
21
- return;
22
- try {
23
- setIsDeleting(true);
24
- setError("");
25
- await deleteProject(project.id);
26
- onDone();
27
- }
28
- catch (err) {
29
- setError(err instanceof Error ? err.message : String(err));
30
- setIsDeleting(false);
31
- }
32
- }
33
- return (_jsx("box", { style: {
34
- position: "absolute",
35
- top: 0,
36
- left: 0,
37
- width: "100%",
38
- height: "100%",
39
- justifyContent: "center",
40
- alignItems: "center",
41
- zIndex: 100,
42
- }, children: _jsxs("box", { title: t("project.deleteTitle"), border: true, style: {
43
- flexDirection: "column",
44
- padding: 1,
45
- minWidth: Math.min(50, width - 4),
46
- backgroundColor: "#111827",
47
- }, children: [_jsx("text", { fg: "#e5e7eb", children: t("project.deleteConfirm", { name: project.name }) }), loopCount > 0 ? (_jsx("text", { fg: "#f59e0b", children: t("project.deleteWarning", { count: String(loopCount) }) })) : null, error ? _jsx("text", { fg: "#f87171", children: error }) : null, _jsx("text", { children: " " }), _jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("box", { border: true, onMouseDown: () => void handleDelete(), style: {
48
- paddingLeft: 1,
49
- paddingRight: 1,
50
- marginRight: 1,
51
- backgroundColor: "#7f1d1d",
52
- }, children: _jsx("text", { fg: "#fca5a5", children: _jsx("strong", { children: isDeleting ? "Deleting..." : t("project.moveToDefault") }) }) }), _jsx("box", { border: true, onMouseDown: onCancel, style: { paddingLeft: 1, paddingRight: 1, backgroundColor: "#0b0b0b" }, children: _jsx("text", { fg: "#e5e7eb", children: _jsx("strong", { children: t("board.cancel") }) }) })] })] }) }));
53
- }
@@ -1,25 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
3
- import { t } from "../../i18n/index.js";
4
- import { commandLine, describeLoop, statusColor, statusLabel, timingLabel } from "../format.js";
5
- import { RunHistory } from "./RunHistory.js";
6
- export function DetailView(props) {
7
- const { loop, selectedRunIndex, onSelectRun, onOpenRun } = props;
8
- const [, setTick] = useState(0);
9
- const cmd = commandLine(loop.command, loop.commandArgs);
10
- const maxRuns = loop.maxRuns !== null ? String(loop.maxRuns) : t("board.unlimited");
11
- const summary = [
12
- `${t("board.detailSummaryStatus")} ${statusLabel(loop.status)}`,
13
- `${t("board.detailSummaryTiming")} ${timingLabel(loop)}`,
14
- `${t("board.detailSummaryInterval")} ${loop.intervalHuman}`,
15
- `${t("board.detailSummaryRuns")} ${loop.runCount}/${maxRuns}`,
16
- `${t("board.detailSummaryExit")} ${loop.lastExitCode ?? t("format.dash")}`,
17
- ].join(" | ");
18
- useEffect(() => {
19
- const timer = setInterval(() => {
20
- setTick((tick) => tick + 1);
21
- }, 1000);
22
- return () => clearInterval(timer);
23
- }, []);
24
- return (_jsxs("box", { style: { flexDirection: "column", flexGrow: 1, backgroundColor: "#0b0b0b" }, children: [_jsxs("box", { title: t("board.detailTitle"), border: true, style: { flexDirection: "column", backgroundColor: "#0b0b0b" }, children: [_jsx("text", { fg: "#9ca3af", children: summary }), _jsx("text", { children: " " }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldId") }), loop.id] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldDesc") }), describeLoop(loop)] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldCommand") }), cmd] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldDir") }), loop.cwd || t("board.inherit")] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldInterval") }), loop.intervalHuman] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldStatus") }), _jsx("span", { fg: statusColor(loop.status), children: statusLabel(loop.status) })] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldRuns") }), loop.runCount, " / ", maxRuns] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldCreated") }), loop.createdAt] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldLastRun") }), loop.lastRunAt ?? t("format.dash")] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldLastExit") }), loop.lastExitCode ?? t("format.dash")] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldNextRun") }), loop.nextRunAt ?? t("format.dash")] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.detailFieldPid") }), loop.pid] })] }), _jsx(RunHistory, { loop: loop, selectedRunIndex: selectedRunIndex, focused: true, onSelectRun: onSelectRun, onOpenRun: onOpenRun })] }));
25
- }
@@ -1,99 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useRef } from "react";
3
- import { useTabNav } from "../hooks/useTabNav.js";
4
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
5
- import { t } from "../../i18n/index.js";
6
- import { updateProject } from "../daemon.js";
7
- import { PROJECT_COLORS, PROJECT_COLOR_KEYS } from "../../config/constants.js";
8
- export function EditProjectModal(props) {
9
- const { project, onDone, onCancel } = props;
10
- const { width } = useTerminalDimensions();
11
- const [name, setName] = useState(project.name);
12
- const [selectedColorKey, setSelectedColorKey] = useState(() => {
13
- const found = PROJECT_COLOR_KEYS.find((k) => PROJECT_COLORS[k] === project.color);
14
- return found ?? PROJECT_COLOR_KEYS[0] ?? "white";
15
- });
16
- const [error, setError] = useState("");
17
- const [isSubmitting, setIsSubmitting] = useState(false);
18
- const { focusedItem: focusField, setFocusIndex } = useTabNav(["name", "color", "save", "cancel"]);
19
- const focusFieldRef = useRef(focusField);
20
- focusFieldRef.current = focusField;
21
- useKeyboard((key) => {
22
- if (key.name === "escape") {
23
- onCancel();
24
- return;
25
- }
26
- if (key.name === "return" || key.name === "enter") {
27
- if (focusFieldRef.current === "cancel") {
28
- onCancel();
29
- return;
30
- }
31
- void submit();
32
- return;
33
- }
34
- if (focusFieldRef.current === "color") {
35
- if (key.name === "left" || key.name === "right") {
36
- const idx = PROJECT_COLOR_KEYS.indexOf(selectedColorKey);
37
- const dir = key.name === "left" ? -1 : 1;
38
- setSelectedColorKey(PROJECT_COLOR_KEYS[(idx + dir + PROJECT_COLOR_KEYS.length) % PROJECT_COLOR_KEYS.length] ?? selectedColorKey);
39
- key.preventDefault();
40
- return;
41
- }
42
- }
43
- });
44
- async function submit() {
45
- if (isSubmitting)
46
- return;
47
- if (!name.trim()) {
48
- setError(t("project.error.nameEmpty"));
49
- return;
50
- }
51
- try {
52
- setIsSubmitting(true);
53
- setError("");
54
- const color = PROJECT_COLORS[selectedColorKey] ?? project.color;
55
- await updateProject(project.id, name.trim(), color);
56
- onDone();
57
- }
58
- catch (err) {
59
- setError(err instanceof Error ? err.message : String(err));
60
- }
61
- finally {
62
- setIsSubmitting(false);
63
- }
64
- }
65
- return (_jsx("box", { style: {
66
- position: "absolute",
67
- top: 0,
68
- left: 0,
69
- width: "100%",
70
- height: "100%",
71
- justifyContent: "center",
72
- alignItems: "center",
73
- zIndex: 100,
74
- }, children: _jsxs("box", { title: t("project.editTitle"), border: true, style: {
75
- flexDirection: "column",
76
- padding: 1,
77
- minWidth: Math.min(44, width - 4),
78
- backgroundColor: "#111827",
79
- }, children: [_jsx("text", { fg: focusField === "name" ? "#38bdf8" : "#e5e7eb", children: t("project.labelName") }), _jsx("text", { fg: "#6b7280", children: t("project.hintName") }), _jsx("box", { border: true, borderColor: focusField === "name" ? "#38bdf8" : undefined, style: { height: 3, backgroundColor: "#0b0b0b", marginBottom: 1 }, children: _jsx("input", { focused: focusField === "name", value: name, placeholder: project.name, onInput: (value) => setName(value) }) }), _jsx("text", { fg: focusField === "color" ? "#38bdf8" : "#e5e7eb", children: t("project.labelColor") }), _jsx("text", { fg: "#6b7280", children: t("project.hintColor") }), _jsx("box", { border: true, borderColor: focusField === "color" ? "#38bdf8" : undefined, style: { height: 3, flexDirection: "row", backgroundColor: "#0b0b0b", alignItems: "center", marginBottom: 1 }, children: PROJECT_COLOR_KEYS.map((colorKey) => {
80
- const isActive = selectedColorKey === colorKey;
81
- return (_jsx("box", { onMouseDown: () => { setFocusIndex(1); setSelectedColorKey(colorKey); }, style: {
82
- backgroundColor: isActive ? "#1e3a8a" : undefined,
83
- paddingLeft: 1,
84
- paddingRight: 1,
85
- marginRight: 1,
86
- }, children: _jsxs("text", { children: [_jsx("span", { fg: PROJECT_COLORS[colorKey] ?? "#ffffff", children: "\u25CF" }), ` ${colorKey}`] }) }, colorKey));
87
- }) }), error ? _jsx("text", { fg: "#f87171", children: error }) : null, _jsxs("box", { style: { flexDirection: "row", marginTop: 1 }, children: [_jsx("box", { border: true, borderColor: focusField === "save" ? "#38bdf8" : undefined, onMouseDown: () => void submit(), style: {
88
- width: 12,
89
- justifyContent: "center",
90
- alignItems: "center",
91
- marginRight: 1,
92
- backgroundColor: focusField === "save" ? "#1e3a8a" : "#0b0b0b",
93
- }, children: _jsx("text", { fg: "#e5e7eb", children: _jsx("strong", { children: isSubmitting ? t("board.saving") : t("board.save") }) }) }), _jsx("box", { border: true, borderColor: focusField === "cancel" ? "#38bdf8" : undefined, onMouseDown: onCancel, style: {
94
- width: 12,
95
- justifyContent: "center",
96
- alignItems: "center",
97
- backgroundColor: focusField === "cancel" ? "#1e3a8a" : "#0b0b0b",
98
- }, children: _jsx("text", { fg: "#e5e7eb", children: _jsx("strong", { children: t("board.cancel") }) }) })] })] }) }));
99
- }
@@ -1,17 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } 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
- import { SearchBox } from "./SearchBox.js";
6
- export function FilterBar(props) {
7
- const { filters, sort, searchActive, focusedPanel, onStatusCycle, onSortCycle, onSelectProject, currentProjectName, onQueryChange, onSearchActivate, onSearchDismiss } = props;
8
- const statusDisplay = filters.status === "waiting" ? "waiting" : filters.status;
9
- return (_jsxs("box", { style: { flexDirection: "row", height: 3, paddingLeft: 1, paddingRight: 1, backgroundColor: "#0b0b0b" }, children: [_jsx(SearchBox, { query: filters.query, searchActive: searchActive, focused: focusedPanel === "search", onQueryChange: onQueryChange, onActivate: onSearchActivate, onDismiss: onSearchDismiss }), onSelectProject ? (_jsx(ClickableBadge, { title: t("project.filterTitle"), text: currentProjectName ?? "Default", textColor: "#f59e0b", focused: focusedPanel === "project-filter", onMouseDown: onSelectProject, marginRight: 1 })) : null, _jsx(ClickableBadge, { title: t("board.statusFilterTitle"), text: statusDisplay, textColor: "#38bdf8", focused: focusedPanel === "status", onMouseDown: onStatusCycle, marginRight: 1 }), _jsx(ClickableBadge, { title: t("board.sortTitle"), text: sort, textColor: "#a3e635", focused: focusedPanel === "sort", onMouseDown: onSortCycle })] }));
10
- }
11
- function ClickableBadge(props) {
12
- const { isHovered, hoverProps } = useHoverState();
13
- const bg = props.focused ? "#1e2a4a" : isHovered ? HOVER_BG : "#0b0b0b";
14
- const borderColor = props.focused ? "#38bdf8" : undefined;
15
- const titleProp = props.title ? { title: props.title } : {};
16
- return (_jsx("box", { ...titleProp, border: true, borderColor: borderColor, onMouseDown: props.onMouseDown, style: { flexGrow: 1, height: 3, marginRight: props.marginRight, paddingLeft: 1, backgroundColor: bg }, ...hoverProps, children: _jsx("text", { fg: props.textColor, children: props.text }) }));
17
- }
@@ -1,52 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { t } from "../../i18n/index.js";
3
- import { ENTITY_COLORS } from "../../config/constants.js";
4
- export function Footer(props) {
5
- const { mode } = props;
6
- const badge = {
7
- normal: { label: t("board.badgeNormal"), bg: ENTITY_COLORS.loop },
8
- search: { label: t("board.badgeSearch"), bg: "#38bdf8" },
9
- create: { label: t("board.badgeCreate"), bg: ENTITY_COLORS.loop },
10
- task: { label: t("board.badgeTask"), bg: ENTITY_COLORS.task },
11
- help: { label: t("board.badgeHelp"), bg: "#facc15" },
12
- confirm: { label: t("board.badgeConfirm"), bg: "#f87171" },
13
- projects: { label: t("project.projectsLabel"), bg: ENTITY_COLORS.project },
14
- };
15
- const hints = {
16
- normal: [
17
- ["←/→", t("board.hintSwitchPanel")],
18
- ["/", t("board.hintSearch")],
19
- ["h", t("board.hintHelp")],
20
- ["esc", t("board.hintQuit")],
21
- ],
22
- search: [
23
- [t("board.hintKeyEnter"), t("board.hintApply")],
24
- [t("board.hintKeyEsc"), t("board.hintCancel")],
25
- ],
26
- create: [
27
- [t("board.hintKeyTab"), t("board.hintNextField")],
28
- [t("board.hintKeyEnter"), t("board.hintCreate")],
29
- [t("board.hintKeyEsc"), t("board.hintCancel")],
30
- ],
31
- task: [
32
- [t("board.hintKeyTab"), t("board.hintNextField")],
33
- [t("board.hintKeyEnter"), t("board.hintCreate")],
34
- [t("board.hintKeyEsc"), t("board.hintCancel")],
35
- ],
36
- help: [[t("board.hintKeyHelpEsc"), t("board.hintBack")]],
37
- confirm: [
38
- [t("board.hintKeyArrows"), t("board.hintChoose")],
39
- [t("board.hintKeyEnter"), t("board.hintConfirm")],
40
- [t("board.hintKeyYN"), t("board.hintYesNo")],
41
- [t("board.hintKeyEsc"), t("board.hintCancel")],
42
- ],
43
- projects: [
44
- ["n", t("project.keyNewHint")],
45
- ["e", t("project.keyEditHint")],
46
- ["d", t("project.keyDeleteHint")],
47
- [t("board.hintKeyEsc"), t("board.hintBack")],
48
- ],
49
- };
50
- const current = badge[mode];
51
- return (_jsxs("box", { style: { flexDirection: "row", height: 1 }, children: [_jsx("box", { style: { backgroundColor: current.bg, paddingLeft: 1, paddingRight: 1 }, children: _jsx("text", { fg: "#0b0b0b", children: _jsx("strong", { children: current.label }) }) }), _jsx("box", { style: { flexGrow: 1, paddingLeft: 1, flexDirection: "row" }, children: _jsx("text", { children: hints[mode].map(([k, a], i) => (_jsxs("span", { children: [i > 0 ? _jsx("span", { fg: "#374151", children: " " }) : null, _jsx("span", { fg: "#38bdf8", children: k }), _jsxs("span", { fg: "#6b7280", children: [":", a] })] }, k))) }) })] }));
52
- }