parallel-codex-tui 0.1.3 → 0.1.5

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 (78) hide show
  1. package/.parallel-codex/config.example.toml +136 -3
  2. package/README.md +299 -21
  3. package/dist/bootstrap.js +37 -17
  4. package/dist/cli-args.js +34 -3
  5. package/dist/cli-help.js +20 -0
  6. package/dist/cli-startup-preflight.js +18 -0
  7. package/dist/cli-startup-recovery.js +82 -0
  8. package/dist/cli-workspace-picker.js +330 -0
  9. package/dist/cli-workspace-transition.js +33 -0
  10. package/dist/cli-workspace.js +7 -71
  11. package/dist/cli.js +234 -24
  12. package/dist/core/collaboration-timeline.js +268 -0
  13. package/dist/core/config-errors.js +14 -0
  14. package/dist/core/config.js +297 -109
  15. package/dist/core/file-store.js +119 -6
  16. package/dist/core/lease-finalization.js +22 -0
  17. package/dist/core/paths.js +7 -0
  18. package/dist/core/process-identity.js +48 -0
  19. package/dist/core/process-mutation-turn.js +128 -0
  20. package/dist/core/process-ownership.js +276 -0
  21. package/dist/core/process-tree.js +90 -0
  22. package/dist/core/router-audit.js +155 -0
  23. package/dist/core/router-redaction.js +31 -0
  24. package/dist/core/router.js +462 -35
  25. package/dist/core/session-index.js +412 -88
  26. package/dist/core/session-manager.js +1110 -40
  27. package/dist/core/task-session-details.js +175 -0
  28. package/dist/core/task-state-machine.js +18 -0
  29. package/dist/core/workspace-commit-recovery.js +118 -0
  30. package/dist/core/workspace.js +19 -11
  31. package/dist/doctor.js +373 -34
  32. package/dist/domain/schemas.js +142 -7
  33. package/dist/orchestrator/collaboration-channel.js +289 -6
  34. package/dist/orchestrator/feature-plan.js +70 -0
  35. package/dist/orchestrator/final-acceptance.js +86 -0
  36. package/dist/orchestrator/judge-artifacts.js +236 -0
  37. package/dist/orchestrator/orchestrator.js +2086 -203
  38. package/dist/orchestrator/prompts.js +168 -5
  39. package/dist/orchestrator/supervisor-summary.js +56 -2
  40. package/dist/orchestrator/workspace-sandbox.js +927 -0
  41. package/dist/tui/App.js +3187 -161
  42. package/dist/tui/AppShell.js +196 -25
  43. package/dist/tui/CollaborationTimelineView.js +327 -0
  44. package/dist/tui/FeatureBoardView.js +232 -0
  45. package/dist/tui/InputBar.js +581 -57
  46. package/dist/tui/RouterDiagnosticsView.js +469 -0
  47. package/dist/tui/StatusBar.js +610 -57
  48. package/dist/tui/StatusDetailView.js +164 -0
  49. package/dist/tui/TaskSessionDetailView.js +222 -0
  50. package/dist/tui/TaskSessionsView.js +210 -0
  51. package/dist/tui/TerminalOutput.js +53 -9
  52. package/dist/tui/WorkerOutputView.js +1404 -161
  53. package/dist/tui/WorkerOverviewView.js +269 -0
  54. package/dist/tui/chat-history.js +25 -0
  55. package/dist/tui/chat-input.js +67 -19
  56. package/dist/tui/chat-paste.js +76 -0
  57. package/dist/tui/display-width.js +41 -3
  58. package/dist/tui/incremental-text-file.js +101 -0
  59. package/dist/tui/keyboard.js +49 -0
  60. package/dist/tui/markdown-text.js +14 -0
  61. package/dist/tui/raw-input-decoder.js +3 -0
  62. package/dist/tui/scrolling.js +2 -1
  63. package/dist/tui/status-line.js +360 -11
  64. package/dist/tui/task-memory.js +13 -1
  65. package/dist/tui/task-result.js +105 -0
  66. package/dist/tui/terminal-screen.js +13 -1
  67. package/dist/tui/theme-contrast.js +144 -0
  68. package/dist/tui/theme-preview.js +109 -0
  69. package/dist/tui/theme.js +158 -0
  70. package/dist/version.js +1 -1
  71. package/dist/workers/capabilities.js +213 -0
  72. package/dist/workers/live-probe.js +177 -0
  73. package/dist/workers/mock-adapter.js +73 -8
  74. package/dist/workers/native-attach.js +106 -16
  75. package/dist/workers/process-adapter.js +572 -77
  76. package/dist/workers/provider.js +26 -0
  77. package/dist/workers/registry.js +12 -20
  78. package/package.json +11 -2
