loop-task 2.0.1 → 2.0.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/i18n/en.json
CHANGED
|
@@ -539,11 +539,11 @@
|
|
|
539
539
|
"cmdInput.searchPlaceholder": "Write something to filter about...",
|
|
540
540
|
"cmdInput.confirmYes": "yes",
|
|
541
541
|
"cmdInput.confirmCancel": "cancel",
|
|
542
|
-
"confirm.deleteLoop": "
|
|
543
|
-
"confirm.deleteTask": "
|
|
544
|
-
"confirm.deleteProject": "
|
|
545
|
-
"confirm.stopLoop": "
|
|
546
|
-
"confirm.quit": "
|
|
542
|
+
"confirm.deleteLoop": "Type yes to delete \"{name}\"",
|
|
543
|
+
"confirm.deleteTask": "Type yes to delete task \"{id}\"",
|
|
544
|
+
"confirm.deleteProject": "Type yes to delete project \"{name}\"",
|
|
545
|
+
"confirm.stopLoop": "Type yes to stop \"{name}\"",
|
|
546
|
+
"confirm.quit": "Type yes to exit loop-task",
|
|
547
547
|
"wizard.newLoop": "New loop",
|
|
548
548
|
"wizard.editLoop": "Edit loop",
|
|
549
549
|
"wizard.newTask": "New task",
|
|
@@ -560,7 +560,7 @@
|
|
|
560
560
|
"wizard.runNowNow": "Run now, then every interval",
|
|
561
561
|
"wizard.cwdPrompt": "Working directory? (optional)",
|
|
562
562
|
"wizard.cwdHint": "Directory the command runs in",
|
|
563
|
-
"wizard.descriptionPrompt": "Description?
|
|
563
|
+
"wizard.descriptionPrompt": "Description?",
|
|
564
564
|
"wizard.descriptionHint": "Short label shown in the list",
|
|
565
565
|
"wizard.maxRunsPrompt": "Max runs? (optional)",
|
|
566
566
|
"wizard.maxRunsHint": "Stop after N runs. Leave blank to run forever",
|
package/dist/tui/app.js
CHANGED
|
@@ -233,7 +233,7 @@ export function App(props) {
|
|
|
233
233
|
onConfirm: () => { void deleteTask(selectedTask.id).then(() => { void refreshTasks(); }); },
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
|
-
else if (activeTab === "projects" && selectedProjectEntity) {
|
|
236
|
+
else if (activeTab === "projects" && selectedProjectEntity && !selectedProjectEntity.isSystem) {
|
|
237
237
|
setConfirmState({
|
|
238
238
|
prompt: t("confirm.deleteProject", { name: selectedProjectEntity.name }),
|
|
239
239
|
onConfirm: async () => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useCallback } from "react";
|
|
2
|
+
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
4
|
import { useAutocompleteState, } from "ink-combobox";
|
|
5
5
|
import { darkTheme as theme } from "../theme.js";
|
|
6
6
|
import { t } from "../../i18n/index.js";
|
|
7
7
|
import { buildCommands, rankCommands } from "../commands.js";
|
|
8
|
-
import { COMMAND_INPUT_DROPDOWN_MAX_VISIBLE, COMMAND_INPUT_HEIGHT,
|
|
8
|
+
import { COMMAND_INPUT_DROPDOWN_MAX_VISIBLE, COMMAND_INPUT_HEIGHT, } from "../../config/constants.js";
|
|
9
9
|
import { sanitizePaste } from "../utils/paste.js";
|
|
10
10
|
export { sanitizePaste } from "../utils/paste.js";
|
|
11
11
|
// ── Rendered input with cursor ───────────────────────────────────────
|
|
@@ -76,13 +76,6 @@ function CommandDropdown({ state, rankedFiltered, }) {
|
|
|
76
76
|
}), belowCount > 0 && (_jsx(Text, { color: theme.text.muted, children: ` \u2193 ${belowCount} more` }))] }));
|
|
77
77
|
}
|
|
78
78
|
// ── Confirm inline options ────────────────────────────────────────────
|
|
79
|
-
function ConfirmInlineOptions({ focusedIndex, yesLabel, cancelLabel, }) {
|
|
80
|
-
// Options order: [cancel(0), yes(1)] in the autocomplete state,
|
|
81
|
-
// displayed inline as "❯ yes cancel" (yes first, cancel second).
|
|
82
|
-
const yesFocused = focusedIndex === 1;
|
|
83
|
-
const cancelFocused = focusedIndex === 0;
|
|
84
|
-
return (_jsxs(Box, { paddingLeft: 3, children: [yesFocused ? (_jsx(Text, { color: theme.text.inverse, backgroundColor: theme.bg.active, children: `\u276f ${yesLabel}` })) : (_jsx(Text, { color: theme.text.muted, children: ` ${yesLabel}` })), _jsx(Text, { color: theme.text.muted, children: " " }), cancelFocused ? (_jsx(Text, { color: theme.text.inverse, backgroundColor: theme.bg.active, children: `\u276f ${cancelLabel}` })) : (_jsx(Text, { color: theme.text.muted, children: ` ${cancelLabel}` }))] }));
|
|
85
|
-
}
|
|
86
79
|
// ── Hint bar ──────────────────────────────────────────────────────────
|
|
87
80
|
function HintBar({ leftHint, rightHint, }) {
|
|
88
81
|
return (_jsxs(Box, { justifyContent: "space-between", paddingX: 1, children: [_jsx(Box, { children: leftHint }), _jsx(Box, { children: rightHint })] }));
|
|
@@ -219,54 +212,39 @@ function CommandMode({ context, onCommand, onCopy, onPanelAction, disabled, navO
|
|
|
219
212
|
}
|
|
220
213
|
// ── Confirm mode ─────────────────────────────────────────────────────
|
|
221
214
|
function ConfirmMode({ confirmState, onConfirmYes, onConfirmCancel, disabled, }) {
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
const options = useMemo(() => [
|
|
225
|
-
{ label: cancelLabel, value: CONFIRM_CANCEL },
|
|
226
|
-
{ label: yesLabel, value: CONFIRM_YES },
|
|
227
|
-
], [cancelLabel, yesLabel]);
|
|
228
|
-
const handleSelect = useCallback((value) => {
|
|
229
|
-
if (value === CONFIRM_YES) {
|
|
230
|
-
onConfirmYes();
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
onConfirmCancel();
|
|
234
|
-
}
|
|
235
|
-
}, [onConfirmYes, onConfirmCancel]);
|
|
236
|
-
const { state, dispatch } = useAutocompleteState({
|
|
237
|
-
options,
|
|
238
|
-
visibleOptionCount: 2,
|
|
239
|
-
onSelect: handleSelect,
|
|
240
|
-
});
|
|
241
|
-
useInput((_input, key) => {
|
|
215
|
+
const [value, setValue] = useState("");
|
|
216
|
+
useInput((input, key) => {
|
|
242
217
|
if (key.ctrl)
|
|
243
218
|
return;
|
|
244
|
-
if (
|
|
219
|
+
if (input.length > 1 && (input.includes("\r") || input.includes("\n")))
|
|
245
220
|
return;
|
|
246
221
|
if (key.escape) {
|
|
222
|
+
setValue("");
|
|
247
223
|
onConfirmCancel();
|
|
248
224
|
return;
|
|
249
225
|
}
|
|
250
226
|
if (key.return) {
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
227
|
+
if (value.toLowerCase() === "yes") {
|
|
228
|
+
setValue("");
|
|
229
|
+
onConfirmYes();
|
|
254
230
|
}
|
|
255
231
|
else {
|
|
232
|
+
setValue("");
|
|
256
233
|
onConfirmCancel();
|
|
257
234
|
}
|
|
258
235
|
return;
|
|
259
236
|
}
|
|
260
|
-
if (key.
|
|
261
|
-
|
|
237
|
+
if (key.backspace || key.delete) {
|
|
238
|
+
setValue((v) => v.slice(0, -1));
|
|
262
239
|
return;
|
|
263
240
|
}
|
|
264
|
-
if (key.
|
|
265
|
-
|
|
241
|
+
if (input && !key.ctrl && !key.meta && input.length === 1) {
|
|
242
|
+
setValue((v) => v + input);
|
|
266
243
|
return;
|
|
267
244
|
}
|
|
268
245
|
}, { isActive: !disabled });
|
|
269
|
-
|
|
246
|
+
const cursor = "\x1b[7m \x1b[27m";
|
|
247
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.semantic.danger, children: "│ " }), _jsx(Text, { color: theme.text.muted, children: confirmState.prompt + " " }), value.length > 0 ? (_jsx(Text, { children: value + cursor })) : (_jsx(Text, { children: cursor }))] }), _jsx(HintBar, { leftHint: _jsxs(Box, { children: [_jsx(Text, { color: theme.text.muted, children: "\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 " }), _jsx(KeyHint, { keyLabel: "esc", action: "cancel" })] }), rightHint: _jsx(KeyHint, { keyLabel: "enter", action: "confirm" }) })] }));
|
|
270
248
|
}
|
|
271
249
|
// ── Search mode ──────────────────────────────────────────────────────
|
|
272
250
|
function SearchMode({ value, onSearchChange, onSearchSubmit, onSearchCancel, disabled, }) {
|
|
@@ -61,7 +61,7 @@ export function CreateView(props) {
|
|
|
61
61
|
hint: t("board.hintTask"),
|
|
62
62
|
required: true,
|
|
63
63
|
inputType: "text",
|
|
64
|
-
defaultValue: initial.taskId ?? undefined,
|
|
64
|
+
defaultValue: selectedTaskId ?? initial.taskId ?? undefined,
|
|
65
65
|
skip: (values) => !values.taskMode?.includes("Existing"),
|
|
66
66
|
onActivate: () => setTaskPickerOpen(true),
|
|
67
67
|
renderCustom: ({ isActive }) => (_jsx(SelectValueField, { label: resolvedTaskName, placeholder: t("board.chooseTask"), isActive: isActive })),
|
|
@@ -106,7 +106,7 @@ export function CreateView(props) {
|
|
|
106
106
|
key: "description",
|
|
107
107
|
prompt: t("wizard.descriptionPrompt"),
|
|
108
108
|
hint: t("wizard.descriptionHint"),
|
|
109
|
-
required:
|
|
109
|
+
required: true,
|
|
110
110
|
inputType: "text",
|
|
111
111
|
defaultValue: initial.description ?? undefined,
|
|
112
112
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback, useMemo } from "react";
|
|
2
|
+
import { useState, useCallback, useMemo, useRef } from "react";
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
4
|
import { darkTheme as theme } from "../theme.js";
|
|
5
5
|
import { t } from "../../i18n/index.js";
|
|
@@ -28,9 +28,22 @@ export function WizardForm(props) {
|
|
|
28
28
|
result[s.key] = values[s.key] ?? s.defaultValue ?? "";
|
|
29
29
|
return result;
|
|
30
30
|
}, [steps, values]);
|
|
31
|
+
// Synchronous mirror of resolvedValues — updated inside setValue so
|
|
32
|
+
// findNextField sees the new value immediately, before React re-renders.
|
|
33
|
+
// Without this, onChange("Existing task") + onAdvance() run in the same
|
|
34
|
+
// tick: setValues queues a state update, but findNextField reads stale
|
|
35
|
+
// resolvedValues where taskMode is still "" → taskId is wrongly skipped.
|
|
36
|
+
const resolvedValuesRef = useRef({});
|
|
37
|
+
const computeResolved = (raw) => {
|
|
38
|
+
const result = {};
|
|
39
|
+
for (const s of steps)
|
|
40
|
+
result[s.key] = raw[s.key] ?? s.defaultValue ?? "";
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
31
43
|
const setValue = useCallback((key, next) => {
|
|
44
|
+
resolvedValuesRef.current = computeResolved({ ...resolvedValuesRef.current, [key]: next });
|
|
32
45
|
setValues((prev) => ({ ...prev, [key]: next }));
|
|
33
|
-
}, []);
|
|
46
|
+
}, [steps]);
|
|
34
47
|
const clearError = useCallback((key) => {
|
|
35
48
|
setValidationErrors((prev) => {
|
|
36
49
|
if (!(key in prev))
|
|
@@ -75,14 +88,15 @@ export function WizardForm(props) {
|
|
|
75
88
|
const findNextField = useCallback((from, delta) => {
|
|
76
89
|
let next = from + delta;
|
|
77
90
|
const len = steps.length;
|
|
91
|
+
const vals = resolvedValuesRef.current;
|
|
78
92
|
while (next >= 0 && next < len) {
|
|
79
93
|
const candidate = steps[next];
|
|
80
|
-
if (!candidate.skip || !candidate.skip(
|
|
94
|
+
if (!candidate.skip || !candidate.skip(vals))
|
|
81
95
|
return next;
|
|
82
96
|
next += delta;
|
|
83
97
|
}
|
|
84
98
|
return from;
|
|
85
|
-
}, [steps
|
|
99
|
+
}, [steps]);
|
|
86
100
|
const moveField = useCallback((delta) => {
|
|
87
101
|
if (step) {
|
|
88
102
|
const err = validateField(step.key, resolvedValues);
|
package/dist/tui/index.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { render } from "ink";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { App } from "./
|
|
3
|
+
import { App } from "./App.js";
|
|
4
4
|
import { BRACKETED_PASTE_ENABLE, BRACKETED_PASTE_DISABLE } from "../config/constants.js";
|
|
5
5
|
export async function launchBoard() {
|
|
6
6
|
// Enable bracketed paste so a native paste (Ctrl+Shift+V / Cmd+V / right-click)
|
|
7
7
|
// arrives as one delimited chunk instead of a stream of keypresses.
|
|
8
8
|
process.stdout.write(BRACKETED_PASTE_ENABLE);
|
|
9
9
|
const disableBracketedPaste = () => process.stdout.write(BRACKETED_PASTE_DISABLE);
|
|
10
|
-
const instance = render(React.createElement(App, {
|
|
10
|
+
const instance = render(React.createElement(App, {
|
|
11
|
+
onQuit: () => {
|
|
11
12
|
disableBracketedPaste();
|
|
12
13
|
instance.unmount();
|
|
13
|
-
}
|
|
14
|
+
}
|
|
15
|
+
}));
|
|
14
16
|
process.on("exit", disableBracketedPaste);
|
|
15
17
|
process.on("uncaughtException", (error) => {
|
|
16
18
|
console.error("Uncaught exception:", error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loop-task",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.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": {
|