loop-task 1.4.3 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -92,6 +92,31 @@ export function CreateView(props) {
92
92
  return 0;
93
93
  return next;
94
94
  });
95
+ key.preventDefault();
96
+ return;
97
+ }
98
+ if (key.name === "up" || key.name === "down") {
99
+ const field = filteredFields[focusIndex];
100
+ if (field === "project" && projectOptions.length > 1) {
101
+ const currentIdx = projectOptions.findIndex((p) => p.value === valuesRef.current.project);
102
+ const nextIdx = key.name === "down"
103
+ ? (currentIdx + 1) % projectOptions.length
104
+ : (currentIdx - 1 + projectOptions.length) % projectOptions.length;
105
+ const next = projectOptions[nextIdx];
106
+ if (next)
107
+ updateValues({ ...valuesRef.current, project: next.value });
108
+ key.preventDefault();
109
+ return;
110
+ }
111
+ setFocusIndex((i) => {
112
+ const next = key.name === "up" ? i - 1 : i + 1;
113
+ if (next < 0)
114
+ return cancelIndex;
115
+ if (next > cancelIndex)
116
+ return 0;
117
+ return next;
118
+ });
119
+ key.preventDefault();
95
120
  return;
96
121
  }
97
122
  if (key.name === "return" || key.name === "enter" || key.name === " ") {
@@ -99,35 +124,27 @@ export function CreateView(props) {
99
124
  if (field === "taskMode") {
100
125
  const next = valuesRef.current.taskMode === TASK_MODE_INLINE ? TASK_MODE_EXISTING : TASK_MODE_INLINE;
101
126
  updateValues({ ...valuesRef.current, taskMode: next });
127
+ key.preventDefault();
102
128
  return;
103
129
  }
104
130
  if (field === "runNow") {
105
131
  const next = valuesRef.current.runNow === "y" ? "n" : "y";
106
132
  updateValues({ ...valuesRef.current, runNow: next });
133
+ key.preventDefault();
107
134
  return;
108
135
  }
109
136
  if (focusIndex === chooseTaskIdx && !isInline) {
110
137
  props.onChooseTask();
138
+ key.preventDefault();
111
139
  return;
112
140
  }
113
141
  if (focusIndex === saveIndex) {
114
142
  void submit(valuesRef.current);
143
+ key.preventDefault();
115
144
  }
116
145
  else if (focusIndex === cancelIndex) {
117
146
  props.onCancel();
118
- }
119
- }
120
- if (key.name === "up" || key.name === "down") {
121
- const field = filteredFields[focusIndex];
122
- if (field === "project" && projectOptions.length > 1) {
123
- const currentIdx = projectOptions.findIndex((p) => p.value === valuesRef.current.project);
124
- const nextIdx = key.name === "down"
125
- ? (currentIdx + 1) % projectOptions.length
126
- : (currentIdx - 1 + projectOptions.length) % projectOptions.length;
127
- const next = projectOptions[nextIdx];
128
- if (next)
129
- updateValues({ ...valuesRef.current, project: next.value });
130
- return;
147
+ key.preventDefault();
131
148
  }
132
149
  }
133
150
  });
