@deepseek-harness-tui/dsh-tui 0.8.8 → 0.9.0
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 +27 -8
- package/bin/dsh-tui.js +1 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +4 -2
- package/lib/types/components/ActivityPicker.d.ts +4 -1
- package/lib/types/components/ActivityPicker.d.ts.map +1 -1
- package/lib/types/components/ActivityPicker.js +2 -2
- package/lib/types/components/BtwPanel.d.ts.map +1 -1
- package/lib/types/components/BtwPanel.js +1 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts +2 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -1
- package/lib/types/components/Chat/SubagentMessage.js +11 -3
- package/lib/types/components/CommandSuggestions.d.ts +21 -4
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
- package/lib/types/components/CommandSuggestions.js +41 -14
- package/lib/types/components/EffortSlider.d.ts +4 -1
- package/lib/types/components/EffortSlider.d.ts.map +1 -1
- package/lib/types/components/EffortSlider.js +4 -2
- package/lib/types/components/ExtensionDialog.js +6 -2
- package/lib/types/components/FileSuggestions.d.ts +24 -7
- package/lib/types/components/FileSuggestions.d.ts.map +1 -1
- package/lib/types/components/FileSuggestions.js +39 -12
- package/lib/types/components/GoalTodoPanel.d.ts +26 -2
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
- package/lib/types/components/GoalTodoPanel.js +86 -17
- package/lib/types/components/HelpMenu.d.ts +7 -1
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +12 -4
- package/lib/types/components/HistorySearchDialog.d.ts +4 -1
- package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
- package/lib/types/components/HistorySearchDialog.js +2 -2
- package/lib/types/components/LangPicker.d.ts +14 -0
- package/lib/types/components/LangPicker.d.ts.map +1 -0
- package/lib/types/components/LangPicker.js +17 -0
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
- package/lib/types/components/LoadedContextPanel.js +3 -1
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +12 -3
- package/lib/types/components/MessageList.d.ts +43 -2
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +553 -41
- package/lib/types/components/ModelPicker.d.ts +4 -1
- package/lib/types/components/ModelPicker.d.ts.map +1 -1
- package/lib/types/components/ModelPicker.js +2 -2
- package/lib/types/components/PermissionsPicker.d.ts +21 -0
- package/lib/types/components/PermissionsPicker.d.ts.map +1 -0
- package/lib/types/components/PermissionsPicker.js +27 -0
- package/lib/types/components/PlanPicker.d.ts +15 -0
- package/lib/types/components/PlanPicker.d.ts.map +1 -0
- package/lib/types/components/PlanPicker.js +18 -0
- package/lib/types/components/PresetPicker.d.ts +4 -1
- package/lib/types/components/PresetPicker.d.ts.map +1 -1
- package/lib/types/components/PresetPicker.js +2 -2
- package/lib/types/components/PromptInput.d.ts +4 -3
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +248 -29
- package/lib/types/components/RewindPicker.d.ts +12 -1
- package/lib/types/components/RewindPicker.d.ts.map +1 -1
- package/lib/types/components/RewindPicker.js +6 -4
- package/lib/types/components/ScrollbarGutter.d.ts +28 -0
- package/lib/types/components/ScrollbarGutter.d.ts.map +1 -0
- package/lib/types/components/ScrollbarGutter.js +68 -0
- package/lib/types/components/Select.d.ts +8 -1
- package/lib/types/components/Select.d.ts.map +1 -1
- package/lib/types/components/Select.js +4 -2
- package/lib/types/components/SkillsPicker.d.ts +4 -1
- package/lib/types/components/SkillsPicker.d.ts.map +1 -1
- package/lib/types/components/SkillsPicker.js +2 -2
- package/lib/types/components/SubagentCard.d.ts +2 -1
- package/lib/types/components/SubagentCard.d.ts.map +1 -1
- package/lib/types/components/SubagentCard.js +6 -1
- package/lib/types/components/SubagentDashboard.d.ts +4 -0
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
- package/lib/types/components/SubagentDashboard.js +9 -1
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
- package/lib/types/components/SubagentDetailScene.js +5 -4
- package/lib/types/components/SuggestionCard.d.ts +63 -0
- package/lib/types/components/SuggestionCard.d.ts.map +1 -0
- package/lib/types/components/SuggestionCard.js +77 -0
- package/lib/types/components/ThemePicker.d.ts +4 -1
- package/lib/types/components/ThemePicker.d.ts.map +1 -1
- package/lib/types/components/ThemePicker.js +2 -2
- package/lib/types/components/ThinkingToggle.d.ts +4 -1
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
- package/lib/types/components/ThinkingToggle.js +2 -2
- package/lib/types/components/TimelineRail.d.ts +53 -0
- package/lib/types/components/TimelineRail.d.ts.map +1 -0
- package/lib/types/components/TimelineRail.js +190 -0
- package/lib/types/components/WorkspaceFlowPicker.d.ts +6 -1
- package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -1
- package/lib/types/components/WorkspaceFlowPicker.js +4 -2
- package/lib/types/components/WorkspaceMenuPicker.d.ts +19 -0
- package/lib/types/components/WorkspaceMenuPicker.d.ts.map +1 -0
- package/lib/types/components/WorkspaceMenuPicker.js +19 -0
- package/lib/types/components/WorkspacePicker.d.ts +4 -1
- package/lib/types/components/WorkspacePicker.d.ts.map +1 -1
- package/lib/types/components/WorkspacePicker.js +2 -2
- package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
- package/lib/types/components/approvals/ApprovalPanel.js +5 -1
- package/lib/types/components/design-system/ListItem.d.ts +8 -1
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
- package/lib/types/components/design-system/ListItem.js +8 -3
- package/lib/types/components/messages/AssistantTextMessage.d.ts +5 -2
- package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantTextMessage.js +6 -2
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts +2 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +10 -4
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +8 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +4 -2
- package/lib/types/components/messages/UserPromptMessage.d.ts +2 -1
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +34 -2
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +13 -2
- package/lib/types/components/sessions/SessionListRow.d.ts +4 -1
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
- package/lib/types/components/sessions/SessionListRow.js +4 -2
- package/lib/types/components/trajectory/HotspotView.d.ts +7 -1
- package/lib/types/components/trajectory/HotspotView.d.ts.map +1 -1
- package/lib/types/components/trajectory/HotspotView.js +8 -2
- package/lib/types/components/trajectory/Ledger.d.ts +9 -1
- package/lib/types/components/trajectory/Ledger.d.ts.map +1 -1
- package/lib/types/components/trajectory/Ledger.js +23 -5
- package/lib/types/components/trajectory/WaveBand.d.ts +10 -1
- package/lib/types/components/trajectory/WaveBand.d.ts.map +1 -1
- package/lib/types/components/trajectory/WaveBand.js +7 -2
- package/lib/types/dsh-adapter/channel.d.ts +16 -5
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +332 -29
- package/lib/types/dsh-adapter/index.d.ts +10 -2
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +4 -1
- package/lib/types/dsh-adapter/plugin.d.ts +0 -9
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +136 -6
- package/lib/types/i18n.d.ts +176 -15
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +51 -8
- package/lib/types/ink/components/AlternateScreen.d.ts.map +1 -1
- package/lib/types/ink/components/AlternateScreen.js +13 -1
- package/lib/types/ink/components/App.d.ts +12 -1
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +101 -4
- package/lib/types/ink/components/ClockContext.d.ts.map +1 -1
- package/lib/types/ink/components/ClockContext.js +2 -0
- package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
- package/lib/types/ink/components/ScrollBox.js +170 -91
- package/lib/types/ink/constants.d.ts +9 -0
- package/lib/types/ink/constants.d.ts.map +1 -1
- package/lib/types/ink/constants.js +9 -0
- package/lib/types/ink/events/click-event.d.ts +7 -16
- package/lib/types/ink/events/click-event.d.ts.map +1 -1
- package/lib/types/ink/events/click-event.js +16 -19
- package/lib/types/ink/events/event-handlers.d.ts +11 -1
- package/lib/types/ink/events/event-handlers.d.ts.map +1 -1
- package/lib/types/ink/events/event-handlers.js +2 -0
- package/lib/types/ink/events/input-event.d.ts +9 -0
- package/lib/types/ink/events/input-event.d.ts.map +1 -1
- package/lib/types/ink/events/input-event.js +4 -0
- package/lib/types/ink/events/pointer-event.d.ts +61 -0
- package/lib/types/ink/events/pointer-event.d.ts.map +1 -0
- package/lib/types/ink/events/pointer-event.js +61 -0
- package/lib/types/ink/events/wheel-event.d.ts +17 -0
- package/lib/types/ink/events/wheel-event.d.ts.map +1 -0
- package/lib/types/ink/events/wheel-event.js +20 -0
- package/lib/types/ink/geometry-trace.d.ts +51 -0
- package/lib/types/ink/geometry-trace.d.ts.map +1 -0
- package/lib/types/ink/geometry-trace.js +83 -0
- package/lib/types/ink/hit-test.d.ts +56 -1
- package/lib/types/ink/hit-test.d.ts.map +1 -1
- package/lib/types/ink/hit-test.js +146 -8
- package/lib/types/ink/ink.d.ts +58 -1
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +230 -29
- package/lib/types/ink/node-cache.d.ts +5 -0
- package/lib/types/ink/node-cache.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.d.ts +8 -0
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +101 -12
- package/lib/types/ink/render-node-to-output.d.ts +10 -0
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +111 -18
- package/lib/types/ink/timeline-rail.d.ts +138 -0
- package/lib/types/ink/timeline-rail.d.ts.map +1 -0
- package/lib/types/ink/timeline-rail.js +179 -0
- package/lib/types/ink/wrap-text.d.ts.map +1 -1
- package/lib/types/ink/wrap-text.js +47 -5
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +819 -337
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
- package/lib/types/screens/SessionBrowser.js +56 -29
- package/lib/types/screens/Settings.d.ts.map +1 -1
- package/lib/types/screens/Settings.js +64 -22
- package/lib/types/screens/StatusLine.d.ts +2 -1
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +17 -1
- package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
- package/lib/types/screens/TrajectoryScene.js +81 -14
- package/lib/types/screens/chatOverlay.d.ts +221 -0
- package/lib/types/screens/chatOverlay.d.ts.map +1 -0
- package/lib/types/screens/chatOverlay.js +114 -0
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +42 -6
- package/lib/types/tuiDisplayPrefs.d.ts +15 -0
- package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
- package/lib/types/tuiDisplayPrefs.js +9 -0
- package/lib/types/utils/debug.d.ts +10 -0
- package/lib/types/utils/debug.d.ts.map +1 -1
- package/lib/types/utils/debug.js +24 -0
- package/lib/types/utils/paths.d.ts.map +1 -1
- package/lib/types/utils/paths.js +1 -0
- package/package.json +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionBrowser.d.ts","sourceRoot":"","sources":["../../../src/screens/SessionBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAsBzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAoDxD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,WAAW,EACX,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"SessionBrowser.d.ts","sourceRoot":"","sources":["../../../src/screens/SessionBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAsBzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAoDxD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,WAAW,EACX,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CAkhBlB"}
|
|
@@ -103,6 +103,8 @@ export function SessionBrowser({ channel, home, sameProject, onClose, }) {
|
|
|
103
103
|
const topRef = React.useRef(0);
|
|
104
104
|
const [mode, setMode] = React.useState('list');
|
|
105
105
|
const [renameText, setRenameText] = React.useState('');
|
|
106
|
+
/** Hover state for the confirm rows (only one is ever mounted). */
|
|
107
|
+
const [confirmHovered, setConfirmHovered] = React.useState(false);
|
|
106
108
|
// The browser owns the whole screen, so it has to carry its own messages.
|
|
107
109
|
// `channel.notify` renders into the conversation — which is exactly what is
|
|
108
110
|
// NOT on screen right now — so a failed delete, a refused resume, or an
|
|
@@ -165,6 +167,38 @@ export function SessionBrowser({ channel, home, sameProject, onClose, }) {
|
|
|
165
167
|
return byId >= 0 ? byId : Math.max(0, seekSelectable(view.rows, 0, 1));
|
|
166
168
|
}, [view.rows, focusId]);
|
|
167
169
|
const focused = sessionAt(view.rows, focus);
|
|
170
|
+
/**
|
|
171
|
+
* Resume a session (Enter and row-click share this path). The screen closes
|
|
172
|
+
* only once the resume actually happened. Closing first and letting a
|
|
173
|
+
* refusal fall through to a notification would send that explanation to the
|
|
174
|
+
* conversation the user is not looking at, and leave them staring at an
|
|
175
|
+
* unchanged transcript wondering what Enter did. `resumeTo` reports its own
|
|
176
|
+
* reasons; this only has to stay put.
|
|
177
|
+
*/
|
|
178
|
+
const resumeSession = (target) => {
|
|
179
|
+
runAction(async () => {
|
|
180
|
+
try {
|
|
181
|
+
const result = await channel.resumeTo(target.id);
|
|
182
|
+
if (result.ok) {
|
|
183
|
+
channel.notify(t('resume-resumed'));
|
|
184
|
+
onClose();
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
if (result.reason === 'cancelled')
|
|
188
|
+
return;
|
|
189
|
+
const text = result.reason === 'working'
|
|
190
|
+
? t('resume-while-working')
|
|
191
|
+
: result.reason === 'unavailable'
|
|
192
|
+
? t('resume-unavailable')
|
|
193
|
+
: t('session-resume-failed', { err: result.error });
|
|
194
|
+
setNotice({ text, tone: 'error' });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
setNotice({ text: t('session-resume-failed', { err: message(error) }), tone: 'error' });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
};
|
|
168
202
|
// Where the cursor is RIGHT NOW, including moves made earlier in this same
|
|
169
203
|
// tick. A held arrow key (or a paste) delivers several key events from one
|
|
170
204
|
// stdin chunk, and every one of them runs before React re-renders — so a
|
|
@@ -338,6 +372,13 @@ export function SessionBrowser({ channel, home, sameProject, onClose, }) {
|
|
|
338
372
|
else if (key.downArrow) {
|
|
339
373
|
step(1);
|
|
340
374
|
}
|
|
375
|
+
else if (key.wheelUp) {
|
|
376
|
+
// 滚轮在列表上:与 ↑ 同路径——焦点跟随窗口下移焦点即滚动
|
|
377
|
+
step(-1);
|
|
378
|
+
}
|
|
379
|
+
else if (key.wheelDown) {
|
|
380
|
+
step(1);
|
|
381
|
+
}
|
|
341
382
|
else if (key.pageUp || key.pageDown) {
|
|
342
383
|
// A page is "as many rows as the window holds", taken as repeated single
|
|
343
384
|
// steps so it lands on a selectable row like every other move.
|
|
@@ -346,34 +387,7 @@ export function SessionBrowser({ channel, home, sameProject, onClose, }) {
|
|
|
346
387
|
else if (isPlainReturn(key)) {
|
|
347
388
|
if (focused === undefined)
|
|
348
389
|
return;
|
|
349
|
-
|
|
350
|
-
// The screen closes only once the resume actually happened. Closing
|
|
351
|
-
// first and letting a refusal fall through to a notification would send
|
|
352
|
-
// that explanation to the conversation the user is not looking at, and
|
|
353
|
-
// leave them staring at an unchanged transcript wondering what Enter
|
|
354
|
-
// did. `resumeTo` reports its own reasons; this only has to stay put.
|
|
355
|
-
runAction(async () => {
|
|
356
|
-
try {
|
|
357
|
-
const result = await channel.resumeTo(target.id);
|
|
358
|
-
if (result.ok) {
|
|
359
|
-
channel.notify(t('resume-resumed'));
|
|
360
|
-
onClose();
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
if (result.reason === 'cancelled')
|
|
364
|
-
return;
|
|
365
|
-
const text = result.reason === 'working'
|
|
366
|
-
? t('resume-while-working')
|
|
367
|
-
: result.reason === 'unavailable'
|
|
368
|
-
? t('resume-unavailable')
|
|
369
|
-
: t('session-resume-failed', { err: result.error });
|
|
370
|
-
setNotice({ text, tone: 'error' });
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
catch (error) {
|
|
374
|
-
setNotice({ text: t('session-resume-failed', { err: message(error) }), tone: 'error' });
|
|
375
|
-
}
|
|
376
|
-
});
|
|
390
|
+
resumeSession(focused);
|
|
377
391
|
}
|
|
378
392
|
else if (key.escape) {
|
|
379
393
|
// Esc backs out one layer at a time: a live query first, the screen
|
|
@@ -455,5 +469,18 @@ export function SessionBrowser({ channel, home, sameProject, onClose, }) {
|
|
|
455
469
|
t('session-hint-list-mid', { mod: modLabel }),
|
|
456
470
|
t('session-hint-list-short'),
|
|
457
471
|
], inputBudget);
|
|
458
|
-
return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "remember", bold: true, children: header.left }), _jsx(Text, { dimColor: true, children: `${' '.repeat(header.gap)}${header.right}` })] }), rules.has(0) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(filters.query, inputBudget), isFocused: mode === 'list', isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('session-search-placeholder', { scope }), inputBudget), borderless: true }) }), rules.has(1) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsxs(Box, { flexGrow: 1, flexShrink: 1, children: [!soloPreview && (_jsxs(Box, { flexDirection: "column", width: listWidth, height: listHeight, flexShrink: 0, children: [!loaded && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('session-loading'), listWidth - 2)}` })), loaded && view.rows.length === 0 && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('resume-none-in-cwd'), listWidth - 2)}` })), visible.map((row, index) => row.kind === 'project' ? (_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "planMode", children: truncateWidth(` ${formatProject(row.project, home)}`, listWidth - 6) }), _jsx(Text, { dimColor: true, children: ` ${row.count}` })] }, `project:${row.project}:${index}`)) : (_jsx(SessionListRow, { session: row.session, width: listWidth, depth: row.depth, focused: windowTop + index === focus, now: now
|
|
472
|
+
return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "remember", bold: true, children: header.left }), _jsx(Text, { dimColor: true, children: `${' '.repeat(header.gap)}${header.right}` })] }), rules.has(0) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(filters.query, inputBudget), isFocused: mode === 'list', isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('session-search-placeholder', { scope }), inputBudget), borderless: true }) }), rules.has(1) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsxs(Box, { flexGrow: 1, flexShrink: 1, children: [!soloPreview && (_jsxs(Box, { flexDirection: "column", width: listWidth, height: listHeight, flexShrink: 0, children: [!loaded && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('session-loading'), listWidth - 2)}` })), loaded && view.rows.length === 0 && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('resume-none-in-cwd'), listWidth - 2)}` })), visible.map((row, index) => row.kind === 'project' ? (_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "planMode", children: truncateWidth(` ${formatProject(row.project, home)}`, listWidth - 6) }), _jsx(Text, { dimColor: true, children: ` ${row.count}` })] }, `project:${row.project}:${index}`)) : (_jsx(SessionListRow, { session: row.session, width: listWidth, depth: row.depth, focused: windowTop + index === focus, now: now,
|
|
473
|
+
// 点击行 = 聚焦 + 恢复该会话(与 Enter 同路径)
|
|
474
|
+
onClick: () => {
|
|
475
|
+
setFocusId(row.session.id);
|
|
476
|
+
resumeSession(row.session);
|
|
477
|
+
} }, row.session.id)))] })), (splitPreview || soloPreview) && focused !== undefined && (_jsx(SessionPreview, { session: focused, entries: preview, loading: previewLoading, width: previewWidth, height: listHeight, home: home, now: now }))] }), notice !== undefined && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: notice.tone === 'error' ? 'error' : 'success', children: ` ${truncateWidth(notice.text, Math.max(0, columns - 2))}` }) })), mode === 'confirm-delete' && focused !== undefined && (_jsx(Box, { flexShrink: 0, onClick: () => {
|
|
478
|
+
// 点击确认行 = 确认删除(与 Enter 同路径);确认屏本身就是
|
|
479
|
+
// 显式确认层,取消保留键盘 Esc,防误点
|
|
480
|
+
setMode('list');
|
|
481
|
+
runDelete(focused);
|
|
482
|
+
}, onMouseEnter: () => setConfirmHovered(true), onMouseLeave: () => setConfirmHovered(false), backgroundColor: confirmHovered ? 'userMessageBackgroundHover' : undefined, children: _jsx(Text, { color: "error", children: ` ${truncateWidth(t('resume-delete-confirm', { name: focused.title.text }), inputBudget)}` }) })), mode === 'confirm-clean' && (_jsx(Box, { flexShrink: 0, onClick: () => {
|
|
483
|
+
setMode('list');
|
|
484
|
+
runClean();
|
|
485
|
+
}, onMouseEnter: () => setConfirmHovered(true), onMouseLeave: () => setConfirmHovered(false), backgroundColor: confirmHovered ? 'userMessageBackgroundHover' : undefined, children: _jsx(Text, { color: "warning", children: ` ${truncateWidth(t('session-clean-confirm', { n: view.emptyCount }), inputBudget)}` }) })), mode === 'rename' && (_jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(renameText, inputBudget), isFocused: true, isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('resume-rename-placeholder'), inputBudget), prefix: "\u270E", borderless: true }) })), rules.has(2) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: hint }) }) })] }));
|
|
459
486
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Settings.d.ts","sourceRoot":"","sources":["../../../src/screens/Settings.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"Settings.d.ts","sourceRoot":"","sources":["../../../src/screens/Settings.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAWzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AA+CxD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CAkdlB"}
|
|
@@ -160,6 +160,39 @@ export function Settings({ channel, onClose, }) {
|
|
|
160
160
|
setNotice(undefined);
|
|
161
161
|
bump();
|
|
162
162
|
};
|
|
163
|
+
/**
|
|
164
|
+
* Activate one focusable entry — the keyboard Enter path, shared with the
|
|
165
|
+
* mouse click. Groups open their subpage; boolean/select fields cycle their
|
|
166
|
+
* value; text/secret fields enter the edit mode.
|
|
167
|
+
*/
|
|
168
|
+
const activateEntry = (entry) => {
|
|
169
|
+
if (entry.kind === 'group') {
|
|
170
|
+
setActiveGroup({ ns: entry.ns, id: entry.group.id });
|
|
171
|
+
setFocusIndex(0);
|
|
172
|
+
setWindowStart(0);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// A save in flight owns the section's drafts; starting an edit mid-write
|
|
176
|
+
// is exactly the lost-draft race the staged model exists to prevent.
|
|
177
|
+
const form = forms.get(entry.ns);
|
|
178
|
+
if (form === undefined || !form.available || form.saving)
|
|
179
|
+
return;
|
|
180
|
+
if (entry.field.kind === 'boolean' || entry.field.kind === 'select') {
|
|
181
|
+
cycleField(entry.ns, entry.field);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
setEditing({ ns: entry.ns, field: entry.field, draft: form.field(entry.field).text });
|
|
185
|
+
setMode('edit');
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
/** Mouse wheel moves the focus (the window is focus-follow, so moving the
|
|
189
|
+
* focus IS scrolling); the edit mode keeps the keyboard as sole owner. */
|
|
190
|
+
const handleWheel = (event) => {
|
|
191
|
+
if (mode === 'edit')
|
|
192
|
+
return;
|
|
193
|
+
const direction = event.deltaY >= 0 ? 1 : -1;
|
|
194
|
+
setFocusIndex(previous => Math.min(Math.max(0, focusable.length - 1), Math.max(0, previous + direction)));
|
|
195
|
+
};
|
|
163
196
|
useInput((input, key) => {
|
|
164
197
|
if (mode === 'edit' && editing !== null) {
|
|
165
198
|
if (isPlainReturn(key)) {
|
|
@@ -192,23 +225,7 @@ export function Settings({ channel, onClose, }) {
|
|
|
192
225
|
setFocusIndex(Math.min(Math.max(0, focusable.length - 1), effFocus + 1));
|
|
193
226
|
}
|
|
194
227
|
else if (isPlainReturn(key) && focused !== undefined) {
|
|
195
|
-
|
|
196
|
-
setActiveGroup({ ns: focused.ns, id: focused.group.id });
|
|
197
|
-
setFocusIndex(0);
|
|
198
|
-
setWindowStart(0);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
// A save in flight owns the section's drafts; starting an edit mid-write
|
|
202
|
-
// is exactly the lost-draft race the staged model exists to prevent.
|
|
203
|
-
if (focusedForm === undefined || !focusedForm.available || focusedForm.saving)
|
|
204
|
-
return;
|
|
205
|
-
if (focused.field.kind === 'boolean' || focused.field.kind === 'select') {
|
|
206
|
-
cycleField(focused.ns, focused.field);
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
setEditing({ ns: focused.ns, field: focused.field, draft: focusedForm.field(focused.field).text });
|
|
210
|
-
setMode('edit');
|
|
211
|
-
}
|
|
228
|
+
activateEntry(focused);
|
|
212
229
|
}
|
|
213
230
|
else if (input === 's' && focused !== undefined) {
|
|
214
231
|
commitSave(focused.ns);
|
|
@@ -246,7 +263,7 @@ export function Settings({ channel, onClose, }) {
|
|
|
246
263
|
}
|
|
247
264
|
}
|
|
248
265
|
});
|
|
249
|
-
const renderField = (section, field) => {
|
|
266
|
+
const renderField = (section, field, focus) => {
|
|
250
267
|
const ns = section.ns;
|
|
251
268
|
const form = forms.get(ns);
|
|
252
269
|
const state = form?.field(field) ?? { text: '', overridden: false, invalid: false };
|
|
@@ -254,6 +271,20 @@ export function Settings({ channel, onClose, }) {
|
|
|
254
271
|
const isEditing = isFocused && mode === 'edit' && editing !== null;
|
|
255
272
|
const label = pick(field.label, field.descriptions);
|
|
256
273
|
const hint = field.hint !== undefined ? pick(field.hint, field.hintDescriptions) : undefined;
|
|
274
|
+
// 鼠标:编辑态整屏不响应(草稿由键盘独占,防误触抢焦点);列表态
|
|
275
|
+
// 悬停即移动焦点(lazygit 语义——焦点就是选中),点击 = 焦点 + 该行
|
|
276
|
+
// 的 Enter 动作(boolean/select 循环值,文本/secret 进编辑态)。
|
|
277
|
+
const rowEvents = mode === 'edit'
|
|
278
|
+
? undefined
|
|
279
|
+
: {
|
|
280
|
+
onClick: () => {
|
|
281
|
+
setFocusIndex(focus);
|
|
282
|
+
activateEntry({ kind: 'field', ns: section.ns, field });
|
|
283
|
+
},
|
|
284
|
+
onMouseEnter: () => {
|
|
285
|
+
setFocusIndex(focus);
|
|
286
|
+
},
|
|
287
|
+
};
|
|
257
288
|
let value;
|
|
258
289
|
if (field.secret !== undefined) {
|
|
259
290
|
const configured = secrets.get(`${ns}:${field.path.join('.')}`) === true;
|
|
@@ -276,7 +307,7 @@ export function Settings({ channel, onClose, }) {
|
|
|
276
307
|
else {
|
|
277
308
|
value = state.text;
|
|
278
309
|
}
|
|
279
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: isFocused ? 'suggestion' : undefined, children: isFocused ? '❯ ' : ' ' }), _jsx(Text, { bold: isFocused, children: label }), _jsx(Box, { flexGrow: 1 }), state.invalid && _jsxs(Text, { color: "error", children: [t('settings-field-invalid'), " "] }), state.overridden && _jsxs(Text, { dimColor: true, children: ["[", t('settings-badge-override'), "] "] }), form?.isStaged(field) === true && !isEditing && _jsx(Text, { color: "suggestion", children: "* " }), _jsx(Text, { color: isEditing || isFocused ? 'suggestion' : undefined, dimColor: !isFocused && !isEditing && state.text === '', children: value }), hint !== undefined && isFocused && (_jsxs(Text, { dimColor: true, children: ['\n ', hint] }))] }));
|
|
310
|
+
return (_jsxs(Box, { ...rowEvents, children: [_jsx(Text, { color: isFocused ? 'suggestion' : undefined, children: isFocused ? '❯ ' : ' ' }), _jsx(Text, { bold: isFocused, children: label }), _jsx(Box, { flexGrow: 1 }), state.invalid && _jsxs(Text, { color: "error", children: [t('settings-field-invalid'), " "] }), state.overridden && _jsxs(Text, { dimColor: true, children: ["[", t('settings-badge-override'), "] "] }), form?.isStaged(field) === true && !isEditing && _jsx(Text, { color: "suggestion", children: "* " }), _jsx(Text, { color: isEditing || isFocused ? 'suggestion' : undefined, dimColor: !isFocused && !isEditing && state.text === '', children: value }), hint !== undefined && isFocused && (_jsxs(Text, { dimColor: true, children: ['\n ', hint] }))] }));
|
|
280
311
|
};
|
|
281
312
|
// ── Layout: a flat entry list with accounted line heights, windowed so the
|
|
282
313
|
// focused row is always on screen no matter how long the current page gets. ─
|
|
@@ -290,7 +321,7 @@ export function Settings({ channel, onClose, }) {
|
|
|
290
321
|
key: `field:${section.ns}:${field.path.join('.')}`,
|
|
291
322
|
lines: field.hint !== undefined && isFocused ? 2 : 1,
|
|
292
323
|
focus: index,
|
|
293
|
-
node: renderField(section, field),
|
|
324
|
+
node: renderField(section, field, index),
|
|
294
325
|
});
|
|
295
326
|
};
|
|
296
327
|
if (activeSection !== undefined && activeGroupSpec !== undefined) {
|
|
@@ -319,11 +350,22 @@ export function Settings({ channel, onClose, }) {
|
|
|
319
350
|
const isFocused = focused?.kind === 'group' && focused.ns === section.ns && focused.group === group;
|
|
320
351
|
const index = focusCursor;
|
|
321
352
|
focusCursor += 1;
|
|
353
|
+
const groupRowEvents = mode === 'edit'
|
|
354
|
+
? undefined
|
|
355
|
+
: {
|
|
356
|
+
onClick: () => {
|
|
357
|
+
setFocusIndex(index);
|
|
358
|
+
activateEntry({ kind: 'group', ns: section.ns, group });
|
|
359
|
+
},
|
|
360
|
+
onMouseEnter: () => {
|
|
361
|
+
setFocusIndex(index);
|
|
362
|
+
},
|
|
363
|
+
};
|
|
322
364
|
entries.push({
|
|
323
365
|
key: `group:${section.ns}:${group.id}`,
|
|
324
366
|
lines: 1,
|
|
325
367
|
focus: index,
|
|
326
|
-
node: (_jsxs(Box, { children: [_jsx(Text, { color: isFocused ? 'suggestion' : undefined, children: isFocused ? '❯ ' : ' ' }), _jsx(Text, { bold: isFocused, children: pick(group.title, group.descriptions) }), _jsx(Box, { flexGrow: 1 }), _jsx(Text, { color: isFocused ? 'suggestion' : undefined, children: "\u203A" })] })),
|
|
368
|
+
node: (_jsxs(Box, { ...groupRowEvents, children: [_jsx(Text, { color: isFocused ? 'suggestion' : undefined, children: isFocused ? '❯ ' : ' ' }), _jsx(Text, { bold: isFocused, children: pick(group.title, group.descriptions) }), _jsx(Box, { flexGrow: 1 }), _jsx(Text, { color: isFocused ? 'suggestion' : undefined, children: "\u203A" })] })),
|
|
327
369
|
});
|
|
328
370
|
}
|
|
329
371
|
});
|
|
@@ -388,5 +430,5 @@ export function Settings({ channel, onClose, }) {
|
|
|
388
430
|
? `${t('settings-title')} › ${pick(activeSection.title, activeSection.descriptions)} › ${pick(activeGroupSpec.title, activeGroupSpec.descriptions)}`
|
|
389
431
|
: t('settings-title');
|
|
390
432
|
const navigationHint = activeGroupSpec === undefined ? t('settings-hint-list') : t('settings-hint-group');
|
|
391
|
-
return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: title }), _jsx(Box, { flexGrow: 1 }), host === undefined && _jsx(Text, { color: "warning", children: t('settings-unavailable') })] }), _jsx(Divider, {}), visible.map(entry => (_jsx(React.Fragment, { children: entry.node }, entry.key))), _jsx(Box, { flexGrow: 1 }), notice !== undefined && (_jsx(Text, { color: notice.tone === 'error' ? 'error' : 'success', children: notice.text })), _jsx(Divider, {}), _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: mode === 'edit' ? t('settings-hint-edit') : navigationHint }) })] }));
|
|
433
|
+
return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: title }), _jsx(Box, { flexGrow: 1 }), host === undefined && _jsx(Text, { color: "warning", children: t('settings-unavailable') })] }), _jsx(Divider, {}), _jsx("ink-box", { style: { flexDirection: 'column', flexGrow: 1, flexShrink: 1, overflow: 'hidden' }, onWheel: handleWheel, children: visible.map(entry => (_jsx(React.Fragment, { children: entry.node }, entry.key))) }), _jsx(Box, { flexGrow: 1 }), notice !== undefined && (_jsx(Text, { color: notice.tone === 'error' ? 'error' : 'success', children: notice.text })), _jsx(Divider, {}), _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: mode === 'edit' ? t('settings-hint-edit') : navigationHint }) })] }));
|
|
392
434
|
}
|
|
@@ -5,7 +5,8 @@ import type { WaveBand } from '../dsh-adapter/types.js';
|
|
|
5
5
|
* The footer under the prompt input, in Claude Code's PromptInputFooter
|
|
6
6
|
* layout: the segmented context progress bar on its own first line, the
|
|
7
7
|
* status line below (left group: model · tokens · think level · cache · tps
|
|
8
|
-
* gauge/sparkline; right group: git · cwd · title
|
|
8
|
+
* gauge/sparkline; right group: git · cwd · title · short session id,
|
|
9
|
+
* right-aligned), and the
|
|
9
10
|
* mode/hint line last. The right side of the footer shows the latest
|
|
10
11
|
* transient notification (errors in red, warnings in amber — CC style).
|
|
11
12
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusLine.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusLine.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"StatusLine.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusLine.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAQzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAQvD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,eAAuB,EACvB,QAAgB,EAChB,IAAI,GACL,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACvD,qBA8QA;AAED,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKvF"}
|
|
@@ -5,6 +5,7 @@ import { t } from '../i18n.js';
|
|
|
5
5
|
import { formatContextUsage, DEFAULT_STATUS_BAR, normalizeStatusBar } from '../tuiDisplayPrefs.js';
|
|
6
6
|
import { Byline } from '../components/design-system/Byline.js';
|
|
7
7
|
import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
|
|
8
|
+
import { GoalStatusChip } from '../components/GoalTodoPanel.js';
|
|
8
9
|
import { modeDisplayName } from '../sessionModes.js';
|
|
9
10
|
import { MiniWake } from '../components/trajectory/MiniWake.js';
|
|
10
11
|
import { renderContextBar, renderTpsGauge, renderTpsSparkline, speedColor, } from './StatusMetrics.js';
|
|
@@ -12,7 +13,8 @@ import { renderContextBar, renderTpsGauge, renderTpsSparkline, speedColor, } fro
|
|
|
12
13
|
* The footer under the prompt input, in Claude Code's PromptInputFooter
|
|
13
14
|
* layout: the segmented context progress bar on its own first line, the
|
|
14
15
|
* status line below (left group: model · tokens · think level · cache · tps
|
|
15
|
-
* gauge/sparkline; right group: git · cwd · title
|
|
16
|
+
* gauge/sparkline; right group: git · cwd · title · short session id,
|
|
17
|
+
* right-aligned), and the
|
|
16
18
|
* mode/hint line last. The right side of the footer shows the latest
|
|
17
19
|
* transient notification (errors in red, warnings in amber — CC style).
|
|
18
20
|
*/
|
|
@@ -75,6 +77,12 @@ export function StatusLine({ channel, selectionActive = false, helpOpen = false,
|
|
|
75
77
|
: []),
|
|
76
78
|
];
|
|
77
79
|
const rightParts = [
|
|
80
|
+
// Goal chip first: session-level state outranks repo/location details.
|
|
81
|
+
...(statusBar.goal && channel.goal !== undefined
|
|
82
|
+
? [
|
|
83
|
+
_jsx(GoalStatusChip, { goal: channel.goal, minimal: channel.minimal }, "goal"),
|
|
84
|
+
]
|
|
85
|
+
: []),
|
|
78
86
|
...(statusBar.gitBranch && channel.gitBranch
|
|
79
87
|
? [
|
|
80
88
|
_jsx(Text, { color: "professionalBlue", children: channel.gitBranch }, "git"),
|
|
@@ -90,6 +98,14 @@ export function StatusLine({ channel, selectionActive = false, helpOpen = false,
|
|
|
90
98
|
_jsx(Text, { dimColor: true, children: channel.sessionTitle }, "title"),
|
|
91
99
|
]
|
|
92
100
|
: []),
|
|
101
|
+
// Short id last: a provenance tag trails the content it identifies, and
|
|
102
|
+
// the 8-char form is what the session log filename starts with, so a
|
|
103
|
+
// truncated rendering still names the right log for --resume.
|
|
104
|
+
...(statusBar.sessionId && channel.agentId
|
|
105
|
+
? [
|
|
106
|
+
_jsx(Text, { dimColor: true, children: `#${channel.agentId.slice(0, 8)}` }, "sessionId"),
|
|
107
|
+
]
|
|
108
|
+
: []),
|
|
93
109
|
];
|
|
94
110
|
const hint = selectionActive
|
|
95
111
|
? t('statusline-hint-select')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrajectoryScene.d.ts","sourceRoot":"","sources":["../../../src/screens/TrajectoryScene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"TrajectoryScene.d.ts","sourceRoot":"","sources":["../../../src/screens/TrajectoryScene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAezB,OAAO,EAML,KAAK,SAAS,EACf,MAAM,oCAAoC,CAAA;AAE3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AA+BxD,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,SAAS,CAAA;AAEnD,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,KAAK,EACL,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,sDAAsD;IACtD,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CA2hBlB"}
|
|
@@ -64,6 +64,11 @@ export function TrajectoryScene({ channel, build, onClose, }) {
|
|
|
64
64
|
const [arrivalFrom, setArrivalFrom] = React.useState(Number.MAX_SAFE_INTEGER);
|
|
65
65
|
/** Cursor pinned to the tail until the user scrolls away from it. */
|
|
66
66
|
const [follow, setFollow] = React.useState(true);
|
|
67
|
+
/** Mouse hover states for the tab segments and the sort/projection label. */
|
|
68
|
+
const [hoverTab, setHoverTab] = React.useState(null);
|
|
69
|
+
const [hoverAxis, setHoverAxis] = React.useState(false);
|
|
70
|
+
/** Hover state for the header ✕ exit button. */
|
|
71
|
+
const [closeHovered, setCloseHovered] = React.useState(false);
|
|
67
72
|
// ── projection ───────────────────────────────────────────────────────────
|
|
68
73
|
const nodes = build.nodes;
|
|
69
74
|
const query = React.useMemo(() => parseQuery(queryText), [queryText]);
|
|
@@ -147,6 +152,40 @@ export function TrajectoryScene({ channel, build, onClose, }) {
|
|
|
147
152
|
setExpanded(false);
|
|
148
153
|
setInspectScroll(0);
|
|
149
154
|
}, [tick]);
|
|
155
|
+
/**
|
|
156
|
+
* Jump back to the timeline, positioned on a hotspot group's first member —
|
|
157
|
+
* the keyboard Enter path, shared with the mouse row click.
|
|
158
|
+
*/
|
|
159
|
+
const jumpFromHotspot = React.useCallback((row) => {
|
|
160
|
+
switchView('timeline');
|
|
161
|
+
if (row !== undefined) {
|
|
162
|
+
const target = indexes.indexOf(row.firstIndex);
|
|
163
|
+
setCursor(target >= 0 ? target : 0);
|
|
164
|
+
setFollow(false);
|
|
165
|
+
}
|
|
166
|
+
}, [indexes, switchView]);
|
|
167
|
+
/** Mouse wheel over the content area: move the selection, not a viewport. */
|
|
168
|
+
const handleWheel = React.useCallback((event) => {
|
|
169
|
+
if (view === 'hotspot') {
|
|
170
|
+
const total = hotspotRows(agg).length;
|
|
171
|
+
const direction = event.deltaY >= 0 ? 1 : -1;
|
|
172
|
+
setHotCursor(previous => Math.max(0, Math.min(total - 1, previous + direction)));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (expanded) {
|
|
176
|
+
// Expanded inspector owns the wheel: same page step as j/k.
|
|
177
|
+
const direction = event.deltaY >= 0 ? 1 : -1;
|
|
178
|
+
setInspectScroll(previous => Math.max(0, previous + direction * Math.max(1, inspectorRows - 2)));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
move(event.deltaY);
|
|
182
|
+
}, [view, agg, expanded, inspectorRows, move]);
|
|
183
|
+
/** Jump the timeline cursor to a filtered index (keyboard jump semantics). */
|
|
184
|
+
const jumpTo = React.useCallback((index) => {
|
|
185
|
+
setInspectScroll(0);
|
|
186
|
+
setCursor(index);
|
|
187
|
+
setFollow(index >= filtered.length - 1);
|
|
188
|
+
}, [filtered.length]);
|
|
150
189
|
// ── keys ─────────────────────────────────────────────────────────────────
|
|
151
190
|
useInput((input, key) => {
|
|
152
191
|
// The query line owns the keyboard while open, so a `q` typed into a
|
|
@@ -208,14 +247,7 @@ export function TrajectoryScene({ channel, build, onClose, }) {
|
|
|
208
247
|
}
|
|
209
248
|
if (key.return) {
|
|
210
249
|
// Jump back to the timeline, positioned on the group's first member.
|
|
211
|
-
|
|
212
|
-
switchView('timeline');
|
|
213
|
-
if (row !== undefined) {
|
|
214
|
-
const target = indexes.indexOf(row.firstIndex);
|
|
215
|
-
setCursor(target >= 0 ? target : 0);
|
|
216
|
-
setFollow(false);
|
|
217
|
-
}
|
|
218
|
-
return;
|
|
250
|
+
return jumpFromHotspot(hotspotRows(agg)[hotCursor]);
|
|
219
251
|
}
|
|
220
252
|
return;
|
|
221
253
|
}
|
|
@@ -286,16 +318,42 @@ export function TrajectoryScene({ channel, build, onClose, }) {
|
|
|
286
318
|
(totals.errors > 0 ? ` \u00b7 ${t('traj-errors', { n: totals.errors })}` : '') +
|
|
287
319
|
(totals.retries > 0 ? ` \u00b7 ${t('traj-retries', { n: totals.retries })}` : '') +
|
|
288
320
|
` \u00b7 ${formatDuration(totals.spanMs)}`;
|
|
289
|
-
|
|
290
|
-
const
|
|
321
|
+
// ✕ 退出按钮占 2 格(` ✕`):预量测行给右端留出预算,按钮钉在末列
|
|
322
|
+
const CLOSE_WIDTH = 2;
|
|
323
|
+
const headerLine = spread(`\u2726 ${t('traj-title')} ${channel.sessionTitle ?? channel.cwd}`, totalsText, bandWidth - CLOSE_WIDTH);
|
|
324
|
+
const header = (_jsxs(Box, { width: "100%", height: 1, flexShrink: 0, children: [_jsx(Box, { flexShrink: 0, width: bandWidth - CLOSE_WIDTH, children: _jsxs(Text, { children: [_jsx(Text, { color: "claude", bold: true, children: `\u2726 ${t('traj-title')}` }), _jsx(Text, { color: "subtle", children: headerLine.left.slice((`\u2726 ${t('traj-title')}`).length) }), _jsx(Text, { children: headerLine.gap }), _jsx(Text, { color: totals.errors > 0 ? 'error' : 'subtle', children: headerLine.right })] }) }), _jsx(Box, { flexShrink: 0, width: CLOSE_WIDTH,
|
|
325
|
+
// 可点击退出(q/Esc 的鼠标等价)——hover 提亮给出可点指示
|
|
326
|
+
onClick: () => onClose(), onMouseEnter: () => setCloseHovered(true), onMouseLeave: () => setCloseHovered(false), children: _jsx(Text, { color: closeHovered ? 'text' : 'subtle', children: ' ✕' }) })] }));
|
|
291
327
|
const axisLabel = view === 'hotspot' ? t(`traj-sort-${sort}`) : t(`traj-proj-${projection}`);
|
|
292
|
-
const
|
|
293
|
-
|
|
328
|
+
const tabTimelineText = `${view === 'timeline' ? '\u25cf' : '\u25cb'} ${t('traj-tab-timeline')} `;
|
|
329
|
+
const tabHotspotText = `${view === 'hotspot' ? '\u25cf' : '\u25cb'} ${t('traj-tab-hotspot')}`;
|
|
294
330
|
const queryText_ = queryOpen || !query.empty
|
|
295
331
|
? ` / ${queryText}${queryOpen ? '\u258c' : ''} ${t('traj-matches', { n: filtered.length, total: nodes.length })}`
|
|
296
332
|
: '';
|
|
333
|
+
const tabsLeft = tabTimelineText + tabHotspotText;
|
|
297
334
|
const tabsLine = spread(tabsLeft + queryText_, axisLabel, bandWidth);
|
|
298
|
-
|
|
335
|
+
// Segment truncation mirrors spread's left-clip: the query tail yields
|
|
336
|
+
// first, then the far tab label — the line stays exactly one row.
|
|
337
|
+
const leftRoom = stringWidth(tabsLine.left);
|
|
338
|
+
const hotspotShown = truncateWidth(tabHotspotText, Math.max(0, leftRoom - stringWidth(tabTimelineText)));
|
|
339
|
+
const queryShown = queryText_ === ''
|
|
340
|
+
? ''
|
|
341
|
+
: truncateWidth(queryText_, Math.max(0, leftRoom - stringWidth(tabTimelineText) - stringWidth(hotspotShown)));
|
|
342
|
+
const tabs = (_jsxs(Box, { width: "100%", height: 1, flexShrink: 0, children: [_jsx(Box, { flexShrink: 0, width: stringWidth(tabTimelineText), onClick: () => switchView('timeline'), onMouseEnter: () => setHoverTab('timeline'), onMouseLeave: () => setHoverTab(previous => (previous === 'timeline' ? null : previous)), children: _jsx(Text, { color: view === 'timeline' ? 'permission' : hoverTab === 'timeline' ? 'text' : 'subtle', bold: view === 'timeline', children: tabTimelineText }) }), _jsx(Box, { flexShrink: 0, width: stringWidth(hotspotShown), onClick: () => switchView('hotspot'), onMouseEnter: () => setHoverTab('hotspot'), onMouseLeave: () => setHoverTab(previous => (previous === 'hotspot' ? null : previous)), children: _jsx(Text, { color: view === 'hotspot' ? 'permission' : hoverTab === 'hotspot' ? 'text' : 'subtle', bold: view === 'hotspot', children: hotspotShown }) }), queryShown !== '' && (_jsx(Box, { flexShrink: 0, width: stringWidth(queryShown), onClick: () => setQueryOpen(true), children: _jsx(Text, { color: "suggestion", children: queryShown }) })), _jsx(Box, { flexGrow: 1, flexShrink: 1, onClick: () => { if (!queryOpen)
|
|
343
|
+
setQueryOpen(true); }, children: _jsx(Text, { children: tabsLine.gap }) }), _jsx(Box, { flexShrink: 0,
|
|
344
|
+
// +1 slack: ink breaks a wrap line exactly AT the box width, so a
|
|
345
|
+
// wide-char-ending label (按耗时) at an exact fit loses its last
|
|
346
|
+
// glyph to a clipped second line (the truncateWidth doc's trap).
|
|
347
|
+
// The flexGrow gap absorbs the extra cell.
|
|
348
|
+
width: stringWidth(tabsLine.right) + 1, onClick: () => {
|
|
349
|
+
if (view === 'hotspot') {
|
|
350
|
+
setSort(previous => HOTSPOT_SORTS[(HOTSPOT_SORTS.indexOf(previous) + 1) % HOTSPOT_SORTS.length]);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
setProjection(previous => WAVE_PROJECTIONS[(WAVE_PROJECTIONS.indexOf(previous) + 1) % WAVE_PROJECTIONS.length]);
|
|
354
|
+
}
|
|
355
|
+
setSwitchTick(tick);
|
|
356
|
+
}, onMouseEnter: () => setHoverAxis(true), onMouseLeave: () => setHoverAxis(false), children: _jsx(Text, { color: hoverAxis ? 'text' : 'subtle', children: tabsLine.right }) })] }));
|
|
299
357
|
const hints = view === 'hotspot'
|
|
300
358
|
? t('traj-hint-hotspot')
|
|
301
359
|
: queryOpen
|
|
@@ -303,5 +361,14 @@ export function TrajectoryScene({ channel, build, onClose, }) {
|
|
|
303
361
|
: expanded
|
|
304
362
|
? t('traj-hint-expanded')
|
|
305
363
|
: t('traj-hint-timeline');
|
|
306
|
-
return (_jsxs(Box, { ref: ref, flexDirection: "column", width: "100%", paddingX: 1, children: [header, tabs, _jsx(WaveBand, { band: band, width: bandWidth, cursorColumn: columnOfIndex(band, indexes[clampedCursor] ?? 0), viewportStart: columnOfIndex(band, indexes[windowStart] ?? 0), viewportEnd: columnOfIndex(band, indexes[Math.min(filtered.length - 1, windowStart + ledgerRows - 1)] ?? 0), matches: matchColumns, tick: tick, alertTick: alertTick
|
|
364
|
+
return (_jsxs(Box, { ref: ref, flexDirection: "column", width: "100%", paddingX: 1, children: [header, tabs, _jsx(WaveBand, { band: band, width: bandWidth, cursorColumn: columnOfIndex(band, indexes[clampedCursor] ?? 0), viewportStart: columnOfIndex(band, indexes[windowStart] ?? 0), viewportEnd: columnOfIndex(band, indexes[Math.min(filtered.length - 1, windowStart + ledgerRows - 1)] ?? 0), matches: matchColumns, tick: tick, alertTick: alertTick, onColumnClick: column => {
|
|
365
|
+
// 点击波形列(或标尺行)= 跳到该列最近事件;空列继承前驱的
|
|
366
|
+
// firstIndex,空档区点击落在空档开始处。查询过滤掉的行不跳。
|
|
367
|
+
const nodeIndex = band.buckets[column]?.firstIndex ?? -1;
|
|
368
|
+
if (nodeIndex < 0)
|
|
369
|
+
return;
|
|
370
|
+
const target = indexes.indexOf(nodeIndex);
|
|
371
|
+
if (target >= 0)
|
|
372
|
+
jumpTo(target);
|
|
373
|
+
} }), _jsx(Box, { height: 1, flexShrink: 0, children: _jsx(Text, { children: " " }) }), _jsx("ink-box", { style: { flexDirection: 'column', flexGrow: 1, flexShrink: 1, overflow: 'hidden', width: '100%' }, onWheel: handleWheel, children: view === 'timeline' ? (_jsxs(_Fragment, { children: [_jsx(Ledger, { rows: filtered, start: windowStart, height: ledgerRows, cursor: clampedCursor, width: columns - 4, tick: tick, arrivalTick: arrivalTick, arrivalFrom: arrivalFrom, onRowClick: index => jumpTo(index) }), _jsx(Divider, { color: "permission", width: bandWidth }), _jsx(Inspector, { node: focused, detail: detail, height: inspectorRows, width: columns - 4, expanded: expanded, scroll: inspectScroll })] })) : (_jsx(HotspotView, { agg: agg, sort: sort, width: columns - 4, height: ledgerRows + inspectorRows + 1, cursor: hotCursor, tick: tick, switchTick: switchTick, onRowClick: index => jumpFromHotspot(hotspotRows(agg)[index]) })) }), _jsx(Box, { height: 1, flexShrink: 0, children: _jsx(Text, { children: " " }) }), _jsx(Box, { width: "100%", height: 1, flexShrink: 0, children: _jsxs(Text, { dimColor: true, italic: true, wrap: "truncate", children: [_jsx(HintLine, { text: hints }), totals.tokens.input > 0 ? (_jsx(Text, { color: "subtle", children: ` ${formatTokens(totals.tokens.input)}→${formatTokens(totals.tokens.output)}` })) : ('')] }) })] }));
|
|
307
374
|
}
|