loop-task 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board/App.js +16 -3
- package/dist/board/components/ActionButtons.js +1 -0
- package/dist/board/components/CreateForm.js +1 -1
- package/dist/board/components/HelpModal.js +29 -3
- package/dist/board/components/TaskBrowser.js +5 -6
- package/dist/board/hooks/useBoardKeybindings.js +1 -1
- package/dist/i18n/en.json +1 -0
- package/package.json +1 -1
package/dist/board/App.js
CHANGED
|
@@ -61,6 +61,7 @@ export function App(props) {
|
|
|
61
61
|
const [confirm, setConfirm] = useState(null);
|
|
62
62
|
const [confirmChoice, setConfirmChoice] = useState(0);
|
|
63
63
|
const [editTarget, setEditTarget] = useState(null);
|
|
64
|
+
const [cloneMode, setCloneMode] = useState(false);
|
|
64
65
|
const [editTask, setEditTask] = useState(null);
|
|
65
66
|
const [pendingTaskSelection, setPendingTaskSelection] = useState(null);
|
|
66
67
|
const [selectedRunIndex, setSelectedRunIndex] = useState(0);
|
|
@@ -175,6 +176,17 @@ export function App(props) {
|
|
|
175
176
|
push("create");
|
|
176
177
|
return;
|
|
177
178
|
}
|
|
179
|
+
if (action === "clone") {
|
|
180
|
+
const copy = {
|
|
181
|
+
...selected,
|
|
182
|
+
id: "",
|
|
183
|
+
description: `${selected.description} - Copy`,
|
|
184
|
+
};
|
|
185
|
+
setEditTarget(copy);
|
|
186
|
+
setCloneMode(true);
|
|
187
|
+
push("create");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
178
190
|
if (action === "delete") {
|
|
179
191
|
confirmAction(t("board.confirmDelete", { desc: selectedDesc }), t("board.toastDeleted", { desc: selectedDesc }), () => deleteLoop(selectedId));
|
|
180
192
|
return;
|
|
@@ -237,7 +249,7 @@ export function App(props) {
|
|
|
237
249
|
});
|
|
238
250
|
}
|
|
239
251
|
}
|
|
240
|
-
const cancelCreate = () => { setEditTarget(null); setPendingTaskSelection(null); pop(); };
|
|
252
|
+
const cancelCreate = () => { setEditTarget(null); setCloneMode(false); setPendingTaskSelection(null); pop(); };
|
|
241
253
|
const cancelTask = () => { setEditTask(null); pop(); };
|
|
242
254
|
const cancelTaskList = () => pop();
|
|
243
255
|
function handleChooseTask() {
|
|
@@ -250,6 +262,7 @@ export function App(props) {
|
|
|
250
262
|
}
|
|
251
263
|
const onCreateDone = (updated, _id, desc) => {
|
|
252
264
|
setEditTarget(null);
|
|
265
|
+
setCloneMode(false);
|
|
253
266
|
setPendingTaskSelection(null);
|
|
254
267
|
pop();
|
|
255
268
|
pushToast("success", updated ? t("board.toastUpdated", { desc }) : t("board.toastStarted", { desc }));
|
|
@@ -332,9 +345,9 @@ export function App(props) {
|
|
|
332
345
|
}
|
|
333
346
|
else {
|
|
334
347
|
push("projects");
|
|
335
|
-
} } }), view === "board" ? (_jsx(FilterBar, { filters: filters, sort: sort, searchActive: searchActive, focusedPanel: focusedPanel, 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") })) : view === "task-list" ? (_jsx(TaskFilterBar, { query: taskQuery, searchActive: taskSearchActive, focusedPanel: taskFocusedPanel })) : null, _jsx("box", { style: { flexGrow: 1, backgroundColor: "#0b0b0b" }, children: view === "create" ? (_jsx(CreateView, { mode: editTarget ? "edit" : "create", editId: editTarget
|
|
348
|
+
} } }), view === "board" ? (_jsx(FilterBar, { filters: filters, sort: sort, searchActive: searchActive, focusedPanel: focusedPanel, 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") })) : view === "task-list" ? (_jsx(TaskFilterBar, { query: taskQuery, searchActive: taskSearchActive, focusedPanel: taskFocusedPanel })) : 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 })) : 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: taskFocusedPanel === "tasks", query: taskQuery, onSelect: (index) => { setTaskSelectedIndex(index); setTaskFocusedPanel("tasks"); }, 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: taskFocusedPanel === "actions", selectedAction: taskSelectedAction, selectable: stack.includes("create") || stack.includes("task-edit"), onAction: handleTaskAction }, `tab-${selectedTask?.id}`)] })] })) : view === "projects" ? (_jsx(ProjectsPage, { projects: projects, loops: loops, onClose: () => pop(), onRefresh: refreshProjects, onOpenCreate: (trigger) => { createProjectTriggerRef.current = trigger; } })) : (_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: focusedPanel === "loops", projects: projects, onSelect: (index) => { setSelectedIndex(index); setFocusedPanel("loops"); }, onActivate: (index) => { setSelectedIndex(index); const loop = visible[index]; if (loop) {
|
|
336
349
|
setEditTarget(loop);
|
|
337
350
|
push("create");
|
|
338
|
-
} } }), _jsxs("box", { style: { flexDirection: "column", flexGrow: 1, backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx(Inspector, { loop: selected }), _jsx(RunHistory, { loop: selected, selectedRunIndex: selectedRunIndex, focused: focusedPanel === "runs", onSelectRun: (index) => { setSelectedRunIndex(index); setFocusedPanel("runs"); }, onOpenRun: handleOpenRunLog }), _jsx(ActionButtons, { loop: selected, focused: focusedPanel === "actions", selectedAction: selectedAction, 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, {}) : 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 })] }));
|
|
351
|
+
} } }), _jsxs("box", { style: { flexDirection: "column", flexGrow: 1, backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx(Inspector, { loop: selected }), _jsx(RunHistory, { loop: selected, selectedRunIndex: selectedRunIndex, focused: focusedPanel === "runs", onSelectRun: (index) => { setSelectedRunIndex(index); setFocusedPanel("runs"); }, onOpenRun: handleOpenRunLog }), _jsx(ActionButtons, { loop: selected, focused: focusedPanel === "actions", selectedAction: selectedAction, 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, 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 })] }));
|
|
339
352
|
}
|
|
340
353
|
const TASK_FORM_VIEWS = new Set(["task-create", "task-edit"]);
|
|
@@ -19,6 +19,7 @@ export function getActions(status) {
|
|
|
19
19
|
actions.push({ key: "play", label: t("board.actionPlay") });
|
|
20
20
|
}
|
|
21
21
|
if (status !== "running") {
|
|
22
|
+
actions.push({ key: "clone", label: t("board.actionClone") });
|
|
22
23
|
actions.push({ key: "trigger", label: t("board.actionTrigger") });
|
|
23
24
|
}
|
|
24
25
|
return actions;
|
|
@@ -40,7 +40,7 @@ export function createInitialValues(loop, currentProjectId) {
|
|
|
40
40
|
export function CreateView(props) {
|
|
41
41
|
const [values, setValues] = useState({
|
|
42
42
|
...props.initial,
|
|
43
|
-
project: props.
|
|
43
|
+
project: props.initial.project ?? props.currentProjectId ?? "default",
|
|
44
44
|
});
|
|
45
45
|
const valuesRef = useRef(values);
|
|
46
46
|
const [focusIndex, setFocusIndex] = useState(0);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
2
|
import { useTerminalDimensions } from "@opentui/react";
|
|
3
3
|
import { t } from "../../i18n/index.js";
|
|
4
|
-
export function HelpModal() {
|
|
4
|
+
export function HelpModal(props) {
|
|
5
|
+
const { view } = props;
|
|
5
6
|
const { width } = useTerminalDimensions();
|
|
6
|
-
const
|
|
7
|
+
const loopRows = [
|
|
7
8
|
["e", "edit loop"],
|
|
8
9
|
["d/del", "delete loop"],
|
|
10
|
+
["c", "clone loop"],
|
|
9
11
|
["f", "force run"],
|
|
10
12
|
["p", "pause/play (contextual)"],
|
|
11
13
|
["s", "stop loop (resets schedule)"],
|
|
@@ -13,11 +15,35 @@ export function HelpModal() {
|
|
|
13
15
|
["n", t("board.helpCreate")],
|
|
14
16
|
["t", t("board.helpCreateTask")],
|
|
15
17
|
["o", t("board.helpCycleSort")],
|
|
18
|
+
["x", "cycle status filter"],
|
|
19
|
+
["r", "project filter"],
|
|
16
20
|
["←/→", t("board.helpSwitchPanel")],
|
|
17
21
|
["/", t("board.helpSearch")],
|
|
18
22
|
["h", t("board.helpToggleHelp")],
|
|
19
23
|
["esc", t("board.helpQuit")],
|
|
20
24
|
];
|
|
25
|
+
const taskRows = [
|
|
26
|
+
["e", "edit task"],
|
|
27
|
+
["d/del", "delete task"],
|
|
28
|
+
["s", "select task (when creating/editing a loop)"],
|
|
29
|
+
["enter", "focus actions panel"],
|
|
30
|
+
["n", t("board.helpCreateTask")],
|
|
31
|
+
["←/→", t("board.helpSwitchPanel")],
|
|
32
|
+
["/", t("board.helpSearch")],
|
|
33
|
+
["h", t("board.helpToggleHelp")],
|
|
34
|
+
["esc", t("board.helpQuit")],
|
|
35
|
+
];
|
|
36
|
+
const projectRows = [
|
|
37
|
+
["enter", "focus actions"],
|
|
38
|
+
["n", "new project"],
|
|
39
|
+
["e", "edit project"],
|
|
40
|
+
["d", "delete project"],
|
|
41
|
+
["←/→", "switch panel (list/actions)"],
|
|
42
|
+
["h", t("board.helpToggleHelp")],
|
|
43
|
+
["esc", t("board.helpQuit")],
|
|
44
|
+
];
|
|
45
|
+
const rows = view === "task-list" ? taskRows : view === "projects" ? projectRows : loopRows;
|
|
46
|
+
const title = view === "task-list" ? "Task List Shortcuts" : view === "projects" ? "Projects Shortcuts" : t("board.helpTitle");
|
|
21
47
|
return (_jsx("box", { style: {
|
|
22
48
|
position: "absolute",
|
|
23
49
|
top: 0,
|
|
@@ -27,7 +53,7 @@ export function HelpModal() {
|
|
|
27
53
|
justifyContent: "center",
|
|
28
54
|
alignItems: "center",
|
|
29
55
|
zIndex: 90,
|
|
30
|
-
}, children: _jsx("box", { title:
|
|
56
|
+
}, children: _jsx("box", { title: title, border: true, style: {
|
|
31
57
|
flexDirection: "column",
|
|
32
58
|
padding: 1,
|
|
33
59
|
minWidth: Math.min(52, width - 4),
|
|
@@ -80,17 +80,16 @@ export function TaskActionButtons(props) {
|
|
|
80
80
|
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") }) }));
|
|
81
81
|
}
|
|
82
82
|
const allActions = [
|
|
83
|
-
{ key: "select", label: t("board.taskActionSelect")
|
|
84
|
-
{ key: "edit", label: t("board.taskActionEdit")
|
|
85
|
-
{ key: "delete", label: t("board.taskActionDelete")
|
|
83
|
+
{ key: "select", label: t("board.taskActionSelect") },
|
|
84
|
+
{ key: "edit", label: t("board.taskActionEdit") },
|
|
85
|
+
{ key: "delete", label: t("board.taskActionDelete") },
|
|
86
86
|
];
|
|
87
87
|
const actions = selectable ? allActions : allActions.filter((a) => a.key !== "select");
|
|
88
|
-
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(TaskActionButton, { label: action.label,
|
|
88
|
+
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(TaskActionButton, { label: action.label, selected: focused && selectedAction === i, onMouseDown: () => onAction(action.key) }, action.key))) }));
|
|
89
89
|
}
|
|
90
90
|
function TaskActionButton(props) {
|
|
91
91
|
const { isHovered, hoverProps } = useHoverState();
|
|
92
92
|
const bg = props.selected ? "#1e3a8a" : isHovered ? HOVER_BG : undefined;
|
|
93
93
|
const fg = props.selected ? "#ffffff" : isHovered ? "#e5e7eb" : "#9ca3af";
|
|
94
|
-
|
|
95
|
-
return (_jsxs("box", { onMouseDown: props.onMouseDown, style: { backgroundColor: bg, paddingLeft: 1, paddingRight: 1, marginRight: 1, flexDirection: "row", alignItems: "center" }, ...hoverProps, children: [_jsxs("text", { fg: keycapFg, children: ["[", props.hotkey, "] "] }), _jsx("text", { fg: fg, children: _jsx("strong", { children: props.label }) })] }));
|
|
94
|
+
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 }) }) }));
|
|
96
95
|
}
|
|
@@ -82,6 +82,7 @@ const GLOBAL_KEYS = {
|
|
|
82
82
|
e: (p) => p.onAction("edit"),
|
|
83
83
|
d: (p) => p.onAction("delete"),
|
|
84
84
|
delete: (p) => p.onAction("delete"),
|
|
85
|
+
c: (p) => p.onAction("clone"),
|
|
85
86
|
p: (p) => p.onAction("pause-or-play"),
|
|
86
87
|
s: (p) => p.onAction("stop"),
|
|
87
88
|
f: (p) => p.onAction("trigger"),
|
|
@@ -192,7 +193,6 @@ const BOARD_SHORTCUTS = {
|
|
|
192
193
|
o: (p) => p.setSort((prev) => cycleSortMode(prev)),
|
|
193
194
|
x: (p) => p.setFilters((prev) => ({ ...prev, status: cycleStatusFilter(prev.status) })),
|
|
194
195
|
r: (p) => p.onSelectProject?.(),
|
|
195
|
-
c: (p) => p.onSelectProject?.(),
|
|
196
196
|
};
|
|
197
197
|
export function useBoardKeybindings(params) {
|
|
198
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, onAddLoop, onSelectProject, } = params;
|
package/dist/i18n/en.json
CHANGED
|
@@ -275,6 +275,7 @@
|
|
|
275
275
|
"board.actionPause": "Pause",
|
|
276
276
|
"board.actionStop": "Stop",
|
|
277
277
|
"board.actionPlay": "Play",
|
|
278
|
+
"board.actionClone": "Clone",
|
|
278
279
|
"board.actionTrigger": "Force Run",
|
|
279
280
|
"board.noActions": "Select a loop to see actions",
|
|
280
281
|
"board.confirmPause": "Pause loop \"{desc}\"?",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loop-task",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
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": {
|