@@ -1,39 +1,122 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
- import { compactTailByDisplayWidth, displayWidth } from "./display-width.js";
4
- const INPUT_BAR_BACKGROUND = "ansi256(234)";
5
- export function InputBar({ mode, busy = false, hasWorkers = false, nativeClosed = false, value, terminalWidth = process.stdout.columns || 120, onChange, onSubmit }) {
3
+ import { compactEndByDisplayWidth, compactTailByDisplayWidth, displayWidth } from "./display-width.js";
4
+ import { TUI_THEME } from "./theme.js";
5
+ export function InputBar({ mode, ready = true, busy = false, routeFallback = false, collaborationDetail = false, collaborationUnresolved = false, collaborationBack = "workers", featureCanCancel = false, featureCanPause = false, featureCanReassign = false, featureCancelConfirm = false, featurePauseConfirm = false, featureAssignment = false, taskSessionAction = null, taskSessionsIncludeArchived = false, taskSessionDetail = false, taskSessionDetailHasNative = false, taskSessionDetailCanFork = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
6
+ const terminalWidth = providedTerminalWidth ?? process.stdout.columns ?? 120;
7
+ const fillRail = providedTerminalWidth !== undefined || typeof process.stdout.columns === "number";
8
+ if (mode === "worker-search") {
9
+ const suffix = workerSearchInputSuffix(terminalWidth, searchMatchIndex, searchMatchCount);
10
+ const prefix = terminalWidth < 4 ? "/" : "/ ";
11
+ const textBudget = Math.max(1, terminalWidth - (terminalWidth > 1 ? 2 : 0));
12
+ const valueWidth = Math.max(1, textBudget - displayWidth(prefix) - 1 - displayWidth(suffix));
13
+ const display = chatInputDisplayParts(value, cursor ?? Array.from(value).length, valueWidth + 6);
14
+ const textWidth = displayWidth(`${prefix}${display.before}|${display.after}${suffix}`);
15
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: textWidth, fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: prefix }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.before }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: "|" }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.after }), suffix ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: suffix }) : null] }));
16
+ }
17
+ if (mode === "status") {
18
+ const hints = statusDetailInputHints(terminalWidth);
19
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
20
+ }
21
+ if (mode === "sessions") {
22
+ if (taskSessionAction?.type === "rename") {
23
+ const prefix = terminalWidth < 12 ? "> " : "rename > ";
24
+ const valueWidth = Math.max(1, terminalWidth - displayWidth(prefix) - 3);
25
+ const display = chatInputDisplayParts(taskSessionAction.value, taskSessionAction.cursor, valueWidth);
26
+ const textWidth = displayWidth(`${prefix}${display.before}|${display.after}`);
27
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: textWidth, fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: prefix }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.before }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: "|" }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.after })] }));
28
+ }
29
+ if (taskSessionAction?.type === "delete") {
30
+ const hints = taskSessionDeleteInputHints(terminalWidth, taskSessionAction.title);
31
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.danger, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
32
+ }
33
+ if (taskSessionDetail) {
34
+ const hints = taskSessionDetailInputHints(terminalWidth, taskSessionDetailHasNative, taskSessionDetailCanFork);
35
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
36
+ }
37
+ const hints = taskSessionsInputHints(terminalWidth, taskSessionsIncludeArchived);
38
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
39
+ }
40
+ if (mode === "collaboration") {
41
+ const hints = collaborationDetail
42
+ ? collaborationDetailInputHints(terminalWidth)
43
+ : collaborationTimelineInputHints(terminalWidth, collaborationUnresolved, collaborationBack);
44
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
45
+ }
46
+ if (mode === "features") {
47
+ const hints = featureBoardInputHints(terminalWidth, {
48
+ canCancel: featureCanCancel,
49
+ canPause: featureCanPause,
50
+ canReassign: featureCanReassign,
51
+ confirmCancel: featureCancelConfirm,
52
+ confirmPause: featurePauseConfirm,
53
+ assignment: featureAssignment,
54
+ canRetry
55
+ });
56
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
57
+ }
58
+ if (mode === "workers") {
59
+ const hints = workerOverviewInputHints(terminalWidth);
60
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
61
+ }
6
62
  if (mode === "worker") {
7
63
  const hints = workerInputHints(terminalWidth);
8
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "cyan", bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, dimColor: true, children: hints.detail }) : null] }));
64
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
9
65
  }
10
66
  if (mode === "native") {
11
67
  const hints = nativeInputHints(terminalWidth, nativeClosed);
12
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "cyan", bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, dimColor: true, children: hints.detail }) : null] }));
68
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
69
+ }
70
+ if (mode === "router") {
71
+ const hints = routerInputHints(terminalWidth);
72
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
73
+ }
74
+ if (!ready) {
75
+ const starting = chatStartingDisplayValue(terminalWidth);
76
+ return (_jsx(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(starting), fill: fillRail, children: _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: starting }) }));
77
+ }
78
+ if (routeFallback) {
79
+ const hints = routeFallbackInputHints(terminalWidth);
80
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: hints.detail }) : null] }));
13
81
  }
14
82
  const busyText = chatBusyDisplayValue(terminalWidth);
15
83
  const prompt = busy ? "run" : ">";
16
84
  if (busy) {
17
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(busyText ? `${prompt} ${busyText}` : prompt), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "yellow", bold: true, children: prompt }), busyText ? (_jsxs(_Fragment, { children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " " }), _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "yellow", children: busyText })] })) : null] }));
85
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(busyText ? `${prompt} ${busyText}` : prompt), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning, bold: true, children: prompt }), busyText ? (_jsxs(_Fragment, { children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, children: " " }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning, children: busyText })] })) : null] }));
18
86
  }
