@pellux/goodvibes-tui 1.1.0 → 1.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/CHANGELOG.md +85 -21
- package/README.md +28 -18
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +90 -14
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +104 -51
- package/src/core/turn-cancellation.ts +25 -0
- package/src/core/turn-event-wiring.ts +72 -3
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/export/cost-utils.ts +36 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +84 -5
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +232 -0
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +10 -2
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +73 -35
- package/src/input/commands/operator-panel-runtime.ts +71 -31
- package/src/input/commands/planning-runtime.ts +116 -4
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +26 -6
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +16 -4
- package/src/input/commands/shell-core.ts +25 -8
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +488 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +175 -0
- package/src/input/config-modal.ts +592 -0
- package/src/input/feed-context-factory.ts +10 -8
- package/src/input/handler-command-route.ts +37 -20
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +74 -114
- package/src/input/handler-feed.ts +79 -27
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +136 -6
- package/src/input/handler-modal-stack.ts +14 -7
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +26 -102
- package/src/input/handler-shortcuts.ts +74 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +12 -29
- package/src/input/keybindings.ts +23 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +77 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +146 -2
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +49 -51
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +18 -46
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +758 -0
- package/src/panels/fleet-read-model.ts +520 -0
- package/src/panels/fleet-steer.ts +125 -0
- package/src/panels/fleet-stop.ts +153 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +166 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +42 -0
- package/src/panels/modals/pairing-modal.ts +163 -0
- package/src/panels/modals/planning-modal.ts +304 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +177 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +123 -9
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -11
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +37 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +216 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +95 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +15 -3
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +43 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +62 -4
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +133 -0
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +27 -1
- package/src/runtime/bootstrap-command-parts.ts +41 -10
- package/src/runtime/bootstrap-core.ts +47 -15
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +48 -17
- package/src/runtime/bootstrap.ts +129 -22
- package/src/runtime/code-index-services.ts +135 -0
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +26 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +59 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +98 -46
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +27 -3
- package/src/runtime/workstream-services.ts +327 -0
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +68 -4
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +171 -26
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -8,11 +8,9 @@ import { renderModelWorkspace } from './model-workspace.ts';
|
|
|
8
8
|
import { renderSelectionModalOverlay } from './selection-modal-overlay.ts';
|
|
9
9
|
import { renderSearchOverlay } from './search-overlay.ts';
|
|
10
10
|
import { renderHistorySearchOverlay } from './history-search-overlay.ts';
|
|
11
|
-
import { renderProcessModal } from './process-modal.ts';
|
|
12
|
-
import { renderAgentDetailModal } from './agent-detail-modal.ts';
|
|
13
|
-
import { renderLiveTailModal } from './live-tail-modal.ts';
|
|
14
11
|
import { renderContextInspector } from './context-inspector.ts';
|
|
15
12
|
import { renderSettingsModal } from './settings-modal.ts';
|
|
13
|
+
import { renderConfigModal } from './config-modal.ts';
|
|
16
14
|
import { renderMcpWorkspace } from './mcp-workspace.ts';
|
|
17
15
|
import { renderSessionPickerModal } from './session-picker-modal.ts';
|
|
18
16
|
import { renderProfilePickerModal } from './profile-picker-modal.ts';
|
|
@@ -78,20 +76,9 @@ export function applyConversationOverlays(
|
|
|
78
76
|
next.push(...renderHistorySearchOverlay(input.historySearch, conversationWidth));
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!fullscreenClaimed && input.agentDetailModal.active) {
|
|
87
|
-
const lines = renderAgentDetailModal(input.agentDetailModal, conversationWidth, viewportHeight);
|
|
88
|
-
next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!fullscreenClaimed && input.liveTailModal.active) {
|
|
92
|
-
const lines = renderLiveTailModal(input.liveTailModal, conversationWidth, viewportHeight);
|
|
93
|
-
next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
|
|
94
|
-
}
|
|
79
|
+
// W6.1 retirement: the process, agent-detail, and live-tail modal overlays
|
|
80
|
+
// were removed with those modals — F2 now opens the Fleet panel, which
|
|
81
|
+
// subsumes the live process tree.
|
|
95
82
|
|
|
96
83
|
if (!fullscreenClaimed && input.contextInspectorModal.active) {
|
|
97
84
|
const lines = renderContextInspector(conversation, conversationWidth, viewportHeight, contextWindow);
|
|
@@ -104,6 +91,12 @@ export function applyConversationOverlays(
|
|
|
104
91
|
fullscreenClaimed = true;
|
|
105
92
|
}
|
|
106
93
|
|
|
94
|
+
if (!fullscreenClaimed && input.configModal.active) {
|
|
95
|
+
const lines = renderConfigModal(input.configModal, conversationWidth, viewportHeight);
|
|
96
|
+
next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
|
|
97
|
+
fullscreenClaimed = true;
|
|
98
|
+
}
|
|
99
|
+
|
|
107
100
|
if (input.mcpWorkspace.active) {
|
|
108
101
|
const lines = renderMcpWorkspace(input.mcpWorkspace, conversationWidth, viewportHeight);
|
|
109
102
|
next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// fleet-tab-strip.ts
|
|
3
|
+
//
|
|
4
|
+
// Wave-3 (W3.1 Part C3) — renders FleetPanel's session-tab strip by reusing
|
|
5
|
+
// renderTabStrip exactly as panel-workspace-bar.ts does for the workspace
|
|
6
|
+
// tab bar. Deliberately a SEPARATE, visually distinct strip from the
|
|
7
|
+
// workspace bar: workspace tabs switch PANELS, fleet session tabs switch
|
|
8
|
+
// ATTACHED PROCESSES within one panel — conflating their styling would blur
|
|
9
|
+
// that distinction for the operator.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
import type { Line } from '../types/grid.ts';
|
|
13
|
+
import type { FleetTabsState } from '../panels/fleet-tabs.ts';
|
|
14
|
+
import { renderTabStrip, type TabHitRegion } from './tab-strip.ts';
|
|
15
|
+
import { UI_TONES } from './ui-primitives.ts';
|
|
16
|
+
|
|
17
|
+
const ACTIVE_FG = UI_TONES.fg.primary;
|
|
18
|
+
const ACTIVE_BG = UI_TONES.bg.selected;
|
|
19
|
+
const INACTIVE_FG = UI_TONES.fg.muted;
|
|
20
|
+
const SEPARATOR_FG = UI_TONES.fg.dim;
|
|
21
|
+
const LABEL_FG = UI_TONES.fg.secondary;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Render the fleet session-tab strip, or `null` when there are no attached
|
|
25
|
+
* tabs — the panel omits the strip entirely in that case (root-tab-only),
|
|
26
|
+
* which is what keeps the pre-Wave-3 fleet-panel goldens byte-identical.
|
|
27
|
+
*/
|
|
28
|
+
export function renderFleetTabStrip(
|
|
29
|
+
state: FleetTabsState,
|
|
30
|
+
width: number,
|
|
31
|
+
onLayout?: (regions: readonly TabHitRegion[]) => void,
|
|
32
|
+
): Line | null {
|
|
33
|
+
if (state.tabs.length === 0) return null;
|
|
34
|
+
return renderTabStrip({
|
|
35
|
+
width,
|
|
36
|
+
onLayout,
|
|
37
|
+
tabs: [
|
|
38
|
+
{ label: 'Tree', active: state.activeTabIndex === 0 },
|
|
39
|
+
...state.tabs.map((tab, index) => ({
|
|
40
|
+
label: tab.label,
|
|
41
|
+
active: state.activeTabIndex === index + 1,
|
|
42
|
+
})),
|
|
43
|
+
],
|
|
44
|
+
prefixLabel: ' SESSIONS ',
|
|
45
|
+
style: {
|
|
46
|
+
activeFg: ACTIVE_FG,
|
|
47
|
+
activeBg: ACTIVE_BG,
|
|
48
|
+
activeBold: true,
|
|
49
|
+
inactiveFg: INACTIVE_FG,
|
|
50
|
+
separatorFg: SEPARATOR_FG,
|
|
51
|
+
labelFg: LABEL_FG,
|
|
52
|
+
overflowFg: SEPARATOR_FG,
|
|
53
|
+
trailingFg: INACTIVE_FG,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -24,10 +24,22 @@ export function isAgentActive(composerStatus: string | undefined): boolean {
|
|
|
24
24
|
return composerStatus !== undefined && ACTIVE_TURN_STATUSES.has(composerStatus);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
// UX-C: Ctrl+P is a TOGGLE (open+focus when nothing is open or unfocused,
|
|
28
|
+
// hide when the workspace already has focus — see openPanelPicker in
|
|
29
|
+
// shell/ui-openers.ts). The bare noun 'panels' undersold that; naming the verb
|
|
30
|
+
// keeps the tip honest about what the chord actually does.
|
|
31
|
+
const TIP_PANELS = 'Ctrl+P toggle panels';
|
|
32
|
+
// W6.2 e: F2 now opens the Fleet panel (the process modal was retired), so the
|
|
33
|
+
// tip names 'fleet', not 'processes'.
|
|
34
|
+
const TIP_PROCESSES = 'F2 fleet';
|
|
29
35
|
const TIP_HELP = '? help';
|
|
30
|
-
|
|
36
|
+
// W6.2 f: an empty-composer Ctrl+C does NOT quit on the first press — it arms a
|
|
37
|
+
// ~1s "press again to exit" confirm, and only a SECOND Ctrl+C within that window
|
|
38
|
+
// exits (an intentional accidental-exit guard). The old 'Ctrl+C quit' tip
|
|
39
|
+
// implied a single press quits, so two presses seconds apart appeared to do
|
|
40
|
+
// nothing. 'Ctrl+C x2 quit' matches the real double-press contract (and the
|
|
41
|
+
// help overlay's "Ctrl+C x2 Exit" row).
|
|
42
|
+
const TIP_QUIT = 'Ctrl+C x2 quit';
|
|
31
43
|
|
|
32
44
|
/**
|
|
33
45
|
* Build the footer discoverability tip. Segments join with the shared middle
|
|
@@ -2,30 +2,40 @@ import type { Line } from '../types/grid.ts';
|
|
|
2
2
|
import { createEmptyLine, createStyledCell } from '../types/grid.ts';
|
|
3
3
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
4
4
|
import { GLYPHS } from './ui-primitives.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { activeUiTones, registerThemeRefresh } from './theme.ts';
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
7
|
+
// Built from the mode-resolved chrome tones (activeUiTones) and rebuilt IN PLACE
|
|
8
|
+
// on a mode flip via the registered refresher (read by reference across the
|
|
9
|
+
// fullscreen workspace surfaces — see theme.ts's active-mode runtime note).
|
|
10
|
+
function buildFullscreenPalette(): Record<string, string> {
|
|
11
|
+
const t = activeUiTones();
|
|
12
|
+
return {
|
|
13
|
+
border: t.border,
|
|
14
|
+
title: t.accent.browser,
|
|
15
|
+
subtitle: t.accent.conversation,
|
|
16
|
+
text: t.fg.primary,
|
|
17
|
+
muted: t.fg.muted,
|
|
18
|
+
dim: t.border,
|
|
19
|
+
selectedBg: t.bg.selected,
|
|
20
|
+
categoryBg: t.bg.section,
|
|
21
|
+
contextBg: t.bg.surface,
|
|
22
|
+
controlsBg: t.bg.base,
|
|
23
|
+
footerBg: t.bg.footer,
|
|
24
|
+
good: t.state.good,
|
|
25
|
+
warn: t.state.warn,
|
|
26
|
+
bad: t.state.bad,
|
|
27
|
+
info: t.state.info,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
11
30
|
|
|
12
|
-
export const FULLSCREEN_PALETTE = {
|
|
13
|
-
border:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
categoryBg: TONES.bg.section,
|
|
21
|
-
contextBg: TONES.bg.surface,
|
|
22
|
-
controlsBg: TONES.bg.base,
|
|
23
|
-
footerBg: TONES.bg.footer,
|
|
24
|
-
good: TONES.state.good,
|
|
25
|
-
warn: TONES.state.warn,
|
|
26
|
-
bad: TONES.state.bad,
|
|
27
|
-
info: TONES.state.info,
|
|
28
|
-
} as const;
|
|
31
|
+
export const FULLSCREEN_PALETTE = buildFullscreenPalette() as {
|
|
32
|
+
readonly border: string; readonly title: string; readonly subtitle: string;
|
|
33
|
+
readonly text: string; readonly muted: string; readonly dim: string;
|
|
34
|
+
readonly selectedBg: string; readonly categoryBg: string; readonly contextBg: string;
|
|
35
|
+
readonly controlsBg: string; readonly footerBg: string; readonly good: string;
|
|
36
|
+
readonly warn: string; readonly bad: string; readonly info: string;
|
|
37
|
+
};
|
|
38
|
+
registerThemeRefresh(() => Object.assign(FULLSCREEN_PALETTE as Record<string, string>, buildFullscreenPalette()));
|
|
29
39
|
|
|
30
40
|
export type FullscreenTextStyle = Partial<Omit<Line[number], 'char'>>;
|
|
31
41
|
|
|
@@ -24,6 +24,7 @@ export class GitStatusProvider {
|
|
|
24
24
|
private lastFetch = 0;
|
|
25
25
|
private readonly ttlMs = 2000;
|
|
26
26
|
private fetching = false;
|
|
27
|
+
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
27
28
|
|
|
28
29
|
constructor(private readonly workingDirectory: string) {}
|
|
29
30
|
|
|
@@ -53,6 +54,45 @@ export class GitStatusProvider {
|
|
|
53
54
|
return this.cache;
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Start a lightweight live-repo-state poll. Mirrors DiffPanel's principle of
|
|
59
|
+
* never trusting a cached "is this a repo" flag (see diff-panel.ts
|
|
60
|
+
* showGitDiff/showFileDiffs/showStagedDiff, all gated on a fresh
|
|
61
|
+
* GitService.isGitRepo() check) but amortizes the cost for the header: the
|
|
62
|
+
* cheap synchronous isGitRepo() spawn runs every tick; the heavier async
|
|
63
|
+
* status()+branch() fetch (this.refresh()) only runs when that boolean
|
|
64
|
+
* actually flips — e.g. an external `git init`, or `.git` removed.
|
|
65
|
+
*/
|
|
66
|
+
startPolling(intervalMs: number, onChange: (info: GitHeaderInfo) => void): void {
|
|
67
|
+
if (this.pollTimer !== null) return;
|
|
68
|
+
const checkIsRepo = (): boolean | null => {
|
|
69
|
+
try {
|
|
70
|
+
return GitService.isGitRepo(this.workingDirectory);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
logger.debug('GitStatusProvider: isGitRepo poll check failed', { error: summarizeError(err) });
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
let lastKnownIsRepo = checkIsRepo();
|
|
77
|
+
this.pollTimer = setInterval(() => {
|
|
78
|
+
const isRepoNow = checkIsRepo();
|
|
79
|
+
if (isRepoNow !== null && isRepoNow !== lastKnownIsRepo) {
|
|
80
|
+
lastKnownIsRepo = isRepoNow;
|
|
81
|
+
this.refresh().then(onChange).catch((err) => {
|
|
82
|
+
logger.debug('GitStatusProvider: poll-triggered refresh failed', { error: summarizeError(err) });
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}, intervalMs);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Stop the live-repo-state poll started by startPolling(). No-op if not polling. */
|
|
89
|
+
stopPolling(): void {
|
|
90
|
+
if (this.pollTimer !== null) {
|
|
91
|
+
clearInterval(this.pollTimer);
|
|
92
|
+
this.pollTimer = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
56
96
|
private async _fetch(): Promise<void> {
|
|
57
97
|
if (this.fetching) return;
|
|
58
98
|
this.fetching = true;
|
|
@@ -70,7 +110,9 @@ export class GitStatusProvider {
|
|
|
70
110
|
statusResult.conflicted.length > 0 ||
|
|
71
111
|
statusResult.not_added.length > 0;
|
|
72
112
|
this.cache = {
|
|
73
|
-
|
|
113
|
+
// Empty `current` means an unborn HEAD — a repo with no commits yet.
|
|
114
|
+
// Show "new" instead of an unfriendly "?" for that first-run case. (5a)
|
|
115
|
+
branch: branchResult.current || 'new',
|
|
74
116
|
dirty,
|
|
75
117
|
ahead: statusResult.ahead ?? 0,
|
|
76
118
|
behind: statusResult.behind ?? 0,
|
|
@@ -289,7 +289,7 @@ export function renderShortcutsOverlay(
|
|
|
289
289
|
row(kb('block-copy'), 'Copy block to clipboard'),
|
|
290
290
|
row(kb('block-save'), 'Save block to file'),
|
|
291
291
|
row(kb('copy-selection'), 'Copy selection'),
|
|
292
|
-
row('F2', '
|
|
292
|
+
row('F2', 'Open the Fleet panel'),
|
|
293
293
|
row('?', 'Help overlay'),
|
|
294
294
|
row(`${kb('clear-cancel')} x2`, 'Exit'),
|
|
295
295
|
'',
|
|
@@ -303,7 +303,7 @@ export function renderShortcutsOverlay(
|
|
|
303
303
|
row(kb('panel-focus-toggle'), 'Swap focus between top / bottom pane'),
|
|
304
304
|
row(kb('panel-close'), 'Close active panel'),
|
|
305
305
|
row(kb('panel-close-all'), 'Close all panels'),
|
|
306
|
-
row(kb('panel-ops'), 'Open the
|
|
306
|
+
row(kb('panel-ops'), 'Open and focus the Fleet panel'),
|
|
307
307
|
'',
|
|
308
308
|
' In-Panel Controls',
|
|
309
309
|
' ' + '\u2500'.repeat(40),
|
package/src/renderer/layout.ts
CHANGED
|
@@ -20,10 +20,6 @@ export const TOOL_STATUS = {
|
|
|
20
20
|
TOOL_NAME_PAD: 8,
|
|
21
21
|
} as const;
|
|
22
22
|
|
|
23
|
-
export const COLORS = {
|
|
24
|
-
DIM_TEXT: UI_TONES.fg.dim,
|
|
25
|
-
} as const;
|
|
26
|
-
|
|
27
23
|
export const BORDERS = {
|
|
28
24
|
THINKING: { char: '▌', color: UI_TONES.state.reasoning },
|
|
29
25
|
ERROR: { char: '▌', color: UI_TONES.state.bad },
|
package/src/renderer/markdown.ts
CHANGED
|
@@ -3,10 +3,11 @@ import { UIFactory } from './ui-factory.ts';
|
|
|
3
3
|
import { renderCodeBlock } from './code-block.ts';
|
|
4
4
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
5
5
|
import { LAYOUT } from './layout.ts';
|
|
6
|
-
import {
|
|
6
|
+
import { activeTheme } from './theme.ts';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Transcript tokens are read live per render (const T = activeTheme() at the top
|
|
9
|
+
// of each render function below) so a dark→light repaint re-resolves with no
|
|
10
|
+
// module reload. See theme.ts's active-mode runtime note.
|
|
10
11
|
|
|
11
12
|
export interface MarkdownRenderOptions {
|
|
12
13
|
codeBlockLineNumbers?: boolean;
|
|
@@ -62,6 +63,7 @@ export function renderMarkdownTracked(
|
|
|
62
63
|
width: number,
|
|
63
64
|
options: MarkdownRenderOptions = {},
|
|
64
65
|
): { lines: ReturnType<typeof renderMarkdown>; codeBlocks: CodeBlockSpan[] } {
|
|
66
|
+
const T = activeTheme();
|
|
65
67
|
const lines: ReturnType<typeof renderMarkdown> = [];
|
|
66
68
|
const codeBlocks: CodeBlockSpan[] = [];
|
|
67
69
|
const rawLines = text.split('\n');
|
|
@@ -236,6 +238,7 @@ function stripMarkdown(text: string): string {
|
|
|
236
238
|
* truncates long content, and renders with proper styling.
|
|
237
239
|
*/
|
|
238
240
|
function renderTable(rows: string[], width: number, indent: number): Line[] {
|
|
241
|
+
const T = activeTheme();
|
|
239
242
|
const lines: Line[] = [];
|
|
240
243
|
|
|
241
244
|
// Parse rows into cells, skip separator
|
|
@@ -542,6 +545,7 @@ function compositeInlineLine(
|
|
|
542
545
|
prefixStyle: Partial<Cell>,
|
|
543
546
|
textStartX: number
|
|
544
547
|
): Line[] {
|
|
548
|
+
const T = activeTheme();
|
|
545
549
|
const lines: Line[] = [];
|
|
546
550
|
|
|
547
551
|
// Flatten tokens to [char, style] pairs
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from './overlay-box.ts';
|
|
10
10
|
import { getOverlayMaxWidth } from './overlay-viewport.ts';
|
|
11
11
|
import { GLYPHS } from './ui-primitives.ts';
|
|
12
|
-
import {
|
|
12
|
+
import { activeUiTones, registerThemeRefresh } from './theme.ts';
|
|
13
13
|
import { HINT_SEPARATOR } from './hint-grammar.ts';
|
|
14
14
|
|
|
15
15
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -92,25 +92,30 @@ export interface ModalConfig {
|
|
|
92
92
|
|
|
93
93
|
// ── Defaults ─────────────────────────────────────────────────────────────────
|
|
94
94
|
|
|
95
|
-
// DEFAULT_STYLE is built from the mode-resolved chrome tones (
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
95
|
+
// DEFAULT_STYLE is built from the mode-resolved chrome tones (activeUiTones) and
|
|
96
|
+
// rebuilt IN PLACE on a mode flip via the registered refresher. createModal reads
|
|
97
|
+
// it per call via `{ ...DEFAULT_STYLE, ...config.style }`, so the in-place rebuild
|
|
98
|
+
// reaches every modal — see theme.ts's active-mode runtime note.
|
|
99
|
+
function buildDefaultModalStyle(): Required<ModalStyle> {
|
|
100
|
+
const t = activeUiTones();
|
|
101
|
+
return {
|
|
102
|
+
titleFg: t.fg.primary,
|
|
103
|
+
borderFg: t.fg.dim,
|
|
104
|
+
hintFg: t.fg.muted,
|
|
105
|
+
selectedFg: t.fg.primary,
|
|
106
|
+
selectedBg: t.bg.selected,
|
|
107
|
+
textFg: t.fg.primary,
|
|
108
|
+
accentFg: t.state.info,
|
|
109
|
+
titleRowFg: t.fg.secondary,
|
|
110
|
+
titleBg: t.bg.title,
|
|
111
|
+
sectionBg: t.bg.section,
|
|
112
|
+
inputBg: t.bg.input,
|
|
113
|
+
surfaceBg: t.bg.surface,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const DEFAULT_STYLE: Required<ModalStyle> = buildDefaultModalStyle();
|
|
118
|
+
registerThemeRefresh(() => Object.assign(DEFAULT_STYLE, buildDefaultModalStyle()));
|
|
114
119
|
|
|
115
120
|
// ── ModalFactory ─────────────────────────────────────────────────────────────
|
|
116
121
|
|
|
@@ -124,6 +124,10 @@ function modelKey(model: ModelDefinition): string {
|
|
|
124
124
|
|
|
125
125
|
function targetSummary(info: ModelPickerTargetInfo): string {
|
|
126
126
|
if (!info.enabled) return 'disabled';
|
|
127
|
+
// 'embeddings' has no model concept — configuredNote carries the honest
|
|
128
|
+
// provider id + dimensions + configured-state summary instead of a
|
|
129
|
+
// computed provider:model route (which would print a phantom "model:").
|
|
130
|
+
if (info.configuredNote) return info.configuredNote;
|
|
127
131
|
const route = formatRoute(info.provider, info.model);
|
|
128
132
|
if (info.inherited) return `inherits ${route}`;
|
|
129
133
|
return route;
|
|
@@ -145,6 +149,7 @@ function targetLabelFor(target: string): string {
|
|
|
145
149
|
if (target === 'helper') return 'Helper Model';
|
|
146
150
|
if (target === 'tool') return 'Tool LLM';
|
|
147
151
|
if (target === 'tts') return 'TTS LLM';
|
|
152
|
+
if (target === 'embeddings') return 'Embeddings';
|
|
148
153
|
return 'Main Chat';
|
|
149
154
|
}
|
|
150
155
|
|
|
@@ -174,10 +179,25 @@ function detailLines(picker: ModelPickerModal, width: number): string[] {
|
|
|
174
179
|
}
|
|
175
180
|
} else if (picker.mode === 'effort') {
|
|
176
181
|
lines.push(`Reasoning effort applies to the main chat model. Select the default effort for this model.`);
|
|
182
|
+
} else if (picker.mode === 'embeddingProvider') {
|
|
183
|
+
lines.push('Embedding provider selection: choose the provider memory search and the code index use to embed content.');
|
|
184
|
+
const selectedEmbedding = picker.embeddingProviders[picker.selectedIndex];
|
|
185
|
+
if (selectedEmbedding) {
|
|
186
|
+
const status = selectedEmbedding.configured ? 'configured' : 'unconfigured';
|
|
187
|
+
lines.push(`Selected: ${selectedEmbedding.id} | ${selectedEmbedding.dimensions} dimensions | ${status}${selectedEmbedding.detail ? ` | ${selectedEmbedding.detail}` : ''}`);
|
|
188
|
+
}
|
|
177
189
|
} else {
|
|
178
190
|
lines.push(`Context cap overrides the detected local-model context window for this selection.`);
|
|
179
191
|
}
|
|
180
|
-
|
|
192
|
+
// W3-T2: a visible cursor glyph on the query itself when search is focused —
|
|
193
|
+
// the only prior affordance was a footer-hint text swap ('/ search' vs.
|
|
194
|
+
// 'Typing filters search...'), buried at the end of a long hint line and
|
|
195
|
+
// easy to miss (confirmed via live tmux repro). This puts the "you are
|
|
196
|
+
// typing here" signal where the eye already is, on the Search: value.
|
|
197
|
+
const searchDisplay = picker.query.length > 0
|
|
198
|
+
? `${picker.query}${picker.searchFocused ? GLYPHS.surface.cursor : ''}`
|
|
199
|
+
: (picker.searchFocused ? GLYPHS.surface.cursor : '(none)');
|
|
200
|
+
const filterText = `Search: ${searchDisplay} | Price: ${picker.categoryFilter} | Capability: ${picker.capabilityFilter} | Group: ${picker.groupBy} | Available only: ${picker.availableOnly ? 'yes' : 'no'}`;
|
|
181
201
|
lines.push(filterText);
|
|
182
202
|
return lines.flatMap((line) => wrapText(line, Math.max(1, width)));
|
|
183
203
|
}
|
|
@@ -288,6 +308,28 @@ function renderContextCapRows(picker: ModelPickerModal, lines: Line[], rows: num
|
|
|
288
308
|
}
|
|
289
309
|
}
|
|
290
310
|
|
|
311
|
+
function renderEmbeddingProviderRows(picker: ModelPickerModal, lines: Line[], rows: number, startX: number, width: number): void {
|
|
312
|
+
const providers = picker.embeddingProviders;
|
|
313
|
+
const { start, end } = stableWindow(providers.length, picker.selectedIndex, rows);
|
|
314
|
+
for (let visibleRow = 0; visibleRow < rows; visibleRow += 1) {
|
|
315
|
+
const absolute = start + visibleRow;
|
|
316
|
+
const provider = providers[absolute];
|
|
317
|
+
const line = lines[visibleRow]!;
|
|
318
|
+
if (!provider) continue;
|
|
319
|
+
const selected = absolute === picker.selectedIndex;
|
|
320
|
+
const bg = selected ? PALETTE.selectedBg : PALETTE.bodyBg;
|
|
321
|
+
fillRange(line, startX, startX + width - 1, bg);
|
|
322
|
+
const marker = selected ? (picker.focusPane === 'items' ? GLYPHS.navigation.selected : '•') : ' ';
|
|
323
|
+
const status = provider.configured ? 'configured' : 'unconfigured';
|
|
324
|
+
writeText(line, startX + 1, width, padDisplay(marker, 2), { fg: PALETTE.text, bg, bold: selected });
|
|
325
|
+
writeText(line, startX + 3, Math.max(0, width - 30), padDisplay(provider.label, Math.max(0, width - 30)), { fg: selected ? PALETTE.text : PALETTE.muted, bg, bold: selected });
|
|
326
|
+
writeText(line, startX + Math.max(4, width - 25), 12, padDisplay(`${provider.dimensions}d`, 12), { fg: PALETTE.dim, bg });
|
|
327
|
+
writeText(line, startX + Math.max(4, width - 13), 12, padDisplay(status, 12), { fg: provider.configured ? PALETTE.good : PALETTE.warn, bg });
|
|
328
|
+
}
|
|
329
|
+
if (start > 0 && lines[0]) writeText(lines[0], startX + 1, width - 2, `${GLYPHS.navigation.moreAbove} ${start} more provider(s) above`, { fg: PALETTE.dim, bg: PALETTE.bodyBg, dim: true });
|
|
330
|
+
if (end < providers.length && lines[rows - 1]) writeText(lines[rows - 1]!, startX + 1, width - 2, `${GLYPHS.navigation.moreBelow} ${providers.length - end} more provider(s) below`, { fg: PALETTE.dim, bg: PALETTE.bodyBg, dim: true });
|
|
331
|
+
}
|
|
332
|
+
|
|
291
333
|
function writeTableHeader(line: Line, picker: ModelPickerModal, startX: number, width: number): void {
|
|
292
334
|
fillRange(line, startX, startX + width - 1, PALETTE.footerBg);
|
|
293
335
|
const style = { fg: PALETTE.muted, bg: PALETTE.footerBg, bold: true };
|
|
@@ -303,6 +345,10 @@ function writeTableHeader(line: Line, picker: ModelPickerModal, startX: number,
|
|
|
303
345
|
writeText(line, startX + 1, width - 2, 'Context cap input', style);
|
|
304
346
|
return;
|
|
305
347
|
}
|
|
348
|
+
if (picker.mode === 'embeddingProvider') {
|
|
349
|
+
writeText(line, startX + 1, width - 2, 'Embedding provider Dimensions Status', style);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
306
352
|
const providerW = clamp(Math.floor(width * 0.14), 10, 18);
|
|
307
353
|
const ctxW = 8;
|
|
308
354
|
const tierW = 8;
|
|
@@ -337,7 +383,7 @@ export function renderModelWorkspace(picker: ModelPickerModal, width: number, vi
|
|
|
337
383
|
const header = contentLine(safeWidth, PALETTE.footerBg);
|
|
338
384
|
writeText(header, 2, targetW - 2, 'Targets', { fg: PALETTE.subtitle, bold: true, bg: PALETTE.footerBg });
|
|
339
385
|
drawVertical(header, targetW, PALETTE.footerBg);
|
|
340
|
-
const modeLabel = picker.mode === 'provider' ? 'Provider list' : picker.mode === 'model' ? 'Model list' : picker.mode === 'effort' ? 'Reasoning effort' : 'Context cap';
|
|
386
|
+
const modeLabel = picker.mode === 'provider' ? 'Provider list' : picker.mode === 'model' ? 'Model list' : picker.mode === 'effort' ? 'Reasoning effort' : picker.mode === 'embeddingProvider' ? 'Embedding providers' : 'Context cap';
|
|
341
387
|
writeText(header, contentX + 1, contentW - 2, `${modeLabel} • ${picker.getItemCount()} item(s)`, { fg: PALETTE.subtitle, bold: true, bg: PALETTE.footerBg });
|
|
342
388
|
lines.push(header);
|
|
343
389
|
|
|
@@ -380,14 +426,23 @@ export function renderModelWorkspace(picker: ModelPickerModal, width: number, vi
|
|
|
380
426
|
renderModelRows(picker, listLines, listLines.length, contentX, contentW - 1);
|
|
381
427
|
} else if (picker.mode === 'contextCap') {
|
|
382
428
|
renderContextCapRows(picker, listLines, listLines.length, contentX, contentW - 1);
|
|
429
|
+
} else if (picker.mode === 'embeddingProvider') {
|
|
430
|
+
renderEmbeddingProviderRows(picker, listLines, listLines.length, contentX, contentW - 1);
|
|
383
431
|
} else {
|
|
384
432
|
renderEffortRows(picker, listLines, listLines.length, contentX, contentW - 1);
|
|
385
433
|
}
|
|
386
434
|
|
|
387
435
|
const footer = contentLine(safeWidth, PALETTE.footerBg);
|
|
388
436
|
const targetHint = picker.focusPane === 'targets' ? 'Focus targets' : 'Focus list';
|
|
389
|
-
|
|
390
|
-
|
|
437
|
+
// W3 Finding 2: while search is focused, every printable key (including
|
|
438
|
+
// c/a/b/g) types into the query — handler-picker-routes.ts guards those
|
|
439
|
+
// single-key shortcuts with `!searchFocused` (Up/Down blur search first).
|
|
440
|
+
// Advertising them here regardless of focus state was a footer that lied
|
|
441
|
+
// about what the keys currently do. Tab still cycles price in both states
|
|
442
|
+
// (its handler has no searchFocused guard), so it stays in both hint sets.
|
|
443
|
+
const hints = picker.searchFocused
|
|
444
|
+
? `${targetHint} • Typing filters search; Esc clears search • Down: list shortcuts • Tab price • Esc close`
|
|
445
|
+
: `${targetHint} • Up/Down navigate • Left/Right pane • Enter select • / search • Tab price • C caps • A available • B benchmark • G group • Esc close`;
|
|
391
446
|
writeText(footer, 2, safeWidth - 4, hints, { fg: PALETTE.muted, bg: PALETTE.footerBg });
|
|
392
447
|
lines.push(footer);
|
|
393
448
|
lines.push(borderLine(safeWidth, GLYPHS.frame.bottomLeft, GLYPHS.frame.horizontal, GLYPHS.frame.bottomRight));
|
|
@@ -432,6 +487,8 @@ function getRenderCacheKey(picker: ModelPickerModal, width: number, viewportHeig
|
|
|
432
487
|
base.push(objectId(picker.effortLevels), picker.effortLevels.join('\u001f'), picker.pendingModel?.registryKey ?? picker.pendingModel?.id ?? '');
|
|
433
488
|
} else if (picker.mode === 'contextCap') {
|
|
434
489
|
base.push(picker.contextCapQuery, picker.contextCapPendingModel?.registryKey ?? picker.contextCapPendingModel?.id ?? '');
|
|
490
|
+
} else if (picker.mode === 'embeddingProvider') {
|
|
491
|
+
base.push(objectId(picker.embeddingProviders), picker.embeddingProviders.length);
|
|
435
492
|
}
|
|
436
493
|
|
|
437
494
|
return base.join('\u001e');
|
|
@@ -467,6 +524,7 @@ function keyForTargets(values: readonly ModelPickerTargetInfo[]): string {
|
|
|
467
524
|
entry.model,
|
|
468
525
|
entry.enabled ? 1 : 0,
|
|
469
526
|
entry.inherited ? 1 : 0,
|
|
527
|
+
entry.configuredNote ?? '',
|
|
470
528
|
].join('\u001d'))
|
|
471
529
|
.join('\u001f');
|
|
472
530
|
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { createStyledCell, type Line } from '../types/grid.ts';
|
|
2
2
|
import { getOverlayMaxWidth } from './overlay-viewport.ts';
|
|
3
3
|
import { GLYPHS } from './ui-primitives.ts';
|
|
4
|
-
import {
|
|
4
|
+
import { activeUiTones, registerThemeRefresh } from './theme.ts';
|
|
5
5
|
import { fillWidth, makeLine, writeText } from './fullscreen-primitives.ts';
|
|
6
6
|
|
|
7
|
-
// DEFAULT_OVERLAY_PALETTE is built from the mode-resolved chrome tones
|
|
8
|
-
// (resolveUiTones) rather than the static UI_TONES constant — WO-001 single
|
|
9
|
-
// read path. Mode is fixed to 'dark' until the terminal-bg-probe lands.
|
|
10
|
-
const TONES = resolveUiTones('dark');
|
|
11
|
-
|
|
12
7
|
export interface OverlayBoxPalette {
|
|
13
8
|
readonly borderFg: string;
|
|
14
9
|
readonly titleFg: string;
|
|
@@ -21,17 +16,26 @@ export interface OverlayBoxPalette {
|
|
|
21
16
|
readonly bodyBg: string;
|
|
22
17
|
}
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
// Built from the mode-resolved chrome tones (activeUiTones) and rebuilt IN PLACE
|
|
20
|
+
// on a mode flip via the registered refresher (60+ call sites read it by
|
|
21
|
+
// reference — see theme.ts's active-mode runtime note).
|
|
22
|
+
function buildOverlayPalette(): OverlayBoxPalette {
|
|
23
|
+
const t = activeUiTones();
|
|
24
|
+
return {
|
|
25
|
+
borderFg: t.fg.secondary,
|
|
26
|
+
titleFg: t.fg.primary,
|
|
27
|
+
bodyFg: t.fg.primary,
|
|
28
|
+
mutedFg: t.fg.dim,
|
|
29
|
+
selectedBg: t.bg.selected,
|
|
30
|
+
titleBg: t.bg.title,
|
|
31
|
+
sectionBg: t.bg.section,
|
|
32
|
+
inputBg: t.bg.input,
|
|
33
|
+
bodyBg: t.bg.surface,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_OVERLAY_PALETTE: Readonly<OverlayBoxPalette> = buildOverlayPalette();
|
|
38
|
+
registerThemeRefresh(() => Object.assign(DEFAULT_OVERLAY_PALETTE as OverlayBoxPalette, buildOverlayPalette()));
|
|
35
39
|
|
|
36
40
|
export interface OverlayBoxLayout {
|
|
37
41
|
readonly margin: number;
|
|
@@ -21,12 +21,18 @@ export function renderPanelWorkspaceBar(
|
|
|
21
21
|
return renderTabStrip({
|
|
22
22
|
width,
|
|
23
23
|
onLayout,
|
|
24
|
-
tabs: tabs.map((tab) => ({
|
|
24
|
+
tabs: tabs.map((tab, i) => ({
|
|
25
25
|
// tab.active = selected in its own pane (drives highlighted background).
|
|
26
26
|
// tab.focused = has keyboard focus (drives brighter text / focus indicator).
|
|
27
27
|
// A tab can be active-but-not-focused (selected in the unfocused pane) or
|
|
28
28
|
// active-and-focused (selected in the focused pane).
|
|
29
|
-
|
|
29
|
+
//
|
|
30
|
+
// A14: the pane marker uses ▲/▼ (top/bottom pane). The old '^'/'v'
|
|
31
|
+
// prefixes read as a Ctrl caret (^) implying a non-existent Ctrl+<tab>
|
|
32
|
+
// hotkey. The REAL per-tab jump key is Alt+N (panel-tab-1..9), rendered
|
|
33
|
+
// here as ⌥N for the first nine tabs so the shown affordance matches the
|
|
34
|
+
// binding (tabs past nine have no Alt+N chord, so no index is shown).
|
|
35
|
+
label: `${tab.pane === 'bottom' ? '▼' : '▲'}${i < 9 ? ` ⌥${i + 1}` : ''} ${tab.icon} ${tab.name}${tab.focused ? ' ▸' : ''}`,
|
|
30
36
|
active: tab.active,
|
|
31
37
|
})),
|
|
32
38
|
prefixLabel: ' PANELS ',
|