@arvorco/relentless 0.1.17 → 0.1.19
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/package.json +1 -1
- package/src/tui/App.tsx +15 -31
- package/src/tui/TUIRunner.tsx +17 -1
- package/src/tui/components/AgentOutput.tsx +15 -17
- package/src/tui/components/AgentStatus.tsx +1 -2
- package/src/tui/components/Header.tsx +1 -2
- package/src/tui/components/StoryGrid.tsx +64 -29
- package/src/tui/types.ts +4 -0
package/package.json
CHANGED
package/src/tui/App.tsx
CHANGED
|
@@ -21,38 +21,22 @@ interface AppProps {
|
|
|
21
21
|
|
|
22
22
|
export function App({ state }: AppProps): React.ReactElement {
|
|
23
23
|
const { stdout } = useStdout();
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
React.useEffect(() => {
|
|
27
|
-
if (!stdout.isTTY) return;
|
|
28
|
-
|
|
29
|
-
const handleResize = () => {
|
|
30
|
-
setTerminalRows(stdout.rows ?? 24);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
stdout.on("resize", handleResize);
|
|
34
|
-
return () => {
|
|
35
|
-
stdout.off("resize", handleResize);
|
|
36
|
-
};
|
|
37
|
-
}, [stdout]);
|
|
38
|
-
|
|
24
|
+
const terminalRows = stdout.rows ?? 24;
|
|
25
|
+
|
|
39
26
|
const completedCount = state.stories.filter((s) => s.passes).length;
|
|
40
27
|
const totalCount = state.stories.length;
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
agentOutputLines -= 1;
|
|
54
|
-
storyGridRows = 1;
|
|
55
|
-
}
|
|
28
|
+
|
|
29
|
+
// Calculate available rows for stories based on terminal height
|
|
30
|
+
// Chrome: Header(2) + Feature/Progress(2) + CurrentStory(2) + AgentOutputHeader(1) + AgentStatusFooter(2) + Padding(2) = ~11 lines
|
|
31
|
+
// AgentOutput: 6 lines
|
|
32
|
+
// Remaining space for stories
|
|
33
|
+
const chromeHeight = 11;
|
|
34
|
+
const agentOutputLines = 6;
|
|
35
|
+
const availableForStories = Math.max(8, terminalRows - chromeHeight - agentOutputLines);
|
|
36
|
+
|
|
37
|
+
// Calculate story rows needed for 2-column layout
|
|
38
|
+
const storyRows = Math.ceil(totalCount / 2);
|
|
39
|
+
const maxStoryRows = Math.min(storyRows, availableForStories);
|
|
56
40
|
|
|
57
41
|
return (
|
|
58
42
|
<Box flexDirection="column" width="100%">
|
|
@@ -87,7 +71,7 @@ export function App({ state }: AppProps): React.ReactElement {
|
|
|
87
71
|
<StoryGrid
|
|
88
72
|
stories={state.stories}
|
|
89
73
|
currentStoryId={state.currentStory?.id}
|
|
90
|
-
maxRows={
|
|
74
|
+
maxRows={maxStoryRows}
|
|
91
75
|
/>
|
|
92
76
|
|
|
93
77
|
{/* Agent status footer */}
|
package/src/tui/TUIRunner.tsx
CHANGED
|
@@ -113,6 +113,10 @@ function TUIRunnerComponent({
|
|
|
113
113
|
id: s.id,
|
|
114
114
|
title: s.title,
|
|
115
115
|
passes: s.passes,
|
|
116
|
+
priority: s.priority,
|
|
117
|
+
criteriaCount: s.acceptanceCriteria.length,
|
|
118
|
+
research: s.research,
|
|
119
|
+
phase: s.phase,
|
|
116
120
|
})),
|
|
117
121
|
agents: agentStates,
|
|
118
122
|
}));
|
|
@@ -153,7 +157,15 @@ function TUIRunnerComponent({
|
|
|
153
157
|
// Update current story
|
|
154
158
|
setState((prev) => ({
|
|
155
159
|
...prev,
|
|
156
|
-
currentStory: {
|
|
160
|
+
currentStory: {
|
|
161
|
+
id: story.id,
|
|
162
|
+
title: story.title,
|
|
163
|
+
passes: story.passes,
|
|
164
|
+
priority: story.priority,
|
|
165
|
+
criteriaCount: story.acceptanceCriteria.length,
|
|
166
|
+
research: story.research,
|
|
167
|
+
phase: story.phase,
|
|
168
|
+
},
|
|
157
169
|
elapsedSeconds: 0,
|
|
158
170
|
}));
|
|
159
171
|
|
|
@@ -334,6 +346,10 @@ function TUIRunnerComponent({
|
|
|
334
346
|
id: s.id,
|
|
335
347
|
title: s.title,
|
|
336
348
|
passes: s.passes,
|
|
349
|
+
priority: s.priority,
|
|
350
|
+
criteriaCount: s.acceptanceCriteria.length,
|
|
351
|
+
research: s.research,
|
|
352
|
+
phase: s.phase,
|
|
337
353
|
})),
|
|
338
354
|
}));
|
|
339
355
|
|
|
@@ -21,27 +21,25 @@ export function AgentOutput({
|
|
|
21
21
|
const displayLines = clampedMaxLines > 0 ? lines.slice(-clampedMaxLines) : [];
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
|
-
<Box flexDirection="column"
|
|
25
|
-
<Box paddingX={1}
|
|
24
|
+
<Box flexDirection="column" paddingY={1}>
|
|
25
|
+
<Box paddingX={1}>
|
|
26
26
|
<Text color={colors.dim} bold>
|
|
27
|
-
Agent Output
|
|
27
|
+
── Agent Output ──
|
|
28
28
|
</Text>
|
|
29
29
|
</Box>
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{line}
|
|
36
|
-
</Text>
|
|
37
|
-
))
|
|
38
|
-
) : (
|
|
39
|
-
<Text color={colors.dim} dimColor>
|
|
40
|
-
Waiting for agent output...
|
|
30
|
+
<Box flexDirection="column" paddingX={1}>
|
|
31
|
+
{displayLines.length > 0 ? (
|
|
32
|
+
displayLines.slice(0, clampedMaxLines).map((line, i) => (
|
|
33
|
+
<Text key={i} color={colors.dim} wrap="truncate">
|
|
34
|
+
{line}
|
|
41
35
|
</Text>
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
))
|
|
37
|
+
) : (
|
|
38
|
+
<Text color={colors.dim} dimColor>
|
|
39
|
+
Waiting for agent output...
|
|
40
|
+
</Text>
|
|
41
|
+
)}
|
|
42
|
+
</Box>
|
|
45
43
|
</Box>
|
|
46
44
|
);
|
|
47
45
|
}
|
|
@@ -16,9 +16,8 @@ interface HeaderProps {
|
|
|
16
16
|
export function Header({ agent }: HeaderProps): React.ReactElement {
|
|
17
17
|
return (
|
|
18
18
|
<Box
|
|
19
|
-
borderStyle="single"
|
|
20
|
-
borderColor={colors.primary}
|
|
21
19
|
paddingX={1}
|
|
20
|
+
paddingY={1}
|
|
22
21
|
flexDirection="row"
|
|
23
22
|
justifyContent="space-between"
|
|
24
23
|
>
|
|
@@ -37,43 +37,47 @@ export function StoryGrid({
|
|
|
37
37
|
rows.push(row);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
//
|
|
41
|
-
// When constrained, keep the current story visible by windowing around its row.
|
|
42
|
-
const totalRows = rows.length;
|
|
43
|
-
const clampedMaxRows = maxRows === undefined ? undefined : Math.max(0, maxRows);
|
|
44
|
-
|
|
40
|
+
// Window around current story to show context
|
|
45
41
|
let visibleRows = rows;
|
|
46
42
|
let startRow = 0;
|
|
43
|
+
let endRow = rows.length;
|
|
47
44
|
|
|
48
|
-
if (
|
|
45
|
+
if (maxRows && rows.length > maxRows) {
|
|
46
|
+
// Find the row containing the current story
|
|
49
47
|
const currentRowIdx = currentStoryId
|
|
50
|
-
? rows.findIndex((
|
|
48
|
+
? rows.findIndex((row) => row.some((story) => story.id === currentStoryId))
|
|
51
49
|
: -1;
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
if (currentRowIdx >= 0) {
|
|
52
|
+
// Center the window around the current story
|
|
53
|
+
const half = Math.floor(maxRows / 2);
|
|
54
|
+
startRow = Math.max(0, currentRowIdx - half);
|
|
55
|
+
|
|
56
|
+
// Adjust if window goes past the end
|
|
57
|
+
if (startRow + maxRows > rows.length) {
|
|
58
|
+
startRow = Math.max(0, rows.length - maxRows);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
endRow = Math.min(rows.length, startRow + maxRows);
|
|
62
|
+
visibleRows = rows.slice(startRow, endRow);
|
|
63
|
+
} else {
|
|
64
|
+
// No current story, just show first N rows
|
|
65
|
+
visibleRows = rows.slice(0, maxRows);
|
|
66
|
+
endRow = Math.min(rows.length, maxRows);
|
|
59
67
|
}
|
|
60
|
-
|
|
61
|
-
visibleRows = rows.slice(startRow, startRow + windowSize);
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
return (
|
|
65
|
-
<Box flexDirection="column"
|
|
66
|
-
<Box paddingX={1}
|
|
71
|
+
<Box flexDirection="column" paddingY={1}>
|
|
72
|
+
<Box paddingX={1}>
|
|
67
73
|
<Text color={colors.dim} bold>
|
|
68
|
-
{visibleRows.length <
|
|
69
|
-
?
|
|
70
|
-
:
|
|
74
|
+
{visibleRows.length < rows.length
|
|
75
|
+
? `── Stories (rows ${startRow + 1}-${endRow} of ${rows.length}, ${stories.length} total) ──`
|
|
76
|
+
: `── Stories (${stories.length}) ──`}
|
|
71
77
|
</Text>
|
|
72
78
|
</Box>
|
|
73
|
-
<Box flexDirection="column" paddingX={1}
|
|
74
|
-
{visibleRows.map((row,
|
|
75
|
-
const rowIdx = startRow + visibleRowIdx;
|
|
76
|
-
return (
|
|
79
|
+
<Box flexDirection="column" paddingX={1}>
|
|
80
|
+
{visibleRows.map((row, rowIdx) => (
|
|
77
81
|
<Box key={rowIdx} flexDirection="row">
|
|
78
82
|
{row.map((story, colIdx) => {
|
|
79
83
|
const isCurrent = story.id === currentStoryId;
|
|
@@ -88,29 +92,60 @@ export function StoryGrid({
|
|
|
88
92
|
? colors.success
|
|
89
93
|
: colors.dim;
|
|
90
94
|
|
|
95
|
+
const priorityColor =
|
|
96
|
+
story.priority <= 2 ? colors.error : story.priority <= 5 ? colors.warning : colors.dim;
|
|
97
|
+
|
|
91
98
|
return (
|
|
92
99
|
<Box key={colIdx} width="50%">
|
|
100
|
+
{/* Status symbol */}
|
|
93
101
|
<Text color={symbolColor}>{symbol} </Text>
|
|
102
|
+
|
|
103
|
+
{/* Story ID */}
|
|
94
104
|
<Text
|
|
95
105
|
color={story.passes ? colors.success : isCurrent ? colors.warning : undefined}
|
|
96
106
|
dimColor={story.passes}
|
|
97
107
|
>
|
|
98
|
-
{story.id.padEnd(
|
|
108
|
+
{story.id.padEnd(9)}
|
|
99
109
|
</Text>
|
|
110
|
+
|
|
111
|
+
{/* Priority badge */}
|
|
112
|
+
<Text color={priorityColor} bold={story.priority <= 3}>
|
|
113
|
+
P{story.priority}{" "}
|
|
114
|
+
</Text>
|
|
115
|
+
|
|
116
|
+
{/* Title (longer) */}
|
|
100
117
|
<Text
|
|
101
|
-
color={colors.dim}
|
|
118
|
+
color={story.passes ? colors.dim : undefined}
|
|
102
119
|
dimColor={story.passes}
|
|
103
120
|
strikethrough={story.passes}
|
|
104
121
|
wrap="truncate"
|
|
105
122
|
>
|
|
106
|
-
{story.title.substring(0,
|
|
123
|
+
{story.title.substring(0, 40)}
|
|
124
|
+
</Text>
|
|
125
|
+
|
|
126
|
+
{/* Acceptance criteria count */}
|
|
127
|
+
<Text color={colors.dim} dimColor>
|
|
128
|
+
{" "}
|
|
129
|
+
({story.criteriaCount}c)
|
|
107
130
|
</Text>
|
|
131
|
+
|
|
132
|
+
{/* Research indicator */}
|
|
133
|
+
{story.research && (
|
|
134
|
+
<Text color={colors.dim}> 🔍</Text>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{/* Phase badge */}
|
|
138
|
+
{story.phase && (
|
|
139
|
+
<Text color={colors.dim} dimColor>
|
|
140
|
+
{" "}
|
|
141
|
+
[{story.phase}]
|
|
142
|
+
</Text>
|
|
143
|
+
)}
|
|
108
144
|
</Box>
|
|
109
145
|
);
|
|
110
146
|
})}
|
|
111
147
|
</Box>
|
|
112
|
-
|
|
113
|
-
})}
|
|
148
|
+
))}
|
|
114
149
|
</Box>
|
|
115
150
|
</Box>
|
|
116
151
|
);
|