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,143 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useCallback } from "react";
3
- import { Box, Text, useInput } from "ink";
4
- import { darkTheme as theme } from "../theme.js";
5
- import { t } from "../../i18n/index.js";
6
- import { copyToClipboard } from "../../shared/clipboard.js";
7
- import { COMMAND_TEMPLATES } from "../../config/constants.js";
8
- function hasUnbalancedQuotes(text) {
9
- let single = 0;
10
- let double = 0;
11
- for (const ch of text) {
12
- if (ch === "'")
13
- single++;
14
- if (ch === '"')
15
- double++;
16
- }
17
- return single % 2 !== 0 || double % 2 !== 0;
18
- }
19
- function parseParts(command) {
20
- const trimmed = command.trim();
21
- if (!trimmed)
22
- return { executable: "", args: "" };
23
- const firstSpace = trimmed.indexOf(" ");
24
- if (firstSpace === -1)
25
- return { executable: trimmed, args: "" };
26
- return { executable: trimmed.slice(0, firstSpace), args: trimmed.slice(firstSpace + 1) };
27
- }
28
- export function CommandBuilderField(props) {
29
- const { isActive, onChange, onAdvance } = props;
30
- const initial = parseParts(props.value);
31
- const [executable, setExecutable] = useState(initial.executable);
32
- const [args, setArgs] = useState(initial.args);
33
- const [activeSub, setActiveSub] = useState(initial.executable ? "args" : "executable");
34
- const [templateIdx, setTemplateIdx] = useState(0);
35
- const [validationMsg, setValidationMsg] = useState(null);
36
- const assembled = [executable, args].filter(Boolean).join(" ");
37
- const commitValue = useCallback(() => {
38
- onChange(assembled);
39
- }, [assembled, onChange]);
40
- const handleTemplateSelect = useCallback(() => {
41
- const tmpl = COMMAND_TEMPLATES[templateIdx];
42
- if (!tmpl)
43
- return;
44
- setExecutable(tmpl.command);
45
- setArgs(tmpl.args);
46
- setActiveSub("args");
47
- const next = [tmpl.command, tmpl.args].filter(Boolean).join(" ");
48
- onChange(next);
49
- }, [templateIdx, onChange]);
50
- useInput((input, key) => {
51
- if (!isActive)
52
- return;
53
- if (activeSub === "templates") {
54
- if (key.upArrow) {
55
- setTemplateIdx((prev) => (prev > 0 ? prev - 1 : COMMAND_TEMPLATES.length - 1));
56
- return;
57
- }
58
- if (key.downArrow) {
59
- setTemplateIdx((prev) => (prev < COMMAND_TEMPLATES.length - 1 ? prev + 1 : 0));
60
- return;
61
- }
62
- if (key.return) {
63
- handleTemplateSelect();
64
- return;
65
- }
66
- if (key.escape) {
67
- setActiveSub("executable");
68
- return;
69
- }
70
- return;
71
- }
72
- if (key.ctrl && input === "y") {
73
- copyToClipboard(assembled);
74
- return;
75
- }
76
- if (key.escape) {
77
- commitValue();
78
- return;
79
- }
80
- if (activeSub === "executable") {
81
- if (key.return || key.tab) {
82
- if (!executable.trim()) {
83
- setValidationMsg(t("errors.commandEmpty"));
84
- return;
85
- }
86
- setValidationMsg(null);
87
- setActiveSub("args");
88
- commitValue();
89
- return;
90
- }
91
- if (key.delete || key.backspace) {
92
- const next = executable.slice(0, -1);
93
- setExecutable(next);
94
- if (next.trim())
95
- setValidationMsg(null);
96
- onChange([next, args].filter(Boolean).join(" "));
97
- return;
98
- }
99
- if (input.length === 1 && input >= " " && input <= "~") {
100
- const next = executable + input;
101
- setExecutable(next);
102
- if (next.trim())
103
- setValidationMsg(null);
104
- if (!next.trim())
105
- setActiveSub("templates");
106
- onChange([next, args].filter(Boolean).join(" "));
107
- }
108
- return;
109
- }
110
- if (activeSub === "args") {
111
- if (key.return) {
112
- if (hasUnbalancedQuotes(args)) {
113
- setValidationMsg(t("errors.unbalancedQuote"));
114
- return;
115
- }
116
- setValidationMsg(null);
117
- commitValue();
118
- onAdvance();
119
- return;
120
- }
121
- if (key.delete || key.backspace) {
122
- const next = args.slice(0, -1);
123
- setArgs(next);
124
- onChange([executable, next].filter(Boolean).join(" "));
125
- return;
126
- }
127
- if (input.length === 1 && input >= " " && input <= "~") {
128
- const next = args + input;
129
- setArgs(next);
130
- onChange([executable, next].filter(Boolean).join(" "));
131
- }
132
- }
133
- }, { isActive });
134
- const execBorderColor = isActive && activeSub === "executable" ? theme.accent.brand : theme.border.dim;
135
- const execBgColor = isActive && activeSub === "executable" ? theme.bg.input : undefined;
136
- const argsBorderColor = isActive && activeSub === "args" ? theme.accent.brand : theme.border.dim;
137
- const argsBgColor = isActive && activeSub === "args" ? theme.bg.input : undefined;
138
- const showTemplates = isActive && activeSub === "executable" && !executable.trim();
139
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Text, { color: theme.text.muted, children: t("commandBuilder.executableLabel") }), _jsxs(Box, { borderStyle: "single", borderColor: execBorderColor, backgroundColor: execBgColor, paddingLeft: 1, children: [_jsx(Text, { color: executable ? theme.text.primary : theme.text.muted, children: executable || t("commandBuilder.executableHint") }), isActive && activeSub === "executable" ? _jsx(Text, { inverse: true, children: " " }) : null] })] }), _jsxs(Box, { flexDirection: "column", flexGrow: 2, children: [_jsx(Text, { color: theme.text.muted, children: t("commandBuilder.argsLabel") }), _jsxs(Box, { borderStyle: "single", borderColor: argsBorderColor, backgroundColor: argsBgColor, paddingLeft: 1, children: [_jsx(Text, { color: args ? theme.text.primary : theme.text.muted, children: args || t("commandBuilder.argsHint") }), isActive && activeSub === "args" ? _jsx(Text, { inverse: true, children: " " }) : null] })] })] }), assembled ? (_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.text.muted, children: [t("commandBuilder.previewLabel"), " "] }), _jsx(Text, { color: theme.accent.task, bold: true, children: assembled })] })) : null, isActive ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.text.muted, children: [t("commandBuilder.copyHint"), " . ", t("commandBuilder.navHint")] }) })) : null, validationMsg ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.semantic.danger, children: validationMsg }) })) : null, showTemplates ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.text.muted, children: t("commandBuilder.templatesLabel") }), COMMAND_TEMPLATES.map((tmpl, i) => {
140
- const isSel = i === templateIdx;
141
- return (_jsxs(Box, { children: [_jsx(Text, { color: isSel ? theme.accent.brand : theme.text.muted, children: isSel ? "\u276F " : " " }), _jsx(Text, { color: isSel ? theme.accent.brand : theme.text.secondary, children: tmpl.label }), _jsxs(Text, { color: theme.text.muted, children: [" \u2014 ", tmpl.command, " ", tmpl.args] })] }, tmpl.label));
142
- })] })) : null] }));
143
- }
@@ -1,159 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useState, useCallback } from "react";
3
- import { Box, Text, useInput } from "ink";
4
- import { darkTheme as theme } from "../theme.js";
5
- import { t } from "../../i18n/index.js";
6
- const MAX_VISIBLE_LINES = 14;
7
- export function CommandEditorModal(props) {
8
- const [lines, setLines] = useState(() => {
9
- const initial = props.initial.trim();
10
- if (!initial)
11
- return [""];
12
- return initial.split(/\r?\n/);
13
- });
14
- const [cursorRow, setCursorRow] = useState(0);
15
- const [cursorCol, setCursorCol] = useState(0);
16
- const [scrollOffset, setScrollOffset] = useState(0);
17
- const currentLine = lines[cursorRow] ?? "";
18
- const commit = useCallback(() => {
19
- const value = lines.join("\n").trim();
20
- props.onSelect(value);
21
- }, [lines, props]);
22
- const insertChar = useCallback((ch) => {
23
- setLines((prev) => {
24
- const next = [...prev];
25
- const line = next[cursorRow] ?? "";
26
- next[cursorRow] = line.slice(0, cursorCol) + ch + line.slice(cursorCol);
27
- return next;
28
- });
29
- setCursorCol((c) => c + 1);
30
- }, [cursorRow, cursorCol]);
31
- const handleBackspace = useCallback(() => {
32
- if (cursorCol > 0) {
33
- setLines((prev) => {
34
- const next = [...prev];
35
- const line = next[cursorRow] ?? "";
36
- next[cursorRow] = line.slice(0, cursorCol - 1) + line.slice(cursorCol);
37
- return next;
38
- });
39
- setCursorCol((c) => c - 1);
40
- }
41
- else if (cursorRow > 0) {
42
- const prevLine = lines[cursorRow - 1] ?? "";
43
- const merged = prevLine + currentLine;
44
- setLines((prev) => {
45
- const next = [...prev];
46
- next[cursorRow - 1] = merged;
47
- next.splice(cursorRow, 1);
48
- return next;
49
- });
50
- setCursorRow((r) => r - 1);
51
- setCursorCol(prevLine.length);
52
- }
53
- }, [cursorCol, cursorRow, currentLine, lines]);
54
- const handleEnter = useCallback(() => {
55
- setLines((prev) => {
56
- const next = [...prev];
57
- const line = next[cursorRow] ?? "";
58
- next.splice(cursorRow, 1, line.slice(0, cursorCol), line.slice(cursorCol));
59
- return next;
60
- });
61
- setCursorRow((r) => r + 1);
62
- setCursorCol(0);
63
- }, [cursorRow, cursorCol]);
64
- const moveUp = useCallback(() => {
65
- if (cursorRow > 0) {
66
- setCursorRow((r) => r - 1);
67
- setCursorCol((c) => Math.min(c, (lines[cursorRow - 1] ?? "").length));
68
- if (cursorRow - 1 < scrollOffset) {
69
- setScrollOffset((s) => Math.max(0, s - 1));
70
- }
71
- }
72
- }, [cursorRow, cursorCol, lines, scrollOffset]);
73
- const moveDown = useCallback(() => {
74
- if (cursorRow < lines.length - 1) {
75
- setCursorRow((r) => r + 1);
76
- setCursorCol((c) => Math.min(c, (lines[cursorRow + 1] ?? "").length));
77
- if (cursorRow + 1 >= scrollOffset + MAX_VISIBLE_LINES) {
78
- setScrollOffset((s) => s + 1);
79
- }
80
- }
81
- }, [cursorRow, cursorCol, lines, scrollOffset]);
82
- const moveLeft = useCallback(() => {
83
- if (cursorCol > 0) {
84
- setCursorCol((c) => c - 1);
85
- }
86
- else if (cursorRow > 0) {
87
- const prevLine = lines[cursorRow - 1] ?? "";
88
- setCursorRow((r) => r - 1);
89
- setCursorCol(prevLine.length);
90
- }
91
- }, [cursorCol, cursorRow, lines]);
92
- const moveRight = useCallback(() => {
93
- if (cursorCol < currentLine.length) {
94
- setCursorCol((c) => c + 1);
95
- }
96
- else if (cursorRow < lines.length - 1) {
97
- setCursorRow((r) => r + 1);
98
- setCursorCol(0);
99
- }
100
- }, [cursorCol, cursorRow, currentLine, lines]);
101
- useInput((input, key) => {
102
- if (key.ctrl) {
103
- if (input === "s") {
104
- commit();
105
- }
106
- return;
107
- }
108
- if (key.escape) {
109
- props.onClose();
110
- return;
111
- }
112
- if (key.return) {
113
- handleEnter();
114
- return;
115
- }
116
- if (key.upArrow) {
117
- moveUp();
118
- return;
119
- }
120
- if (key.downArrow) {
121
- moveDown();
122
- return;
123
- }
124
- if (key.leftArrow) {
125
- moveLeft();
126
- return;
127
- }
128
- if (key.rightArrow) {
129
- moveRight();
130
- return;
131
- }
132
- if (key.backspace || key.delete) {
133
- handleBackspace();
134
- return;
135
- }
136
- if (input.length === 1 && input >= " " && input <= "~") {
137
- insertChar(input);
138
- }
139
- });
140
- const totalLines = lines.length;
141
- const visibleStart = scrollOffset;
142
- const visibleEnd = Math.min(totalLines, visibleStart + MAX_VISIBLE_LINES);
143
- const visibleLines = lines.slice(visibleStart, visibleEnd);
144
- const lineNumWidth = String(totalLines).length;
145
- function renderLine(lineText, rowIdx) {
146
- const isCursor = rowIdx === cursorRow;
147
- const displayRow = rowIdx + 1;
148
- const prefix = String(displayRow).padStart(lineNumWidth, " ");
149
- const lineNumColor = isCursor ? theme.accent.brand : theme.text.muted;
150
- if (isCursor) {
151
- const before = lineText.slice(0, cursorCol);
152
- const char = cursorCol < lineText.length ? lineText[cursorCol] : " ";
153
- const after = cursorCol < lineText.length ? lineText.slice(cursorCol + 1) : "";
154
- return (_jsxs(Box, { children: [_jsxs(Text, { color: lineNumColor, children: [prefix, " "] }), _jsx(Text, { color: theme.text.primary, children: before }), _jsx(Text, { inverse: true, children: char }), _jsx(Text, { color: theme.text.primary, children: after })] }, rowIdx));
155
- }
156
- return (_jsxs(Box, { children: [_jsxs(Text, { color: lineNumColor, children: [prefix, " "] }), _jsx(Text, { color: theme.text.secondary, children: lineText })] }, rowIdx));
157
- }
158
- return (_jsx(Box, { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", justifyContent: "center", alignItems: "center", children: _jsxs(Box, { width: 70, flexDirection: "column", backgroundColor: theme.bg.elevated, borderStyle: "round", borderColor: theme.accent.brand, paddingX: 1, paddingY: 0, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: theme.accent.brand, bold: true, children: t("cmdEditor.title") }), _jsxs(Text, { color: theme.text.muted, children: [totalLines, " ", t("cmdEditor.lines")] })] }), _jsx(Box, { flexDirection: "column", marginTop: 0, children: visibleLines.map((line, visIdx) => renderLine(line, visibleStart + visIdx)) }), visibleEnd < totalLines ? (_jsx(Text, { color: theme.text.muted, children: "\u2193 " + (totalLines - visibleEnd) + " more" })) : null, _jsxs(Box, { marginTop: 1, justifyContent: "space-between", children: [_jsx(Text, { color: theme.text.muted, children: t("cmdEditor.hint") }), _jsxs(Text, { color: theme.text.muted, children: [visibleEnd, "/", totalLines] })] })] }) }));
159
- }
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box } from "ink";
3
- import { darkTheme as theme } from "../theme.js";
4
- import { Modal } from "./Modal.js";
5
- import { FocusableButton } from "./FocusableButton.js";
6
- import { t } from "../../i18n/index.js";
7
- export function ConfirmModal(props) {
8
- return (_jsx(Modal, { title: props.message, onClose: props.onNo, width: "50%", children: _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(FocusableButton, { label: t("board.yes"), color: theme.accent.focus, onPress: props.onYes }), _jsx(FocusableButton, { label: t("board.no"), color: theme.semantic.danger, onPress: props.onNo, variant: "danger" })] }) }));
9
- }
@@ -1,13 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from "ink";
3
- import { darkTheme as theme } from "../theme.js";
4
- import { t } from "../../i18n/index.js";
5
- import { FocusableButton } from "./FocusableButton.js";
6
- import { FocusableInput } from "./FocusableInput.js";
7
- export function FilterBar(props) {
8
- const { filters, sort, onStatusCycle, onSortCycle, onSelectProject, currentProjectName, onQueryChange, onSearchDismiss, } = props;
9
- const statusLabel = `${t("board.statusFilterTitle")} ${filters.status}`;
10
- const sortLabel = `${t("board.sortTitle")} ${sort}`;
11
- const projectLabel = currentProjectName ?? t("project.showAll");
12
- return (_jsxs(Box, { flexDirection: "column", height: 4, children: [_jsxs(Box, { children: [_jsx(Box, { width: currentProjectName ? 36 : 48, children: _jsx(FocusableInput, { value: filters.query, onChange: onQueryChange, onSubmit: onSearchDismiss, placeholder: t("board.searchPlaceholder") }) }), onSelectProject ? (_jsx(Box, { marginLeft: 1, children: _jsx(FocusableButton, { label: projectLabel, color: theme.accent.project, onPress: onSelectProject }) })) : null, _jsx(Box, { marginLeft: 1, children: _jsx(FocusableButton, { label: statusLabel, color: theme.accent.focus, onPress: onStatusCycle }) }), _jsx(Box, { marginLeft: 1, children: _jsx(FocusableButton, { label: sortLabel, color: theme.accent.focus, onPress: onSortCycle }) })] }), _jsx(Box, { children: _jsx(Text, { color: theme.text.muted, children: t("board.searchEmpty") }) })] }));
13
- }
@@ -1,82 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useMemo } from "react";
3
- import { Box, Text, useFocus, useInput } from "ink";
4
- import { darkTheme as theme } from "../theme.js";
5
- const MAX_VISIBLE = 6;
6
- export function FocusableSearchSelect(props) {
7
- const { options, value, onChange } = props;
8
- const placeholder = props.placeholder ?? "type to filter...";
9
- const { isFocused } = useFocus();
10
- const [filter, setFilter] = useState("");
11
- const [selectedIndex, setSelectedIndex] = useState(0);
12
- const filtered = useMemo(() => {
13
- if (!filter)
14
- return options;
15
- const q = filter.toLowerCase();
16
- return options.filter((o) => o.name.toLowerCase().includes(q) ||
17
- o.value.toLowerCase().includes(q));
18
- }, [options, filter]);
19
- const clampedIndex = Math.min(selectedIndex, Math.max(0, filtered.length - 1));
20
- const visibleStart = clampedIndex < MAX_VISIBLE
21
- ? 0
22
- : clampedIndex - MAX_VISIBLE + 1;
23
- const visible = filtered.slice(visibleStart, visibleStart + MAX_VISIBLE);
24
- useInput((input, key) => {
25
- if (key.upArrow) {
26
- setSelectedIndex((prev) => prev <= 0 ? filtered.length - 1 : prev - 1);
27
- return;
28
- }
29
- if (key.downArrow) {
30
- setSelectedIndex((prev) => prev >= filtered.length - 1 ? 0 : prev + 1);
31
- return;
32
- }
33
- if (key.return) {
34
- const option = filtered[clampedIndex];
35
- if (option) {
36
- onChange(option.value);
37
- }
38
- return;
39
- }
40
- if (key.escape) {
41
- setFilter("");
42
- setSelectedIndex(0);
43
- return;
44
- }
45
- if (key.backspace || key.delete) {
46
- setFilter((f) => f.slice(0, -1));
47
- setSelectedIndex(0);
48
- return;
49
- }
50
- if (input &&
51
- !key.ctrl &&
52
- !key.meta &&
53
- input.length === 1 &&
54
- /[a-z0-9 _\-./]/i.test(input)) {
55
- setFilter((f) => f + input);
56
- setSelectedIndex(0);
57
- return;
58
- }
59
- }, { isActive: isFocused });
60
- const borderColor = isFocused ? theme.accent.focus : theme.border.default;
61
- const backgroundColor = isFocused
62
- ? theme.bg.input
63
- : theme.bg.surface;
64
- const selectedOption = options.find((o) => o.value === value);
65
- const displayLabel = selectedOption?.name ?? placeholder;
66
- if (!isFocused) {
67
- return (_jsx(Box, { borderStyle: "single", borderColor: theme.border.dim, paddingLeft: 1, children: _jsx(Text, { color: value ? theme.text.primary : theme.text.muted, children: displayLabel }) }));
68
- }
69
- return (_jsxs(Box, { borderStyle: "single", borderColor: borderColor, backgroundColor: backgroundColor, flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 1, children: [_jsx(Text, { color: theme.text.muted, children: "/ " }), _jsx(Text, { color: filter ? theme.text.primary : theme.text.muted, children: filter || placeholder })] }), filtered.length > 0 ? (_jsx(Box, { flexDirection: "column", children: visible.map((option, i) => {
70
- const realIndex = visibleStart + i;
71
- const isSelected = realIndex === clampedIndex;
72
- const isActive = option.value === value;
73
- const prefix = isSelected ? "\u203a " : " ";
74
- const bg = isSelected ? theme.bg.active : undefined;
75
- const fg = isSelected
76
- ? theme.text.inverse
77
- : isActive
78
- ? theme.accent.focus
79
- : option.color ?? theme.text.secondary;
80
- return (_jsx(Box, { backgroundColor: bg, children: _jsxs(Text, { color: fg, children: [prefix, option.name] }) }, option.value));
81
- }) })) : null] }));
82
- }
@@ -1,42 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { Box, Text } from "ink";
3
- import { darkTheme as theme } from "../theme.js";
4
- function modeBadgeColor(mode) {
5
- switch (mode) {
6
- case "normal": return "#38bdf8";
7
- case "search": return "#38bdf8";
8
- case "create": return "#38bdf8";
9
- case "task": return "#a78bfa";
10
- case "help": return "#38bdf8";
11
- case "confirm": return "#f87171";
12
- case "projects": return "#34d399";
13
- }
14
- }
15
- function modeLabelText(mode) {
16
- switch (mode) {
17
- case "normal": return "Loops";
18
- case "search": return "Search";
19
- case "create": return "Create";
20
- case "task": return "Tasks";
21
- case "help": return "Help";
22
- case "confirm": return "Confirm";
23
- case "projects": return "Projects";
24
- }
25
- }
26
- function modeHints(mode) {
27
- switch (mode) {
28
- case "normal": return "Tab:panel /:search h:help Ctrl+C:quit";
29
- case "search": return "Enter:apply Esc:cancel";
30
- case "create": return "Tab:next Enter:save Esc:cancel";
31
- case "task": return "Tab:next Enter:save Esc:cancel";
32
- case "help": return "Esc:close";
33
- case "confirm": return "arrows:choose Enter:confirm y/n Esc:cancel";
34
- case "projects": return "Tab:navigate n:new e:edit d:delete Esc:back";
35
- }
36
- }
37
- export function Footer(props) {
38
- const badgeColor = modeBadgeColor(props.mode);
39
- const label = modeLabelText(props.mode);
40
- const hints = modeHints(props.mode);
41
- return (_jsxs(Box, { height: 1, justifyContent: "space-between", children: [_jsx(Box, { children: _jsxs(Text, { backgroundColor: badgeColor, color: theme.bg.base, bold: true, children: [" ", label, " "] }) }), _jsx(Box, { children: _jsx(Text, { color: theme.text.muted, children: hints }) })] }));
42
- }
@@ -1,78 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text, useInput } from "ink";
3
- import { darkTheme as theme } from "../theme.js";
4
- import { FocusableInput } from "./FocusableInput.js";
5
- import { t } from "../../i18n/index.js";
6
- import { copyToClipboard } from "../../shared/clipboard.js";
7
- const LABEL_WIDTH = 15;
8
- const DIVIDER_CHAR = "\u2500";
9
- const FOCUSED_MARKER = "\u203A";
10
- export function PatchEditForm(props) {
11
- const { title, fields, activeField, activeFieldValue, onActiveFieldChange, onActiveFieldCommit, onActiveFieldCancel, onActiveFieldActivate, pendingChanges, focusedRowIndex, onFocusedRowChange, validationErrors, onSave, onCancel, onCopy, } = props;
12
- useInput((_input, key) => {
13
- if (activeField === null) {
14
- if (key.upArrow) {
15
- onFocusedRowChange(Math.max(0, focusedRowIndex - 1));
16
- return;
17
- }
18
- if (key.downArrow) {
19
- onFocusedRowChange(Math.min(fields.length - 1, focusedRowIndex + 1));
20
- return;
21
- }
22
- if (key.return) {
23
- const field = fields[focusedRowIndex];
24
- if (field) {
25
- const shown = displayValue(field.key, field.value);
26
- onActiveFieldActivate(field.key, shown);
27
- }
28
- return;
29
- }
30
- if (key.ctrl && _input === "y") {
31
- const field = fields[focusedRowIndex];
32
- if (field) {
33
- const shown = displayValue(field.key, field.value);
34
- copyToClipboard(shown);
35
- onCopy(shown);
36
- }
37
- return;
38
- }
39
- if (key.ctrl && _input === "s") {
40
- onSave();
41
- return;
42
- }
43
- if (key.escape) {
44
- onCancel();
45
- return;
46
- }
47
- }
48
- else {
49
- if (key.escape) {
50
- onActiveFieldCancel();
51
- return;
52
- }
53
- }
54
- });
55
- const pendingCount = Object.keys(pendingChanges).length;
56
- const divider = DIVIDER_CHAR.repeat(50);
57
- function displayValue(fieldKey, originalValue) {
58
- if (fieldKey in pendingChanges) {
59
- return pendingChanges[fieldKey];
60
- }
61
- return originalValue;
62
- }
63
- const hintText = activeField !== null
64
- ? t("patch.hintEditing")
65
- : pendingCount > 0
66
- ? `${t("patch.hintNavPending")} . Ctrl+Y ${t("patch.hintCopy").toLowerCase()}`
67
- : `${t("patch.hintNavIdle")}`;
68
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { paddingLeft: 1, children: _jsx(Text, { bold: true, color: theme.accent.loop, children: title }) }), pendingCount > 0 ? (_jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.semantic.warning, children: t("patch.pendingChanges", { count: String(pendingCount) }) }) })) : (_jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.text.muted, children: t("patch.noChanges") }) })), _jsx(Box, { children: _jsx(Text, { color: theme.border.dim, children: divider }) }), fields.map((field, index) => {
69
- const isActive = activeField === field.key;
70
- const hasPending = field.key in pendingChanges;
71
- const shownValue = displayValue(field.key, field.value);
72
- const isFocused = activeField === null && index === focusedRowIndex;
73
- const marker = isFocused ? FOCUSED_MARKER : " ";
74
- const markerColor = isFocused ? theme.accent.brand : theme.text.muted;
75
- const fieldError = validationErrors[field.key];
76
- return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: markerColor, children: marker }) }), _jsx(Box, { width: LABEL_WIDTH, flexShrink: 0, children: _jsx(Text, { color: theme.text.muted, children: field.label }) }), _jsx(Box, { flexGrow: 1, children: isActive ? (_jsx(FocusableInput, { value: activeFieldValue, onChange: onActiveFieldChange, onSubmit: onActiveFieldCommit })) : (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.text.primary, children: shownValue }), hasPending ? (_jsx(Text, { color: theme.semantic.warning, children: " \u25CF" })) : null] })) })] }), fieldError ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { children: " " }) }), _jsx(Box, { width: LABEL_WIDTH, flexShrink: 0, children: _jsx(Text, { children: " " }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: theme.semantic.danger, children: fieldError }) })] })) : null] }, field.key));
77
- }), _jsx(Box, { children: _jsx(Text, { color: theme.border.dim, children: divider }) }), pendingCount > 0 ? (_jsxs(Box, { paddingLeft: 1, flexDirection: "row", children: [_jsx(Text, { color: theme.accent.brand, children: "[Save] Ctrl+S " }), _jsx(Text, { color: theme.text.muted, children: "[Cancel] Esc" })] })) : null, _jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.text.muted, children: hintText }) })] }));
78
- }
@@ -1,67 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useMemo, useEffect } from "react";
3
- import { Box, Text, useInput } from "ink";
4
- import { darkTheme as theme } from "../theme.js";
5
- export function SearchSelect(props) {
6
- const { options, value, onChange, focused } = props;
7
- const placeholder = props.placeholder ?? "type to filter...";
8
- const [filter, setFilter] = useState("");
9
- const [selectedIndex, setSelectedIndex] = useState(0);
10
- const filtered = useMemo(() => {
11
- if (!filter)
12
- return options;
13
- const q = filter.toLowerCase();
14
- return options.filter((o) => o.name.toLowerCase().includes(q) || o.value.toLowerCase().includes(q));
15
- }, [options, filter]);
16
- const currentIdx = filtered.findIndex((o) => o.value === value);
17
- const clampedSelected = Math.min(currentIdx >= 0 && !filter ? currentIdx : selectedIndex, Math.max(0, filtered.length - 1));
18
- useEffect(() => {
19
- if (focused) {
20
- setFilter("");
21
- setSelectedIndex(currentIdx >= 0 ? currentIdx : 0);
22
- }
23
- }, [focused]);
24
- useInput((input, key) => {
25
- if (!focused)
26
- return;
27
- if (key.upArrow) {
28
- setSelectedIndex((prev) => prev <= 0 ? filtered.length - 1 : prev - 1);
29
- return;
30
- }
31
- if (key.downArrow) {
32
- setSelectedIndex((prev) => prev >= filtered.length - 1 ? 0 : prev + 1);
33
- return;
34
- }
35
- if (key.return) {
36
- const option = filtered[clampedSelected];
37
- if (option) {
38
- onChange(option.value);
39
- }
40
- return;
41
- }
42
- if (key.escape) {
43
- setFilter("");
44
- setSelectedIndex(0);
45
- return;
46
- }
47
- if (key.backspace || key.delete) {
48
- setFilter((f) => f.slice(0, -1));
49
- setSelectedIndex(0);
50
- return;
51
- }
52
- if (input && !key.ctrl && !key.meta && input.length === 1 && /[a-z0-9 _\-./]/i.test(input)) {
53
- setFilter((f) => f + input);
54
- setSelectedIndex(0);
55
- return;
56
- }
57
- });
58
- const listHeight = Math.min(filtered.length, 6);
59
- return (_jsxs(Box, { borderStyle: "single", borderColor: focused ? theme.accent.focus : theme.border.default, backgroundColor: focused ? theme.bg.input : theme.bg.surface, flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 1, children: [_jsx(Text, { color: theme.text.muted, children: "/ " }), _jsx(Text, { color: filter ? theme.text.primary : theme.text.muted, children: filter || placeholder })] }), filtered.length > 0 ? (_jsx(Box, { flexDirection: "column", height: listHeight, children: filtered.map((option, i) => {
60
- const isSelected = i === clampedSelected;
61
- const isActive = option.value === value;
62
- const prefix = isSelected ? "\u203a " : " ";
63
- const bg = isSelected ? theme.bg.active : undefined;
64
- const fg = isSelected ? theme.text.inverse : isActive ? theme.accent.focus : theme.text.secondary;
65
- return (_jsx(Box, { backgroundColor: bg, children: _jsxs(Text, { color: fg, children: [prefix, option.name] }) }, option.value));
66
- }) })) : null] }));
67
- }
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from "ink";
3
- import { darkTheme as theme } from "../theme.js";
4
- import { FocusableInput } from "./FocusableInput.js";
5
- import { t } from "../../i18n/index.js";
6
- export function TaskFilterBar(props) {
7
- const { query, onQueryChange, onSearchDismiss } = props;
8
- return (_jsxs(Box, { height: 3, children: [_jsx(Box, { flexGrow: 1, children: _jsx(FocusableInput, { value: query, onChange: onQueryChange, onSubmit: onSearchDismiss, placeholder: t("board.searchPlaceholder") }) }), _jsx(Box, { paddingLeft: 1, justifyContent: "flex-end", children: _jsx(Text, { color: theme.text.muted, children: t("board.searchEmpty") }) })] }));
9
- }
@@ -1,3 +0,0 @@
1
- import type { BoardApp } from "./app.js";
2
- export declare function openCreateLoopWizard(app: BoardApp, refresh: () => Promise<void>): Promise<void>;
3
- //# sourceMappingURL=create-loop.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create-loop.d.ts","sourceRoot":"","sources":["../../src/tui/create-loop.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAkDzC,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CAyDf"}