@ohzw/worktree-command-tui 0.1.0 → 0.1.2

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 (49) hide show
  1. package/README.md +5 -0
  2. package/dist/app.d.ts +3 -2
  3. package/dist/app.js +458 -42
  4. package/dist/components/ActionPanel.d.ts +6 -2
  5. package/dist/components/ActionPanel.js +141 -82
  6. package/dist/components/ContextBar.d.ts +4 -1
  7. package/dist/components/ContextBar.js +37 -4
  8. package/dist/components/FloatingLogWindow.d.ts +7 -0
  9. package/dist/components/FloatingLogWindow.js +5 -0
  10. package/dist/components/HelpWindow.d.ts +7 -0
  11. package/dist/components/HelpWindow.js +29 -0
  12. package/dist/components/LogPanel.d.ts +22 -0
  13. package/dist/components/LogPanel.js +260 -0
  14. package/dist/components/WorktreeList.d.ts +3 -1
  15. package/dist/components/WorktreeList.js +25 -30
  16. package/dist/core/command-runner.d.ts +11 -0
  17. package/dist/core/command-runner.js +44 -0
  18. package/dist/core/config-lifecycle.d.ts +25 -0
  19. package/dist/core/config-lifecycle.js +143 -0
  20. package/dist/core/config.d.ts +2 -3
  21. package/dist/core/config.js +0 -48
  22. package/dist/core/git-metadata.d.ts +25 -0
  23. package/dist/core/git-metadata.js +84 -0
  24. package/dist/core/git-worktrees.d.ts +2 -1
  25. package/dist/core/git-worktrees.js +30 -11
  26. package/dist/core/github-metadata.d.ts +14 -0
  27. package/dist/core/github-metadata.js +137 -0
  28. package/dist/core/init.d.ts +3 -2
  29. package/dist/core/init.js +9 -57
  30. package/dist/core/log-reader.d.ts +7 -0
  31. package/dist/core/log-reader.js +43 -0
  32. package/dist/core/runtime-state.d.ts +42 -0
  33. package/dist/core/runtime-state.js +125 -0
  34. package/dist/core/runtime.d.ts +20 -33
  35. package/dist/core/runtime.js +116 -173
  36. package/dist/core/tui-interaction.d.ts +31 -0
  37. package/dist/core/tui-interaction.js +59 -0
  38. package/dist/core/worktree-projection.d.ts +76 -0
  39. package/dist/core/worktree-projection.js +124 -0
  40. package/dist/main.js +24 -2
  41. package/dist/render-options.d.ts +1 -0
  42. package/dist/render-options.js +1 -0
  43. package/dist/repro.d.ts +1 -0
  44. package/dist/repro.js +13 -0
  45. package/dist/terminal/viewport.d.ts +15 -0
  46. package/dist/terminal/viewport.js +49 -0
  47. package/dist/ui-theme.d.ts +3 -0
  48. package/dist/ui-theme.js +38 -0
  49. package/package.json +2 -1
@@ -1,129 +1,188 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- function formatTags(tags) {
4
- return tags.length === 0 ? '-' : tags.join(' · ');
5
- }
6
- function sanitizeInlineText(value) {
7
- return value
8
- .replace(/[\r\n\t\u2028\u2029]+/g, ' ')
9
- .replace(/[\u0000-\u001f\u007f-\u009f]/g, '')
10
- .replace(/\p{Cf}/gu, '')
11
- .replace(/\s+/g, ' ')
12
- .trim();
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;
13
7
  }
