loop-task 2.0.1 → 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 (126) hide show
  1. package/dist/i18n/en.json +6 -6
  2. package/dist/tui/{app.js → App.js} +1 -1
  3. package/dist/tui/components/CommandInput.js +16 -38
  4. package/dist/tui/components/CreateForm.js +2 -2
  5. package/dist/tui/components/WizardForm.js +18 -4
  6. package/dist/tui/index.js +5 -3
  7. package/package.json +2 -2
  8. package/dist/board/App.js +0 -405
  9. package/dist/board/board-log.js +0 -18
  10. package/dist/board/components/ActionButtons.js +0 -46
  11. package/dist/board/components/BoardButton.js +0 -58
  12. package/dist/board/components/ConfirmModal.js +0 -36
  13. package/dist/board/components/ContextHelpModal.js +0 -22
  14. package/dist/board/components/CreateForm.js +0 -271
  15. package/dist/board/components/CreateProjectModal.js +0 -97
  16. package/dist/board/components/DeleteProjectConfirm.js +0 -53
  17. package/dist/board/components/DetailView.js +0 -25
  18. package/dist/board/components/EditProjectModal.js +0 -99
  19. package/dist/board/components/FilterBar.js +0 -17
  20. package/dist/board/components/Footer.js +0 -52
  21. package/dist/board/components/Header.js +0 -46
  22. package/dist/board/components/HelpModal.js +0 -65
  23. package/dist/board/components/Inspector.js +0 -12
  24. package/dist/board/components/LogModal.js +0 -82
  25. package/dist/board/components/Navigator.js +0 -84
  26. package/dist/board/components/ProjectsModal.js +0 -70
  27. package/dist/board/components/ProjectsPage.js +0 -134
  28. package/dist/board/components/RunHistory.js +0 -83
  29. package/dist/board/components/SearchBox.js +0 -10
  30. package/dist/board/components/SearchSelect.js +0 -132
  31. package/dist/board/components/TaskBrowser.js +0 -95
  32. package/dist/board/components/TaskFilterBar.js +0 -6
  33. package/dist/board/components/TaskForm.js +0 -155
  34. package/dist/board/components/Timeline.js +0 -5
  35. package/dist/board/daemon.js +0 -117
  36. package/dist/board/focus-context.js +0 -98
  37. package/dist/board/hooks/useBoardKeybindings.js +0 -344
  38. package/dist/board/hooks/useBreakpoint.js +0 -6
  39. package/dist/board/hooks/useHoverState.js +0 -11
  40. package/dist/board/hooks/useInputShortcuts.js +0 -61
  41. package/dist/board/hooks/useLogStream.js +0 -32
  42. package/dist/board/hooks/useLoopPolling.js +0 -28
  43. package/dist/board/hooks/useTabNav.js +0 -60
  44. package/dist/board/hooks/useTaskKeybindings.js +0 -120
  45. package/dist/board/index.js +0 -32
  46. package/dist/board/router.js +0 -16
  47. package/dist/board/state.js +0 -99
  48. package/dist/board/toast.js +0 -65
  49. package/dist/board/types.js +0 -1
  50. package/dist/cli.d.ts +0 -3
  51. package/dist/cli.d.ts.map +0 -1
  52. package/dist/cli.js.map +0 -1
  53. package/dist/client/commands.d.ts +0 -11
  54. package/dist/client/commands.d.ts.map +0 -1
  55. package/dist/client/commands.js.map +0 -1
  56. package/dist/client/ipc.d.ts +0 -5
  57. package/dist/client/ipc.d.ts.map +0 -1
  58. package/dist/client/ipc.js.map +0 -1
  59. package/dist/daemon/index.d.ts +0 -2
  60. package/dist/daemon/index.d.ts.map +0 -1
  61. package/dist/daemon/index.js.map +0 -1
  62. package/dist/daemon/manager.d.ts +0 -17
  63. package/dist/daemon/manager.d.ts.map +0 -1
  64. package/dist/daemon/manager.js.map +0 -1
  65. package/dist/daemon/server.d.ts +0 -15
  66. package/dist/daemon/server.d.ts.map +0 -1
  67. package/dist/daemon/server.js.map +0 -1
  68. package/dist/daemon/spawner.d.ts +0 -3
  69. package/dist/daemon/spawner.d.ts.map +0 -1
  70. package/dist/daemon/spawner.js.map +0 -1
  71. package/dist/daemon/state.d.ts +0 -15
  72. package/dist/daemon/state.d.ts.map +0 -1
  73. package/dist/daemon/state.js.map +0 -1
  74. package/dist/duration.d.ts +0 -3
  75. package/dist/duration.d.ts.map +0 -1
  76. package/dist/duration.js.map +0 -1
  77. package/dist/logger.d.ts +0 -11
  78. package/dist/logger.d.ts.map +0 -1
  79. package/dist/logger.js.map +0 -1
  80. package/dist/loop-config.d.ts +0 -13
  81. package/dist/loop-config.d.ts.map +0 -1
  82. package/dist/loop-config.js.map +0 -1
  83. package/dist/loop.d.ts +0 -46
  84. package/dist/loop.d.ts.map +0 -1
  85. package/dist/loop.js +0 -363
  86. package/dist/loop.js.map +0 -1
  87. package/dist/open-board.d.ts +0 -2
  88. package/dist/open-board.d.ts.map +0 -1
  89. package/dist/open-board.js +0 -19
  90. package/dist/open-board.js.map +0 -1
  91. package/dist/tui/app.d.ts +0 -57
  92. package/dist/tui/app.d.ts.map +0 -1
  93. package/dist/tui/app.js.map +0 -1
  94. package/dist/tui/components/ActionButtons.js +0 -49
  95. package/dist/tui/components/CommandBuilderField.js +0 -143
  96. package/dist/tui/components/CommandEditorModal.js +0 -159
  97. package/dist/tui/components/ConfirmModal.js +0 -9
  98. package/dist/tui/components/FilterBar.js +0 -13
  99. package/dist/tui/components/FocusableSearchSelect.js +0 -82
  100. package/dist/tui/components/Footer.js +0 -42
  101. package/dist/tui/components/PatchEditForm.js +0 -78
  102. package/dist/tui/components/SearchSelect.js +0 -67
  103. package/dist/tui/components/TaskFilterBar.js +0 -9
  104. package/dist/tui/create-loop.d.ts +0 -3
  105. package/dist/tui/create-loop.d.ts.map +0 -1
  106. package/dist/tui/create-loop.js +0 -90
  107. package/dist/tui/create-loop.js.map +0 -1
  108. package/dist/tui/dashboard.d.ts +0 -2
  109. package/dist/tui/dashboard.d.ts.map +0 -1
  110. package/dist/tui/dashboard.js +0 -45
  111. package/dist/tui/dashboard.js.map +0 -1
  112. package/dist/tui/events.d.ts +0 -3
  113. package/dist/tui/events.d.ts.map +0 -1
  114. package/dist/tui/events.js +0 -244
  115. package/dist/tui/events.js.map +0 -1
  116. package/dist/tui/popup.d.ts +0 -3
  117. package/dist/tui/popup.d.ts.map +0 -1
  118. package/dist/tui/popup.js +0 -58
  119. package/dist/tui/popup.js.map +0 -1
  120. package/dist/tui/render.d.ts +0 -9
  121. package/dist/tui/render.d.ts.map +0 -1
  122. package/dist/tui/render.js +0 -305
  123. package/dist/tui/render.js.map +0 -1
  124. package/dist/types.d.ts +0 -94
  125. package/dist/types.d.ts.map +0 -1
  126. package/dist/types.js.map +0 -1
