@ohzw/worktree-command-tui 0.1.1 → 0.1.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.
- package/README.md +91 -26
- package/dist/app.d.ts +1 -1
- package/dist/app.js +242 -114
- package/dist/components/ActionPanel.d.ts +4 -2
- package/dist/components/ActionPanel.js +87 -135
- package/dist/components/ContextBar.d.ts +4 -1
- package/dist/components/ContextBar.js +29 -3
- package/dist/components/Header.js +5 -1
- package/dist/components/HelpWindow.d.ts +7 -0
- package/dist/components/HelpWindow.js +29 -0
- package/dist/components/LogPanel.d.ts +10 -3
- package/dist/components/LogPanel.js +240 -33
- package/dist/components/WorktreeList.js +20 -40
- package/dist/core/command-runner.d.ts +11 -0
- package/dist/core/command-runner.js +59 -7
- package/dist/core/config-lifecycle.d.ts +25 -0
- package/dist/core/config-lifecycle.js +160 -0
- package/dist/core/config.d.ts +2 -3
- package/dist/core/config.js +0 -48
- package/dist/core/git-metadata.d.ts +25 -0
- package/dist/core/git-metadata.js +84 -0
- package/dist/core/git-worktrees.d.ts +2 -1
- package/dist/core/git-worktrees.js +30 -11
- package/dist/core/github-metadata.d.ts +21 -0
- package/dist/core/github-metadata.js +153 -0
- package/dist/core/init.d.ts +3 -2
- package/dist/core/init.js +9 -57
- package/dist/core/log-reader.d.ts +7 -0
- package/dist/core/log-reader.js +59 -0
- package/dist/core/posix-process.d.ts +2 -2
- package/dist/core/posix-process.js +19 -4
- package/dist/core/process-control.d.ts +2 -2
- package/dist/core/process-control.js +5 -2
- package/dist/core/runtime-state.d.ts +42 -0
- package/dist/core/runtime-state.js +125 -0
- package/dist/core/runtime.d.ts +19 -39
- package/dist/core/runtime.js +112 -216
- package/dist/core/session-store.js +22 -7
- package/dist/core/tui-interaction.d.ts +31 -0
- package/dist/core/tui-interaction.js +59 -0
- package/dist/core/worktree-projection.d.ts +76 -0
- package/dist/core/worktree-projection.js +132 -0
- package/dist/main.js +6 -5
- package/dist/terminal/viewport.d.ts +15 -0
- package/dist/terminal/viewport.js +49 -0
- package/package.json +1 -1
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { getOrderedNonActiveTags, projectAction, projectNote, projectPullRequest, projectUpstream, projectWorkingTree, sanitizeInlineText, } from '../core/worktree-projection.js';
|
|
4
|
+
import { getScrollbarThumbRows, sliceListViewport } from '../terminal/viewport.js';
|
|
5
|
+
export function getActionVariant(selectedRow, activePath) {
|
|
6
|
+
return projectAction(selectedRow, activePath).severity;
|
|
7
|
+
}
|
|
8
|
+
function formatUpstream(selectedRow) {
|
|
9
|
+
const upstream = projectUpstream(selectedRow);
|
|
10
|
+
if (upstream.kind === 'unavailable') {
|
|
11
|
+
return 'unavailable';
|
|
12
|
+
}
|
|
13
|
+
if (upstream.kind === 'none') {
|
|
14
|
+
return '-';
|
|
15
|
+
}
|
|
16
|
+
return `${upstream.branch} (↑${upstream.ahead} ↓${upstream.behind})`;
|
|
17
|
+
}
|
|
3
18
|
function getTagColor(tag) {
|
|
4
19
|
if (tag === 'active') {
|
|
5
20
|
return 'green';
|
|
@@ -15,145 +30,89 @@ function getTagColor(tag) {
|
|
|
15
30
|
}
|
|
16
31
|
return 'magenta';
|
|
17
32
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return 'error';
|
|
21
|
-
}
|
|
22
|
-
if (selectedRow.path === activePath) {
|
|
23
|
-
return 'success';
|
|
24
|
-
}
|
|
25
|
-
if ((selectedRow.workingTree?.conflicts ?? 0) > 0) {
|
|
26
|
-
return 'error';
|
|
27
|
-
}
|
|
28
|
-
if ((selectedRow.workingTree?.staged ?? 0) > 0
|
|
29
|
-
|| (selectedRow.workingTree?.unstaged ?? 0) > 0
|
|
30
|
-
|| (selectedRow.workingTree?.untracked ?? 0) > 0) {
|
|
31
|
-
return 'info';
|
|
32
|
-
}
|
|
33
|
-
return 'info';
|
|
34
|
-
}
|
|
35
|
-
function getNoteVariant(selectedRow) {
|
|
36
|
-
if (selectedRow.invalidReason || selectedRow.tags.includes('external')) {
|
|
37
|
-
return selectedRow.invalidReason ? 'error' : 'info';
|
|
38
|
-
}
|
|
39
|
-
if ((selectedRow.workingTree?.conflicts ?? 0) > 0) {
|
|
40
|
-
return 'error';
|
|
41
|
-
}
|
|
42
|
-
if ((selectedRow.workingTree?.staged ?? 0) > 0
|
|
43
|
-
|| (selectedRow.workingTree?.unstaged ?? 0) > 0
|
|
44
|
-
|| (selectedRow.workingTree?.untracked ?? 0) > 0) {
|
|
45
|
-
return 'info';
|
|
46
|
-
}
|
|
47
|
-
return 'info';
|
|
33
|
+
function getTagLabel(tag) {
|
|
34
|
+
return tag === 'main' ? 'root' : tag;
|
|
48
35
|
}
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.replace(/[\u0000-\u001f\u007f-\u009f]/g, '')
|
|
53
|
-
.replace(/\p{Cf}/gu, '')
|
|
54
|
-
.replace(/\s+/g, ' ')
|
|
55
|
-
.trim();
|
|
56
|
-
}
|
|
57
|
-
function formatUpstream(selectedRow) {
|
|
58
|
-
if (selectedRow.upstreamUnavailable) {
|
|
59
|
-
return 'unavailable';
|
|
36
|
+
function getWorkingTreePartLabel(kind) {
|
|
37
|
+
if (kind === 'staged') {
|
|
38
|
+
return 'index';
|
|
60
39
|
}
|
|
61
|
-
if (
|
|
62
|
-
return '
|
|
40
|
+
if (kind === 'unstaged') {
|
|
41
|
+
return 'worktree';
|
|
63
42
|
}
|
|
64
|
-
return
|
|
43
|
+
return kind;
|
|
65
44
|
}
|
|
66
45
|
function formatWorkingTree(selectedRow) {
|
|
67
|
-
|
|
46
|
+
const workingTree = projectWorkingTree(selectedRow);
|
|
47
|
+
if (workingTree.kind === 'unavailable') {
|
|
68
48
|
return 'unavailable';
|
|
69
49
|
}
|
|
70
|
-
|
|
71
|
-
if (staged === 0 && unstaged === 0 && untracked === 0 && conflicts === 0) {
|
|
50
|
+
if (workingTree.kind === 'clean') {
|
|
72
51
|
return 'clean';
|
|
73
52
|
}
|
|
74
|
-
const parts =
|
|
75
|
-
if (staged > 0) {
|
|
76
|
-
parts.push(`index ${staged}`);
|
|
77
|
-
}
|
|
78
|
-
if (unstaged > 0) {
|
|
79
|
-
parts.push(`worktree ${unstaged}`);
|
|
80
|
-
}
|
|
81
|
-
if (untracked > 0) {
|
|
82
|
-
parts.push(`untracked ${untracked}`);
|
|
83
|
-
}
|
|
84
|
-
if (conflicts > 0) {
|
|
85
|
-
parts.push(`conflicts ${conflicts}`);
|
|
86
|
-
}
|
|
53
|
+
const parts = workingTree.parts.map(part => `${getWorkingTreePartLabel(part.kind)} ${part.count}`);
|
|
87
54
|
return `dirty (${parts.join(' · ')})`;
|
|
88
55
|
}
|
|
56
|
+
function formatUtcDateTime(timestampMs) {
|
|
57
|
+
if (timestampMs === undefined || !Number.isFinite(timestampMs)) {
|
|
58
|
+
return '-';
|
|
59
|
+
}
|
|
60
|
+
const iso = new Date(timestampMs).toISOString();
|
|
61
|
+
return `${iso.slice(0, 10)} ${iso.slice(11, 19)} UTC`;
|
|
62
|
+
}
|
|
89
63
|
function formatPullRequest(selectedRow) {
|
|
90
|
-
|
|
64
|
+
const pullRequest = projectPullRequest(selectedRow);
|
|
65
|
+
if (pullRequest.kind === 'none') {
|
|
91
66
|
return 'none';
|
|
92
67
|
}
|
|
93
|
-
if (
|
|
68
|
+
if (pullRequest.kind === 'unavailable') {
|
|
94
69
|
return 'unavailable';
|
|
95
70
|
}
|
|
96
|
-
const draft =
|
|
97
|
-
|
|
71
|
+
const draft = pullRequest.isDraft ? 'draft/' : '';
|
|
72
|
+
const stateText = pullRequest.state.toLowerCase();
|
|
73
|
+
return `#${pullRequest.number} ${draft}${stateText} → ${pullRequest.baseBranch}`;
|
|
98
74
|
}
|
|
99
|
-
function
|
|
100
|
-
if (
|
|
101
|
-
return 'Last PR';
|
|
102
|
-
}
|
|
103
|
-
return 'PR';
|
|
104
|
-
}
|
|
105
|
-
function getPullRequestTitleLabel(selectedRow) {
|
|
106
|
-
if (selectedRow.pullRequest?.kind === 'found' && selectedRow.pullRequest.state !== 'OPEN') {
|
|
107
|
-
return 'Last PR Title';
|
|
108
|
-
}
|
|
109
|
-
return 'PR Title';
|
|
110
|
-
}
|
|
111
|
-
export function getPullRequestColor(selectedRow) {
|
|
112
|
-
if (!selectedRow.pullRequest || selectedRow.pullRequest.kind === 'none') {
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
115
|
-
if (selectedRow.pullRequest.kind === 'unavailable') {
|
|
75
|
+
function getPullRequestColorFromProjection(pullRequest) {
|
|
76
|
+
if (pullRequest.kind === 'unavailable') {
|
|
116
77
|
return 'red';
|
|
117
78
|
}
|
|
118
|
-
if (
|
|
79
|
+
if (pullRequest.kind !== 'found' || pullRequest.isHistorical) {
|
|
119
80
|
return undefined;
|
|
120
81
|
}
|
|
121
|
-
return
|
|
82
|
+
return pullRequest.isDraft ? 'yellow' : 'green';
|
|
83
|
+
}
|
|
84
|
+
export function getPullRequestColor(selectedRow) {
|
|
85
|
+
return getPullRequestColorFromProjection(projectPullRequest(selectedRow));
|
|
86
|
+
}
|
|
87
|
+
function getPullRequestLabel(pullRequest) {
|
|
88
|
+
return pullRequest.kind === 'found' && pullRequest.isHistorical ? 'Last PR' : 'PR';
|
|
89
|
+
}
|
|
90
|
+
function getPullRequestTitleLabel(pullRequest) {
|
|
91
|
+
return pullRequest.kind === 'found' && pullRequest.isHistorical ? 'Last PR Title' : 'PR Title';
|
|
92
|
+
}
|
|
93
|
+
function getPullRequestDimColor(pullRequest) {
|
|
94
|
+
return pullRequest.kind === 'found' && pullRequest.isHistorical;
|
|
122
95
|
}
|
|
123
96
|
function getActionMessage(selectedRow, activePath) {
|
|
124
|
-
|
|
97
|
+
const action = projectAction(selectedRow, activePath);
|
|
98
|
+
if (action.kind === 'blocked') {
|
|
125
99
|
return 'Cannot start this worktree.';
|
|
126
100
|
}
|
|
127
|
-
if (
|
|
101
|
+
if (action.kind === 'active') {
|
|
128
102
|
return 'Already active. Press s to stop the current session.';
|
|
129
103
|
}
|
|
130
104
|
return 'Press Enter to start here and switch the active session.';
|
|
131
105
|
}
|
|
132
106
|
function getNotes(selectedRow) {
|
|
133
|
-
|
|
134
|
-
|
|
107
|
+
const note = projectNote(selectedRow);
|
|
108
|
+
if (note.kind === 'invalid') {
|
|
109
|
+
return note.invalidReason;
|
|
135
110
|
}
|
|
136
|
-
if (
|
|
111
|
+
if (note.kind === 'external') {
|
|
137
112
|
return 'External worktree managed outside the main checkout path.';
|
|
138
113
|
}
|
|
139
114
|
return 'Ready to launch with the configured command in this worktree.';
|
|
140
115
|
}
|
|
141
|
-
function getOrderedTags(tags) {
|
|
142
|
-
const tagPriority = {
|
|
143
|
-
active: 0,
|
|
144
|
-
main: 1,
|
|
145
|
-
external: 2,
|
|
146
|
-
invalid: 3,
|
|
147
|
-
};
|
|
148
|
-
return [...tags].sort((a, b) => {
|
|
149
|
-
const aPriority = tagPriority[a] ?? 10;
|
|
150
|
-
const bPriority = tagPriority[b] ?? 10;
|
|
151
|
-
if (aPriority === bPriority) {
|
|
152
|
-
return a.localeCompare(b);
|
|
153
|
-
}
|
|
154
|
-
return aPriority - bPriority;
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
116
|
function getVariantColor(variant) {
|
|
158
117
|
if (variant === 'success') {
|
|
159
118
|
return 'green';
|
|
@@ -178,59 +137,52 @@ function section(label) {
|
|
|
178
137
|
function divider() {
|
|
179
138
|
return { text: ' ', dimColor: true };
|
|
180
139
|
}
|
|
181
|
-
function getPanelLines(selectedRow, activePath, compactDetails) {
|
|
140
|
+
function getPanelLines(selectedRow, activePath, setupAvailable, compactDetails) {
|
|
182
141
|
if (!selectedRow) {
|
|
183
142
|
return [{ text: 'No worktrees found.', dimColor: true }];
|
|
184
143
|
}
|
|
185
144
|
const lines = [section('Identity')];
|
|
186
145
|
const showFullPath = !compactDetails && selectedRow.shortPath !== selectedRow.path;
|
|
187
146
|
const showTags = !compactDetails;
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
: null;
|
|
147
|
+
const pullRequest = projectPullRequest(selectedRow);
|
|
148
|
+
const pullRequestTitle = pullRequest.kind === 'found' && !compactDetails ? pullRequest.title : null;
|
|
191
149
|
lines.push({ text: `Branch: ${sanitizeInlineText(selectedRow.branch)}`, bold: true }, { text: `Path: ${sanitizeInlineText(selectedRow.shortPath)}` });
|
|
192
150
|
if (showFullPath) {
|
|
193
151
|
lines.push({ text: `Full Path: ${sanitizeInlineText(selectedRow.path)}` });
|
|
194
152
|
}
|
|
195
153
|
lines.push({ text: `HEAD: ${selectedRow.headSha || '-'}` });
|
|
154
|
+
lines.push({ text: `Branch Created: ${formatUtcDateTime(selectedRow.branchCreatedAtMs)}` });
|
|
196
155
|
if (showTags) {
|
|
197
|
-
for (const tag of
|
|
198
|
-
lines.push({ text: tag.toUpperCase(), color: getTagColor(tag) });
|
|
156
|
+
for (const { tag } of getOrderedNonActiveTags(selectedRow.tags)) {
|
|
157
|
+
lines.push({ text: getTagLabel(tag).toUpperCase(), color: getTagColor(tag) });
|
|
199
158
|
}
|
|
200
159
|
}
|
|
201
160
|
lines.push(divider(), section('Git / PR'), { text: `Upstream: ${formatUpstream(selectedRow)}` }, { text: `Status: ${formatWorkingTree(selectedRow)}` }, {
|
|
202
|
-
text: `${getPullRequestLabel(
|
|
203
|
-
color:
|
|
204
|
-
dimColor:
|
|
161
|
+
text: `${getPullRequestLabel(pullRequest)}: ${formatPullRequest(selectedRow)}`,
|
|
162
|
+
color: getPullRequestColorFromProjection(pullRequest),
|
|
163
|
+
dimColor: getPullRequestDimColor(pullRequest),
|
|
205
164
|
});
|
|
206
165
|
if (pullRequestTitle) {
|
|
207
|
-
lines.push({ text: `${getPullRequestTitleLabel(
|
|
166
|
+
lines.push({ text: `${getPullRequestTitleLabel(pullRequest)}: ${pullRequestTitle}`, dimColor: getPullRequestDimColor(pullRequest) });
|
|
208
167
|
}
|
|
209
168
|
const actionVariant = getActionVariant(selectedRow, activePath);
|
|
210
|
-
const noteVariant =
|
|
211
|
-
lines.push(divider(), section('Action'), { text: `${getVariantIcon(actionVariant)} ${getActionMessage(selectedRow, activePath)}`, color: getVariantColor(actionVariant) }
|
|
169
|
+
const noteVariant = projectNote(selectedRow).severity;
|
|
170
|
+
lines.push(divider(), section('Action'), { text: `${getVariantIcon(actionVariant)} ${getActionMessage(selectedRow, activePath)}`, color: getVariantColor(actionVariant) });
|
|
171
|
+
if (setupAvailable) {
|
|
172
|
+
lines.push({ text: 'ℹ Press i to run setup in this worktree.', color: 'blue' });
|
|
173
|
+
}
|
|
174
|
+
lines.push(section('Notes'), { text: `${getVariantIcon(noteVariant)} ${getNotes(selectedRow)}`, color: getVariantColor(noteVariant) });
|
|
212
175
|
return lines;
|
|
213
176
|
}
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
const
|
|
221
|
-
return new Set(Array.from({ length: thumbSize }, (_, index) => thumbStart + index));
|
|
222
|
-
}
|
|
223
|
-
export function ActionPanel({ selectedRow, activePath, stacked, width, height, compactDetails, scrollOffset = 0, }) {
|
|
224
|
-
const lines = getPanelLines(selectedRow, activePath, compactDetails ?? false);
|
|
225
|
-
const contentViewportHeight = height === undefined ? undefined : Math.max(1, height - 3);
|
|
226
|
-
const maxScrollOffset = contentViewportHeight === undefined ? 0 : Math.max(0, lines.length - contentViewportHeight);
|
|
227
|
-
const effectiveScrollOffset = Math.min(Math.max(scrollOffset, 0), maxScrollOffset);
|
|
228
|
-
const visibleLines = contentViewportHeight === undefined
|
|
229
|
-
? lines
|
|
230
|
-
: lines.slice(effectiveScrollOffset, effectiveScrollOffset + contentViewportHeight);
|
|
231
|
-
const showScrollbar = contentViewportHeight !== undefined && lines.length > contentViewportHeight;
|
|
177
|
+
export function ActionPanel({ selectedRow, activePath, setupAvailable, stacked, width, height, compactDetails, scrollOffset = 0, }) {
|
|
178
|
+
const lines = getPanelLines(selectedRow, activePath, setupAvailable, compactDetails ?? false);
|
|
179
|
+
const viewport = height === undefined ? undefined : sliceListViewport(lines, height - 3, scrollOffset);
|
|
180
|
+
const contentViewportHeight = viewport?.viewportHeight;
|
|
181
|
+
const effectiveScrollOffset = viewport?.scrollOffset ?? 0;
|
|
182
|
+
const visibleLines = viewport?.visibleItems ?? lines;
|
|
183
|
+
const showScrollbar = viewport !== undefined && lines.length > viewport.viewportHeight;
|
|
232
184
|
const scrollbarThumbRows = showScrollbar
|
|
233
|
-
? getScrollbarThumbRows(lines.length,
|
|
185
|
+
? getScrollbarThumbRows(lines.length, viewport.viewportHeight, viewport.scrollOffset)
|
|
234
186
|
: new Set();
|
|
235
187
|
return (_jsxs(Box, { width: width, height: height, flexGrow: stacked ? 0 : 1, flexShrink: 1, borderStyle: "round", borderColor: "magenta", flexDirection: "column", paddingX: 1, overflow: "hidden", children: [_jsx(Text, { bold: true, color: "magenta", wrap: "truncate-end", children: "Selection / Action" }), _jsx(Box, { height: contentViewportHeight, flexDirection: "column", overflow: "hidden", children: visibleLines.map((line, index) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, children: _jsx(Text, { color: line.color, dimColor: line.dimColor, bold: line.bold, wrap: "truncate-end", children: line.text }) }), showScrollbar ? (_jsx(Text, { color: scrollbarThumbRows.has(index) ? 'magenta' : 'gray', dimColor: !scrollbarThumbRows.has(index), children: scrollbarThumbRows.has(index) ? '█' : '│' })) : null] }, `${effectiveScrollOffset + index}-${line.text}`))) })] }));
|
|
236
188
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { AppStatus } from '../core/runtime.js';
|
|
3
|
-
export declare function ContextBar({ status }: {
|
|
3
|
+
export declare function ContextBar({ status, setupAvailable, editorAvailable, confirmationOpen, }: {
|
|
4
4
|
status: AppStatus;
|
|
5
|
+
setupAvailable: boolean;
|
|
6
|
+
editorAvailable: boolean;
|
|
7
|
+
confirmationOpen: boolean;
|
|
5
8
|
}): React.JSX.Element;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
2
3
|
import { Box, Text } from 'ink';
|
|
3
4
|
import { Spinner } from '@inkjs/ui';
|
|
5
|
+
import { sanitizeInlineText } from '../core/worktree-projection.js';
|
|
4
6
|
const KIND_TO_ICON = {
|
|
5
7
|
idle: 'ℹ',
|
|
6
8
|
starting: '⚠',
|
|
9
|
+
'setting-up': '⚠',
|
|
7
10
|
running: '✓',
|
|
8
11
|
stopping: '⚠',
|
|
9
12
|
error: '✘',
|
|
@@ -11,11 +14,34 @@ const KIND_TO_ICON = {
|
|
|
11
14
|
const KIND_TO_COLOR = {
|
|
12
15
|
idle: 'blue',
|
|
13
16
|
starting: 'yellow',
|
|
17
|
+
'setting-up': 'yellow',
|
|
14
18
|
running: 'green',
|
|
15
19
|
stopping: 'yellow',
|
|
16
20
|
error: 'red',
|
|
17
21
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
function buildKeyHints(setupAvailable, editorAvailable, confirmationOpen) {
|
|
23
|
+
if (confirmationOpen) {
|
|
24
|
+
return [
|
|
25
|
+
{ binding: 'd/y', label: 'Confirm' },
|
|
26
|
+
{ binding: 'Esc/n/q', label: 'Cancel' },
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
const hints = [
|
|
30
|
+
{ binding: '↑↓/jk', label: 'Move' },
|
|
31
|
+
{ binding: 'Enter', label: 'Switch' },
|
|
32
|
+
];
|
|
33
|
+
if (setupAvailable) {
|
|
34
|
+
hints.push({ binding: 'i', label: 'Setup' });
|
|
35
|
+
}
|
|
36
|
+
if (editorAvailable) {
|
|
37
|
+
hints.push({ binding: 'e', label: 'Editor' });
|
|
38
|
+
}
|
|
39
|
+
hints.push({ binding: 'o', label: 'Open PR' }, { binding: 'd', label: 'Delete' }, { binding: 'L', label: 'Logs' }, { binding: 's', label: 'Stop' }, { binding: 'r', label: 'Refresh' }, { binding: '?', label: 'Help' }, { binding: 'q', label: 'Quit' });
|
|
40
|
+
return hints;
|
|
41
|
+
}
|
|
42
|
+
export function ContextBar({ status, setupAvailable, editorAvailable, confirmationOpen, }) {
|
|
43
|
+
const isBusy = status.kind === 'setting-up' || status.kind === 'starting' || status.kind === 'stopping';
|
|
44
|
+
const keyHints = buildKeyHints(setupAvailable, editorAvailable, confirmationOpen);
|
|
45
|
+
const statusMessage = sanitizeInlineText(status.message);
|
|
46
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: KIND_TO_COLOR[status.kind], flexDirection: "column", paddingX: 1, children: [isBusy ? (_jsx(Spinner, { label: `Status: ${status.kind} — ${statusMessage}` })) : (_jsxs(Text, { color: KIND_TO_COLOR[status.kind], wrap: "truncate-end", children: [KIND_TO_ICON[status.kind], " Status: ", status.kind, " \u2014 ", statusMessage] })), _jsx(Text, { wrap: "truncate-end", children: keyHints.map((hint, hintIndex) => (_jsxs(React.Fragment, { children: [hintIndex === 0 ? null : _jsx(Text, { dimColor: true, children: " | " }), _jsx(Text, { color: "white", children: hint.binding }), _jsxs(Text, { dimColor: true, children: [" ", hint.label] })] }, hint.binding))) })] }));
|
|
21
47
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { sanitizeInlineText } from '../core/worktree-projection.js';
|
|
3
4
|
export function Header({ repoName, namespace, activeBranch, }) {
|
|
4
|
-
|
|
5
|
+
const safeRepoName = sanitizeInlineText(repoName);
|
|
6
|
+
const safeNamespace = sanitizeInlineText(namespace);
|
|
7
|
+
const safeActiveBranch = activeBranch === null ? '-' : sanitizeInlineText(activeBranch);
|
|
8
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "blue", flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "blue", wrap: "truncate-end", children: ["Worktree Command TUI \u00B7 Repo: ", safeRepoName] }), _jsxs(Text, { color: "green", wrap: "truncate-end", children: ["Active: ", safeActiveBranch] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["Namespace: ", safeNamespace] })] }));
|
|
5
9
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
function buildHelpLines(setupAvailable, editorAvailable) {
|
|
4
|
+
const lines = [
|
|
5
|
+
{ section: 'Movement', binding: '↑↓/jk', description: 'move selection' },
|
|
6
|
+
{ section: 'Movement', binding: 'g/G', description: 'first/last' },
|
|
7
|
+
{ section: 'Scroll', binding: 'Wheel', description: 'pane under cursor' },
|
|
8
|
+
{ section: 'Scroll', binding: 'PageUp/PageDn', description: 'selection page' },
|
|
9
|
+
{ section: 'Logs', binding: 'L', description: 'full-screen logs' },
|
|
10
|
+
{ section: 'Logs', binding: '[/]', description: 'scroll log' },
|
|
11
|
+
{ section: 'Actions', binding: 'Enter', description: 'start/switch worktree' },
|
|
12
|
+
];
|
|
13
|
+
if (setupAvailable) {
|
|
14
|
+
lines.push({ section: 'Actions', binding: 'i', description: 'setup selected worktree' });
|
|
15
|
+
}
|
|
16
|
+
if (editorAvailable) {
|
|
17
|
+
lines.push({ section: 'Actions', binding: 'e', description: 'open selected worktree in editor' });
|
|
18
|
+
}
|
|
19
|
+
lines.push({ section: 'Actions', binding: 'o', description: 'open selected pull request' }, { section: 'Actions', binding: 'd', description: 'arm worktree deletion' }, { section: 'Actions', binding: 's', description: 'stop active session' }, { section: 'Actions', binding: 'r', description: 'refresh' }, { section: 'Actions', binding: 'q', description: 'quit' }, { section: 'Help', binding: 'Esc/q/?', description: 'close help' }, { section: 'Help', binding: 'd/y', description: 'confirm delete after arming' }, { section: 'Help', binding: 'Esc/n/q', description: 'cancel delete confirmation' });
|
|
20
|
+
return lines;
|
|
21
|
+
}
|
|
22
|
+
export function HelpWindow({ setupAvailable, editorAvailable, width, height }) {
|
|
23
|
+
let previousSection = '';
|
|
24
|
+
return (_jsxs(Box, { width: width, height: height, borderStyle: "round", borderColor: "blue", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "blue", wrap: "truncate-end", children: "Keyboard Help" }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: "Primary shortcuts are shown in the status footer. Advanced shortcuts live here." }), buildHelpLines(setupAvailable, editorAvailable).map(line => {
|
|
25
|
+
const section = line.section === previousSection ? '' : line.section;
|
|
26
|
+
previousSection = line.section;
|
|
27
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 10, children: _jsx(Text, { dimColor: true, wrap: "truncate-end", children: section }) }), _jsx(Box, { width: 16, children: _jsx(Text, { color: "white", wrap: "truncate-end", children: line.binding }) }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: line.description })] }, `${line.section}-${line.binding}`));
|
|
28
|
+
})] }));
|
|
29
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { AppLogEntry } from '../core/runtime.js';
|
|
2
|
-
type
|
|
3
|
-
|
|
4
|
-
color?:
|
|
2
|
+
type LogColor = 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray';
|
|
3
|
+
type LineStyle = {
|
|
4
|
+
color?: LogColor;
|
|
5
5
|
dimColor?: boolean;
|
|
6
|
+
bold?: boolean;
|
|
7
|
+
};
|
|
8
|
+
type LineSegment = {
|
|
9
|
+
text: string;
|
|
10
|
+
} & LineStyle;
|
|
11
|
+
type LineSpec = {
|
|
12
|
+
segments: LineSegment[];
|
|
6
13
|
};
|
|
7
14
|
export declare function buildLogLines(logs: AppLogEntry[]): LineSpec[];
|
|
8
15
|
export declare function LogPanel({ logs, width, height, scrollOffset, title, }: {
|