14
8
  function formatUpstream(selectedRow) {
15
- if (selectedRow.upstreamUnavailable) {
9
+ const upstream = projectUpstream(selectedRow);
10
+ if (upstream.kind === 'unavailable') {
16
11
  return 'unavailable';
17
12
  }
18
- if (!selectedRow.upstream) {
13
+ if (upstream.kind === 'none') {
19
14
  return '-';
20
15
  }
21
- return `${sanitizeInlineText(selectedRow.upstream.branch)} (↑${selectedRow.upstream.ahead} ↓${selectedRow.upstream.behind})`;
16
+ return `${upstream.branch} (↑${upstream.ahead} ↓${upstream.behind})`;
22
17
  }
23
- function formatWorkingTree(selectedRow) {
24
- if (!selectedRow.workingTree) {
25
- return 'unavailable';
18
+ function getTagColor(tag) {
19
+ if (tag === 'active') {
20
+ return 'green';
26
21
  }
27
- const { staged, unstaged, untracked, conflicts } = selectedRow.workingTree;
28
- if (staged === 0 && unstaged === 0 && untracked === 0 && conflicts === 0) {
29
- return 'clean';
22
+ if (tag === 'external') {
23
+ return 'yellow';
30
24
  }
31
- const parts = [];
32
- if (staged > 0) {
33
- parts.push(`index ${staged}`);
25
+ if (tag === 'main') {
26
+ return 'blue';
34
27
  }
35
- if (unstaged > 0) {
36
- parts.push(`worktree ${unstaged}`);
28
+ if (tag === 'invalid') {
29
+ return 'red';
37
30
  }
38
- if (untracked > 0) {
39
- parts.push(`untracked ${untracked}`);
31
+ return 'magenta';
32
+ }
33
+ function getTagLabel(tag) {
34
+ return tag === 'main' ? 'root' : tag;
35
+ }
36
+ function getWorkingTreePartLabel(kind) {
37
+ if (kind === 'staged') {
38
+ return 'index';
40
39
  }
41
- if (conflicts > 0) {
42
- parts.push(`conflicts ${conflicts}`);
40
+ if (kind === 'unstaged') {
41
+ return 'worktree';
43
42
  }
44
- return `dirty (${parts.join(' · ')})`;
43
+ return kind;
45
44
  }
46
- function formatPullRequest(selectedRow) {
47
- if (!selectedRow.pullRequest || selectedRow.pullRequest.kind === 'none') {
48
- return 'none';
49
- }
50
- if (selectedRow.pullRequest.kind === 'unavailable') {
45
+ function formatWorkingTree(selectedRow) {
46
+ const workingTree = projectWorkingTree(selectedRow);
47
+ if (workingTree.kind === 'unavailable') {
51
48
  return 'unavailable';
52
49
  }
53
- const draft = selectedRow.pullRequest.isDraft ? 'draft/' : '';
54
- return `#${selectedRow.pullRequest.number} ${draft}${selectedRow.pullRequest.state.toLowerCase()} → ${sanitizeInlineText(selectedRow.pullRequest.baseBranch)}`;
55
- }
56
- function getPullRequestLabel(selectedRow) {
57
- if (selectedRow.pullRequest?.kind === 'found' && selectedRow.pullRequest.state !== 'OPEN') {
58
- return 'Last PR';
50
+ if (workingTree.kind === 'clean') {
51
+ return 'clean';
59
52
  }
60
- return 'PR';
53
+ const parts = workingTree.parts.map(part => `${getWorkingTreePartLabel(part.kind)} ${part.count}`);
54
+ return `dirty (${parts.join(' · ')})`;
61
55
  }
62
- function getPullRequestTitleLabel(selectedRow) {
63
- if (selectedRow.pullRequest?.kind === 'found' && selectedRow.pullRequest.state !== 'OPEN') {
64
- return 'Last PR Title';
56
+ function formatUtcDateTime(timestampMs) {
57
+ if (timestampMs === undefined || !Number.isFinite(timestampMs)) {
58
+ return '-';
65
59
  }
66
- return 'PR Title';
60
+ const iso = new Date(timestampMs).toISOString();
61
+ return `${iso.slice(0, 10)} ${iso.slice(11, 19)} UTC`;
67
62
  }
68
- export function getPullRequestColor(selectedRow) {
69
- if (!selectedRow.pullRequest || selectedRow.pullRequest.kind === 'none') {
70
- return undefined;
63
+ function formatPullRequest(selectedRow) {
64
+ const pullRequest = projectPullRequest(selectedRow);
65
+ if (pullRequest.kind === 'none') {
66
+ return 'none';
67
+ }
68
+ if (pullRequest.kind === 'unavailable') {
69
+ return 'unavailable';
71
70
  }
72
- if (selectedRow.pullRequest.kind === 'unavailable') {
71
+ const draft = pullRequest.isDraft ? 'draft/' : '';
72
+ const stateText = pullRequest.state.toLowerCase();
73
+ return `#${pullRequest.number} ${draft}${stateText} → ${pullRequest.baseBranch}`;
74
+ }
75
+ function getPullRequestColorFromProjection(pullRequest) {
76
+ if (pullRequest.kind === 'unavailable') {
73
77
  return 'red';
74
78
  }
75
- if (selectedRow.pullRequest.state !== 'OPEN') {
79
+ if (pullRequest.kind !== 'found' || pullRequest.isHistorical) {
76
80
  return undefined;
77
81
  }
78
- return selectedRow.pullRequest.isDraft ? 'yellow' : 'green';
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;
79
95
  }
80
96
  function getActionMessage(selectedRow, activePath) {
81
- if (selectedRow.invalidReason) {
97
+ const action = projectAction(selectedRow, activePath);
98
+ if (action.kind === 'blocked') {
82
99
  return 'Cannot start this worktree.';
83
100
  }
84
- if (selectedRow.path === activePath) {
101
+ if (action.kind === 'active') {
85
102
  return 'Already active. Press s to stop the current session.';
86
103
  }
87
104
  return 'Press Enter to start here and switch the active session.';
88
105
  }
89
- export function getActionColor(selectedRow) {
90
- if (selectedRow.invalidReason) {
91
- return 'red';
92
- }
93
- if ((selectedRow.workingTree?.conflicts ?? 0) > 0) {
94
- return 'red';
106
+ function getNotes(selectedRow) {
107
+ const note = projectNote(selectedRow);
108
+ if (note.kind === 'invalid') {
109
+ return note.invalidReason;
95
110
  }
96
- if ((selectedRow.workingTree?.staged ?? 0) > 0
97
- || (selectedRow.workingTree?.unstaged ?? 0) > 0
98
- || (selectedRow.workingTree?.untracked ?? 0) > 0) {
99
- return 'yellow';
111
+ if (note.kind === 'external') {
112
+ return 'External worktree managed outside the main checkout path.';
100
113
  }
101
- return undefined;
114
+ return 'Ready to launch with the configured command in this worktree.';
102
115
  }
103
- function getNotes(selectedRow) {
104
- if (selectedRow.invalidReason) {
105
- return selectedRow.invalidReason;
116
+ function getVariantColor(variant) {
117
+ if (variant === 'success') {
118
+ return 'green';
106
119
  }
107
- if (selectedRow.tags.includes('external')) {
108
- return 'External worktree managed outside the main checkout path.';
120
+ if (variant === 'error') {
121
+ return 'red';
109
122
  }
110
- if (selectedRow.tags.includes('active')) {
111
- return 'This worktree currently owns the running command session.';
123
+ return 'blue';
124
+ }
125
+ function getVariantIcon(variant) {
126
+ if (variant === 'success') {
127
+ return '✓';
112
128
  }
113
- return 'Ready to launch with the configured command in this worktree.';
129
+ if (variant === 'error') {
130
+ return '✘';
131
+ }
132
+ return 'ℹ';
114
133
  }
115
- function SectionHeader({ label }) {
116
- return (_jsxs(Text, { bold: true, color: "cyan", children: ["[", label, "]"] }));
134
+ function section(label) {
135
+ return { text: `[${label}]`, color: 'cyan', bold: true };
117
136
  }
118
- export function ActionPanel({ selectedRow, activePath, stacked, width, compactDetails, }) {
137
+ function divider() {
138
+ return { text: ' ', dimColor: true };
139
+ }
140
+ function getPanelLines(selectedRow, activePath, setupAvailable, compactDetails) {
119
141
  if (!selectedRow) {
120
- return (_jsxs(Box, { width: width, flexGrow: stacked ? 0 : 1, flexShrink: 1, borderStyle: "round", borderColor: "magenta", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "magenta", children: "Selection / Action" }), _jsx(Text, { dimColor: true, children: "No worktrees found." })] }));
142
+ return [{ text: 'No worktrees found.', dimColor: true }];
121
143
  }
122
- const actionMessage = getActionMessage(selectedRow, activePath);
144
+ const lines = [section('Identity')];
123
145
  const showFullPath = !compactDetails && selectedRow.shortPath !== selectedRow.path;
124
146
  const showTags = !compactDetails;
125
- const pullRequestTitle = selectedRow.pullRequest?.kind === 'found' && !compactDetails
126
- ? sanitizeInlineText(selectedRow.pullRequest.title)
127
- : null;
128
- return (_jsxs(Box, { width: width, flexGrow: stacked ? 0 : 1, flexShrink: 1, borderStyle: "round", borderColor: "magenta", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "magenta", children: "Selection / Action" }), _jsx(SectionHeader, { label: "Identity" }), _jsxs(Text, { bold: true, color: selectedRow.tags.includes('active') ? 'green' : undefined, wrap: "truncate-end", children: ["Branch: ", sanitizeInlineText(selectedRow.branch)] }), _jsxs(Text, { wrap: "truncate-end", children: ["Path: ", sanitizeInlineText(selectedRow.shortPath)] }), showFullPath ? _jsxs(Text, { wrap: "truncate-end", children: ["Full Path: ", sanitizeInlineText(selectedRow.path)] }) : undefined, _jsxs(Text, { wrap: "truncate-end", children: ["HEAD: ", selectedRow.headSha || '-'] }), showTags ? _jsxs(Text, { wrap: "truncate-end", children: ["Tags: ", formatTags(selectedRow.tags)] }) : undefined, _jsx(SectionHeader, { label: "Git / PR" }), _jsxs(Text, { wrap: "truncate-end", children: ["Upstream: ", formatUpstream(selectedRow)] }), _jsxs(Text, { wrap: "truncate-end", children: ["Status: ", formatWorkingTree(selectedRow)] }), _jsxs(Text, { color: getPullRequestColor(selectedRow), dimColor: selectedRow.pullRequest?.kind === 'found' && selectedRow.pullRequest.state !== 'OPEN', wrap: "truncate-end", children: [getPullRequestLabel(selectedRow), ": ", formatPullRequest(selectedRow)] }), pullRequestTitle ? _jsxs(Text, { dimColor: selectedRow.pullRequest?.kind === 'found' && selectedRow.pullRequest.state !== 'OPEN', wrap: "truncate-end", children: [getPullRequestTitleLabel(selectedRow), ": ", pullRequestTitle] }) : undefined, _jsx(SectionHeader, { label: "Action" }), _jsx(Text, { color: getActionColor(selectedRow), wrap: "truncate-end", children: actionMessage }), _jsx(SectionHeader, { label: "Notes" }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: getNotes(selectedRow) })] }));
147
+ const pullRequest = projectPullRequest(selectedRow);
148
+ const pullRequestTitle = pullRequest.kind === 'found' && !compactDetails ? pullRequest.title : null;
149
+ lines.push({ text: `Branch: ${sanitizeInlineText(selectedRow.branch)}`, bold: true }, { text: `Path: ${sanitizeInlineText(selectedRow.shortPath)}` });
150
+ if (showFullPath) {
151
+ lines.push({ text: `Full Path: ${sanitizeInlineText(selectedRow.path)}` });
152
+ }
153
+ lines.push({ text: `HEAD: ${selectedRow.headSha || '-'}` });
154
+ lines.push({ text: `Branch Created: ${formatUtcDateTime(selectedRow.branchCreatedAtMs)}` });
155
+ if (showTags) {
156
+ for (const { tag } of getOrderedNonActiveTags(selectedRow.tags)) {
157
+ lines.push({ text: getTagLabel(tag).toUpperCase(), color: getTagColor(tag) });
158
+ }
159
+ }
160
+ lines.push(divider(), section('Git / PR'), { text: `Upstream: ${formatUpstream(selectedRow)}` }, { text: `Status: ${formatWorkingTree(selectedRow)}` }, {
161
+ text: `${getPullRequestLabel(pullRequest)}: ${formatPullRequest(selectedRow)}`,
162
+ color: getPullRequestColorFromProjection(pullRequest),
163
+ dimColor: getPullRequestDimColor(pullRequest),
164
+ });
165
+ if (pullRequestTitle) {
166
+ lines.push({ text: `${getPullRequestTitleLabel(pullRequest)}: ${pullRequestTitle}`, dimColor: getPullRequestDimColor(pullRequest) });
167
+ }
168
+ const actionVariant = getActionVariant(selectedRow, activePath);
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) });
175
+ return lines;
176
+ }
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;
184
+ const scrollbarThumbRows = showScrollbar
185
+ ? getScrollbarThumbRows(lines.length, viewport.viewportHeight, viewport.scrollOffset)
186
+ : new Set();
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}`))) })] }));
129
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,12 +1,45 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
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
- const COLOR_BY_KIND = {
4
+ import { Spinner } from '@inkjs/ui';
5
+ const KIND_TO_ICON = {
6
+ idle: 'ℹ',
7
+ starting: '⚠',
8
+ 'setting-up': '⚠',
9
+ running: '✓',
10
+ stopping: '⚠',
11
+ error: '✘',
12
+ };
13
+ const KIND_TO_COLOR = {
4
14
  idle: 'blue',
5
15
  starting: 'yellow',
16
+ 'setting-up': 'yellow',
6
17
  running: 'green',
7
18
  stopping: 'yellow',
8
19
  error: 'red',
9
20
  };
10
- export function ContextBar({ status }) {
11
- return (_jsxs(Box, { borderStyle: "round", borderColor: COLOR_BY_KIND[status.kind], flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { color: COLOR_BY_KIND[status.kind], wrap: "truncate-end", children: ["Status: ", status.kind, " \u2014 ", status.message] }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: "Keys: \u2191\u2193/jk move g/G first/last Enter start/switch s stop r refresh q quit" })] }));
21
+ function buildKeyHints(setupAvailable, editorAvailable, confirmationOpen) {
22
+ if (confirmationOpen) {
23
+ return [
24
+ { binding: 'd/y', label: 'Confirm' },
25
+ { binding: 'Esc/n/q', label: 'Cancel' },
26
+ ];
27
+ }
28
+ const hints = [
29
+ { binding: '↑↓/jk', label: 'Move' },
30
+ { binding: 'Enter', label: 'Switch' },
31
+ ];
32
+ if (setupAvailable) {
33
+ hints.push({ binding: 'i', label: 'Setup' });
34
+ }
35
+ if (editorAvailable) {
36
+ hints.push({ binding: 'e', label: 'Editor' });
37
+ }
38
+ 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' });
39
+ return hints;
40
+ }
41
+ export function ContextBar({ status, setupAvailable, editorAvailable, confirmationOpen, }) {
42
+ const isBusy = status.kind === 'setting-up' || status.kind === 'starting' || status.kind === 'stopping';
43
+ const keyHints = buildKeyHints(setupAvailable, editorAvailable, confirmationOpen);
44
+ return (_jsxs(Box, { borderStyle: "round", borderColor: KIND_TO_COLOR[status.kind], flexDirection: "column", paddingX: 1, children: [isBusy ? (_jsx(Spinner, { label: `Status: ${status.kind} — ${status.message}` })) : (_jsxs(Text, { color: KIND_TO_COLOR[status.kind], wrap: "truncate-end", children: [KIND_TO_ICON[status.kind], " Status: ", status.kind, " \u2014 ", status.message] })), _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))) })] }));
12
45
  }
@@ -0,0 +1,7 @@
1
+ import type { AppLogEntry } from '../core/runtime.js';
2
+ export declare function FloatingLogWindow({ logs, width, height, scrollOffset, }: {
3
+ logs: AppLogEntry[];
4
+ width: number;
5
+ height: number;
6
+ scrollOffset: number;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { LogPanel } from './LogPanel.js';
3
+ export function FloatingLogWindow({ logs, width, height, scrollOffset, }) {
4
+ return (_jsx(LogPanel, { logs: logs, width: width, height: height, scrollOffset: scrollOffset, title: "Logs (*.log \u00B7 tail 120 \u00B7 full screen)" }));
5
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ export declare function HelpWindow({ setupAvailable, editorAvailable, width, height }: {
3
+ setupAvailable: boolean;
4
+ editorAvailable: boolean;
5
+ width: number;
6
+ height: number;
7
+ }): React.JSX.Element;
@@ -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
+ }
@@ -0,0 +1,22 @@
1
+ import type { AppLogEntry } from '../core/runtime.js';
2
+ type LogColor = 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray';
3
+ type LineStyle = {
4
+ color?: LogColor;
5
+ dimColor?: boolean;
6
+ bold?: boolean;
7
+ };
8
+ type LineSegment = {
9
+ text: string;
10
+ } & LineStyle;
11
+ type LineSpec = {
12
+ segments: LineSegment[];
13
+ };
14
+ export declare function buildLogLines(logs: AppLogEntry[]): LineSpec[];
15
+ export declare function LogPanel({ logs, width, height, scrollOffset, title, }: {
16
+ logs: AppLogEntry[];
17
+ width?: number;
18
+ height?: number;
19
+ scrollOffset?: number;
20
+ title?: string;
21
+ }): import("react").JSX.Element;
22
+ export {};