@deepseek-harness-tui/dsh-tui 0.9.3 → 0.10.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -6
- package/cordis.patch.yml +117 -39
- package/cordis.yml +13 -10
- package/lib/types/api.d.ts +7 -0
- package/lib/types/api.d.ts.map +1 -0
- package/lib/types/api.js +1 -0
- package/lib/types/cc/hyperlink.d.ts.map +1 -1
- package/lib/types/cc/hyperlink.js +42 -4
- package/lib/types/cc/markdown.d.ts.map +1 -1
- package/lib/types/cc/markdown.js +15 -5
- package/lib/types/commands.d.ts +6 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +12 -8
- package/lib/types/components/MessageList.d.ts +8 -4
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +94 -32
- package/lib/types/components/PermissionsPicker.d.ts +6 -7
- package/lib/types/components/PermissionsPicker.d.ts.map +1 -1
- package/lib/types/components/PermissionsPicker.js +20 -14
- package/lib/types/components/PromptEditor.d.ts +46 -0
- package/lib/types/components/PromptEditor.d.ts.map +1 -0
- package/lib/types/components/PromptEditor.js +60 -0
- package/lib/types/components/PromptInput.d.ts +12 -0
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +1240 -205
- package/lib/types/components/RewindPicker.js +1 -1
- package/lib/types/components/SplitDiffView.d.ts +11 -1
- package/lib/types/components/SplitDiffView.d.ts.map +1 -1
- package/lib/types/components/SplitDiffView.js +9 -2
- package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
- package/lib/types/components/StreamingMarkdown.js +140 -3
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
- package/lib/types/components/SubagentDashboard.js +2 -2
- package/lib/types/components/SubagentDetailScene.js +1 -1
- package/lib/types/components/ThemePicker.d.ts +13 -15
- package/lib/types/components/ThemePicker.d.ts.map +1 -1
- package/lib/types/components/ThemePicker.js +24 -26
- package/lib/types/components/Tooltip.d.ts +67 -0
- package/lib/types/components/Tooltip.d.ts.map +1 -0
- package/lib/types/components/Tooltip.js +220 -0
- package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
- package/lib/types/components/approvals/ApprovalPanel.js +1 -1
- package/lib/types/components/design-system/Divider.d.ts +14 -1
- package/lib/types/components/design-system/Divider.d.ts.map +1 -1
- package/lib/types/components/design-system/Divider.js +86 -8
- package/lib/types/components/design-system/ThemeProvider.d.ts +5 -2
- package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemeProvider.js +52 -16
- package/lib/types/components/design-system/ThemedBox.d.ts +23 -2
- package/lib/types/components/design-system/ThemedBox.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts +11 -6
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +10 -6
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +13 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +37 -6
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/messages/UserPromptMessage.js +7 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +1 -1
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +29 -3
- package/lib/types/components/sessions/SessionListRow.d.ts +8 -1
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
- package/lib/types/components/sessions/SessionListRow.js +9 -2
- package/lib/types/components/sessions/SessionPreview.d.ts.map +1 -1
- package/lib/types/components/sessions/SessionPreview.js +5 -3
- package/lib/types/components/sessions/WorkspaceListRow.d.ts +17 -0
- package/lib/types/components/sessions/WorkspaceListRow.d.ts.map +1 -0
- package/lib/types/components/sessions/WorkspaceListRow.js +27 -0
- package/lib/types/components/smoothReveal.d.ts +97 -0
- package/lib/types/components/smoothReveal.d.ts.map +1 -0
- package/lib/types/components/smoothReveal.js +279 -0
- package/lib/types/customTheme.d.ts +4 -0
- package/lib/types/customTheme.d.ts.map +1 -1
- package/lib/types/customTheme.js +9 -5
- package/lib/types/dsh-adapter/approvals.d.ts +114 -2
- package/lib/types/dsh-adapter/approvals.d.ts.map +1 -1
- package/lib/types/dsh-adapter/approvals.js +241 -3
- package/lib/types/dsh-adapter/channel.d.ts +59 -2
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +499 -80
- package/lib/types/dsh-adapter/childStderr.d.ts.map +1 -1
- package/lib/types/dsh-adapter/childStderr.js +9 -0
- package/lib/types/dsh-adapter/contract.d.ts +17 -13
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +36 -27
- package/lib/types/dsh-adapter/credentialRefGuard.d.ts +40 -0
- package/lib/types/dsh-adapter/credentialRefGuard.d.ts.map +1 -0
- package/lib/types/dsh-adapter/credentialRefGuard.js +43 -0
- package/lib/types/dsh-adapter/effect-ledger.d.ts +1 -1
- package/lib/types/dsh-adapter/effect-ledger.d.ts.map +1 -1
- package/lib/types/dsh-adapter/effect-ledger.js +1 -0
- package/lib/types/dsh-adapter/extensions.d.ts +4 -1
- package/lib/types/dsh-adapter/extensions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/extensions.js +8 -1
- package/lib/types/dsh-adapter/index.d.ts +11 -1
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +2 -0
- package/lib/types/dsh-adapter/plugin-test-utils.d.ts +62 -0
- package/lib/types/dsh-adapter/plugin-test-utils.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugin-test-utils.js +61 -0
- package/lib/types/dsh-adapter/plugin.d.ts +20 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +174 -39
- package/lib/types/dsh-adapter/plugins-info.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugins-info.js +12 -0
- package/lib/types/dsh-adapter/preset-resolution.d.ts +49 -0
- package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -0
- package/lib/types/dsh-adapter/preset-resolution.js +42 -0
- package/lib/types/dsh-adapter/presets.d.ts +0 -26
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/presets.js +4 -14
- package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -1
- package/lib/types/dsh-adapter/promptDebug.js +4 -1
- package/lib/types/dsh-adapter/providerGuard.d.ts +75 -0
- package/lib/types/dsh-adapter/providerGuard.d.ts.map +1 -0
- package/lib/types/dsh-adapter/providerGuard.js +104 -0
- package/lib/types/dsh-adapter/questions-answerer.d.ts +49 -0
- package/lib/types/dsh-adapter/questions-answerer.d.ts.map +1 -0
- package/lib/types/dsh-adapter/questions-answerer.js +61 -0
- package/lib/types/dsh-adapter/recap.d.ts.map +1 -1
- package/lib/types/dsh-adapter/recap.js +12 -6
- package/lib/types/dsh-adapter/sanitize.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sanitize.js +6 -1
- package/lib/types/dsh-adapter/sessions/digest.d.ts +21 -1
- package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/digest.js +229 -3
- package/lib/types/dsh-adapter/sessions/frames.d.ts +3 -0
- package/lib/types/dsh-adapter/sessions/frames.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/frames.js +34 -19
- package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/list.js +148 -31
- package/lib/types/dsh-adapter/sessions/store.d.ts +8 -0
- package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/store.js +24 -4
- package/lib/types/dsh-adapter/sessions/types.d.ts +3 -0
- package/lib/types/dsh-adapter/sessions/types.d.ts.map +1 -1
- package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
- package/lib/types/dsh-adapter/settings-sections.js +13 -1
- package/lib/types/dsh-adapter/settingsEditor.d.ts +6 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -1
- package/lib/types/dsh-adapter/settingsEditor.js +15 -3
- package/lib/types/dsh-adapter/subagents.d.ts +4 -0
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
- package/lib/types/dsh-adapter/subagents.js +8 -0
- package/lib/types/dsh-adapter/themes.d.ts +51 -0
- package/lib/types/dsh-adapter/themes.d.ts.map +1 -0
- package/lib/types/dsh-adapter/themes.js +272 -0
- package/lib/types/dsh-adapter/toast.d.ts +67 -0
- package/lib/types/dsh-adapter/toast.d.ts.map +1 -0
- package/lib/types/dsh-adapter/toast.js +141 -0
- package/lib/types/dsh-adapter/types.d.ts +1 -1
- package/lib/types/extensions.d.ts +4 -0
- package/lib/types/extensions.d.ts.map +1 -1
- package/lib/types/extensions.js +5 -1
- package/lib/types/history.d.ts.map +1 -1
- package/lib/types/history.js +7 -2
- package/lib/types/hooks/useRuntimeThemeSnapshot.d.ts +10 -0
- package/lib/types/hooks/useRuntimeThemeSnapshot.d.ts.map +1 -0
- package/lib/types/hooks/useRuntimeThemeSnapshot.js +32 -0
- package/lib/types/i18n.d.ts +199 -41
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +82 -20
- package/lib/types/ink/components/App.d.ts +20 -0
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +142 -13
- package/lib/types/ink/components/Box.d.ts +39 -2
- package/lib/types/ink/components/Box.d.ts.map +1 -1
- package/lib/types/ink/components/Box.js +61 -37
- package/lib/types/ink/events/context-menu-event.d.ts +19 -0
- package/lib/types/ink/events/context-menu-event.d.ts.map +1 -0
- package/lib/types/ink/events/context-menu-event.js +20 -0
- package/lib/types/ink/events/drag-event.d.ts +44 -0
- package/lib/types/ink/events/drag-event.d.ts.map +1 -0
- package/lib/types/ink/events/drag-event.js +39 -0
- package/lib/types/ink/events/event-handlers.d.ts +8 -0
- package/lib/types/ink/events/event-handlers.d.ts.map +1 -1
- package/lib/types/ink/events/event-handlers.js +8 -0
- package/lib/types/ink/hit-test.d.ts +69 -0
- package/lib/types/ink/hit-test.d.ts.map +1 -1
- package/lib/types/ink/hit-test.js +260 -6
- package/lib/types/ink/ink.d.ts +25 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +74 -10
- package/lib/types/ink/parse-keypress.d.ts +20 -0
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +190 -22
- package/lib/types/ink/renderer.d.ts.map +1 -1
- package/lib/types/ink/renderer.js +15 -1
- package/lib/types/ink/screen.d.ts +7 -0
- package/lib/types/ink/screen.d.ts.map +1 -1
- package/lib/types/ink/screen.js +8 -1
- package/lib/types/ink/termio/osc.d.ts +2 -1
- package/lib/types/ink/termio/osc.d.ts.map +1 -1
- package/lib/types/ink/termio/osc.js +26 -5
- package/lib/types/plugin-spec/registry.d.ts +1 -1
- package/lib/types/plugin-spec/registry.js +1 -1
- package/lib/types/presetPrefs.d.ts +6 -5
- package/lib/types/presetPrefs.d.ts.map +1 -1
- package/lib/types/presetPrefs.js +10 -5
- package/lib/types/screens/Chat.d.ts +4 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +169 -68
- package/lib/types/screens/SessionBrowser.d.ts +9 -4
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
- package/lib/types/screens/SessionBrowser.js +483 -75
- package/lib/types/screens/SessionTree.d.ts.map +1 -1
- package/lib/types/screens/SessionTree.js +5 -1
- package/lib/types/screens/Settings.d.ts.map +1 -1
- package/lib/types/screens/Settings.js +5 -1
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +5 -1
- package/lib/types/screens/chatOverlay.d.ts +3 -2
- package/lib/types/screens/chatOverlay.d.ts.map +1 -1
- package/lib/types/screens/chatOverlay.js +2 -0
- package/lib/types/sessionPins.d.ts +33 -0
- package/lib/types/sessionPins.d.ts.map +1 -0
- package/lib/types/sessionPins.js +181 -0
- package/lib/types/sessions/format.d.ts +2 -0
- package/lib/types/sessions/format.d.ts.map +1 -1
- package/lib/types/sessions/format.js +9 -0
- package/lib/types/sessions/view.d.ts +42 -6
- package/lib/types/sessions/view.d.ts.map +1 -1
- package/lib/types/sessions/view.js +133 -35
- package/lib/types/test-utils.d.ts +2 -0
- package/lib/types/test-utils.d.ts.map +1 -0
- package/lib/types/test-utils.js +8 -0
- package/lib/types/theme.d.ts +22 -5
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +57 -7
- package/lib/types/themeCatalog.d.ts +36 -0
- package/lib/types/themeCatalog.d.ts.map +1 -0
- package/lib/types/themeCatalog.js +157 -0
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +114 -18
- package/lib/types/update.d.ts +159 -3
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +497 -16
- package/lib/types/utils/debug.d.ts.map +1 -1
- package/lib/types/utils/debug.js +2 -1
- package/lib/types/utils/keymap.d.ts +1 -1
- package/lib/types/utils/keymap.d.ts.map +1 -1
- package/lib/types/utils/keymap.js +19 -2
- package/lib/types/utils/urlGuard.d.ts +28 -0
- package/lib/types/utils/urlGuard.d.ts.map +1 -0
- package/lib/types/utils/urlGuard.js +21 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +3 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.d.ts +7 -11
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.d.ts +16 -1
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.js +23 -2
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.d.ts +5 -5
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/pi-ai.d.ts +23 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/pi-ai.js +27 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.d.ts +17 -3
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.js +36 -4
- package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +11 -12
- package/package.json +114 -74
- package/lib/types/dsh-adapter/packaged-skills.d.ts +0 -19
- package/lib/types/dsh-adapter/packaged-skills.d.ts.map +0 -1
- package/lib/types/dsh-adapter/packaged-skills.js +0 -78
- package/skills/audit/SKILL.md +0 -24
- package/skills/bug/SKILL.md +0 -27
- package/skills/pr-comments/SKILL.md +0 -20
- package/skills/practice/SKILL.md +0 -22
- package/skills/release-notes/SKILL.md +0 -25
- package/skills/review/SKILL.md +0 -25
- package/skills/vuln-check/SKILL.md +0 -25
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import stripAnsi from 'strip-ansi';
|
|
4
|
+
import { Box, Text, useTerminalSize } from '../ui.js';
|
|
5
|
+
import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
|
|
6
|
+
import { stringWidth } from '../ink/stringWidth.js';
|
|
7
|
+
import { getGraphemeSegmenter } from '../utils/intl.js';
|
|
8
|
+
/**
|
|
9
|
+
* Tooltip hover system — the terminal equivalent of the HTML `title`
|
|
10
|
+
* attribute. Attach {@link useTooltip} props to any Box whose visible text
|
|
11
|
+
* may be truncated (tool-call header paths, wrapped prompts, status-line
|
|
12
|
+
* fields); after a ~600ms dwell the full content pops up in a floating card
|
|
13
|
+
* anchored at the pointer, and disappears the moment the pointer leaves.
|
|
14
|
+
*
|
|
15
|
+
* Hover events only exist in fullscreen (alternate screen + mouse
|
|
16
|
+
* tracking); inline mode never fires the handlers, so the store can only
|
|
17
|
+
* be written from a live fullscreen tree.
|
|
18
|
+
*/
|
|
19
|
+
/** Default dwell before the tooltip appears (GUI `title` convention). */
|
|
20
|
+
const DEFAULT_TOOLTIP_DELAY_MS = 600;
|
|
21
|
+
// Module-level store (the `src/ink/instances.ts` single-instance pattern):
|
|
22
|
+
// writers are the hover handlers, the single reader is TooltipLayer.
|
|
23
|
+
let state = null;
|
|
24
|
+
let invalidationGeneration = 0;
|
|
25
|
+
const listeners = new Set();
|
|
26
|
+
function notify() {
|
|
27
|
+
for (const listener of listeners)
|
|
28
|
+
listener();
|
|
29
|
+
}
|
|
30
|
+
/** Subscribe to tooltip show/hide; returns an unsubscribe function. */
|
|
31
|
+
export function subscribeTooltip(listener) {
|
|
32
|
+
listeners.add(listener);
|
|
33
|
+
return () => {
|
|
34
|
+
listeners.delete(listener);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Stable snapshot for useSyncExternalStore (identity kept until changed). */
|
|
38
|
+
export function getTooltipSnapshot() {
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
function showTooltip(owner, col, row, content) {
|
|
42
|
+
state = { owner, anchorCol: col, anchorRow: row, content };
|
|
43
|
+
notify();
|
|
44
|
+
}
|
|
45
|
+
function hideTooltip(owner) {
|
|
46
|
+
if (state !== null && state.owner === owner) {
|
|
47
|
+
state = null;
|
|
48
|
+
notify();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Hide shown content and invalidate every pending dwell timer. */
|
|
52
|
+
export function clearTooltip() {
|
|
53
|
+
invalidationGeneration++;
|
|
54
|
+
if (state !== null) {
|
|
55
|
+
state = null;
|
|
56
|
+
notify();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Hover props that pop a tooltip with the full content after a dwell.
|
|
61
|
+
*
|
|
62
|
+
* The pointer position (screen col/row) is taken from the PointerEvent the
|
|
63
|
+
* renderer hands to `onMouseEnter` — the runtime always passes one even
|
|
64
|
+
* though the prop type is `() => void`, which is why the handler's parameter
|
|
65
|
+
* is optional (a `() => void` prop accepts a function with only optional
|
|
66
|
+
* parameters).
|
|
67
|
+
*/
|
|
68
|
+
export function useTooltip(content, opts) {
|
|
69
|
+
const ownerRef = React.useRef(null);
|
|
70
|
+
if (ownerRef.current === null)
|
|
71
|
+
ownerRef.current = { timer: null };
|
|
72
|
+
const owner = ownerRef.current;
|
|
73
|
+
// Resolve function content at show time so callers can pass getters that
|
|
74
|
+
// read live channel state without re-binding the handlers.
|
|
75
|
+
const contentRef = React.useRef(content);
|
|
76
|
+
contentRef.current = content;
|
|
77
|
+
const delayMs = opts?.delayMs ?? DEFAULT_TOOLTIP_DELAY_MS;
|
|
78
|
+
React.useEffect(() => () => {
|
|
79
|
+
// Unmount clears our pending timer and our tooltip — a screen swap must
|
|
80
|
+
// not leave a ghost anchored to geometry that no longer exists.
|
|
81
|
+
if (owner.timer !== null)
|
|
82
|
+
clearTimeout(owner.timer);
|
|
83
|
+
hideTooltip(owner);
|
|
84
|
+
}, [owner]);
|
|
85
|
+
return React.useMemo(() => ({
|
|
86
|
+
onMouseEnter: (event) => {
|
|
87
|
+
const col = event?.col ?? 0;
|
|
88
|
+
const row = event?.row ?? 0;
|
|
89
|
+
if (owner.timer !== null)
|
|
90
|
+
clearTimeout(owner.timer);
|
|
91
|
+
const generationAtEnter = invalidationGeneration;
|
|
92
|
+
owner.timer = setTimeout(() => {
|
|
93
|
+
owner.timer = null;
|
|
94
|
+
if (generationAtEnter !== invalidationGeneration)
|
|
95
|
+
return;
|
|
96
|
+
const resolved = typeof contentRef.current === 'function'
|
|
97
|
+
? contentRef.current()
|
|
98
|
+
: contentRef.current;
|
|
99
|
+
if (resolved === '')
|
|
100
|
+
return;
|
|
101
|
+
showTooltip(owner, col, row, resolved);
|
|
102
|
+
}, delayMs);
|
|
103
|
+
},
|
|
104
|
+
onMouseLeave: () => {
|
|
105
|
+
if (owner.timer !== null) {
|
|
106
|
+
clearTimeout(owner.timer);
|
|
107
|
+
owner.timer = null;
|
|
108
|
+
}
|
|
109
|
+
hideTooltip(owner);
|
|
110
|
+
},
|
|
111
|
+
}), [owner, delayMs]);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Convenience wrapper for list rows built from data (a component boundary
|
|
115
|
+
* makes the hook call legal per item). Renders a plain Box carrying the
|
|
116
|
+
* hover props around `children`.
|
|
117
|
+
*/
|
|
118
|
+
export function TooltipTarget({ content, children, }) {
|
|
119
|
+
const hover = useTooltip(content);
|
|
120
|
+
return _jsx(Box, { ...hover, children: children });
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The singleton floating layer: subscribes to the tooltip store and renders
|
|
124
|
+
* the rounded card above the anchor row (below it when there is no room),
|
|
125
|
+
* horizontally clamped inside the terminal. Mount once, LAST in the tree so
|
|
126
|
+
* it paints over everything else. Terminal resize hides the tooltip — its
|
|
127
|
+
* anchor is screen geometry that just went stale.
|
|
128
|
+
*/
|
|
129
|
+
export function TooltipLayer({ invalidationKey, subscribeInvalidation, } = {}) {
|
|
130
|
+
const { columns, rows } = useTerminalSize();
|
|
131
|
+
const terminalFocused = useTerminalFocus();
|
|
132
|
+
const tooltip = React.useSyncExternalStore(subscribeTooltip, getTooltipSnapshot);
|
|
133
|
+
// Remounts and modal/screen transitions invalidate shown AND pending tips.
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
clearTooltip();
|
|
136
|
+
}, [invalidationKey]);
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
if (!terminalFocused)
|
|
139
|
+
clearTooltip();
|
|
140
|
+
}, [terminalFocused]);
|
|
141
|
+
React.useEffect(() => {
|
|
142
|
+
if (subscribeInvalidation === undefined)
|
|
143
|
+
return;
|
|
144
|
+
return subscribeInvalidation(clearTooltip);
|
|
145
|
+
}, [subscribeInvalidation]);
|
|
146
|
+
const prevSize = React.useRef({ columns, rows });
|
|
147
|
+
React.useEffect(() => {
|
|
148
|
+
if (prevSize.current.columns !== columns || prevSize.current.rows !== rows) {
|
|
149
|
+
prevSize.current = { columns, rows };
|
|
150
|
+
clearTooltip();
|
|
151
|
+
}
|
|
152
|
+
}, [columns, rows]);
|
|
153
|
+
if (tooltip === null)
|
|
154
|
+
return null;
|
|
155
|
+
// A bordered card needs at least one content cell plus two border cells.
|
|
156
|
+
// Below that, hiding is safer than creating geometry wider/taller than the
|
|
157
|
+
// terminal (Yoga would clip unpredictably on 1–2 column/row resize states).
|
|
158
|
+
if (columns < 3 || rows < 3)
|
|
159
|
+
return null;
|
|
160
|
+
const maxWidth = Math.max(3, columns - 4);
|
|
161
|
+
// Grapheme-aware wrap (parity with PromptInput's wrapToWidth): code-point
|
|
162
|
+
// iteration splits ZWJ emoji and combining sequences across rows, leaving
|
|
163
|
+
// broken halves at the row edges. Newlines are honoured like wrapWidth.
|
|
164
|
+
let lines = wrapTooltipContent(tooltip.content, maxWidth - 2);
|
|
165
|
+
// A tooltip taller than the terminal is useless; keep the head.
|
|
166
|
+
const maxLines = Math.max(1, rows - 2);
|
|
167
|
+
if (lines.length > maxLines)
|
|
168
|
+
lines = lines.slice(0, maxLines);
|
|
169
|
+
const innerWidth = Math.min(maxWidth - 2, Math.max(1, ...lines.map(stringWidth)));
|
|
170
|
+
const width = innerWidth + 2;
|
|
171
|
+
const height = lines.length + 2;
|
|
172
|
+
// Prefer resting on the row ABOVE the anchor; when there is no room
|
|
173
|
+
// (anchor at the top of the screen), drop below it, clamped on-screen.
|
|
174
|
+
const topAbove = tooltip.anchorRow - height;
|
|
175
|
+
const top = topAbove >= 0
|
|
176
|
+
? topAbove
|
|
177
|
+
: Math.max(0, Math.min(tooltip.anchorRow + 1, rows - height));
|
|
178
|
+
const left = Math.max(0, Math.min(tooltip.anchorCol, columns - width));
|
|
179
|
+
return (_jsx(Box, { position: "absolute", left: left, top: top, width: width, height: height, flexDirection: "column", flexShrink: 0, borderStyle: "round", borderColor: "permission", backgroundColor: "toolCardBackground", children: lines.map((line, index) => (_jsx(Text, { wrap: "truncate-end", children: line === '' ? ' ' : line }, index))) }));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Wrap tooltip content to display width, breaking only BETWEEN grapheme
|
|
183
|
+
* clusters (ZWJ emoji, combining sequences and CJK wide cells stay whole).
|
|
184
|
+
* Newlines in the input are honoured. Mirrors PromptInput.wrapToWidth's
|
|
185
|
+
* break rule so the tooltip shows exactly the text the input would wrap.
|
|
186
|
+
*/
|
|
187
|
+
function wrapTooltipContent(text, width) {
|
|
188
|
+
const rows = [];
|
|
189
|
+
if (width <= 0)
|
|
190
|
+
return rows;
|
|
191
|
+
// Tooltip content is display-only: remove ANSI/C0 controls before grapheme
|
|
192
|
+
// segmentation so a CSI sequence cannot be split and counted as visible
|
|
193
|
+
// columns. Newlines remain structural; tabs become deterministic spaces.
|
|
194
|
+
text = stripAnsi(text)
|
|
195
|
+
.replace(/\t/gu, ' ')
|
|
196
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, '');
|
|
197
|
+
const segmenter = getGraphemeSegmenter();
|
|
198
|
+
for (const line of text.split('\n')) {
|
|
199
|
+
if (line === '') {
|
|
200
|
+
rows.push('');
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
let current = '';
|
|
204
|
+
let used = 0;
|
|
205
|
+
for (const { segment } of segmenter.segment(line)) {
|
|
206
|
+
const w = stringWidth(segment);
|
|
207
|
+
if (used + w > width && current !== '') {
|
|
208
|
+
rows.push(current);
|
|
209
|
+
current = segment;
|
|
210
|
+
used = w;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
current += segment;
|
|
214
|
+
used += w;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
rows.push(current);
|
|
218
|
+
}
|
|
219
|
+
return rows;
|
|
220
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApprovalPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/approvals/ApprovalPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAEtE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,cAAc,GAAG,UAAU,KAAK,IAAI,CAAA;CAClE,CAAA;AAID,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,kBAAkB,GAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"ApprovalPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/approvals/ApprovalPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAEtE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,cAAc,GAAG,UAAU,KAAK,IAAI,CAAA;CAClE,CAAA;AAID,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,kBAAkB,GAAG,KAAK,CAAC,SAAS,CAkFzF"}
|
|
@@ -45,7 +45,7 @@ export function ApprovalPanel({ approval, onDecide }) {
|
|
|
45
45
|
}
|
|
46
46
|
}, { isActive: true });
|
|
47
47
|
const optionLabels = [t('approval-yes'), t('approval-no')];
|
|
48
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: t('approval-waiting', { tool: approval.toolName })
|
|
48
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: t('approval-waiting', { tool: approval.toolName }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [approval.external === true && (_jsxs(Text, { color: "warning", wrap: "wrap", children: ["[external] ", t('approval-external-hint')] })), approval.command !== undefined && (_jsx(Box, { flexDirection: "column", paddingX: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: approval.command }) })), approval.reason !== undefined && (_jsx(Text, { dimColor: true, wrap: "wrap", children: approval.reason })), _jsx(Text, { dimColor: true, children: t('approval-proceed') })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: optionLabels.map((label, index) => {
|
|
49
49
|
const focused = index === focusIndex;
|
|
50
50
|
const hovered = index === hoverIndex;
|
|
51
51
|
return (_jsxs(Box, { flexDirection: "row", marginTop: focused ? 1 : 0, onClick: () => onDecide(OUTCOMES[index]), onMouseEnter: () => setHoverIndex(index), onMouseLeave: () => setHoverIndex(current => (current === index ? -1 : current)), backgroundColor: hovered && !focused ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: focused ? 'claude' : undefined, bold: focused, children: focused ? POINTER : ' ' }) }), _jsxs(Text, { bold: focused, color: focused ? 'claude' : undefined, wrap: "wrap", children: [index + 1, ". ", label] })] }, label));
|
|
@@ -3,7 +3,7 @@ import type { Theme } from '../../theme.js';
|
|
|
3
3
|
type DividerProps = {
|
|
4
4
|
/**
|
|
5
5
|
* Width of the divider in characters.
|
|
6
|
-
* Defaults to
|
|
6
|
+
* Defaults to the available layout width.
|
|
7
7
|
*/
|
|
8
8
|
width?: number;
|
|
9
9
|
/**
|
|
@@ -31,6 +31,19 @@ type DividerProps = {
|
|
|
31
31
|
* A horizontal divider line, optionally with a title in the middle
|
|
32
32
|
* (in the Claude Code visual language).
|
|
33
33
|
*
|
|
34
|
+
* The rule fills the width Yoga actually grants it: the Box is measured
|
|
35
|
+
* after layout (SearchBox pattern — a resize re-layouts without any prop
|
|
36
|
+
* change, so measure on every commit) and the terminal column count only
|
|
37
|
+
* seeds the first frame. A full-terminal-width rule nested in a narrower
|
|
38
|
+
* container (e.g., the transcript beside the 2-column timeline rail)
|
|
39
|
+
* would otherwise wrap onto a second row.
|
|
40
|
+
*
|
|
41
|
+
* Because the rendered rule width feeds back into the width Yoga grants
|
|
42
|
+
* the Box, the measurement negotiation is bounded per terminal-width
|
|
43
|
+
* generation (MAX_APPLIED_MEASUREMENTS below): a non-converging layout
|
|
44
|
+
* degrades to a frozen, truncated rule instead of tripping React's max
|
|
45
|
+
* update depth (error #185, a hard process crash).
|
|
46
|
+
*
|
|
34
47
|
* @example
|
|
35
48
|
* // ─────────── Title ───────────
|
|
36
49
|
* <Divider title="Title" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Divider.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/Divider.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Divider.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/Divider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4C,MAAM,OAAO,CAAA;AAOhE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAE3C,KAAK,YAAY,GAAG;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,KAAK,CAAA;IAEnB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AASD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,KAAK,EACL,IAAU,EACV,OAAW,EACX,KAAK,GACN,EAAE,YAAY,GAAG,KAAK,CAAC,SAAS,CAqEhC"}
|
|
@@ -1,24 +1,102 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import Box from '../../ink/components/Box.js';
|
|
2
4
|
import Text from '../../ink/components/Text.js';
|
|
5
|
+
import measureElement from '../../ink/measure-element.js';
|
|
3
6
|
import { stringWidth } from '../../ink/stringWidth.js';
|
|
4
7
|
import { useTerminalSize } from '../../ink/hooks/use-terminal-size.js';
|
|
8
|
+
// Upper bound on distinct measurements applied per terminal-width
|
|
9
|
+
// generation. Converging layouts apply one or two; a non-converging
|
|
10
|
+
// context (content-sized Box, or a sibling negotiating space the same
|
|
11
|
+
// way) would keep changing the measured width once per commit forever,
|
|
12
|
+
// so freeze well before the reconciler's 50-nested-update limit.
|
|
13
|
+
const MAX_APPLIED_MEASUREMENTS = 8;
|
|
5
14
|
/**
|
|
6
15
|
* A horizontal divider line, optionally with a title in the middle
|
|
7
16
|
* (in the Claude Code visual language).
|
|
8
17
|
*
|
|
18
|
+
* The rule fills the width Yoga actually grants it: the Box is measured
|
|
19
|
+
* after layout (SearchBox pattern — a resize re-layouts without any prop
|
|
20
|
+
* change, so measure on every commit) and the terminal column count only
|
|
21
|
+
* seeds the first frame. A full-terminal-width rule nested in a narrower
|
|
22
|
+
* container (e.g., the transcript beside the 2-column timeline rail)
|
|
23
|
+
* would otherwise wrap onto a second row.
|
|
24
|
+
*
|
|
25
|
+
* Because the rendered rule width feeds back into the width Yoga grants
|
|
26
|
+
* the Box, the measurement negotiation is bounded per terminal-width
|
|
27
|
+
* generation (MAX_APPLIED_MEASUREMENTS below): a non-converging layout
|
|
28
|
+
* degrades to a frozen, truncated rule instead of tripping React's max
|
|
29
|
+
* update depth (error #185, a hard process crash).
|
|
30
|
+
*
|
|
9
31
|
* @example
|
|
10
32
|
* // ─────────── Title ───────────
|
|
11
33
|
* <Divider title="Title" />
|
|
12
34
|
*/
|
|
13
35
|
export function Divider({ width, color, char = '─', padding = 0, title, }) {
|
|
14
36
|
const { columns } = useTerminalSize();
|
|
15
|
-
const
|
|
37
|
+
const [measuredWidth, setMeasuredWidth] = useState(null);
|
|
38
|
+
const boxRef = useRef(null);
|
|
39
|
+
// Measurement feedback guard: the rule width rendered from
|
|
40
|
+
// `measuredWidth` feeds back into the width Yoga grants the Box, so
|
|
41
|
+
// the measured value can drift or ping-pong without ever converging —
|
|
42
|
+
// one setState per commit then trips React's max update depth (error
|
|
43
|
+
// #185) and kills the process. Apply at most MAX_APPLIED_MEASUREMENTS
|
|
44
|
+
// distinct widths per terminal-width generation and freeze once a
|
|
45
|
+
// value repeats (A→B→A). truncate-end keeps an over-wide frozen rule
|
|
46
|
+
// on one row, so freezing degrades gracefully; a resize (columns
|
|
47
|
+
// change) reopens the negotiation.
|
|
48
|
+
const negotiation = useRef({ columns, applied: [], frozen: false });
|
|
49
|
+
useLayoutEffect(() => {
|
|
50
|
+
const node = boxRef.current;
|
|
51
|
+
if (!node)
|
|
52
|
+
return;
|
|
53
|
+
const state = negotiation.current;
|
|
54
|
+
if (state.columns !== columns) {
|
|
55
|
+
state.columns = columns;
|
|
56
|
+
state.applied = [];
|
|
57
|
+
state.frozen = false;
|
|
58
|
+
// The current Yoga layout was rendered from the previous terminal
|
|
59
|
+
// width. Clear it and let the next render seed from the new columns;
|
|
60
|
+
// measuring this stale pass would keep a grown terminal permanently
|
|
61
|
+
// pinned to its old narrow width.
|
|
62
|
+
setMeasuredWidth(null);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (state.frozen)
|
|
66
|
+
return;
|
|
67
|
+
const w = measureElement(node).width;
|
|
68
|
+
if (w <= 0)
|
|
69
|
+
return;
|
|
70
|
+
const applied = state.applied;
|
|
71
|
+
if (w === applied[applied.length - 1])
|
|
72
|
+
return;
|
|
73
|
+
if (applied.includes(w) || applied.length >= MAX_APPLIED_MEASUREMENTS) {
|
|
74
|
+
state.frozen = true;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
applied.push(w);
|
|
78
|
+
setMeasuredWidth(w);
|
|
79
|
+
});
|
|
80
|
+
const available = measuredWidth ?? columns;
|
|
81
|
+
const lineWidth = Math.max(0, Math.min(available, width ?? columns) - padding);
|
|
16
82
|
const titleWidth = title ? stringWidth(title) : 0;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
83
|
+
let text;
|
|
84
|
+
if (title) {
|
|
85
|
+
if (titleWidth < lineWidth) {
|
|
86
|
+
const lineLength = lineWidth - titleWidth;
|
|
87
|
+
const leftLength = Math.floor(lineLength / 2);
|
|
88
|
+
const rightLength = Math.ceil(lineLength / 2);
|
|
89
|
+
text = char.repeat(leftLength) + title + char.repeat(rightLength);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Title wider than the line (long turn-error notices on narrow
|
|
93
|
+
// windows): keep the message — truncate-end clips it to the
|
|
94
|
+
// available width instead of dropping it for a bare rule.
|
|
95
|
+
text = title;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
text = char.repeat(lineWidth);
|
|
22
100
|
}
|
|
23
|
-
return (_jsx(Text, { dimColor: !color, color: color, children:
|
|
101
|
+
return (_jsx(Box, { ref: boxRef, children: _jsx(Text, { dimColor: !color, color: color, wrap: "truncate-end", children: text }) }));
|
|
24
102
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import type { TuiThemeHost } from '../../dsh-adapter/themes.js';
|
|
2
3
|
/**
|
|
3
4
|
* DSH_TUI_THEME skips terminal detection (tests, debugging). Accepts a
|
|
4
|
-
* built-in
|
|
5
|
+
* built-in, static JSON, or runtime plugin theme name; invalid
|
|
5
6
|
* values are warned and ignored by the caller, falling back to detection.
|
|
6
7
|
* Exported for /reload, which must respect the env override's precedence.
|
|
7
8
|
*/
|
|
8
9
|
export declare function envThemeOverride(): string | undefined;
|
|
9
|
-
export declare function ThemeProvider({ children, theme, }: {
|
|
10
|
+
export declare function ThemeProvider({ children, theme, themeHost, }: {
|
|
10
11
|
children: React.ReactNode;
|
|
11
12
|
theme?: string;
|
|
13
|
+
/** Optional runtime theme host; absent in headless/static embeds. */
|
|
14
|
+
themeHost?: TuiThemeHost;
|
|
12
15
|
}): React.ReactNode;
|
|
13
16
|
/**
|
|
14
17
|
* Resolves the active theme name and the runtime setter. Returns
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyD,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyD,MAAM,OAAO,CAAA;AAU7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAgE/D;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,SAAS,CAGrD;AAcD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,KAAK,EACL,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,SAAS,CAAC,EAAE,YAAY,CAAA;CACzB,GAAG,KAAK,CAAC,SAAS,CA2KlB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAG9D"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
3
|
-
import { registerCustomThemeResolver, setActiveThemeName, setAutoThemeBase, getAutoThemeBase, AUTO_THEME_NAME, } from '../../theme.js';
|
|
4
|
-
import {
|
|
3
|
+
import { isThemeAvailable, registerCustomThemeResolver, setActiveThemeName, setAutoThemeBase, getAutoThemeBase, AUTO_THEME_NAME, } from '../../theme.js';
|
|
4
|
+
import { resolveCustomTheme } from '../../customTheme.js';
|
|
5
|
+
import { useRuntimeThemeSnapshot } from '../../hooks/useRuntimeThemeSnapshot.js';
|
|
5
6
|
import { readThemePref, writeThemePref } from '../../themePrefs.js';
|
|
6
7
|
import useStdin from '../../ink/hooks/use-stdin.js';
|
|
7
8
|
import { oscColor } from '../../ink/terminal-querier.js';
|
|
@@ -13,8 +14,8 @@ import { logForDebugging } from '../../utils/debug.js';
|
|
|
13
14
|
* (~/.dsh-tui/theme.json), it queries the terminal's background color
|
|
14
15
|
* (OSC 11) before first paint and picks the Gentle Mist Blue `light` palette
|
|
15
16
|
* on light backgrounds, `dark` otherwise. Priority: explicit `theme` prop >
|
|
16
|
-
* DSH_TUI_THEME (built-in or
|
|
17
|
-
* OSC 11 detection. An invalid forced name is warned and skipped, so
|
|
17
|
+
* DSH_TUI_THEME (built-in, static, or runtime plugin name) > persisted `/theme`
|
|
18
|
+
* choice > OSC 11 detection. An invalid forced name is warned and skipped, so
|
|
18
19
|
* detection still runs. Children render only after the theme settles, so
|
|
19
20
|
* the first frame already carries the final palette — no dark→light flash.
|
|
20
21
|
* Detection never blocks boot: a terminal that ignores OSC 11 (or a 400ms
|
|
@@ -31,12 +32,13 @@ import { logForDebugging } from '../../utils/debug.js';
|
|
|
31
32
|
* system light/dark mode.
|
|
32
33
|
*
|
|
33
34
|
* The context also exposes setTheme() for the runtime `/theme` picker: it
|
|
34
|
-
* validates
|
|
35
|
-
* swaps the palette (and the module-level mirror) immediately.
|
|
35
|
+
* validates static or plugin names, persists the choice to ~/.dsh-tui/theme.json
|
|
36
|
+
* and hot swaps the palette (and the module-level mirror) immediately. A
|
|
37
|
+
* disappearing plugin theme falls back safely without erasing the request.
|
|
36
38
|
*/
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
39
|
+
// Static user themes (~/.dsh-tui/themes/<name>.json) resolve through this
|
|
40
|
+
// registry; the optional runtime host resolver is installed by the Cordis
|
|
41
|
+
// service. Together they serve every themed component and non-React rendering.
|
|
40
42
|
registerCustomThemeResolver(resolveCustomTheme);
|
|
41
43
|
const ThemeContext = createContext({
|
|
42
44
|
theme: 'dark',
|
|
@@ -45,7 +47,7 @@ const ThemeContext = createContext({
|
|
|
45
47
|
});
|
|
46
48
|
/**
|
|
47
49
|
* DSH_TUI_THEME skips terminal detection (tests, debugging). Accepts a
|
|
48
|
-
* built-in
|
|
50
|
+
* built-in, static JSON, or runtime plugin theme name; invalid
|
|
49
51
|
* values are warned and ignored by the caller, falling back to detection.
|
|
50
52
|
* Exported for /reload, which must respect the env override's precedence.
|
|
51
53
|
*/
|
|
@@ -63,7 +65,8 @@ const DETECT_TIMEOUT_MS = 400;
|
|
|
63
65
|
function isLightBackground(r, g, b) {
|
|
64
66
|
return 0.299 * r + 0.587 * g + 0.114 * b > 140;
|
|
65
67
|
}
|
|
66
|
-
export function ThemeProvider({ children, theme, }) {
|
|
68
|
+
export function ThemeProvider({ children, theme, themeHost, }) {
|
|
69
|
+
const runtimeThemeSnapshot = useRuntimeThemeSnapshot(themeHost);
|
|
67
70
|
// Resolution happens once on mount: the forced chain (prop > env >
|
|
68
71
|
// persisted) or null, which arms OSC 11 detection.
|
|
69
72
|
const [forced] = useState(() => theme ?? envThemeOverride() ?? readThemePref());
|
|
@@ -72,12 +75,16 @@ export function ThemeProvider({ children, theme, }) {
|
|
|
72
75
|
return false;
|
|
73
76
|
if (isThemeAvailable(forced))
|
|
74
77
|
return true;
|
|
75
|
-
console.warn(`[dsh-tui] theme "${forced}" not found (built-ins: auto, light, dark, dark-ansi;
|
|
78
|
+
console.warn(`[dsh-tui] theme "${forced}" not found (built-ins: auto, light, dark, dark-ansi; static ~/.dsh-tui/themes/*.json; runtime plugin themes); falling back to auto-detection`);
|
|
76
79
|
return false;
|
|
77
80
|
});
|
|
78
81
|
// `auto` (like the unforced path) stays null until detection settles, so
|
|
79
82
|
// the first frame already carries the detected palette.
|
|
80
83
|
const [active, setActive] = useState(forcedValid && forced !== AUTO_THEME_NAME ? forced ?? null : null);
|
|
84
|
+
// Keep the user's requested name independent from the rendered fallback. If
|
|
85
|
+
// a plugin theme disappears, this lets a later registration restore it while
|
|
86
|
+
// the persisted preference remains untouched.
|
|
87
|
+
const requestedThemeRef = React.useRef(forced);
|
|
81
88
|
const { internal_querier, isRawModeSupported } = useStdin();
|
|
82
89
|
/**
|
|
83
90
|
* The palette `auto` resolves to, as React state so a flip re-renders
|
|
@@ -97,7 +104,11 @@ export function ThemeProvider({ children, theme, }) {
|
|
|
97
104
|
// directly; `auto` records the base and activates as `auto`.
|
|
98
105
|
const settle = (name, why) => {
|
|
99
106
|
logForDebugging(`theme: ${name} (${why})`);
|
|
100
|
-
|
|
107
|
+
const requested = requestedThemeRef.current;
|
|
108
|
+
if (forced !== AUTO_THEME_NAME && requested !== undefined && isThemeAvailable(requested)) {
|
|
109
|
+
setActive(requested);
|
|
110
|
+
}
|
|
111
|
+
else if (forced === AUTO_THEME_NAME) {
|
|
101
112
|
applyAutoBase(name);
|
|
102
113
|
setActive(AUTO_THEME_NAME);
|
|
103
114
|
}
|
|
@@ -173,16 +184,41 @@ export function ThemeProvider({ children, theme, }) {
|
|
|
173
184
|
console.warn('[dsh-tui] failed to write ~/.dsh-tui/theme.json');
|
|
174
185
|
return false;
|
|
175
186
|
}
|
|
187
|
+
requestedThemeRef.current = name;
|
|
176
188
|
setActive(name);
|
|
177
189
|
if (name === AUTO_THEME_NAME)
|
|
178
190
|
redetectAutoBase();
|
|
179
191
|
return true;
|
|
180
192
|
}, [redetectAutoBase]);
|
|
181
|
-
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (active !== null && !isThemeAvailable(active)) {
|
|
195
|
+
// A runtime plugin can disappear while its name remains persisted. Do
|
|
196
|
+
// not render getTheme(name)'s dark fallback under the stale name; keep
|
|
197
|
+
// the requested name in the ref and use a safe auto palette instead.
|
|
198
|
+
setActive(AUTO_THEME_NAME);
|
|
199
|
+
redetectAutoBase();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const requested = requestedThemeRef.current;
|
|
203
|
+
if (requested !== undefined &&
|
|
204
|
+
requested !== AUTO_THEME_NAME &&
|
|
205
|
+
requested !== active &&
|
|
206
|
+
isThemeAvailable(requested)) {
|
|
207
|
+
// The plugin theme was unavailable during boot (or briefly unloaded),
|
|
208
|
+
// but has registered again. Restore the persisted/requested choice.
|
|
209
|
+
setActive(requested);
|
|
210
|
+
}
|
|
211
|
+
}, [active, redetectAutoBase, runtimeThemeSnapshot]);
|
|
212
|
+
const renderedTheme = active === null
|
|
213
|
+
? 'dark'
|
|
214
|
+
: isThemeAvailable(active)
|
|
215
|
+
? active
|
|
216
|
+
: AUTO_THEME_NAME;
|
|
217
|
+
const value = React.useMemo(() => ({ theme: renderedTheme, autoBase, setTheme }), [renderedTheme, autoBase, setTheme]);
|
|
182
218
|
useEffect(() => {
|
|
183
219
|
if (active !== null)
|
|
184
|
-
setActiveThemeName(
|
|
185
|
-
}, [active]);
|
|
220
|
+
setActiveThemeName(renderedTheme);
|
|
221
|
+
}, [active, renderedTheme]);
|
|
186
222
|
if (active === null)
|
|
187
223
|
return null;
|
|
188
224
|
return _jsx(ThemeContext.Provider, { value: value, children: children });
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import React, { type PropsWithChildren, type Ref } from 'react';
|
|
2
2
|
import type { DOMElement } from '../../ink/dom.js';
|
|
3
3
|
import type { ClickEvent } from '../../ink/events/click-event.js';
|
|
4
|
+
import type { ContextMenuEvent } from '../../ink/events/context-menu-event.js';
|
|
5
|
+
import type { DragEvent } from '../../ink/events/drag-event.js';
|
|
4
6
|
import type { FocusEvent } from '../../ink/events/focus-event.js';
|
|
5
7
|
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
|
|
8
|
+
import type { PointerEvent } from '../../ink/events/pointer-event.js';
|
|
9
|
+
import type { WheelEvent } from '../../ink/events/wheel-event.js';
|
|
6
10
|
import type { Color, Styles } from '../../ink/styles.js';
|
|
7
11
|
import { type Theme } from '../../theme.js';
|
|
8
12
|
type ThemedColorProps = {
|
|
@@ -19,14 +23,31 @@ export type Props = BaseStylesWithoutColors & ThemedColorProps & {
|
|
|
19
23
|
tabIndex?: number;
|
|
20
24
|
autoFocus?: boolean;
|
|
21
25
|
onClick?: (event: ClickEvent) => void;
|
|
26
|
+
onContextMenu?: (event: ContextMenuEvent) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Drag protocol handlers, transparently forwarded to the inner Box.
|
|
29
|
+
* Only inside `<AlternateScreen>`, left button, no modifier at press;
|
|
30
|
+
* `dragstart` fires on FIRST movement (not press); a press+release
|
|
31
|
+
* without movement still triggers `onClick`. See Box's JSDoc.
|
|
32
|
+
*/
|
|
33
|
+
onDragStart?: (event: DragEvent) => void;
|
|
34
|
+
/** Fired on each pointer motion after dragstart. See onDragStart. */
|
|
35
|
+
onDragMove?: (event: DragEvent) => void;
|
|
36
|
+
/** Fired on release after dragstart. See onDragStart. */
|
|
37
|
+
onDragEnd?: (event: DragEvent) => void;
|
|
22
38
|
onFocus?: (event: FocusEvent) => void;
|
|
23
39
|
onFocusCapture?: (event: FocusEvent) => void;
|
|
24
40
|
onBlur?: (event: FocusEvent) => void;
|
|
25
41
|
onBlurCapture?: (event: FocusEvent) => void;
|
|
26
42
|
onKeyDown?: (event: KeyboardEvent) => void;
|
|
27
43
|
onKeyDownCapture?: (event: KeyboardEvent) => void;
|
|
28
|
-
onMouseEnter?: () => void;
|
|
29
|
-
onMouseLeave?: () => void;
|
|
44
|
+
onMouseEnter?: (event: PointerEvent) => void;
|
|
45
|
+
onMouseLeave?: (event: PointerEvent) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Wheel events over this Box's rendered rect (position-routed; see the
|
|
48
|
+
* ink Box's onWheel JSDoc). Transparently forwarded.
|
|
49
|
+
*/
|
|
50
|
+
onWheel?: (event: WheelEvent) => void;
|
|
30
51
|
};
|
|
31
52
|
/**
|
|
32
53
|
* Theme-aware Box component that resolves theme color keys to raw colors
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemedBox.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ThemedBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,iBAAiB,EAAE,KAAK,GAAG,EAAE,MAAM,OAAO,CAAA;AAE/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AACvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAY,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAIrD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAC7C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAChD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAC9C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAC/C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;CAC/C,CAAA;AAGD,KAAK,uBAAuB,GAAG,IAAI,CACjC,MAAM,EACJ,UAAU,GACV,aAAa,GACb,gBAAgB,GAChB,mBAAmB,GACnB,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,CACpB,CAAA;AAED,MAAM,MAAM,KAAK,GAAG,uBAAuB,GACzC,gBAAgB,GAAG;IACjB,GAAG,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;IACrC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;IAC5C,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;IAC3C,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAC1C,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IACjD,YAAY,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"ThemedBox.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ThemedBox.tsx"],"names":[],"mappings":"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"}
|
|
@@ -2,9 +2,14 @@ import React from 'react';
|
|
|
2
2
|
import type { ClickEvent } from '../../ink/events/click-event.js';
|
|
3
3
|
type Props = {
|
|
4
4
|
thinking: string;
|
|
5
|
+
/** The FULL un-revealed text (reasoning rows under smooth streaming):
|
|
6
|
+
* `thinking` carries the revealed slice the expanded body paints, while
|
|
7
|
+
* the live preview ticker must follow the newest ARRIVED content — never
|
|
8
|
+
* a lagging reveal. Falls back to `thinking`. */
|
|
9
|
+
textFull?: string;
|
|
5
10
|
/** Adds the top margin between messages (CC: addMargin). */
|
|
6
11
|
addMargin: boolean;
|
|
7
|
-
/**
|
|
12
|
+
/** Show the full text (Ctrl+O, per-row expansion, or live click toggle). */
|
|
8
13
|
verbose: boolean;
|
|
9
14
|
/** True while the reasoning block is still streaming — the leading anchor
|
|
10
15
|
* becomes a rotating braille spinner (Kimi Code style) and settles back
|
|
@@ -21,14 +26,14 @@ type Props = {
|
|
|
21
26
|
onClick?(event: ClickEvent): void;
|
|
22
27
|
};
|
|
23
28
|
/**
|
|
24
|
-
* Thinking block:
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* style), settling back to the static anchor (`⚓`)
|
|
29
|
+
* Thinking block: settled rows fold to `⚓ Thinking (ctrl+o to expand)`;
|
|
30
|
+
* streaming rows switch between a three-line preview and the full reasoning
|
|
31
|
+
* text on click. The live leading mark is a rotating braille spinner
|
|
32
|
+
* (`⠋⠙⠹…`, Kimi Code style), settling back to the static anchor (`⚓`). When
|
|
28
33
|
* the channel records the reasoning duration, the label carries it
|
|
29
34
|
* (`⚓ Thinking · 12s …`) — dsh-tui's take on making thinking time visible in
|
|
30
35
|
* the transcript.
|
|
31
36
|
*/
|
|
32
|
-
export declare function AssistantThinkingMessage({ thinking, addMargin, verbose, streaming, preview, durationMs, isSelected, onClick, }: Props): React.ReactNode;
|
|
37
|
+
export declare function AssistantThinkingMessage({ thinking, textFull, addMargin, verbose, streaming, preview, durationMs, isSelected, onClick, }: Props): React.ReactNode;
|
|
33
38
|
export {};
|
|
34
39
|
//# sourceMappingURL=AssistantThinkingMessage.d.ts.map
|