@@ -5,40 +5,43 @@ export function HelpModal(props) {
5
5
  const { view } = props;
6
6
  const { width } = useTerminalDimensions();
7
7
  const loopRows = [
8
+ ["tab/←→", "switch panels"],
9
+ ["↑/↓", "navigate list items"],
10
+ ["enter", t("board.helpEdit")],
8
11
  ["e", "edit loop"],
9
12
  ["d/del", "delete loop"],
10
13
  ["c", "clone loop"],
11
14
  ["f", "force run"],
12
15
  ["p", "pause/play (contextual)"],
13
16
  ["s", "stop loop (resets schedule)"],
14
- [t("board.helpKeyEnter"), t("board.helpEdit")],
15
17
  ["n", t("board.helpCreate")],
16
18
  ["t", t("board.helpCreateTask")],
17
19
  ["o", t("board.helpCycleSort")],
18
20
  ["x", "cycle status filter"],
19
21
  ["r", "project filter"],
20
- ["←/→", t("board.helpSwitchPanel")],
21
22
  ["/", t("board.helpSearch")],
22
23
  ["h", t("board.helpToggleHelp")],
23
24
  ["esc", t("board.helpQuit")],
24
25
  ];
25
26
  const taskRows = [
27
+ ["tab/←→", "switch panels"],
28
+ ["↑/↓", "navigate tasks"],
29
+ ["enter", "focus actions panel"],
26
30
  ["e", "edit task"],
27
31
  ["d/del", "delete task"],
28
32
  ["s", "select task (when creating/editing a loop)"],
29
- ["enter", "focus actions panel"],
30
33
  ["n", t("board.helpCreateTask")],
31
- ["←/→", t("board.helpSwitchPanel")],
32
34
  ["/", t("board.helpSearch")],
33
35
  ["h", t("board.helpToggleHelp")],
34
36
  ["esc", t("board.helpQuit")],
35
37
  ];
36
38
  const projectRows = [
39
+ ["tab/←→", "switch panel (list/actions)"],
40
+ ["↑/↓", "navigate projects"],
37
41
  ["enter", "focus actions"],
38
42
  ["n", "new project"],
39
43
  ["e", "edit project"],
40
44
  ["d", "delete project"],
41
- ["←/→", "switch panel (list/actions)"],
42
45
  ["h", t("board.helpToggleHelp")],
43
46
  ["esc", t("board.helpQuit")],
44
47
  ];
@@ -41,6 +41,7 @@ export function ProjectsPage(props) {
41
41
  setSelectedIndex((i) => Math.max(0, i - 1));
42
42
  else
43
43
  setSelectedAction((a) => Math.max(0, a - 1));
44
+ key.preventDefault();
44
45
  return;
45
46
  }
46
47
  if (key.name === "down") {
@@ -48,10 +49,12 @@ export function ProjectsPage(props) {
48
49
  setSelectedIndex((i) => Math.min(projects.length - 1, i + 1));
49
50
  else
50
51
  setSelectedAction((a) => Math.min(PROJECT_ACTION_COUNT - 1, a + 1));
52
+ key.preventDefault();
51
53
  return;
52
54
  }
53
- if (key.name === "left" || key.name === "right") {
55
+ if (key.name === "left" || key.name === "right" || key.name === "tab") {
54
56
  setFocusedPanel((p) => (p === "list" ? "actions" : "list"));
57
+ key.preventDefault();
55
58
  return;
56
59
  }
57
60
  if (key.name === "return" || key.name === "enter") {
@@ -61,22 +64,27 @@ export function ProjectsPage(props) {
61
64
  else {
62
65
  runAction(selectedAction);
63
66
  }
67
+ key.preventDefault();
64
68
  return;
65
69
  }
66
70
  if (key.name === "n") {
67
71
  setSubModal("create");
72
+ key.preventDefault();
68
73
  return;
69
74
  }
70
75
  if (key.name === "e" && selectedProject && !selectedProject.isSystem) {
71
76
  setSubModal("edit");
77
+ key.preventDefault();
72
78
  return;
73
79
  }
74
80
  if (key.name === "d" && selectedProject && !selectedProject.isSystem) {
75
81
  setSubModal("delete");
82
+ key.preventDefault();
76
83
  return;
77
84
  }
78
85
  if (key.name === "escape") {
79
86
  onClose();
87
+ key.preventDefault();
80
88
  }
81
89
  });
82
90
  const actions = [
@@ -46,14 +46,29 @@ export function TaskForm(props) {
46
46
  return 0;
47
47
  return next;
48
48
  });
49
+ key.preventDefault();
50
+ return;
51
+ }
52
+ if (key.name === "up" || key.name === "down") {
53
+ setFocusIndex((i) => {
54
+ const next = key.name === "up" ? i - 1 : i + 1;
55
+ if (next < 0)
56
+ return cancelIndex;
57
+ if (next > cancelIndex)
58
+ return 0;
59
+ return next;
60
+ });
61
+ key.preventDefault();
49
62
  return;
50
63
  }
51
64
  if (key.name === "return" || key.name === "enter") {
52
65
  if (focusIndex === saveIndex) {
53
66
  void submit(valuesRef.current);
67
+ key.preventDefault();
54
68
  }
55
69
  else if (focusIndex === cancelIndex) {
56
70
  props.onCancel();
71
+ key.preventDefault();
57
72
  }
58
73
  }
59
74
  });