@@ -1,134 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef } from "react";
3
- import { useKeyboard } from "@opentui/react";
4
- import { t } from "../../i18n/index.js";
5
- import { useHoverState } from "../hooks/useHoverState.js";
6
- import { useTabNav } from "../hooks/useTabNav.js";
7
- import { HOVER_BG, ENTITY_COLORS } from "../../config/constants.js";
8
- import { CreateProjectModal } from "./CreateProjectModal.js";
9
- import { EditProjectModal } from "./EditProjectModal.js";
10
- import { DeleteProjectConfirm } from "./DeleteProjectConfirm.js";
11
- function ProjectActionButton(props) {
12
- const { isHovered, hoverProps } = useHoverState();
13
- const bg = props.selected ? "#1e3a8a" : isHovered ? HOVER_BG : undefined;
14
- const fg = props.selected ? "#ffffff" : isHovered ? "#e5e7eb" : "#9ca3af";
15
- 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 }) }) }));
16
- }
17
- export function ProjectsPage(props) {
18
- const { projects, loops, headerFocused, onClose, onRefresh, onOpenCreate, onEnterHeader } = props;
19
- const [selectedIndex, setSelectedIndex] = useState(0);
20
- const [subModal, setSubModal] = useState("none");
21
- const navItems = ["list", "edit", "delete"];
22
- const { setFocusIndex, focusedItem, isFocused } = useTabNav(navItems, {
23
- onCycleOut: (dir) => {
24
- if (dir === "right")
25
- onEnterHeader?.("right");
26
- else
27
- onEnterHeader?.("left");
28
- },
29
- });
30
- const focusedItemRef = useRef(focusedItem);
31
- focusedItemRef.current = focusedItem;
32
- const prevHeaderFocused = useRef(headerFocused);
33
- useEffect(() => {
34
- if (prevHeaderFocused.current && !headerFocused) {
35
- setFocusIndex(0);
36
- }
37
- prevHeaderFocused.current = headerFocused;
38
- }, [headerFocused]);
39
- // Expose create trigger to parent via callback
40
- useState(() => { onOpenCreate?.(() => setSubModal("create")); });
41
- const clampedIndex = Math.min(selectedIndex, Math.max(0, projects.length - 1));
42
- const selectedProject = projects[clampedIndex] ?? null;
43
- const loopCount = (projectId) => loops.filter((l) => (l.projectId ?? "default") === projectId).length;
44
- function runAction(actionIndex) {
45
- if (!selectedProject || selectedProject.isSystem)
46
- return;
47
- if (actionIndex === 0)
48
- setSubModal("edit");
49
- else if (actionIndex === 1)
50
- setSubModal("delete");
51
- }
52
- useKeyboard((key) => {
53
- if (subModal !== "none")
54
- return;
55
- if (headerFocused)
56
- return;
57
- const fi = focusedItemRef.current;
58
- if (key.name === "up" || key.name === "down") {
59
- if (fi !== "list") {
60
- key.preventDefault();
61
- return;
62
- }
63
- if (key.name === "up") {
64
- setSelectedIndex((i) => Math.max(0, i - 1));
65
- }
66
- else {
67
- setSelectedIndex((i) => Math.min(projects.length - 1, i + 1));
68
- }
69
- key.preventDefault();
70
- return;
71
- }
72
- if (key.name === "left" || key.name === "right") {
73
- key.preventDefault();
74
- return;
75
- }
76
- if (key.name === "return" || key.name === "enter") {
77
- if (fi === "list") {
78
- setFocusIndex(1);
79
- }
80
- else if (fi === "edit") {
81
- if (selectedProject && !selectedProject.isSystem)
82
- setSubModal("edit");
83
- }
84
- else if (fi === "delete") {
85
- if (selectedProject && !selectedProject.isSystem)
86
- setSubModal("delete");
87
- }
88
- key.preventDefault();
89
- return;
90
- }
91
- if (key.name === "n") {
92
- setSubModal("create");
93
- key.preventDefault();
94
- return;
95
- }
96
- if (key.name === "e" && selectedProject && !selectedProject.isSystem) {
97
- setSubModal("edit");
98
- key.preventDefault();
99
- return;
100
- }
101
- if (key.name === "d" && selectedProject && !selectedProject.isSystem) {
102
- setSubModal("delete");
103
- key.preventDefault();
104
- return;
105
- }
106
- if (key.name === "escape") {
107
- onClose();
108
- key.preventDefault();
109
- }
110
- });
111
- const actions = [
112
- { key: "edit", label: t("project.editProjectLabel") },
113
- { key: "delete", label: t("project.deleteProjectLabel") },
114
- ];
115
- return (_jsxs("box", { style: { flexDirection: "row", flexGrow: 1, backgroundColor: "#0b0b0b" }, children: [_jsxs("box", { title: t("project.projectsTitle"), border: true, borderColor: focusedItem === "list" ? ENTITY_COLORS.project : "#1e3a2a", style: { width: "40%", flexDirection: "column", backgroundColor: "#0b0b0b", flexShrink: 0 }, children: [_jsxs("text", { fg: "#9ca3af", children: [t("project.keyNewHint"), " | ", t("project.keyEditHint"), " | ", t("project.keyDeleteHint")] }), projects.map((project, index) => {
116
- const isSelected = index === clampedIndex;
117
- const bg = isSelected ? "#1e3a8a" : undefined;
118
- const count = loopCount(project.id);
119
- return (_jsx("box", { backgroundColor: bg, onMouseDown: () => { setSelectedIndex(index); setFocusIndex(0); }, style: { height: 1 }, children: _jsxs("text", { children: [isSelected ? "› " : " ", _jsx("span", { fg: project.color, children: "\u25CF" }), ` ${project.name} `, _jsx("span", { fg: "#6b7280", children: `(${count})` })] }) }, project.id));
120
- })] }), _jsxs("box", { style: { flexGrow: 1, flexDirection: "column", backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx("box", { title: t("board.inspectorTitle"), border: true, style: { flexGrow: 1, flexDirection: "column", backgroundColor: "#0b0b0b", padding: 1 }, children: selectedProject ? (_jsxs(_Fragment, { children: [_jsxs("text", { children: [_jsx("span", { fg: selectedProject.color, children: "\u25CF" }), ` `, _jsx("strong", { children: selectedProject.name })] }), _jsx("text", { fg: "#6b7280", children: t("project.loopCount", { count: String(loopCount(selectedProject.id)) }) }), selectedProject.isSystem ? (_jsx("text", { fg: "#9ca3af", children: t("project.systemLabel") })) : null] })) : (_jsx("text", { fg: "#9ca3af", children: t("project.noLoops") })) }), _jsx("box", { border: true, borderColor: focusedItem === "edit" || focusedItem === "delete" ? ENTITY_COLORS.project : undefined, style: { flexDirection: "row", height: 3, flexShrink: 0, paddingLeft: 1, backgroundColor: "#0b0b0b", alignItems: "center", justifyContent: selectedProject?.isSystem ? "center" : "flex-start" }, children: selectedProject && !selectedProject.isSystem ? (actions.map((action, i) => (_jsx(ProjectActionButton, { label: action.label, selected: isFocused(action.key), onMouseDown: () => { setFocusIndex(i + 1); runAction(i); } }, action.key)))) : (_jsx("text", { fg: "#9ca3af", children: t("project.systemLabel") })) })] }), subModal === "create" ? (_jsx(CreateProjectModal, { onDone: async (project) => {
121
- setSubModal("none");
122
- await onRefresh();
123
- const newIndex = projects.findIndex((p) => p.id === project.id);
124
- if (newIndex >= 0)
125
- setSelectedIndex(newIndex);
126
- }, onCancel: () => setSubModal("none") })) : null, subModal === "edit" && selectedProject ? (_jsx(EditProjectModal, { project: selectedProject, onDone: async () => {
127
- setSubModal("none");
128
- await onRefresh();
129
- }, onCancel: () => setSubModal("none") })) : null, subModal === "delete" && selectedProject ? (_jsx(DeleteProjectConfirm, { project: selectedProject, loopCount: loopCount(selectedProject.id), onDone: async () => {
130
- setSubModal("none");
131
- setSelectedIndex((i) => Math.max(0, i - 1));
132
- await onRefresh();
133
- }, onCancel: () => setSubModal("none") })) : null] }));
134
- }
@@ -1,83 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useMemo } from "react";
3
- import { useTerminalDimensions } from "@opentui/react";
4
- import { t } from "../../i18n/index.js";
5
- import { formatFileSize, formatRunDuration, formatRunTime } from "../format.js";
6
- import { useHoverState } from "../hooks/useHoverState.js";
7
- import { HOVER_BG } from "../../config/constants.js";
8
- function groupRuns(runs) {
9
- const groups = [];
10
- let i = 0;
11
- while (i < runs.length) {
12
- const current = runs[i];
13
- if (current.chainGroupId) {
14
- const group = runs.filter((r) => r.chainGroupId === current.chainGroupId);
15
- const first = group[0];
16
- const allSuccess = group.every((r) => r.status === "completed" && r.exitCode === 0);
17
- const isRunning = group.some((r) => r.status === "running");
18
- groups.push({
19
- runs: group,
20
- startedAt: first.startedAt,
21
- totalDuration: group.reduce((s, r) => s + r.duration, 0),
22
- totalLogSize: group.reduce((s, r) => s + r.logSize, 0),
23
- allSuccess,
24
- isRunning,
25
- chainName: group.find((r) => r.chainName)?.chainName ?? null,
26
- });
27
- i += group.length;
28
- }
29
- else {
30
- groups.push({
31
- runs: [current],
32
- startedAt: current.startedAt,
33
- totalDuration: current.duration,
34
- totalLogSize: current.logSize,
35
- allSuccess: current.status === "completed" && current.exitCode === 0,
36
- isRunning: current.status === "running",
37
- chainName: null,
38
- });
39
- i++;
40
- }
41
- }
42
- return groups;
43
- }
44
- function fit(text, width) {
45
- if (width <= 0)
46
- return "";
47
- if (text.length <= width)
48
- return text.padEnd(width);
49
- return text.length > width ? text.slice(0, width - 3) + "..." : text;
50
- }
51
- export function RunHistory(props) {
52
- const { loop, selectedRunIndex, focused, onSelectRun, onOpenRun } = props;
53
- const { height } = useTerminalDimensions();
54
- const scrollRef = useRef(null);
55
- const runs = loop?.runHistory ?? [];
56
- const grouped = useMemo(() => groupRuns(runs), [runs]);
57
- useEffect(() => {
58
- const id = `run-row-${selectedRunIndex}`;
59
- scrollRef.current?.scrollChildIntoView(id);
60
- }, [selectedRunIndex]);
61
- const panelHeight = Math.max(4, Math.floor((height - 10) * 0.45));
62
- const timeW = 8;
63
- const durW = 7;
64
- const sizeW = 8;
65
- const header = " " +
66
- fit(t("board.runHistoryTime"), timeW) +
67
- " " +
68
- fit(t("board.runHistoryDuration"), durW) +
69
- " " +
70
- fit(t("board.runHistorySize"), sizeW);
71
- return (_jsxs("box", { title: t("board.runHistoryTitleHint"), border: true, borderColor: focused ? "#38bdf8" : undefined, style: { flexDirection: "column", flexGrow: 1, backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx("text", { fg: "#6b7280", children: header }), grouped.length === 0 ? (_jsx("text", { fg: "#9ca3af", children: t("board.runHistoryEmpty") })) : (_jsx("scrollbox", { ref: scrollRef, style: { flexGrow: 1, maxHeight: panelHeight, backgroundColor: "#0b0b0b" }, children: [...grouped].reverse().map((group, revIndex) => (_jsx(GroupRow, { id: `run-row-${revIndex}`, group: group, isSelected: focused && revIndex === selectedRunIndex, timeW: timeW, durW: durW, sizeW: sizeW, onOpen: () => onOpenRun(group.runs[0]) }, group.runs[0].runNumber + (group.chainName ?? "")))) }, `runs-${loop?.id}-${runs.length}`))] }));
72
- }
73
- function GroupRow(props) {
74
- const { id, group, isSelected, timeW, durW, sizeW, onOpen } = props;
75
- const { isHovered, hoverProps } = useHoverState();
76
- const bg = isSelected ? "#1e3a8a" : isHovered ? HOVER_BG : undefined;
77
- const icon = group.isRunning ? "⟳" : group.allSuccess ? "✓" : "✗";
78
- const iconColor = group.isRunning ? "#facc15" : group.allSuccess ? "#4ade80" : "#f87171";
79
- const label = group.chainName
80
- ? `→ ${group.chainName}`
81
- : "";
82
- return (_jsx("box", { id: id, onMouseDown: onOpen, backgroundColor: bg, ...hoverProps, children: _jsxs("text", { children: [" ", _jsx("span", { fg: "#9ca3af", children: fit(formatRunTime(group.startedAt), timeW) }), " ", _jsx("span", { fg: iconColor, children: icon }), " ", label ? _jsx("span", { fg: "#a78bfa", children: fit(label, 12) }) : null, " ", fit(group.isRunning ? "…" : formatRunDuration(group.totalDuration), durW), " ", fit(formatFileSize(group.totalLogSize), sizeW)] }) }));
83
- }
@@ -1,10 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useRef } from "react";
3
- import { t } from "../../i18n/index.js";
4
- import { useInputShortcuts } from "../hooks/useInputShortcuts.js";
5
- export function SearchBox(props) {
6
- const { query, searchActive, focused, onQueryChange, onActivate, onDismiss } = props;
7
- const inputRef = useRef(null);
8
- useInputShortcuts(() => searchActive ? inputRef.current : null);
9
- return (_jsx("box", { title: t("board.searchTitle"), border: true, borderColor: focused ? "#38bdf8" : undefined, style: { width: "25%", height: 3, marginRight: 1, paddingLeft: 1, backgroundColor: focused ? "#1e2a4a" : "#0b0b0b" }, children: searchActive ? (_jsx("input", { ref: inputRef, focused: true, value: query, placeholder: t("board.searchPlaceholder"), onInput: (value) => onQueryChange(value), onSubmit: () => onDismiss() })) : (_jsx("text", { fg: query ? "#e5e7eb" : "#6b7280", onMouseDown: onActivate, children: query || t("board.searchEmpty") })) }));
10
- }
@@ -1,132 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef, useState, useMemo, useEffect } from "react";
3
- import { useKeyboard } from "@opentui/react";
4
- import { t } from "../../i18n/index.js";
5
- import { SEARCH_SELECT_HEIGHT } from "../../config/constants.js";
6
- export function SearchSelect(props) {
7
- const { options, value, onChange, focused, height } = props;
8
- const placeholder = props.placeholder ?? t("board.searchSelectPlaceholder");
9
- const maxHeight = height ?? SEARCH_SELECT_HEIGHT;
10
- const [filter, setFilter] = useState("");
11
- const [selectedIndex, setSelectedIndex] = useState(0);
12
- const filterRef = useRef(filter);
13
- filterRef.current = filter;
14
- const filtered = useMemo(() => {
15
- if (!filter)
16
- return options;
17
- const q = filter.toLowerCase();
18
- return options.filter((o) => o.name.toLowerCase().includes(q) || o.value.toLowerCase().includes(q));
19
- }, [options, filter]);
20
- const clampedSelected = Math.min(selectedIndex, Math.max(0, filtered.length - 1));
21
- const filteredRef = useRef(filtered);
22
- filteredRef.current = filtered;
23
- const selectedRef = useRef(clampedSelected);
24
- selectedRef.current = clampedSelected;
25
- const focusedRef = useRef(focused);
26
- focusedRef.current = focused;
27
- const onChangeRef = useRef(onChange);
28
- onChangeRef.current = onChange;
29
- const scrollRef = useRef(null);
30
- useEffect(() => {
31
- if (focused) {
32
- const idx = filtered.findIndex((o) => o.value === value);
33
- const target = idx >= 0 ? idx : 0;
34
- setSelectedIndex(target);
35
- requestAnimationFrame(() => {
36
- scrollRef.current?.scrollChildIntoView(`select-option-${target}`);
37
- });
38
- }
39
- else {
40
- setFilter("");
41
- }
42
- }, [focused]);
43
- useEffect(() => {
44
- scrollRef.current?.scrollChildIntoView(`select-option-${clampedSelected}`);
45
- }, [clampedSelected, filter]);
46
- const computeFiltered = (f) => {
47
- if (!f)
48
- return options;
49
- const q = f.toLowerCase();
50
- return options.filter((o) => o.name.toLowerCase().includes(q) || o.value.toLowerCase().includes(q));
51
- };
52
- useKeyboard((key) => {
53
- if (!focusedRef.current)
54
- return;
55
- const name = key.name;
56
- if (name === "up" || name === "k") {
57
- const list = computeFiltered(filterRef.current);
58
- if (list.length > 0) {
59
- const cur = Math.min(selectedRef.current, list.length - 1);
60
- setSelectedIndex(cur <= 0 ? list.length - 1 : cur - 1);
61
- }
62
- key.preventDefault();
63
- key.stopPropagation();
64
- return;
65
- }
66
- if (name === "down" || name === "j") {
67
- const list = computeFiltered(filterRef.current);
68
- if (list.length > 0) {
69
- const cur = Math.min(selectedRef.current, list.length - 1);
70
- setSelectedIndex(cur >= list.length - 1 ? 0 : cur + 1);
71
- }
72
- key.preventDefault();
73
- key.stopPropagation();
74
- return;
75
- }
76
- if (name === "return" || name === "enter") {
77
- const list = computeFiltered(filterRef.current);
78
- const cur = Math.min(selectedRef.current, list.length - 1);
79
- const option = list[cur];
80
- if (option) {
81
- onChangeRef.current(option.value);
82
- }
83
- key.preventDefault();
84
- key.stopPropagation();
85
- return;
86
- }
87
- if (name === "escape") {
88
- setFilter("");
89
- setSelectedIndex(0);
90
- key.preventDefault();
91
- key.stopPropagation();
92
- return;
93
- }
94
- if (name === "backspace") {
95
- const newFilter = filterRef.current.slice(0, -1);
96
- filterRef.current = newFilter;
97
- setFilter(newFilter);
98
- setSelectedIndex(0);
99
- key.preventDefault();
100
- key.stopPropagation();
101
- return;
102
- }
103
- if (key.ctrl)
104
- return;
105
- let char = null;
106
- if (name && name.length === 1 && /[a-z0-9 _\-./]/i.test(name)) {
107
- char = name;
108
- }
109
- else if (key.sequence && key.sequence.length === 1 && /[a-z0-9 _\-./]/i.test(key.sequence)) {
110
- char = key.sequence;
111
- }
112
- if (char) {
113
- const newFilter = filterRef.current + char;
114
- filterRef.current = newFilter;
115
- setFilter(newFilter);
116
- setSelectedIndex(0);
117
- key.preventDefault();
118
- key.stopPropagation();
119
- return;
120
- }
121
- });
122
- const listHeight = Math.min(filtered.length, maxHeight);
123
- return (_jsxs("box", { border: true, borderColor: focused ? "#38bdf8" : undefined, style: { flexDirection: "column", backgroundColor: focused ? "#0f172a" : "#0b0b0b" }, children: [_jsxs("box", { style: { height: 3, flexDirection: "row", alignItems: "center", paddingLeft: 1 }, children: [_jsx("text", { fg: "#6b7280", children: "/ " }), _jsx("text", { fg: filter ? "#e5e7eb" : "#6b7280", children: filter || placeholder })] }), _jsx("scrollbox", { ref: scrollRef, style: { height: listHeight, backgroundColor: "#0b0b0b" }, children: filtered.map((option, i) => {
124
- const isSelected = i === clampedSelected;
125
- const isActive = option.value === value;
126
- const prefix = isSelected ? "› " : " ";
127
- const colorIndicator = option.color ? `\u25cf ` : "";
128
- const bg = isSelected ? "#1e3a8a" : undefined;
129
- const fg = isSelected ? "#ffffff" : isActive ? "#38bdf8" : "#9ca3af";
130
- return (_jsx("box", { id: `select-option-${i}`, style: { flexDirection: "row", backgroundColor: bg }, children: _jsx("text", { fg: fg, children: `${prefix}${colorIndicator}${option.name}` }) }, option.value));
131
- }) })] }));
132
- }
@@ -1,95 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef } from "react";
3
- import { useTerminalDimensions } from "@opentui/react";
4
- import { t } from "../../i18n/index.js";
5
- import { commandLine, truncate } from "../format.js";
6
- import { useHoverState } from "../hooks/useHoverState.js";
7
- import { HOVER_BG, ENTITY_COLORS } from "../../config/constants.js";
8
- function fit(text, width) {
9
- if (width <= 0)
10
- return "";
11
- if (text.length <= width)
12
- return text.padEnd(width);
13
- return truncate(text, width);
14
- }
15
- const TASK_PANEL_ORDER = ["search", "new", "tasks", "actions"];
16
- const TASK_ACTIONS = ["select", "edit", "delete"];
17
- export const TASK_ACTION_COUNT = TASK_ACTIONS.length;
18
- export const TASK_ACTION_KEYS = [...TASK_ACTIONS];
19
- export function nextTaskPanel(current, direction) {
20
- const idx = TASK_PANEL_ORDER.indexOf(current);
21
- return TASK_PANEL_ORDER[(idx + (direction === "right" ? 1 : TASK_PANEL_ORDER.length - 1)) % TASK_PANEL_ORDER.length];
22
- }
23
- export function TaskNavigator(props) {
24
- const { visible, total, selectedIndex, focused, query, onSelect, onActivate } = props;
25
- const { width, height } = useTerminalDimensions();
26
- const scrollRef = useRef(null);
27
- const panelWidth = width * 0.55 - 4;
28
- const panelHeight = height - 7;
29
- const nameW = 20;
30
- const chainsW = 14;
31
- const fixedOverhead = 2 + nameW + 2 + chainsW;
32
- const cmdW = Math.max(10, panelWidth - fixedOverhead);
33
- const header = " " +
34
- fit(t("board.taskHeaderName"), nameW) +
35
- " " +
36
- fit(t("board.taskHeaderCommand"), cmdW) +
37
- " " +
38
- t("board.taskHeaderChains");
39
- useEffect(() => {
40
- const id = `task-row-${selectedIndex}`;
41
- scrollRef.current?.scrollChildIntoView(id);
42
- }, [selectedIndex]);
43
- return (_jsxs("box", { title: t("board.taskBrowserTitle", { visible: visible.length, total }), border: true, borderColor: focused ? ENTITY_COLORS.task : "#2e2545", style: { width: "55%", flexShrink: 0, flexDirection: "column", backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx("text", { fg: "#6b7280", children: header }), visible.length === 0 ? (_jsx("text", { fg: "#9ca3af", children: t("board.taskBrowserEmpty") })) : (_jsx("scrollbox", { ref: scrollRef, style: { flexGrow: 1, maxHeight: panelHeight, backgroundColor: "#0b0b0b" }, children: visible.map((task, index) => (_jsx(TaskNavRow, { id: `task-row-${index}`, task: task, index: index, isSelected: index === selectedIndex, focused: focused, nameW: nameW, cmdW: cmdW, chainsW: chainsW, onSelect: onSelect, onActivate: onActivate }, task.id))) }))] }));
44
- }
45
- function TaskNavRow(props) {
46
- const { id, task, index, isSelected, focused, nameW, cmdW, chainsW, onSelect, onActivate } = props;
47
- const { isHovered, hoverProps } = useHoverState();
48
- const bg = isSelected ? (focused ? "#1e3a8a" : "#1e2a4a") : isHovered ? HOVER_BG : undefined;
49
- const fg = isSelected ? "#ffffff" : "#e5e7eb";
50
- const lastClickRef = useRef(0);
51
- function handleClick() {
52
- const now = Date.now();
53
- if (now - lastClickRef.current < 400) {
54
- onActivate(index);
55
- }
56
- else {
57
- onSelect(index);
58
- }
59
- lastClickRef.current = now;
60
- }
61
- const cmd = commandLine(task.command, task.commandArgs);
62
- const chains = task.onSuccessTaskId || task.onFailureTaskId
63
- ? t("board.taskChainsFormat", { success: task.onSuccessTaskId ?? "-", failure: task.onFailureTaskId ?? "-" })
64
- : t("board.taskChainsNone");
65
- const name = task.name.length > nameW ? task.name.slice(0, nameW - 3) + "..." : task.name;
66
- const truncCmd = cmd.length > cmdW ? cmd.slice(0, cmdW - 3) + "..." : cmd;
67
- return (_jsx("box", { id: id, onMouseDown: handleClick, backgroundColor: bg, ...hoverProps, children: _jsxs("text", { fg: fg, children: [isSelected ? "›" : " ", " ", fit(name, nameW), " ", fit(truncCmd, cmdW), " ", fit(chains, chainsW)] }) }));
68
- }
69
- export function TaskInspector(props) {
70
- const { task } = props;
71
- if (!task) {
72
- return (_jsx("box", { title: t("board.inspectorTitle"), border: true, style: { backgroundColor: "#0b0b0b" }, children: _jsx("text", { fg: "#9ca3af", children: t("board.inspectorEmpty") }) }));
73
- }
74
- const cmd = commandLine(task.command, task.commandArgs);
75
- return (_jsxs("box", { title: t("board.inspectorTitle"), border: true, style: { flexDirection: "column", backgroundColor: "#0b0b0b" }, children: [_jsxs("text", { children: [_jsx("strong", { children: t("board.fieldId") }), " ", task.id] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.taskLabelName") }), " ", task.name] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.fieldCommand") }), " ", cmd] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.taskLabelOnSuccess") }), " ", task.onSuccessTaskId ?? t("board.taskNone")] }), _jsxs("text", { children: [_jsx("strong", { children: t("board.taskLabelOnFailure") }), " ", task.onFailureTaskId ?? t("board.taskNone")] })] }));
76
- }
77
- export function TaskActionButtons(props) {
78
- const { task, focused, selectedAction, selectable = true, onAction } = props;
79
- if (!task) {
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
- }
82
- const allActions = [
83
- { key: "select", label: t("board.taskActionSelect") },
84
- { key: "edit", label: t("board.taskActionEdit") },
85
- { key: "delete", label: t("board.taskActionDelete") },
86
- ];
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, selected: focused && selectedAction === i, onMouseDown: () => onAction(action.key) }, action.key))) }));
89
- }
90
- function TaskActionButton(props) {
91
- const { isHovered, hoverProps } = useHoverState();
92
- const bg = props.selected ? "#1e3a8a" : isHovered ? HOVER_BG : undefined;
93
- const fg = props.selected ? "#ffffff" : isHovered ? "#e5e7eb" : "#9ca3af";
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 }) }) }));
95
- }
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { SearchBox } from "./SearchBox.js";
3
- export function TaskFilterBar(props) {
4
- const { query, searchActive, focusedPanel, onQueryChange, onSearchActivate, onSearchDismiss } = props;
5
- return (_jsx("box", { style: { flexDirection: "row", height: 3, paddingLeft: 1, paddingRight: 1, backgroundColor: "#0b0b0b" }, children: _jsx("box", { style: { flexGrow: 1 }, children: _jsx(SearchBox, { query: query, searchActive: searchActive, focused: focusedPanel === "search", onQueryChange: onQueryChange, onActivate: onSearchActivate, onDismiss: onSearchDismiss }) }) }));
6
- }
@@ -1,155 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef, useState } from "react";
3
- import crypto from "node:crypto";
4
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
5
- import { t } from "../../i18n/index.js";
6
- import { createTask, updateTask, listTasks } from "../daemon.js";
7
- import { useHoverState } from "../hooks/useHoverState.js";
8
- import { useInputShortcuts } from "../hooks/useInputShortcuts.js";
9
- import { useTabNav } from "../hooks/useTabNav.js";
10
- import { copyToClipboard } from "../../shared/clipboard.js";
11
- import { HOVER_BG } from "../../config/constants.js";
12
- import { SearchSelect } from "./SearchSelect.js";
13
- const taskFields = ["name", "command", "onSuccessTaskId", "onFailureTaskId"];
14
- function taskInitialValues(task) {
15
- if (!task) {
16
- return { name: "", command: "", onSuccessTaskId: "", onFailureTaskId: "" };
17
- }
18
- return {
19
- name: task.name,
20
- command: [task.command, ...task.commandArgs].join(" "),
21
- onSuccessTaskId: task.onSuccessTaskId ?? "",
22
- onFailureTaskId: task.onFailureTaskId ?? "",
23
- };
24
- }
25
- export function TaskForm(props) {
26
- const [values, setValues] = useState(taskInitialValues(props.editTask));
27
- const valuesRef = useRef(values);
28
- const [error, setError] = useState("");
29
- const [isSubmitting, setIsSubmitting] = useState(false);
30
- const [allTasks, setAllTasks] = useState([]);
31
- const inputRef = useRef(null);
32
- const { width: termWidth } = useTerminalDimensions();
33
- const btnWidth = Math.max(10, Math.min(14, Math.floor(termWidth / 6)));
34
- const navItems = [...taskFields, "save", "cancel"];
35
- const { setFocusIndex, focusedItem, isFocused } = useTabNav(navItems);
36
- const focusedItemRef = useRef(focusedItem);
37
- focusedItemRef.current = focusedItem;
38
- useInputShortcuts(() => {
39
- const fi = focusedItemRef.current;
40
- if (fi != null && fi !== "save" && fi !== "cancel" && fi !== "onSuccessTaskId" && fi !== "onFailureTaskId") {
41
- return inputRef.current;
42
- }
43
- return null;
44
- });
45
- function updateValues(next) {
46
- valuesRef.current = next;
47
- setValues(next);
48
- }
49
- useState(() => {
50
- void listTasks().then(setAllTasks).catch(() => { });
51
- });
52
- useKeyboard((key) => {
53
- if (key.name === "return" || key.name === "enter") {
54
- const fi = focusedItemRef.current;
55
- if (fi === "save") {
56
- void submit(valuesRef.current);
57
- key.preventDefault();
58
- }
59
- else if (fi === "cancel") {
60
- props.onCancel();
61
- key.preventDefault();
62
- }
63
- }
64
- });
65
- async function submit(current) {
66
- if (isSubmitting)
67
- return;
68
- try {
69
- setIsSubmitting(true);
70
- setError("");
71
- const cmdLine = current.command.trim();
72
- if (!cmdLine) {
73
- setError(t("errors.commandEmpty"));
74
- return;
75
- }
76
- const tokens = cmdLine.split(/\s+/);
77
- const command = tokens[0] ?? "";
78
- const commandArgs = tokens.slice(1);
79
- const onSuccessTaskId = current.onSuccessTaskId || null;
80
- const onFailureTaskId = current.onFailureTaskId || null;
81
- if (props.mode === "edit" && props.editTask) {
82
- await updateTask(props.editTask.id, {
83
- name: current.name.trim() || command,
84
- command,
85
- commandArgs,
86
- onSuccessTaskId,
87
- onFailureTaskId,
88
- });
89
- props.onDone(true, props.editTask.id);
90
- }
91
- else {
92
- const id = crypto.randomUUID().slice(0, 8);
93
- await createTask({
94
- id,
95
- name: current.name.trim() || command,
96
- command,
97
- commandArgs,
98
- onSuccessTaskId,
99
- onFailureTaskId,
100
- });
101
- props.onDone(false, id);
102
- }
103
- }
104
- catch (e) {
105
- setError(e instanceof Error ? e.message : String(e));
106
- }
107
- finally {
108
- setIsSubmitting(false);
109
- }
110
- }
111
- const labels = {
112
- name: t("board.taskLabelName"),
113
- command: t("board.taskLabelCommand"),
114
- onSuccessTaskId: t("board.taskLabelOnSuccess"),
115
- onFailureTaskId: t("board.taskLabelOnFailure"),
116
- };
117
- const hints = {
118
- name: t("board.taskHintName"),
119
- command: t("board.hintCommand"),
120
- onSuccessTaskId: t("board.taskHintOnSuccess"),
121
- onFailureTaskId: t("board.taskHintOnFailure"),
122
- };
123
- const chainOptions = [
124
- { name: t("board.taskNone"), description: "", value: "" },
125
- ...allTasks
126
- .filter((t) => t.id !== props.editTask?.id)
127
- .map((t) => ({ name: `${t.id} ${t.name}`, description: "", value: t.id })),
128
- ];
129
- const title = props.mode === "edit" ? t("board.taskEditTitle") : t("board.taskCreateTitle");
130
- return (_jsxs("box", { title: title, border: true, style: { flexDirection: "column", flexGrow: 1, padding: 1, backgroundColor: "#0b0b0b" }, children: [_jsx("box", { style: { flexDirection: "column", flexGrow: 1 }, children: Array.from({ length: Math.ceil(taskFields.length / 2) }, (_, row) => {
131
- const leftField = taskFields[row * 2];
132
- const rightField = row * 2 + 1 < taskFields.length ? taskFields[row * 2 + 1] : null;
133
- return (_jsxs("box", { style: { flexDirection: "row", marginBottom: 1 }, children: [_jsx(TaskFormRow, { field: leftField, index: row * 2, focused: isFocused(leftField), values: values, valuesRef: valuesRef, updateValues: updateValues, setFocusIndex: setFocusIndex, submit: submit, labels: labels, hints: hints, chainOptions: chainOptions, inputRef: inputRef, onCopy: props.onCopy, style: { width: "50%", paddingRight: 1 } }), rightField ? (_jsx(TaskFormRow, { field: rightField, index: row * 2 + 1, focused: isFocused(rightField), values: values, valuesRef: valuesRef, updateValues: updateValues, setFocusIndex: setFocusIndex, submit: submit, labels: labels, hints: hints, chainOptions: chainOptions, inputRef: inputRef, onCopy: props.onCopy, style: { width: "50%" } })) : (_jsx("box", { style: { width: "50%" } }))] }, row));
134
- }) }), _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] }));
135
- }
136
- function TaskFormRow(props) {
137
- const { field, index, focused, values, valuesRef, updateValues, setFocusIndex, submit, labels, hints, chainOptions, inputRef, onCopy, style } = props;
138
- const isSelect = field === "onSuccessTaskId" || field === "onFailureTaskId";
139
- const selectOpts = isSelect ? chainOptions : [];
140
- const copyHover = useHoverState();
141
- return (_jsxs("box", { style: { flexDirection: "column", ...style }, children: [_jsx("text", { fg: focused ? "#38bdf8" : "#e5e7eb", children: labels[field] }), _jsx("text", { fg: "#6b7280", children: hints[field] }), isSelect ? (_jsx(SearchSelect, { options: selectOpts, value: values[field], onChange: (v) => updateValues({ ...valuesRef.current, [field]: v }), focused: focused })) : (_jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("box", { border: true, borderColor: focused ? "#38bdf8" : undefined, style: { height: 3, width: field === "command" ? "92%" : "100%", backgroundColor: "#0b0b0b" }, children: _jsx("input", { ref: inputRef, focused: focused, value: values[field], placeholder: field === "command" ? t("board.exampleCommand") : "", onInput: (value) => updateValues({ ...valuesRef.current, [field]: value }), onSubmit: () => {
142
- if (index < taskFields.length - 1) {
143
- setFocusIndex(index + 1);
144
- }
145
- else {
146
- void submit(valuesRef.current);
147
- }
148
- } }) }), field === "command" ? (_jsx("box", { border: true, borderColor: copyHover.isHovered ? "#38bdf8" : "#374151", onMouseDown: () => { copyToClipboard(valuesRef.current.command); onCopy?.(valuesRef.current.command); }, style: { width: "8%", height: 3, justifyContent: "center", alignItems: "center", backgroundColor: copyHover.isHovered ? HOVER_BG : "#0b0b0b" }, ...copyHover.hoverProps, children: _jsx("text", { fg: copyHover.isHovered ? "#38bdf8" : "#6b7280", children: "\u2398" }) })) : null] }))] }));
149
- }
150
- function HoverButton(props) {
151
- const { isHovered, hoverProps } = useHoverState();
152
- const bg = props.selected ? "#1e3a8a" : isHovered ? HOVER_BG : undefined;
153
- const borderColor = props.selected ? "#38bdf8" : undefined;
154
- 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 }) }) }));
155
- }
@@ -1,5 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { t } from "../../i18n/index.js";
3
- export function Timeline(props) {
4
- return (_jsx("scrollbox", { title: t("board.timelineTitle"), border: true, style: { flexGrow: 1, backgroundColor: "#0b0b0b" }, stickyScroll: true, stickyStart: "bottom", children: props.logLines.length === 0 ? (_jsx("text", { fg: "#9ca3af", children: t("board.timelineEmpty") })) : (props.logLines.map((line, index) => _jsx("text", { children: line }, index))) }));
5
- }