loop-task 2.1.9 → 2.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/App.js +2 -2
- package/dist/features/commands/useCommandHandlers.js +6 -4
- package/dist/features/commands/useContextualActions.js +8 -5
- package/dist/shared/hooks/useLogStream.js +3 -1
- package/dist/shared/i18n/en.json +1 -1
- package/dist/shared/ui/format.js +1 -1
- package/dist/widgets/log-modal/LogModal.js +24 -7
- package/dist/widgets/right-panel/Inspector.js +6 -12
- package/dist/widgets/right-panel/RightPanel.js +4 -2
- package/dist/widgets/right-panel/RunHistory.js +4 -2
- package/package.json +1 -1
package/dist/app/App.js
CHANGED
|
@@ -48,7 +48,7 @@ export function App(props) {
|
|
|
48
48
|
const s = useAppState(loops, pushToast, refresh, loopService, taskService, projectService, logService, view, push, pop);
|
|
49
49
|
useLogStream(s.selectedId, view, (error) => pushToast("error", error.message));
|
|
50
50
|
const { handleCommand } = useCommandHandlers({
|
|
51
|
-
activeTab: s.activeTab, selected: s.selected, selectedTask: s.selectedTask, selectedProjectEntity: s.selectedProjectEntity,
|
|
51
|
+
activeTab: s.activeTab, selected: s.selected, selectedRunIndex: s.selectedRunIndex, selectedTask: s.selectedTask, selectedProjectEntity: s.selectedProjectEntity,
|
|
52
52
|
tasks: s.tasks, projects: s.projects, currentProjectId: s.currentProjectId, filters: s.filters, projectFilters: s.projectFilters,
|
|
53
53
|
setCloneMode: s.setCloneMode, setEditTarget: s.setEditTarget, setPendingTaskSelection: s.setPendingTaskSelection,
|
|
54
54
|
setEditTask: s.setEditTask, setEditProject: s.setEditProject, setActiveTab: s.setActiveTab,
|
|
@@ -61,7 +61,7 @@ export function App(props) {
|
|
|
61
61
|
runAction: s.runAction, handleOpenRunLog: s.handleOpenRunLog,
|
|
62
62
|
});
|
|
63
63
|
const { handleContextualCopy, triggerContextualAction } = useContextualActions({
|
|
64
|
-
activeTab: s.activeTab, focusedPanel: s.focusedPanel, selected: s.selected, selectedTask: s.selectedTask, selectedProjectEntity: s.selectedProjectEntity,
|
|
64
|
+
activeTab: s.activeTab, focusedPanel: s.focusedPanel, selected: s.selected, selectedRunIndex: s.selectedRunIndex, selectedTask: s.selectedTask, selectedProjectEntity: s.selectedProjectEntity,
|
|
65
65
|
tasks: s.tasks, push, setCloneMode: s.setCloneMode, setEditTarget: s.setEditTarget, setPendingTaskSelection: s.setPendingTaskSelection,
|
|
66
66
|
handleCommand, handleOpenRunLog: s.handleOpenRunLog, pushToast, isBoardView,
|
|
67
67
|
view, logModalRun: s.logModalRun, commandsBrowserOpen: s.commandsBrowserOpen, confirmState: s.confirmState, searchState: s.searchState,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { t } from "../../shared/i18n/index.js";
|
|
2
2
|
import { cycleSortMode, cycleStatusFilter } from "../../entities/loops/filters.js";
|
|
3
3
|
import { cycleProjectSortMode, cycleProjectHasLoopsFilter, cycleProjectIsSystemFilter } from "../../entities/projects/filters.js";
|
|
4
|
+
import { groupRunsByCycle } from "../../widgets/right-panel/RunHistory.js";
|
|
4
5
|
export function useCommandHandlers(context) {
|
|
5
|
-
const { activeTab, selected, selectedTask, selectedProjectEntity, tasks, projects, currentProjectId, setCloneMode, setEditTarget, setPendingTaskSelection, setEditTask, setEditProject, setActiveTab, setConfirmState, setCommandsBrowserOpen, setSearchValue, setSearchState, setFilters, setSort, setCurrentProjectId, setProjectFilters, setProjectSelectedIndex, setDebugMode, setExportModal, push, pop, refresh, refreshTasks, refreshProjects, pushToast, loopService, taskService, projectService, exportService, runAction, handleOpenRunLog, } = context;
|
|
6
|
+
const { activeTab, selected, selectedRunIndex, selectedTask, selectedProjectEntity, tasks, projects, currentProjectId, setCloneMode, setEditTarget, setPendingTaskSelection, setEditTask, setEditProject, setActiveTab, setConfirmState, setCommandsBrowserOpen, setSearchValue, setSearchState, setFilters, setSort, setCurrentProjectId, setProjectFilters, setProjectSelectedIndex, setDebugMode, setExportModal, push, pop, refresh, refreshTasks, refreshProjects, pushToast, loopService, taskService, projectService, exportService, runAction, handleOpenRunLog, } = context;
|
|
6
7
|
const commandHandlers = {
|
|
7
8
|
edit: () => {
|
|
8
9
|
if (activeTab === "loops" && selected) {
|
|
@@ -109,9 +110,10 @@ export function useCommandHandlers(context) {
|
|
|
109
110
|
debug: () => { setDebugMode((prev) => !prev); },
|
|
110
111
|
logs: () => {
|
|
111
112
|
if (activeTab === "loops" && selected) {
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
|
|
113
|
+
const reversed = [...groupRunsByCycle(selected.runHistory)].reverse();
|
|
114
|
+
if (reversed.length > 0) {
|
|
115
|
+
const run = reversed[Math.min(selectedRunIndex, reversed.length - 1)];
|
|
116
|
+
handleOpenRunLog(run ?? reversed[0]);
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
},
|
|
@@ -2,8 +2,9 @@ import { useCallback } from "react";
|
|
|
2
2
|
import { copyToClipboard } from "../../shared/clipboard.js";
|
|
3
3
|
import { commandLine } from "../../shared/ui/format.js";
|
|
4
4
|
import { t } from "../../shared/i18n/index.js";
|
|
5
|
+
import { groupRunsByCycle } from "../../widgets/right-panel/RunHistory.js";
|
|
5
6
|
export function useContextualActions(context) {
|
|
6
|
-
const { activeTab, focusedPanel, selected, selectedTask, selectedProjectEntity, tasks, push, setCloneMode, setEditTarget, setPendingTaskSelection, handleCommand, handleOpenRunLog, pushToast, isBoardView, view, logModalRun, commandsBrowserOpen, confirmState, searchState, setChordState, chordState, } = context;
|
|
7
|
+
const { activeTab, focusedPanel, selected, selectedRunIndex, selectedTask, selectedProjectEntity, tasks, push, setCloneMode, setEditTarget, setPendingTaskSelection, handleCommand, handleOpenRunLog, pushToast, isBoardView, view, logModalRun, commandsBrowserOpen, confirmState, searchState, setChordState, chordState, } = context;
|
|
7
8
|
const handleContextualCopy = useCallback(() => {
|
|
8
9
|
const copyHandlers = {
|
|
9
10
|
loops: () => {
|
|
@@ -57,9 +58,11 @@ export function useContextualActions(context) {
|
|
|
57
58
|
"loops:right": () => {
|
|
58
59
|
if (!selected)
|
|
59
60
|
return;
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
|
|
61
|
+
const reversed = [...groupRunsByCycle(selected.runHistory)].reverse();
|
|
62
|
+
if (reversed.length > 0) {
|
|
63
|
+
const run = reversed[Math.min(selectedRunIndex, reversed.length - 1)];
|
|
64
|
+
handleOpenRunLog(run ?? reversed[0]);
|
|
65
|
+
}
|
|
63
66
|
else
|
|
64
67
|
editSelectedLoop();
|
|
65
68
|
},
|
|
@@ -68,7 +71,7 @@ export function useContextualActions(context) {
|
|
|
68
71
|
const handlerKey = activeTab !== "loops" ? `${activeTab}:` : `loops:${focusedPanel}`;
|
|
69
72
|
handlers[handlerKey]?.();
|
|
70
73
|
}, [activeTab, focusedPanel, view, logModalRun, commandsBrowserOpen, confirmState, searchState,
|
|
71
|
-
chordState, setChordState, selected, selectedTask, selectedProjectEntity, tasks, push,
|
|
74
|
+
chordState, setChordState, selected, selectedRunIndex, selectedTask, selectedProjectEntity, tasks, push,
|
|
72
75
|
setCloneMode, setEditTarget, setPendingTaskSelection, handleCommand, handleOpenRunLog,
|
|
73
76
|
isBoardView]);
|
|
74
77
|
return { handleContextualCopy, triggerContextualAction };
|
|
@@ -4,7 +4,9 @@ import { TYPES } from "../services/types.js";
|
|
|
4
4
|
import { t } from "../i18n/index.js";
|
|
5
5
|
import { LOG_LINES_MAX } from "../config/constants.js";
|
|
6
6
|
export function useLogStream(selectedId, view, onError) {
|
|
7
|
-
const
|
|
7
|
+
const injectedLogService = useInject(TYPES.LogService);
|
|
8
|
+
const logServiceRef = useRef(injectedLogService);
|
|
9
|
+
const logService = logServiceRef.current;
|
|
8
10
|
const [logLines, setLogLines] = useState([]);
|
|
9
11
|
const logSocket = useRef(null);
|
|
10
12
|
useEffect(() => {
|
package/dist/shared/i18n/en.json
CHANGED
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
"board.logModalExit": "exit {code}",
|
|
194
194
|
"board.logModalDuration": "duration",
|
|
195
195
|
"board.logModalSearchHint": "type to filter, enter to apply, esc to exit search",
|
|
196
|
-
"board.logModalFooterHints": "/:search ctrl+x:copy up/down:scroll esc:close",
|
|
196
|
+
"board.logModalFooterHints": "/:search ctrl+x:copy up/down:scroll ctrl+t:top ctrl+b:bottom esc:close",
|
|
197
197
|
"board.logModalEscClose": "esc to close",
|
|
198
198
|
"board.logModalRunning": "running…",
|
|
199
199
|
"board.detailTitle": " Loop Detail ",
|
package/dist/shared/ui/format.js
CHANGED
|
@@ -100,7 +100,7 @@ export function formatRunTime(iso) {
|
|
|
100
100
|
export function formatDate(iso) {
|
|
101
101
|
const d = new Date(iso);
|
|
102
102
|
const date = d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
103
|
-
const time = d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
103
|
+
const time = d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false });
|
|
104
104
|
return `${date} ${time}`;
|
|
105
105
|
}
|
|
106
106
|
export function sinceLabel(loop) {
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { Box, Text, useInput } from "ink";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Box, Text, useInput, useStdout } from "ink";
|
|
4
4
|
import { darkTheme as theme } from "../../shared/ui/theme.js";
|
|
5
5
|
import { t } from "../../shared/i18n/index.js";
|
|
6
6
|
import { useInject } from "../../shared/hooks/useInject.js";
|
|
7
7
|
import { TYPES } from "../../shared/services/types.js";
|
|
8
8
|
import { formatDate } from "../../shared/ui/format.js";
|
|
9
9
|
import { copyToClipboard } from "../../shared/clipboard.js";
|
|
10
|
-
const MAX_VISIBLE_LINES = 20;
|
|
11
10
|
function colorForLine(line, run) {
|
|
12
11
|
if (line.includes("[Run #"))
|
|
13
12
|
return theme.accent.loop;
|
|
@@ -31,7 +30,13 @@ export function LogModal(props) {
|
|
|
31
30
|
const [searchQuery, setSearchQuery] = useState("");
|
|
32
31
|
const [follow, setFollow] = useState(true);
|
|
33
32
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
34
|
-
const
|
|
33
|
+
const bottomDistanceRef = useRef(0);
|
|
34
|
+
const injectedLogService = useInject(TYPES.LogService);
|
|
35
|
+
const logServiceRef = useRef(injectedLogService);
|
|
36
|
+
const logService = logServiceRef.current;
|
|
37
|
+
const { stdout } = useStdout();
|
|
38
|
+
const terminalHeight = stdout?.rows ?? 24;
|
|
39
|
+
const MAX_VISIBLE_LINES = Math.max(1, Math.floor(terminalHeight * 0.7) - 7);
|
|
35
40
|
useEffect(() => {
|
|
36
41
|
setLines(props.logLines);
|
|
37
42
|
}, [props.logLines]);
|
|
@@ -92,14 +97,26 @@ export function LogModal(props) {
|
|
|
92
97
|
props.onCopy?.();
|
|
93
98
|
return;
|
|
94
99
|
}
|
|
100
|
+
if (key.ctrl && input === "t") {
|
|
101
|
+
setFollow(false);
|
|
102
|
+
setScrollOffset(0);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (key.ctrl && input === "b") {
|
|
106
|
+
setFollow(true);
|
|
107
|
+
setScrollOffset(0);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
95
110
|
if (key.downArrow) {
|
|
111
|
+
const base = follow ? Math.max(0, totalLines - MAX_VISIBLE_LINES) : scrollOffset;
|
|
96
112
|
setFollow(false);
|
|
97
|
-
setScrollOffset(
|
|
113
|
+
setScrollOffset(Math.min(base + 1, Math.max(0, totalLines - MAX_VISIBLE_LINES)));
|
|
98
114
|
return;
|
|
99
115
|
}
|
|
100
116
|
if (key.upArrow) {
|
|
117
|
+
const base = follow ? Math.max(0, totalLines - MAX_VISIBLE_LINES) : scrollOffset;
|
|
101
118
|
setFollow(false);
|
|
102
|
-
setScrollOffset(
|
|
119
|
+
setScrollOffset(Math.max(0, base - 1));
|
|
103
120
|
return;
|
|
104
121
|
}
|
|
105
122
|
});
|
|
@@ -118,7 +135,7 @@ export function LogModal(props) {
|
|
|
118
135
|
? t("board.logModalRunning")
|
|
119
136
|
: t("board.logModalExit", { code: props.run.exitCode })] }), props.run.duration > 0 ? (_jsxs(Text, { color: theme.text.muted, children: [" ", t("board.logModalDuration"), " ", props.run.duration, "ms"] })) : null, _jsx(Text, { color: theme.text.muted, children: searchMode
|
|
120
137
|
? `/${searchQuery}`
|
|
121
|
-
: `[${startIdx}-${endIdx}/${totalLines}]` })] }), searchMode ? (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.text.muted, children: "Search: " }), _jsxs(Text, { color: theme.text.primary, children: [searchQuery, "_"] })] })) : null, _jsx(Box, { flexDirection: "column", flexGrow: 1, children: visible.length === 0 ? (_jsx(Text, { color: theme.text.muted, children: isLoading
|
|
138
|
+
: `[${startIdx}-${endIdx}/${totalLines}]` })] }), searchMode ? (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.text.muted, children: "Search: " }), _jsxs(Text, { color: theme.text.primary, children: [searchQuery, "_"] })] })) : null, _jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visible.length === 0 ? (_jsx(Text, { color: theme.text.muted, children: isLoading
|
|
122
139
|
? t("board.logModalLoading")
|
|
123
140
|
: searchQuery
|
|
124
141
|
? t("board.logModalNoMatches")
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { darkTheme as theme, statusColor } from "../../shared/ui/theme.js";
|
|
4
|
-
import { describeLoop, commandLine, timeAgo, timeUntil } from "../../shared/ui/format.js";
|
|
4
|
+
import { describeLoop, commandLine, timeAgo, timeUntil, truncate } from "../../shared/ui/format.js";
|
|
5
5
|
import { t } from "../../shared/i18n/index.js";
|
|
6
|
-
import { resolveEffectiveCwd } from "../../core/command/resolve-cwd.js";
|
|
7
6
|
const LABEL_WIDTH = 11;
|
|
8
7
|
function Field(props) {
|
|
9
|
-
return (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.text.muted, children: props.label.padEnd(LABEL_WIDTH) }), _jsx(Text, { color: theme.text.primary, children: props.children })] }));
|
|
8
|
+
return (_jsxs(Box, { overflow: "hidden", children: [_jsx(Text, { bold: true, color: theme.text.muted, children: props.label.padEnd(LABEL_WIDTH) }), _jsx(Text, { color: theme.text.primary, wrap: "truncate", children: props.children })] }));
|
|
10
9
|
}
|
|
11
10
|
const DIVIDER = "\u2500".repeat(40);
|
|
12
11
|
function MutedField(props) {
|
|
13
|
-
return (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.text.muted, children: props.label.padEnd(LABEL_WIDTH) }), _jsx(Text, { color: theme.text.muted, children: props.children })] }));
|
|
12
|
+
return (_jsxs(Box, { overflow: "hidden", children: [_jsx(Text, { bold: true, color: theme.text.muted, children: props.label.padEnd(LABEL_WIDTH) }), _jsx(Text, { color: theme.text.muted, wrap: "truncate", children: props.children })] }));
|
|
14
13
|
}
|
|
15
14
|
export function Inspector(props) {
|
|
16
15
|
const { loop, projects } = props;
|
|
@@ -22,12 +21,7 @@ export function Inspector(props) {
|
|
|
22
21
|
const lastRun = loop.lastRunAt ? timeAgo(loop.lastRunAt) : t("format.dash");
|
|
23
22
|
const lastExit = loop.lastExitCode !== null ? String(loop.lastExitCode) : t("format.dash");
|
|
24
23
|
const nextRun = loop.nextRunAt ? t("format.timingNext", { timeAgo: timeUntil(loop.nextRunAt) }) : t("format.dash");
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const showEffective = loop.cwd !== effectiveCwd;
|
|
29
|
-
const fullCmd = commandLine(loop.command, loop.commandArgs);
|
|
30
|
-
const desc = describeLoop(loop);
|
|
31
|
-
const showCommand = desc !== fullCmd;
|
|
32
|
-
return (_jsxs(Box, { flexDirection: "column", paddingY: 0, children: [_jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.text.muted, children: t("board.inspectorTitle") }) }), _jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.text.muted, children: DIVIDER }) }), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.text.muted, children: t("board.fieldStatus").padEnd(LABEL_WIDTH) }), _jsx(Text, { color: sColor, children: loop.status })] }), _jsx(Field, { label: t("board.fieldLastExit"), children: _jsx(Text, { color: theme.text.primary, children: lastExit }) }), _jsx(Field, { label: t("board.fieldLastRun"), children: _jsx(Text, { color: theme.text.primary, children: lastRun }) }), _jsx(Field, { label: t("board.fieldNextRun"), children: _jsx(Text, { color: theme.text.primary, children: nextRun }) }), _jsx(Field, { label: t("board.fieldRuns"), children: _jsxs(Text, { color: theme.text.primary, children: [loop.runCount, " / ", maxRunsLabel] }) }), _jsx(Field, { label: t("board.fieldInterval"), children: _jsx(Text, { color: theme.text.primary, children: loop.intervalHuman }) }), _jsx(Field, { label: t("board.fieldDir"), children: _jsxs(Text, { color: theme.text.primary, children: [loop.cwd || t("board.inherit"), showEffective ? ` → ${effectiveCwd}` : ""] }) }), _jsx(MutedField, { label: t("board.fieldId"), children: loop.id }), _jsx(MutedField, { label: t("board.fieldDesc"), children: desc }), showCommand && _jsx(MutedField, { label: t("board.fieldCommand"), children: fullCmd }), _jsx(MutedField, { label: t("board.fieldTask"), children: loop.taskId ?? t("format.dash") }), _jsx(MutedField, { label: t("board.fieldPid"), children: pid })] }), _jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.text.muted, children: DIVIDER }) })] }));
|
|
24
|
+
const fullCmd = truncate(commandLine(loop.command, loop.commandArgs), 38);
|
|
25
|
+
const desc = truncate(describeLoop(loop), 38);
|
|
26
|
+
return (_jsxs(Box, { flexDirection: "column", paddingY: 0, children: [_jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.text.muted, children: t("board.inspectorTitle") }) }), _jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.text.muted, children: DIVIDER }) }), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.text.muted, children: t("board.fieldStatus").padEnd(LABEL_WIDTH) }), _jsx(Text, { color: sColor, children: loop.status })] }), _jsx(Field, { label: t("board.fieldRuns"), children: _jsxs(Text, { color: theme.text.primary, children: [loop.runCount, " / ", maxRunsLabel] }) }), _jsx(Field, { label: t("board.fieldInterval"), children: _jsx(Text, { color: theme.text.primary, children: loop.intervalHuman }) }), _jsx(Field, { label: t("board.fieldLastExit"), children: _jsx(Text, { color: theme.text.primary, children: lastExit }) }), _jsx(Field, { label: t("board.fieldLastRun"), children: _jsx(Text, { color: theme.text.primary, children: lastRun }) }), _jsx(Field, { label: t("board.fieldNextRun"), children: _jsx(Text, { color: theme.text.primary, children: nextRun }) }), _jsx(MutedField, { label: t("board.fieldDesc"), children: desc }), _jsx(MutedField, { label: t("board.fieldCommand"), children: fullCmd })] }), _jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.text.muted, children: DIVIDER }) })] }));
|
|
33
27
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
2
|
+
import { Box, Text, useStdout } from "ink";
|
|
3
3
|
import { darkTheme as theme, tabAccentColor } from "../../shared/ui/theme.js";
|
|
4
4
|
import { t } from "../../shared/i18n/index.js";
|
|
5
5
|
import { Inspector } from "./Inspector.js";
|
|
@@ -10,7 +10,9 @@ const DIVIDER = "\u2500".repeat(40);
|
|
|
10
10
|
export function RightPanel(props) {
|
|
11
11
|
const { isFocused, navActive = true, activeTab, loop, selectedRunIndex, onSelectRun, onOpenRun, selectedTask, allTasks, selectedProject, projectLoopCount, onProjectEdit, onProjectDelete, projects, } = props;
|
|
12
12
|
const borderColor = isFocused ? tabAccentColor(activeTab) : theme.border.default;
|
|
13
|
-
|
|
13
|
+
const { stdout } = useStdout();
|
|
14
|
+
const panelHeight = (stdout?.rows ?? 24) - 8;
|
|
15
|
+
return (_jsx(Box, { flexDirection: "column", width: "40%", height: panelHeight, borderStyle: "single", borderColor: borderColor, children: activeTab === "projects" ? (_jsx(ProjectInspector, { project: selectedProject ?? null, loopCount: projectLoopCount ?? 0, onEdit: onProjectEdit, onDelete: onProjectDelete })) : activeTab === "tasks" ? (_jsx(TaskInspector, { task: selectedTask ?? null, allTasks: allTasks ?? [] })) : (_jsxs(_Fragment, { children: [_jsx(Inspector, { loop: loop, projects: projects }), _jsx(RunHistory, { loop: loop, selectedRunIndex: selectedRunIndex, onSelectRun: onSelectRun, onOpenRun: onOpenRun, isFocused: isFocused, navActive: navActive })] })) }));
|
|
14
16
|
}
|
|
15
17
|
function TaskInspector(props) {
|
|
16
18
|
const { task, allTasks } = props;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
2
|
+
import { Box, Text, useInput, useStdout } from "ink";
|
|
3
3
|
import { ScrollList } from "ink-scroll-list";
|
|
4
4
|
import { darkTheme as theme } from "../../shared/ui/theme.js";
|
|
5
5
|
import { formatRunTime, formatRunDuration, formatFileSize } from "../../shared/ui/format.js";
|
|
@@ -110,9 +110,11 @@ function computeTrends(runs) {
|
|
|
110
110
|
lastFailureAgo,
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
const LIMIT = 15;
|
|
114
113
|
export function RunHistory(props) {
|
|
115
114
|
const { loop, selectedRunIndex, onSelectRun, onOpenRun, isFocused, navActive = true } = props;
|
|
115
|
+
const { stdout } = useStdout();
|
|
116
|
+
const terminalHeight = stdout?.rows ?? 24;
|
|
117
|
+
const LIMIT = Math.max(3, terminalHeight - 22);
|
|
116
118
|
const runs = groupRunsByCycle(loop?.runHistory ?? []);
|
|
117
119
|
const reversed = [...runs].reverse();
|
|
118
120
|
const n = reversed.length;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loop-task",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.11",
|
|
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": {
|