@@ -200,21 +200,25 @@ export function useBoardKeybindings(params) {
200
200
  const name = key.name;
201
201
  if (confirm) {
202
202
  CONFIRM_KEYS[name]?.({ confirm, confirmChoice, setConfirm, setConfirmChoice });
203
+ key.preventDefault();
203
204
  return;
204
205
  }
205
206
  if (logModalRun) {
206
207
  if (name === "escape" || name === "q") {
207
208
  OVERLAY_DISMISS.log({ setLogModalRun, setHelpOpen, setSearchActive, setFocusedPanel }, name);
209
+ key.preventDefault();
208
210
  return;
209
211
  }
210
212
  if (key.ctrl && name === "c") {
211
213
  copyToClipboard(logModalLines.join("\n"));
214
+ key.preventDefault();
212
215
  return;
213
216
  }
214
217
  return;
215
218
  }
216
219
  if (helpOpen && (name === "h" || name === "escape")) {
217
220
  OVERLAY_DISMISS.help({ setLogModalRun, setHelpOpen, setSearchActive, setFocusedPanel }, name);
221
+ key.preventDefault();
218
222
  return;
219
223
  }
220
224
  if (helpOpen)
@@ -223,27 +227,36 @@ export function useBoardKeybindings(params) {
223
227
  if (name === "escape") {
224
228
  setSearchActive(false);
225
229
  setFocusedPanel("loops");
230
+ key.preventDefault();
226
231
  return;
227
232
  }
228
233
  return;
229
234
  }
230
- if (name === "left" || name === "right") {
231
- if (focusedPanel === "actions") {
235
+ if (view !== "board" && name === "escape") {
236
+ VIEW_ESCAPE[view]?.({ setEditTarget, setEditTask, onBack: pop });
237
+ key.preventDefault();
238
+ return;
239
+ }
240
+ if (view !== "board")
241
+ return;
242
+ if (name === "left" || name === "right" || name === "tab") {
243
+ const direction = name === "left" || (name === "tab" && key.shift) ? "left" : "right";
244
+ if (focusedPanel === "actions" && name !== "tab") {
232
245
  const actionCount = selected ? getActionCount(selected.status) : 0;
233
- if (name === "left" && selectedAction === 0) {
246
+ if (direction === "left" && selectedAction === 0) {
234
247
  setFocusedPanel((p) => nextPanel(p, "left"));
235
248
  }
236
- else if (name === "right" && selectedAction === actionCount - 1) {
249
+ else if (direction === "right" && selectedAction === actionCount - 1) {
237
250
  setFocusedPanel((p) => nextPanel(p, "right"));
238
251
  }
239
252
  else {
240
- setSelectedAction((i) => name === "right"
253
+ setSelectedAction((i) => direction === "right"
241
254
  ? Math.min(actionCount - 1, i + 1)
242
255
  : Math.max(0, i - 1));
243
256
  }
244
257
  }
245
258
  else {
246
- const next = nextPanel(focusedPanel, name === "right" ? "right" : "left");
259
+ const next = nextPanel(focusedPanel, direction);
247
260
  if (next === "actions") {
248
261
  setFocusedPanel("actions");
249
262
  setSelectedAction(0);
@@ -252,17 +265,13 @@ export function useBoardKeybindings(params) {
252
265
  setFocusedPanel(next);
253
266
  }
254
267
  }
268
+ key.preventDefault();
255
269
  return;
256
270
  }
257
- if (view !== "board" && name === "escape") {
258
- VIEW_ESCAPE[view]?.({ setEditTarget, setEditTask, onBack: pop });
259
- return;
260
- }
261
- if (view !== "board")
262
- return;
263
271
  const globalHandler = GLOBAL_KEYS[name];
264
272
  if (globalHandler) {
265
273
  globalHandler({ destroyLogSocket, onQuit, setHelpOpen, setEditTarget, setEditTask, push, onAction });
274
+ key.preventDefault();
266
275
  return;
267
276
  }
268
277
  const panelHandler = panelHandlers[focusedPanel];
@@ -271,8 +280,10 @@ export function useBoardKeybindings(params) {
271
280
  setSelectedIndex, setSelectedRunIndex, setFocusedPanel, selectedRunCount, selected,
272
281
  selectedRunIndex, setSelectedAction, selectedAction, onAction, onOpenRunLog,
273
282
  refreshTasks, onViewTasks, onViewProjects, onAddLoop,
274
- }))
283
+ })) {
284
+ key.preventDefault();
275
285
  return;
286
+ }
276
287
  const shortcut = BOARD_SHORTCUTS[name];
277
288
  if (shortcut) {
278
289
  shortcut({
@@ -281,6 +292,7 @@ export function useBoardKeybindings(params) {
281
292
  selectedRunIndex, setSelectedAction, selectedAction, onAction, onOpenRunLog,
282
293
  onSelectProject,
283
294
  });
295
+ key.preventDefault();
284
296
  }
285
297
  });
286
298
  }