19
87
  if (value) {
20
- const displayValue = chatInputDisplayValue(value, terminalWidth);
21
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${prompt} ${displayValue}|`), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "cyan", bold: true, children: prompt }), _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " " }), _jsxs(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: [displayValue, "|"] })] }));
88
+ const display = chatInputDisplayParts(value, cursor ?? Array.from(value).length, terminalWidth);
89
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${prompt} ${display.before}|${display.after}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: prompt }), _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " " }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.before }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: "|" }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.after })] }));
22
90
  }
23
91
  const hasLeadingPromptSpace = terminalWidth >= 10;
24
- const placeholder = chatPlaceholderDisplayText(terminalWidth, { hasWorkers }, { leadingSpace: hasLeadingPromptSpace });
25
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${prompt}${hasLeadingPromptSpace ? " " : ""}|${placeholder}`), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "cyan", bold: true, children: prompt }), hasLeadingPromptSpace ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " " }) : null, _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: "|" }), _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, dimColor: true, children: placeholder })] }));
92
+ const placeholder = chatPlaceholderDisplayText(terminalWidth, {
93
+ hasWorkers,
94
+ hasActiveTask,
95
+ hasTaskResult,
96
+ taskResultExpanded,
97
+ canRetry,
98
+ scrollOffset: chatScrollOffset,
99
+ maxScrollOffset: chatMaxScrollOffset
100
+ }, { leadingSpace: hasLeadingPromptSpace });
101
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${prompt}${hasLeadingPromptSpace ? " " : ""}|${placeholder}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: prompt }), hasLeadingPromptSpace ? _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " " }) : null, _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: "|" }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: canRetry ? TUI_THEME.warning : hasTaskResult ? TUI_THEME.accent : TUI_THEME.muted, children: placeholder })] }));
26
102
  }
27
- function InputRail({ terminalWidth, textWidth, children }) {
103
+ function InputRail({ fill, terminalWidth, textWidth, children }) {
104
+ const { leadingWidth, trailingWidth } = inputRailLayout(terminalWidth, textWidth, { fill });
105
+ return (_jsxs(Box, { children: [leadingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " ".repeat(leadingWidth) }) : null, children, trailingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " ".repeat(trailingWidth) }) : null] }));
106
+ }
107
+ export function inputRailLayout(terminalWidth, textWidth, options = {}) {
28
108
  const leadingWidth = terminalWidth > 1 ? 1 : 0;
109
+ if (options.fill === false) {
110
+ return { leadingWidth, trailingWidth: 0 };
111
+ }
29
112
  const renderWidth = typeof process.stdout.columns === "number"
30
113
  ? Math.max(1, Math.min(terminalWidth, process.stdout.columns))
31
- : null;
32
- const barWidth = renderWidth === null ? null : Math.max(1, renderWidth - 1);
33
- const trailingWidth = barWidth === null
34
- ? 0
35
- : Math.max(0, barWidth - leadingWidth - textWidth);
36
- return (_jsxs(Box, { children: [leadingWidth > 0 ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " ".repeat(leadingWidth) }) : null, children, trailingWidth > 0 ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " ".repeat(trailingWidth) }) : null] }));
114
+ : Math.max(1, terminalWidth);
115
+ const barWidth = Math.max(1, renderWidth - 1);
116
+ return {
117
+ leadingWidth,
118
+ trailingWidth: Math.max(0, barWidth - leadingWidth - Math.max(0, textWidth))
119
+ };
37
120
  }