@@ -16,94 +16,108 @@ export function useTaskKeybindings(params) {
16
16
  if (name === "escape") {
17
17
  setTaskSearchActive(false);
18
18
  setTaskFocusedPanel("tasks");
19
+ key.preventDefault();
19
20
  return;
20
21
  }
21
22
  return;
22
23
  }
23
24
  if (name === "escape") {
24
25
  onCancel();
26
+ key.preventDefault();
25
27
  return;
26
28
  }
27
29
  if (name === "n") {
28
30
  onCreateTask();
31
+ key.preventDefault();
29
32
  return;
30
33
  }
31
34
  if (name === "/" && taskFocusedPanel === "tasks") {
32
35
  setTaskSearchActive(true);
36
+ key.preventDefault();
33
37
  return;
34
38
  }
35
- if (name === "tab") {
36
- setTaskFocusedPanel((p) => nextTaskPanel(p, "right"));
37
- return;
38
- }
39
- if (name === "left" || name === "right") {
40
- if (taskFocusedPanel === "actions") {
41
- if (name === "left" && taskSelectedAction === 0) {
39
+ if (name === "tab" || name === "left" || name === "right") {
40
+ const direction = name === "left" || (name === "tab" && key.shift) ? "left" : "right";
41
+ if (taskFocusedPanel === "actions" && name !== "tab") {
42
+ if (direction === "left" && taskSelectedAction === 0) {
42
43
  setTaskFocusedPanel((p) => nextTaskPanel(p, "left"));
43
44
  }
44
- else if (name === "right" && taskSelectedAction === taskActionCount - 1) {
45
+ else if (direction === "right" && taskSelectedAction === taskActionCount - 1) {
45
46
  setTaskFocusedPanel((p) => nextTaskPanel(p, "right"));
46
47
  }
47
48
  else {
48
- setTaskSelectedAction((i) => name === "right"
49
+ setTaskSelectedAction((i) => direction === "right"
49
50
  ? Math.min(taskActionCount - 1, i + 1)
50
51
  : Math.max(0, i - 1));
51
52
  }
52
53
  }
53
54
  else {
54
- setTaskFocusedPanel((p) => nextTaskPanel(p, name === "right" ? "right" : "left"));
55
+ setTaskFocusedPanel((p) => nextTaskPanel(p, direction));
55
56
  }
57
+ key.preventDefault();
56
58
  return;
57
59
  }
58
60
  if (taskFocusedPanel === "tasks") {
59
61
  if (name === "up" || name === "k") {
60
62
  setTaskSelectedIndex((i) => Math.max(0, i - 1));
63
+ key.preventDefault();
61
64
  return;
62
65
  }
63
66
  if (name === "down" || name === "j") {
64
67
  setTaskSelectedIndex((i) => Math.min(tasks.length - 1, i + 1));
68
+ key.preventDefault();
65
69
  return;
66
70
  }
67
71
  if (name === "return" || name === "enter") {
68
72
  setTaskFocusedPanel("actions");
73
+ key.preventDefault();
69
74
  return;
70
75
  }
71
76
  if (name === "e" && tasks[taskSelectedIndex]) {
72
77
  onTaskAction("edit");
78
+ key.preventDefault();
73
79
  return;
74
80
  }
75
81
  if (name === "d" && tasks[taskSelectedIndex]) {
76
82
  onTaskAction("delete");
83
+ key.preventDefault();
77
84
  return;
78
85
  }
79
86
  if (name === "delete" && tasks[taskSelectedIndex]) {
80
87
  onTaskAction("delete");
88
+ key.preventDefault();
81
89
  return;
82
90
  }
83
91
  if (selectable && name === "s" && tasks[taskSelectedIndex]) {
84
92
  onTaskAction("select");
93
+ key.preventDefault();
85
94
  return;
86
95
  }
87
96
  }
88
97
  if (taskFocusedPanel === "actions") {
89
98
  if (name === "up" || name === "k") {
90
99
  setTaskSelectedAction((i) => Math.max(0, i - 1));
100
+ key.preventDefault();
91
101
  return;
92
102
  }
93
103
  if (name === "down" || name === "j") {
94
104
  setTaskSelectedAction((i) => Math.min(taskActionCount - 1, i + 1));
105
+ key.preventDefault();
95
106
  return;
96
107
  }
97
108
  if (name === "return" || name === "enter") {
98
109
  onTaskAction(taskActions[taskSelectedAction] ?? "select");
110
+ key.preventDefault();
99
111
  return;
100
112
  }
101
113
  if (name === "d") {
102
114
  onTaskAction("delete");
115
+ key.preventDefault();
103
116
  return;
104
117
  }
105
118
  if (selectable && name === "s") {
106
119
  onTaskAction("select");
120
+ key.preventDefault();
107
121
  return;
108
122
  }
109
123
  }
@@ -1,11 +1,13 @@
1
1
  import { LoopManager } from "./manager.js";
2
2
  import { TaskManager } from "./task-manager.js";
3
3
  import { IpcServer } from "./server.js";
4
- import { writeDaemonPid, removeDaemonPid, writeDaemonSignature, removeDaemonSignature, computeCodeSignature, } from "./state.js";
4
+ import { writeDaemonPid, removeDaemonPid, writeDaemonSignature, removeDaemonSignature, computeCodeSignature, migrateTasksToJson, migrateLoopsToJson, } from "./state.js";
5
5
  import { t } from "../i18n/index.js";
6
6
  import { daemonLog } from "./daemon-log.js";
7
7
  async function main() {
8
8
  const taskManager = new TaskManager();
9
+ migrateLoopsToJson();
10
+ migrateTasksToJson();
9
11
  taskManager.init();
10
12
  const manager = new LoopManager(taskManager);
11
13
  const server = new IpcServer(manager, taskManager);
@@ -1,7 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import { LoopController } from "../core/loop-controller.js";
3
3
  import { ProjectManager } from "./projects.js";
4
- import { saveLoop, loadAllLoops, deleteLoop as deleteLoopState, getLogPath, migrateLoopsToJson, migrateTasksToJson, } from "./state.js";
4
+ import { saveLoop, loadAllLoops, deleteLoop as deleteLoopState, getLogPath, } from "./state.js";
5
5
  import { daemonLog } from "./daemon-log.js";
6
6
  export class LoopManager {
7
7
  loops = new Map();
@@ -16,9 +16,6 @@ export class LoopManager {
16
16
  init() {
17
17
  // Initialize projects first
18
18
  this.projectManager.init();
19
- // Migrate from individual files to consolidated JSON
20
- migrateLoopsToJson();
21
- migrateTasksToJson();
22
19
  const saved = loadAllLoops();
23
20
  let restarted = 0;
24
21
  let migrated = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loop-task",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
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": {