38
121
  export function chatPlaceholderDisplayText(terminalWidth, options = {}, display = {}) {
39
122
  const placeholder = chatPlaceholderDisplayValue(terminalWidth, options);
@@ -44,37 +127,211 @@ export function chatPlaceholderDisplayText(terminalWidth, options = {}, display
44
127
  }
45
128
  export function chatInputDisplayValue(value, terminalWidth) {
46
129
  const valueWidth = Math.max(1, terminalWidth - 6);
47
- return compactTailByDisplayWidth(value, valueWidth);
130
+ return compactTailByDisplayWidth(chatInputVisibleValue(value), valueWidth);
131
+ }
132
+ export function chatInputDisplayParts(value, cursor, terminalWidth) {
133
+ const sourceChars = Array.from(value);
134
+ const chars = sourceChars.map(chatInputVisibleCharacter);
135
+ const clampedCursor = Math.min(sourceChars.length, Math.max(0, Math.trunc(cursor)));
136
+ const valueWidth = Math.max(1, terminalWidth - 6);
137
+ if (displayWidth(chars.join("")) <= valueWidth) {
138
+ return {
139
+ before: chars.slice(0, clampedCursor).join(""),
140
+ after: chars.slice(clampedCursor).join("")
141
+ };
142
+ }
143
+ const beforeCursor = chars.slice(0, clampedCursor).join("");
144
+ const afterCursor = chars.slice(clampedCursor).join("");
145
+ if (valueWidth < 6) {
146
+ return clampedCursor > 0
147
+ ? { before: compactTailByDisplayWidth(beforeCursor, valueWidth), after: "" }
148
+ : { before: "", after: compactEndByDisplayWidth(afterCursor, valueWidth) };
149
+ }
150
+ let start = clampedCursor;
151
+ let end = clampedCursor;
152
+ const renderedWidth = (nextStart, nextEnd) => displayWidth([
153
+ nextStart > 0 ? "..." : "",
154
+ chars.slice(nextStart, nextEnd).join(""),
155
+ nextEnd < chars.length ? "..." : ""
156
+ ].join(""));
157
+ while (true) {
158
+ const canExpandLeft = start > 0 && renderedWidth(start - 1, end) <= valueWidth;
159
+ const canExpandRight = end < chars.length && renderedWidth(start, end + 1) <= valueWidth;
160
+ if (!canExpandLeft && !canExpandRight) {
161
+ break;
162
+ }
163
+ if (canExpandLeft && canExpandRight) {
164
+ const leftWidth = displayWidth(chars.slice(start, clampedCursor).join(""));
165
+ const rightWidth = displayWidth(chars.slice(clampedCursor, end).join(""));
166
+ if (leftWidth <= rightWidth) {
167
+ start -= 1;
168
+ }
169
+ else {
170
+ end += 1;
171
+ }
172
+ }
173
+ else if (canExpandLeft) {
174
+ start -= 1;
175
+ }
176
+ else {
177
+ end += 1;
178
+ }
179
+ }
180
+ return {
181
+ before: `${start > 0 ? "..." : ""}${chars.slice(start, clampedCursor).join("")}`,
182
+ after: `${chars.slice(clampedCursor, end).join("")}${end < chars.length ? "..." : ""}`
183
+ };
184
+ }
185
+ function chatInputVisibleValue(value) {
186
+ return Array.from(value).map(chatInputVisibleCharacter).join("");
187
+ }
188
+ function chatInputVisibleCharacter(char) {
189
+ if (char === "\n" || char === "\r") {
190
+ return "↵";
191
+ }
192
+ if (char === "\t") {
193
+ return "⇥";
194
+ }
195
+ return char;
48
196
  }
49
197
  export function chatPlaceholderDisplayValue(terminalWidth, options = {}) {
50
- if (options.hasWorkers) {
51
- return chatTaskPlaceholderDisplayValue(terminalWidth);
198
+ if (options.canRetry) {
199
+ return selectChatPlaceholder(terminalWidth, options.hasActiveTask
200
+ ? ["message · ^R retry · ^N new", "^R retry · ^N", "^R retry", "retry"]
201
+ : ["message · ^R retry", "^R retry", "retry"]);
52
202
  }
53
- if (terminalWidth < 14) {
54
- return "msg";
203
+ const maxScrollOffset = Math.max(0, options.maxScrollOffset ?? 0);
204
+ const scrollOffset = Math.min(Math.max(0, options.scrollOffset ?? 0), maxScrollOffset);
205
+ if (options.hasTaskResult) {
206
+ return chatTaskResultPlaceholderDisplayValue(terminalWidth, Boolean(options.taskResultExpanded), Boolean(options.hasWorkers), Boolean(options.hasActiveTask), scrollOffset, maxScrollOffset);
55
207
  }
56
- if (terminalWidth < 22) {
57
- return "message";
208
+ if (scrollOffset > 0) {
209
+ return chatHistoryPlaceholderDisplayValue(terminalWidth, scrollOffset, maxScrollOffset);
210
+ }
211
+ if (options.hasActiveTask && !options.hasWorkers) {
212
+ return selectChatPlaceholder(terminalWidth, [
213
+ "message · ^N new · ^P project · ^T tasks · ^G routes",
214
+ "message · ^N new · ^P project · ^G routes",
215
+ "message · ^N new · ^P project",
216
+ "message · ^N new",
217
+ "msg · ^N",
218
+ "msg"
219
+ ]);
220
+ }
221
+ if (options.hasWorkers) {
222
+ return chatTaskPlaceholderDisplayValue(terminalWidth, maxScrollOffset > 0, options.hasActiveTask);
223
+ }
224
+ if (maxScrollOffset > 0 && terminalWidth >= 22) {
225
+ return selectChatPlaceholder(terminalWidth, ["message · scroll", "message", "msg"]);
58
226
  }
59
- return chatInputDisplayValue("Type a message", terminalWidth);
227
+ return selectChatPlaceholder(terminalWidth, [
228
+ "message · ^P project · ^T tasks · ^G routes",
229
+ "message · ^P project · ^G routes",
230
+ "message · ^P project",
231
+ "message",
232
+ "msg"
233
+ ]);
234
+ }
235
+ function chatTaskResultPlaceholderDisplayValue(terminalWidth, expanded, hasWorkers, hasActiveTask, scrollOffset, maxScrollOffset) {
236
+ const toggle = expanded ? "^D compact" : "^D details";
237
+ const position = expanded && maxScrollOffset > 0
238
+ ? scrollOffset > 0 ? `result ${scrollOffset}/${maxScrollOffset}` : "result · scroll"
239
+ : "message";
240
+ const candidates = hasWorkers
241
+ ? [
242
+ `${position} · ${toggle} · ^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach`,
243
+ `${position} · ${toggle} · ^N new · ^W logs · ^T tasks · Tab · ^O attach`,
244
+ `${position} · ${toggle} · ^W logs · ^B workers · ^T tasks · Tab · ^O attach`,
245
+ `${position} · ${toggle} · ^W logs · ^T tasks · Tab · ^O attach`,
246
+ `${position} · ${toggle} · ^W logs · Tab · ^O attach`,
247
+ `${position} · ${toggle} · ^W logs`,
248
+ `${toggle} · ^W logs`,
249
+ toggle,
250
+ "^D"
251
+ ]
252
+ : [
253
+ `${position} · ${toggle}${hasActiveTask ? " · ^N new" : ""}`,
254
+ `${position} · ${toggle}`,
255
+ toggle,
256
+ "^D"
257
+ ];
258
+ return selectChatPlaceholder(terminalWidth, candidates);
60
259
  }
61
- function chatTaskPlaceholderDisplayValue(terminalWidth) {
260
+ function chatHistoryPlaceholderDisplayValue(terminalWidth, offset, maxOffset) {
62
261
  if (terminalWidth < 14) {
63
- return "msg";
262
+ return "back";
64
263
  }
65
- if (terminalWidth < 19) {
66
- return "msg · ^W";
67
- }
68
- if (terminalWidth < 24) {
69
- return "msg · ^W · ^O";
264
+ if (terminalWidth < 20) {
265
+ return `back ${offset}`;
70
266
  }
71
267
  if (terminalWidth < 38) {
72
- return chatInputDisplayValue("Message · ^W · ^O", terminalWidth);
268
+ return selectChatPlaceholder(terminalWidth, [
269
+ `back ${offset}/${maxOffset} · PgDn`,
270
+ `back ${offset}/${maxOffset}`,
271
+ `back ${offset}`,
272
+ "back"
273
+ ]);
73
274
  }
74
- if (terminalWidth < 64) {
75
- return chatInputDisplayValue("Message · ^W logs · ^O attach", terminalWidth);
275
+ return selectChatPlaceholder(terminalWidth, [
276
+ `message · back ${offset}/${maxOffset} · PgDn latest`,
277
+ `back ${offset}/${maxOffset} · PgDn latest`,
278
+ `back ${offset}/${maxOffset} · PgDn`,
279
+ `back ${offset}/${maxOffset}`,
280
+ `back ${offset}`,
281
+ "back"
282
+ ]);
283
+ }
284
+ function chatTaskPlaceholderDisplayValue(terminalWidth, scrollable = false, activeTask = false) {
285
+ if (activeTask && terminalWidth >= 72) {
286
+ const activeCandidates = scrollable
287
+ ? [
288
+ "message · scroll · ^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
289
+ "message · scroll · ^N new · ^W logs · ^B workers · Tab · ^O attach · ^G routes"
290
+ ]
291
+ : [
292
+ "message · ^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
293
+ "message · ^N new · ^W logs · ^B workers · Tab · ^O attach · ^G routes"
294
+ ];
295
+ const active = activeCandidates.find((candidate) => displayWidth(candidate) <= chatPlaceholderValueWidth(terminalWidth));
296
+ if (active) {
297
+ return active;
298
+ }
76
299
  }
77
- return chatInputDisplayValue("Type a message · ^W logs · ^O attach", terminalWidth);
300
+ return selectChatPlaceholder(terminalWidth, scrollable
301
+ ? [
302
+ "message · scroll · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
303
+ "message · scroll · ^W logs · ^B workers · Tab · ^O attach · ^G routes",
304
+ "message · scroll · ^W logs · Tab · ^O attach · ^G routes",
305
+ "message · scroll · ^W logs · Tab · ^O attach",
306
+ "scroll · ^W logs · Tab · ^O attach",
307
+ "scroll · ^W logs · ^O attach",
308
+ "^W logs · ^O attach",
309
+ "message · ^W logs",
310
+ "msg · ^W logs",
311
+ "^W logs",
312
+ "msg"
313
+ ]
314
+ : [
315
+ "message · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
316
+ "message · ^W logs · ^B workers · Tab · ^O attach · ^G routes",
317
+ "message · ^W logs · Tab · ^O attach · ^G routes",
318
+ "message · ^W logs · Tab · ^O attach",
319
+ "message · ^W logs · ^O attach",
320
+ "^W logs · ^O attach",
321
+ "message · ^W logs",
322
+ "msg · ^W logs",
323
+ "^W logs",
324
+ "msg"
325
+ ]);
326
+ }
327
+ function selectChatPlaceholder(terminalWidth, candidates) {
328
+ const valueWidth = chatPlaceholderValueWidth(terminalWidth);
329
+ return candidates.find((candidate) => displayWidth(candidate) <= valueWidth)
330
+ ?? candidates.at(-1)
331
+ ?? "";
332
+ }
333
+ function chatPlaceholderValueWidth(terminalWidth) {
334
+ return Math.max(1, terminalWidth - (terminalWidth >= 10 ? 6 : 4));
78
335
  }
79
336
  export function chatBusyDisplayValue(terminalWidth) {
80
337
  if (terminalWidth < 14) {
@@ -83,28 +340,283 @@ export function chatBusyDisplayValue(terminalWidth) {
83
340
  if (terminalWidth < 22) {
84
341
  return "busy";
85
342
  }
86
- return "Running...";
343
+ if (terminalWidth >= 34) {
344
+ return "working · Esc stop";
345
+ }
346
+ return "working";
347
+ }
348
+ function routeFallbackInputHints(width) {
349
+ if (width < 10) {
350
+ return { label: "route", detail: "" };
351
+ }
352
+ if (width < 14) {
353
+ return { label: "choose", detail: "" };
354
+ }
355
+ if (width < 20) {
356
+ return { label: "1M", detail: " · 2P" };
357
+ }
358
+ if (width < 28) {
359
+ return { label: "1 Main", detail: " · 2 Pair" };
360
+ }
361
+ if (width < 40) {
362
+ return { label: "1 Main", detail: " · 2 Parallel" };
363
+ }
364
+ if (width < 54) {
365
+ return { label: "route", detail: " · 1 Main · 2 Parallel · Esc" };
366
+ }
367
+ if (width < 75) {
368
+ return { label: "route failed", detail: " · 1 Main · 2 Parallel · R · Esc" };
369
+ }
370
+ return { label: "route failed", detail: " · 1 Main · 2 Parallel · R retry · Esc cancel" };
371
+ }
372
+ export function chatStartingDisplayValue(terminalWidth) {
373
+ if (terminalWidth < 8) {
374
+ return "";
375
+ }
376
+ return terminalWidth < 12 ? "start" : "starting";
87
377
  }
88
378
  function workerInputHints(width) {
89
- if (width < 12) {
379
+ if (width < 10) {
90
380
  return { label: "log", detail: "" };
91
381
  }
92
- if (width < 15) {
93
- return { label: "log", detail: " · ^O" };
382
+ if (width < 16) {
383
+ return { label: "Esc chat", detail: "" };
384
+ }
385
+ if (width < 17) {
386
+ return { label: "log", detail: " · Esc chat" };
94
387
  }
95
- if (width < 18) {
96
- return { label: "log", detail: " · Pg · ^O" };
388
+ if (width < 22) {
389
+ return { label: "logs", detail: " · Esc chat" };
97
390
  }
98
391
  if (width < 28) {
99
- return { label: "logs", detail: " · Pg · ^O" };
392
+ return { label: "logs", detail: " · Pg · Esc chat" };
393
+ }
394
+ if (width < 32) {
395
+ return { label: "logs", detail: " · Pg · Tab · Esc chat" };
396
+ }
397
+ if (width < 44) {
398
+ return { label: "logs", detail: " · scroll · Tab · Esc chat" };
399
+ }
400
+ if (width < 54) {
401
+ return { label: "logs", detail: " · scroll · Tab · ^O attach · Esc chat" };
100
402
  }
101
- if (width < 56) {
102
- return { label: "logs", detail: " · Pg · Tab · ^O · Esc" };
403
+ if (width < 71) {
404
+ return { label: "logs", detail: " · scroll · ^F find · Tab · ^O attach · Esc chat" };
405
+ }
406
+ if (width < 84) {
407
+ return { label: "logs", detail: " · scroll · ^F find · E err · D diff · Tab · ^O attach · Esc chat" };
408
+ }
409
+ return { label: "logs", detail: " · scroll · ^F find · E err · D diff · Tab · ^B workers · ^O attach · Esc chat" };
410
+ }
411
+ function statusDetailInputHints(width) {
412
+ return selectInputHints(width, [
413
+ { label: "status", detail: " · ^S/Esc back · ^C exit" },
414
+ { label: "status", detail: " · ^S back · ^C exit" },
415
+ { label: "status", detail: " · ^S back" },
416
+ { label: "status", detail: "" },
417
+ { label: "st", detail: "" }
418
+ ]);
419
+ }
420
+ function workerOverviewInputHints(width) {
421
+ if (width < 16) {
422
+ return { label: "wrk", detail: "" };
423
+ }
424
+ if (width < 20) {
425
+ return { label: "wrk", detail: " · Esc back" };
426
+ }
427
+ if (width < 28) {
428
+ return { label: "workers", detail: " · Esc back" };
429
+ }
430
+ if (width < 41) {
431
+ return { label: "workers", detail: " · Up/Dn · Esc back" };
432
+ }
433
+ if (width < 53) {
434
+ return { label: "workers", detail: " · Up/Dn · Enter logs · Esc back" };
103
435
  }
104
436
  if (width < 72) {
105
- return { label: "logs", detail: " · Pg/wheel · Tab · ^O · Esc" };
437
+ return { label: "workers", detail: " · Up/Dn · Enter logs · ^O attach · Esc back" };
438
+ }
439
+ if (width < 86) {
440
+ return { label: "workers", detail: " · Up/Dn · Enter logs · F board · C flow · ^O attach · Esc back" };
441
+ }
442
+ return { label: "workers", detail: " · Up/Dn select · Enter logs · F features · C timeline · ^O attach · Esc back" };
443
+ }
444
+ function featureBoardInputHints(width, options) {
445
+ if (options.assignment) {
446
+ return selectInputHints(width, [
447
+ { label: "assign provider", detail: " · A Actor · C Critic · M/Esc done" },
448
+ { label: "assign", detail: " · A Actor · C Critic · Esc done" },
449
+ { label: "assign", detail: " · A actor · C critic" },
450
+ { label: "provider", detail: " · A · C · Esc" },
451
+ { label: "provider", detail: "" },
452
+ { label: "M", detail: "" }
453
+ ]);
454
+ }
455
+ if (options.confirmPause) {
456
+ return selectInputHints(width, [
457
+ { label: "pause feature?", detail: " · P confirm · Esc keep" },
458
+ { label: "pause?", detail: " · P confirm · Esc keep" },
459
+ { label: "pause?", detail: " · Esc keep" },
460
+ { label: "Esc keep", detail: "" },
461
+ { label: "hold?", detail: "" },
462
+ { label: "?", detail: "" }
463
+ ]);
464
+ }
465
+ if (options.confirmCancel) {
466
+ return selectInputHints(width, [
467
+ { label: "cancel feature?", detail: " · X confirm · Esc keep" },
468
+ { label: "cancel?", detail: " · X confirm · Esc keep" },
469
+ { label: "cancel?", detail: " · Esc keep" },
470
+ { label: "Esc keep", detail: "" },
471
+ { label: "stop?", detail: "" },
472
+ { label: "?", detail: "" }
473
+ ]);
106
474
  }
107
- return { label: "logs", detail: " · read · wheel/Pg · Tab worker · ^O attach · Esc chat" };
475
+ if (options.canCancel || options.canPause) {
476
+ const controls = [
477
+ ...(options.canPause ? ["P pause"] : []),
478
+ ...(options.canCancel ? ["X cancel"] : [])
479
+ ].join(" · ");
480
+ return selectInputHints(width, [
481
+ { label: "features", detail: ` · Up/Dn select · Enter timeline · ${controls} · R refresh · Esc workers` },
482
+ { label: "features", detail: ` · Up/Dn select · ${controls} · R refresh · Esc workers` },
483
+ { label: "features", detail: ` · Up/Dn select · ${controls} · Esc workers` },
484
+ { label: "features", detail: ` · ${controls} · Esc workers` },
485
+ { label: "ft", detail: ` · ${controls} · Esc workers` },
486
+ { label: "f", detail: ` · ${controls} · Esc workers` },
487
+ { label: "features", detail: ` · ${controls}` },
488
+ { label: "ft", detail: ` · ${controls}` },
489
+ { label: "f", detail: ` · ${controls}` },
490
+ { label: "features", detail: "" },
491
+ { label: "ft", detail: "" },
492
+ { label: "f", detail: "" }
493
+ ]);
494
+ }
495
+ if (options.canRetry) {
496
+ return selectInputHints(width, [
497
+ { label: "features", detail: ` · Up/Dn select · Enter timeline · ${options.canReassign ? "M provider · " : ""}^R retry task · R refresh · Esc workers` },
498
+ { label: "features", detail: ` · Up/Dn select · ${options.canReassign ? "M provider · " : ""}^R retry task · R refresh · Esc workers` },
499
+ { label: "features", detail: ` · Up/Dn select · ${options.canReassign ? "M provider · " : ""}^R retry · Esc workers` },
500
+ { label: "features", detail: " · ^R retry · Esc workers" },
501
+ { label: "features", detail: " · Esc workers" },
502
+ { label: "features", detail: "" },
503
+ { label: "ft", detail: "" },
504
+ { label: "f", detail: "" }
505
+ ]);
506
+ }
507
+ return selectInputHints(width, [
508
+ { label: "features", detail: " · Up/Dn select · Enter timeline · R refresh · Esc workers" },
509
+ { label: "features", detail: " · Up/Dn select · Enter timeline · Esc workers" },
510
+ { label: "features", detail: " · Up/Dn select · Esc workers" },
511
+ { label: "features", detail: " · Esc workers" },
512
+ { label: "features", detail: "" },
513
+ { label: "ft", detail: "" },
514
+ { label: "f", detail: "" }
515
+ ]);
516
+ }
517
+ function collaborationTimelineInputHints(width, unresolvedOnly, back) {
518
+ const backAction = `Esc ${back}`;
519
+ const filterAction = unresolvedOnly ? "U all" : "U unresolved";
520
+ const compactFilterAction = unresolvedOnly ? "U all" : "U open";
521
+ return selectInputHints(width, [
522
+ {
523
+ label: "timeline",
524
+ detail: ` · Up/Dn event · Enter detail · Tab feature · ${filterAction} · R refresh · ${backAction}`
525
+ },
526
+ {
527
+ label: "timeline",
528
+ detail: ` · Up/Dn event · Enter detail · Tab feature · ${compactFilterAction} · R refresh · ${backAction}`
529
+ },
530
+ {
531
+ label: "timeline",
532
+ detail: ` · Up/Dn event · Enter detail · Tab feature · ${compactFilterAction} · ${backAction}`
533
+ },
534
+ { label: "timeline", detail: ` · Up/Dn event · Enter detail · Tab feature · ${backAction}` },
535
+ { label: "timeline", detail: ` · Up/Dn event · Enter detail · ${backAction}` },
536
+ { label: "timeline", detail: ` · Enter detail · ${backAction}` },
537
+ { label: "timeline", detail: ` · ${backAction}` },
538
+ { label: backAction, detail: "" },
539
+ { label: "flow", detail: "" },
540
+ { label: "tl", detail: "" },
541
+ { label: "t", detail: "" }
542
+ ]);
543
+ }
544
+ function collaborationDetailInputHints(width) {
545
+ return selectInputHints(width, [
546
+ { label: "event detail", detail: " · scroll · Enter/Esc timeline" },
547
+ { label: "event", detail: " · Pg scroll · Enter/Esc timeline" },
548
+ { label: "event", detail: " · Pg scroll · Esc timeline" },
549
+ { label: "event", detail: " · Esc timeline" },
550
+ { label: "Esc timeline", detail: "" },
551
+ { label: "event", detail: "" },
552
+ { label: "e", detail: "" }
553
+ ]);
554
+ }
555
+ function taskSessionsInputHints(width, includeArchived) {
556
+ const archivedAction = includeArchived ? "H hide archived" : "H archived";
557
+ return selectInputHints(width, [
558
+ { label: "sessions", detail: ` · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · E export · ${archivedAction} · Esc back` },
559
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · E export · Esc back" },
560
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · Esc back" },
561
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · Esc back" },
562
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · Esc back" },
563
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · Esc back" },
564
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · Esc back" },
565
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · Esc back" },
566
+ { label: "sessions", detail: " · Up/Dn select · Esc back" },
567
+ { label: "sessions", detail: " · Esc back" },
568
+ { label: "Esc back", detail: "" },
569
+ { label: "sessions", detail: "" },
570
+ { label: "ses", detail: "" },
571
+ { label: "s", detail: "" }
572
+ ]);
573
+ }
574
+ function taskSessionDetailInputHints(width, hasNative, canFork) {
575
+ const nativeActions = hasNative
576
+ ? `${canFork ? "C continue · B branch" : "C continue"} · `
577
+ : "";
578
+ return selectInputHints(width, [
579
+ { label: "session", detail: ` · Up/Dn worker · Enter logs · ${nativeActions}R refresh · Esc tasks` },
580
+ { label: "session", detail: ` · Up/Dn worker · Enter logs · ${nativeActions}Esc tasks` },
581
+ { label: "session", detail: ` · Up/Dn · Enter logs · ${nativeActions}Esc tasks` },
582
+ { label: "session", detail: ` · Enter logs · ${nativeActions}Esc tasks` },
583
+ { label: "session", detail: ` · ${hasNative ? "C continue · " : ""}Esc tasks` },
584
+ { label: "session", detail: " · Esc tasks" },
585
+ { label: "Esc tasks", detail: "" },
586
+ { label: "session", detail: "" },
587
+ { label: "ses", detail: "" },
588
+ { label: "s", detail: "" }
589
+ ]);
590
+ }
591
+ function taskSessionDeleteInputHints(width, title) {
592
+ const safeTitle = title.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
593
+ return selectInputHints(width, [
594
+ { label: "delete", detail: ` · ${safeTitle} · D confirm · Esc cancel` },
595
+ { label: "delete", detail: " · D confirm · Esc cancel" },
596
+ { label: "D confirm", detail: " · Esc cancel" },
597
+ { label: "D confirm", detail: "" },
598
+ { label: "D", detail: "" }
599
+ ]);
600
+ }
601
+ function workerSearchInputSuffix(width, matchIndex, matchCount) {
602
+ const count = Math.max(0, Math.trunc(matchCount));
603
+ const index = count > 0
604
+ ? Math.min(count - 1, Math.max(0, Math.trunc(matchIndex))) + 1
605
+ : 0;
606
+ const position = `${index}/${count}`;
607
+ if (width < 16) {
608
+ return "";
609
+ }
610
+ if (width < 24) {
611
+ return ` · ${position}`;
612
+ }
613
+ if (width < 36) {
614
+ return ` · ${position} · Esc`;
615
+ }
616
+ if (width < 52) {
617
+ return ` · ${position} · Enter · Esc`;
618
+ }
619
+ return ` · ${position} · Enter next · Up/Dn · Esc logs`;
108
620
  }
109
621
  function nativeInputHints(width, closed = false) {
110
622
  if (closed) {
@@ -117,13 +629,10 @@ function nativeInputHints(width, closed = false) {
117
629
  if (width < 24) {
118
630
  return { label: "closed", detail: " · ^]" };
119
631
  }
120
- if (width < 36) {
632
+ if (width < 27) {
121
633
  return { label: "closed", detail: " · Pg · ^]" };
122
634
  }
123
- if (width < 56) {
124
- return { label: "closed", detail: " · Pg/wheel · ^]" };
125
- }
126
- return { label: "closed", detail: " · wheel/Pg · ^] back" };
635
+ return { label: "closed", detail: " · scroll · ^] logs" };
127
636
  }
128
637
  if (width < 12) {
129
638
  return { label: "nat", detail: " ^]" };
@@ -131,11 +640,26 @@ function nativeInputHints(width, closed = false) {
131
640
  if (width < 24) {
132
641
  return { label: "native", detail: " · ^]" };
133
642
  }
134
- if (width < 36) {
643
+ if (width < 27) {
135
644
  return { label: "native", detail: " · Pg · ^]" };
136
645
  }
137
- if (width < 56) {
138
- return { label: "native", detail: " · Pg/wheel · ^]" };
139
- }
140
- return { label: "native", detail: " · wheel/Pg · ^] detach" };
646
+ return { label: "native", detail: " · scroll · ^] logs" };
647
+ }
648
+ function routerInputHints(width) {
649
+ return selectInputHints(width, [
650
+ { label: "routes", detail: " · scroll · Tab scope · ^G refresh · Esc chat" },
651
+ { label: "routes", detail: " · scroll · ^G refresh · Esc chat" },
652
+ { label: "routes", detail: " · Pg scroll · Esc chat" },
653
+ { label: "routes", detail: " · Esc chat" },
654
+ { label: "Esc chat", detail: "" },
655
+ { label: "routes", detail: "" },
656
+ { label: "rt", detail: "" },
657
+ { label: "r", detail: "" }
658
+ ]);
659
+ }
660
+ function selectInputHints(width, candidates) {
661
+ const contentWidth = Math.max(1, Math.trunc(width) - (width > 1 ? 2 : 0));
662
+ return candidates.find((candidate) => displayWidth(`${candidate.label}${candidate.detail}`) <= contentWidth)
663
+ ?? candidates.at(-1)
664
+ ?? { label: "", detail: "" };
141
665
  }