@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
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { type Line, type Cell, createEmptyLine, createEmptyCell } from '../types/grid.ts';
|
|
2
2
|
import { LAYOUT } from './layout.ts';
|
|
3
3
|
import { VERSION } from '../version.ts';
|
|
4
|
-
import { fitDisplay, getDisplayWidth, truncateDisplay, wrapText, interpolateColor } from '../utils/terminal-width.ts';
|
|
4
|
+
import { fitDisplay, getDisplayWidth, truncateDisplay, wrapText, interpolateColor, joinPrioritizedSegments, type PrioritizedSegment } from '../utils/terminal-width.ts';
|
|
5
5
|
import type { GitHeaderInfo } from './git-status.ts';
|
|
6
6
|
import { renderConversationFragment, renderConversationStatusLine, type ConversationStatusSegment } from './conversation-surface.ts';
|
|
7
|
-
import { GLYPHS
|
|
7
|
+
import { GLYPHS } from './ui-primitives.ts';
|
|
8
|
+
import { activeUiTones } from './theme.ts';
|
|
8
9
|
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
9
10
|
import { abbreviateCount } from '../utils/format-number.ts';
|
|
10
11
|
import { computeContextUsage } from '../core/context-usage.ts';
|
|
11
12
|
import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
|
|
12
13
|
import { buildFooterTip, isAgentActive } from './footer-tips.ts';
|
|
13
14
|
import type { StreamMetrics } from '../core/stream-event-wiring.ts';
|
|
15
|
+
import { waitingPhrase, type WaitingState } from '@pellux/goodvibes-sdk/platform/presentation';
|
|
14
16
|
|
|
15
17
|
/** Number of frames before the animated gradient completes one full cycle. */
|
|
16
18
|
const GRADIENT_CYCLE_FRAMES = 50;
|
|
17
|
-
/** Number of frames before rotating to the next thinking phrase (~30 seconds at 80ms/frame). */
|
|
18
|
-
const PHRASE_ROTATION_FRAMES = 375;
|
|
19
19
|
/**
|
|
20
20
|
* Ms since the last STREAM_DELTA before the whimsical phrase rotation freezes
|
|
21
21
|
* and createThinkingFragment shows an honest "stalled Ns" / "reconnecting"
|
|
@@ -72,13 +72,21 @@ function fmtCost(usd: number): string {
|
|
|
72
72
|
* UIFactory - Generates standard UI fragments without needing Ink/React overhead.
|
|
73
73
|
*/
|
|
74
74
|
export class UIFactory {
|
|
75
|
-
|
|
75
|
+
// `version` defaults to the live build VERSION; tests pass a pinned fixture so
|
|
76
|
+
// golden snapshots don't break on every release bump (version-decoupled goldens,
|
|
77
|
+
// mirroring the splash fixture-version pattern).
|
|
78
|
+
public static createHeader(width: number, model: string, provider: string, title?: string, gitInfo?: GitHeaderInfo, version: string = VERSION): Line[] {
|
|
76
79
|
const lines: Line[] = [];
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
// Header/footer/thinking paint on the transparent terminal background, so
|
|
81
|
+
// they read chrome.* (light-terminal-aware) — NOT fg.*/state.*, which stay
|
|
82
|
+
// tuned for the opaque dark modal/panel boxes. Read live per render so a
|
|
83
|
+
// mode flip re-resolves without any module reload (see theme.ts).
|
|
84
|
+
const t = activeUiTones();
|
|
85
|
+
const CYAN = t.accent.brand;
|
|
86
|
+
const GREY = t.chrome.faint;
|
|
87
|
+
const TITLE_COLOR = t.chrome.label;
|
|
80
88
|
const brand = ` GoodVibes `;
|
|
81
|
-
const ver = `v${
|
|
89
|
+
const ver = `v${version} `;
|
|
82
90
|
const stats = ` ${model} `;
|
|
83
91
|
const prov = `(${provider}) `;
|
|
84
92
|
const line = createEmptyLine(width);
|
|
@@ -110,11 +118,11 @@ export class UIFactory {
|
|
|
110
118
|
}
|
|
111
119
|
// Build git info segment
|
|
112
120
|
let gitStr = '';
|
|
113
|
-
let gitFg: string =
|
|
121
|
+
let gitFg: string = t.chrome.faint;
|
|
114
122
|
if (gitInfo) {
|
|
115
123
|
gitStr = buildGitSegment(gitInfo).text;
|
|
116
124
|
if (gitInfo.dirty || gitInfo.ahead > 0 || gitInfo.behind > 0) {
|
|
117
|
-
gitFg =
|
|
125
|
+
gitFg = t.chrome.warn; // yellow when dirty or out-of-sync
|
|
118
126
|
}
|
|
119
127
|
}
|
|
120
128
|
const rightSideText = stats + prov;
|
|
@@ -124,7 +132,7 @@ export class UIFactory {
|
|
|
124
132
|
for (const char of stats) { if (rightX < width) line[rightX++] = { char, fg: CYAN, bg: '', bold: true, dim: false, underline: false, italic: false, strikethrough: false }; }
|
|
125
133
|
for (const char of prov) { if (rightX < width) line[rightX++] = { char, fg: GREY, bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false }; }
|
|
126
134
|
lines.push(line);
|
|
127
|
-
lines.push(this.stringToLine('━'.repeat(width), width, { fg:
|
|
135
|
+
lines.push(this.stringToLine('━'.repeat(width), width, { fg: t.chrome.faint }));
|
|
128
136
|
return lines;
|
|
129
137
|
}
|
|
130
138
|
|
|
@@ -134,12 +142,16 @@ export class UIFactory {
|
|
|
134
142
|
*/
|
|
135
143
|
public static createMessageBar(
|
|
136
144
|
width: number, text: string,
|
|
137
|
-
bgColor: string = '#2a2a2a', textColor: string =
|
|
145
|
+
bgColor: string = '#2a2a2a', textColor: string = activeUiTones().fg.secondary, prefixStr: string = ' › ',
|
|
138
146
|
strikethrough = false
|
|
139
147
|
): Line[] {
|
|
148
|
+
// A historical user-message pill: it carries its own dark bodyBg, so its fg
|
|
149
|
+
// reads fg.secondary/state.reasoning (light-on-dark) — this is conversation
|
|
150
|
+
// content, not the transparent-terminal chrome, so it is not part of the
|
|
151
|
+
// light-terminal chrome flip.
|
|
140
152
|
return renderConversationFragment(text, width, {
|
|
141
153
|
prefix: prefixStr,
|
|
142
|
-
prefixFg:
|
|
154
|
+
prefixFg: activeUiTones().state.reasoning,
|
|
143
155
|
text: textColor,
|
|
144
156
|
bodyBg: bgColor,
|
|
145
157
|
strikethrough,
|
|
@@ -150,10 +162,11 @@ export class UIFactory {
|
|
|
150
162
|
* createQueuedMessageFragment - Renders a dimmed message bar for queued prompts.
|
|
151
163
|
*/
|
|
152
164
|
public static createQueuedMessageFragment(width: number, text: string): Line[] {
|
|
165
|
+
const t = activeUiTones();
|
|
153
166
|
return renderConversationFragment(text, width, {
|
|
154
167
|
prefix: ' (...) ',
|
|
155
|
-
prefixFg:
|
|
156
|
-
text:
|
|
168
|
+
prefixFg: t.state.reasoning,
|
|
169
|
+
text: t.fg.dim,
|
|
157
170
|
bodyBg: '#1a1a1a',
|
|
158
171
|
dim: true,
|
|
159
172
|
});
|
|
@@ -162,7 +175,7 @@ export class UIFactory {
|
|
|
162
175
|
public static createFooter(
|
|
163
176
|
width: number,
|
|
164
177
|
prompt: string,
|
|
165
|
-
usage: { up: number; down: number; max?: number },
|
|
178
|
+
usage: { up: number; down: number; max?: number; fleetCostUsd?: number | null },
|
|
166
179
|
showExitNotice: boolean,
|
|
167
180
|
lastCopyTime: number,
|
|
168
181
|
model?: string,
|
|
@@ -182,6 +195,9 @@ export class UIFactory {
|
|
|
182
195
|
composerFlags?: readonly string[],
|
|
183
196
|
composerPendingRisk?: 'none' | 'approval-wait' | 'shell' | 'command' | 'remote',
|
|
184
197
|
compact: boolean = false,
|
|
198
|
+
// S3d: honest cross-surface spine posture. Defined ONLY in adopted-daemon
|
|
199
|
+
// mode (undefined in embedded/local), so the segment is absent otherwise.
|
|
200
|
+
sessionSpineStatus?: 'online' | 'offline',
|
|
185
201
|
): Line[] {
|
|
186
202
|
const lines: Line[] = [];
|
|
187
203
|
const promptLines = prompt.split('\n');
|
|
@@ -245,6 +261,21 @@ export class UIFactory {
|
|
|
245
261
|
}
|
|
246
262
|
}
|
|
247
263
|
|
|
264
|
+
// UX-C focus visibility (item 1c): when the panel workspace owns focus
|
|
265
|
+
// and the composer is empty, name the state and the way back — the
|
|
266
|
+
// dimmed prompt alone (TEXT_COLOR/BG_COLOR above) told you nothing was
|
|
267
|
+
// wrong, but not why keystrokes weren't landing here.
|
|
268
|
+
if (!promptFocused && text === '' && i === 0) {
|
|
269
|
+
const hintText = 'panel focused — Esc returns';
|
|
270
|
+
const hintStartX = boxStartX + 2 + prefix.length;
|
|
271
|
+
let hx = hintStartX;
|
|
272
|
+
for (const ch of hintText) {
|
|
273
|
+
if (hx >= boxStartX + boxWidth - 2) break;
|
|
274
|
+
contentLine[hx] = { char: ch, fg: '244', bg: BG_COLOR, bold: false, dim: true, underline: false, italic: false, strikethrough: false };
|
|
275
|
+
hx++;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
248
279
|
// Overlay args hint: dim grey text after cursor on the last prompt line.
|
|
249
280
|
// Only shown when a commandArgsHint is provided and cursor is at the end of input.
|
|
250
281
|
if (commandArgsHint && i === promptLines.length - 1) {
|
|
@@ -261,10 +292,17 @@ export class UIFactory {
|
|
|
261
292
|
if (cursorColOnLine >= text.length) {
|
|
262
293
|
// Hint starts one cell after the cursor block
|
|
263
294
|
const hintStartX = boxStartX + 2 + prefix.length + text.length + 1;
|
|
264
|
-
const
|
|
295
|
+
const hintLimit = boxStartX + boxWidth - 2; // exclusive right bound
|
|
296
|
+
const avail = Math.max(0, hintLimit - hintStartX);
|
|
297
|
+
// Clamp to the available width with an ellipsis so a long arg-spec
|
|
298
|
+
// (e.g. /marketplace) ends in "…" rather than a hard mid-word cut. (5b)
|
|
299
|
+
let hintText = ' ' + commandArgsHint;
|
|
300
|
+
if (hintText.length > avail) {
|
|
301
|
+
hintText = avail > 1 ? `${hintText.slice(0, avail - 1)}…` : hintText.slice(0, Math.max(0, avail));
|
|
302
|
+
}
|
|
265
303
|
let hx = hintStartX;
|
|
266
304
|
for (const ch of hintText) {
|
|
267
|
-
if (hx >=
|
|
305
|
+
if (hx >= hintLimit) break;
|
|
268
306
|
contentLine[hx] = { char: ch, fg: '238', bg: BG_COLOR, bold: false, dim: true, underline: false, italic: false, strikethrough: false };
|
|
269
307
|
hx++;
|
|
270
308
|
}
|
|
@@ -290,16 +328,19 @@ export class UIFactory {
|
|
|
290
328
|
// --- Composer posture block (mode / risk / status / flags) ------------
|
|
291
329
|
// Suppressed in compact mode; the ctx-info line no longer duplicates these
|
|
292
330
|
// tokens, so this block is the single home for mode/status/flags.
|
|
331
|
+
// Footer chrome paints on the transparent terminal bg → chrome.* accents
|
|
332
|
+
// (light-terminal-aware); state.info reads on both light and dark terminals.
|
|
333
|
+
const t = activeUiTones();
|
|
293
334
|
const composerTokens: Array<{ text: string; fg: string; bold?: boolean; dim?: boolean }> = [];
|
|
294
|
-
if (composerMode) composerTokens.push({ text: ` ${GLYPHS.status.active} ${composerMode} `, fg:
|
|
335
|
+
if (composerMode) composerTokens.push({ text: ` ${GLYPHS.status.active} ${composerMode} `, fg: t.state.info, bold: true });
|
|
295
336
|
if (composerPendingRisk && composerPendingRisk !== 'none') {
|
|
296
337
|
const riskColor = composerPendingRisk === 'approval-wait'
|
|
297
|
-
?
|
|
338
|
+
? t.chrome.warn
|
|
298
339
|
: composerPendingRisk === 'shell'
|
|
299
|
-
?
|
|
340
|
+
? t.chrome.bad
|
|
300
341
|
: composerPendingRisk === 'remote'
|
|
301
|
-
?
|
|
302
|
-
:
|
|
342
|
+
? t.chrome.remote
|
|
343
|
+
: t.chrome.warn;
|
|
303
344
|
composerTokens.push({ text: ` risk:${composerPendingRisk} `, fg: riskColor, bold: true });
|
|
304
345
|
}
|
|
305
346
|
if (composerStatus && composerStatus !== 'idle') composerTokens.push({ text: ` state:${composerStatus} `, fg: '244', dim: true });
|
|
@@ -329,7 +370,7 @@ export class UIFactory {
|
|
|
329
370
|
const isRecentlyCopied = Date.now() - lastCopyTime < 2000;
|
|
330
371
|
// Token usage line + running ~$ cost estimate (derived from the usage
|
|
331
372
|
// object and the active model via cost-utils).
|
|
332
|
-
const u = usage as { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; up?: number; down?: number };
|
|
373
|
+
const u = usage as { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; up?: number; down?: number; fleetCostUsd?: number | null };
|
|
333
374
|
const inp = u.input ?? u.up ?? 0;
|
|
334
375
|
const out = u.output ?? u.down ?? 0;
|
|
335
376
|
const cr = u.cacheRead ?? 0;
|
|
@@ -340,12 +381,29 @@ export class UIFactory {
|
|
|
340
381
|
// match the existing "no priceable data" convention used elsewhere
|
|
341
382
|
// (cockpit-panel formatCost, agent-inspector-shared) — the footer has no
|
|
342
383
|
// room for a longer marker before truncation kicks in.
|
|
343
|
-
const
|
|
384
|
+
const mainCostStr = model
|
|
344
385
|
? isModelPriced(model)
|
|
345
|
-
?
|
|
346
|
-
:
|
|
347
|
-
:
|
|
348
|
-
|
|
386
|
+
? `~$${fmtCost(calcSessionCost(inp, out, cr, cw, model))}`
|
|
387
|
+
: '~n/a'
|
|
388
|
+
: null;
|
|
389
|
+
// Honest total = your main session + the delegated fleet. We show a SPLIT
|
|
390
|
+
// ("you ~$X · fleet ~$Y") rather than one summed figure: the two costs are
|
|
391
|
+
// tracked and attributed to different actors, and the split directly answers
|
|
392
|
+
// "where did the money go" — the exact confusion in the eval where the footer
|
|
393
|
+
// showed only the main session (~$0.046) while the fleet cost ~10x more
|
|
394
|
+
// ($0.446). The fleet segment appears only when there is a real fleet cost, so
|
|
395
|
+
// the idle single-session footer is unchanged.
|
|
396
|
+
const fleetCost = u.fleetCostUsd;
|
|
397
|
+
const hasFleetCost = typeof fleetCost === 'number' && fleetCost > 0;
|
|
398
|
+
const costSegment = hasFleetCost
|
|
399
|
+
? `${tokenSep}you ${mainCostStr ?? '~n/a'} · fleet ~$${fmtCost(fleetCost)}`
|
|
400
|
+
: mainCostStr
|
|
401
|
+
? `${tokenSep}${mainCostStr}`
|
|
402
|
+
: '';
|
|
403
|
+
// Input tokens are unknown until the first assistant usage lands (often the
|
|
404
|
+
// first tool turn). Show "—" rather than a false 0 during that window. (5c)
|
|
405
|
+
const inpDisplay = inp > 0 ? fmtNum(inp) : '—';
|
|
406
|
+
const tokenLine = ` Token Usage [ Input: ${inpDisplay}${tokenSep}Output: ${fmtNum(out)}${tokenSep}Cache Read: ${fmtNum(cr)}${tokenSep}Cache Write: ${fmtNum(cw)}${tokenSep}Total: ${fmtNum(total)}${costSegment} ]`;
|
|
349
407
|
const copiedNotice = isRecentlyCopied ? ` [COPIED] ` : '';
|
|
350
408
|
const statsLine = ' ' + tokenLine + ' '.repeat(Math.max(0, width - 4 - getDisplayWidth(tokenLine) - getDisplayWidth(copiedNotice))) + copiedNotice;
|
|
351
409
|
lines.push(this.stringToLine(statsLine, width, { fg: isRecentlyCopied ? '81' : '244', bold: isRecentlyCopied }));
|
|
@@ -353,7 +411,9 @@ export class UIFactory {
|
|
|
353
411
|
if (!compact && contextWindow && contextWindow > 0) {
|
|
354
412
|
const ctxTokens = lastInputTokens ?? 0;
|
|
355
413
|
const label = ' Context Usage: ';
|
|
356
|
-
|
|
414
|
+
// "—" until the first input-token count is known, not a false 0. (5c)
|
|
415
|
+
const ctxDisplay = ctxTokens > 0 ? fmtNum(ctxTokens) : '—';
|
|
416
|
+
const suffix = ` [ ${ctxDisplay} / ${fmtNum(contextWindow)} ]`;
|
|
357
417
|
const barWidth = Math.max(10, Math.min(30, width - getDisplayWidth(label) - getDisplayWidth(suffix) - 8));
|
|
358
418
|
const ctxPct = computeContextUsage(ctxTokens, contextWindow).clampedRatio;
|
|
359
419
|
// Clamp threshold to [0..1]; undefined/0 means no threshold marker.
|
|
@@ -370,19 +430,30 @@ export class UIFactory {
|
|
|
370
430
|
const displayDir = workingDir && home && workingDir.startsWith(home)
|
|
371
431
|
? '~' + workingDir.slice(home.length)
|
|
372
432
|
: workingDir ?? '';
|
|
373
|
-
|
|
374
|
-
|
|
433
|
+
// Segments carry a survival priority (lower = survives longer) so that
|
|
434
|
+
// under width pressure whole low-value segments are dropped before any
|
|
435
|
+
// segment is character-truncated mid-word. cwd/model are the essential
|
|
436
|
+
// orientation segments (priority 0); the spine liveness marker is the
|
|
437
|
+
// daemon-honesty signal and must outlive the two decorative segments
|
|
438
|
+
// (tool count, notify mode) — it is ordered ahead of them too, so a
|
|
439
|
+
// narrow line drops "N tools"/"notify:x" whole before spine is at risk.
|
|
440
|
+
const ctxParts: PrioritizedSegment[] = [];
|
|
441
|
+
if (displayDir) ctxParts.push({ text: displayDir, priority: 0 });
|
|
375
442
|
if (model) {
|
|
376
|
-
ctxParts.push(model + (provider ? ` (${provider})` : ''));
|
|
443
|
+
ctxParts.push({ text: model + (provider ? ` (${provider})` : ''), priority: 0 });
|
|
377
444
|
}
|
|
378
|
-
|
|
445
|
+
// S3d: cross-surface spine posture — plain words, no blame. Adopted mode only.
|
|
446
|
+
if (sessionSpineStatus) ctxParts.push({ text: `spine:${sessionSpineStatus}`, priority: 1 });
|
|
447
|
+
if (toolCount) ctxParts.push({ text: `${toolCount} tools`, priority: 2 });
|
|
379
448
|
// Labeled "notify" (not "hitl") — /mode (aliased /hitl) governs UX
|
|
380
449
|
// notification verbosity (quiet/balanced/operator), not tool
|
|
381
450
|
// auto-approval, so it must not share vocabulary with the DANGER MODE
|
|
382
|
-
// risk banner rendered a few lines below.
|
|
383
|
-
if (hitlMode) ctxParts.push(`notify:${hitlMode}
|
|
384
|
-
const
|
|
385
|
-
|
|
451
|
+
// risk banner rendered a few lines below. Lowest priority: dropped first.
|
|
452
|
+
if (hitlMode) ctxParts.push({ text: `notify:${hitlMode}`, priority: 3 });
|
|
453
|
+
const sep = ` ${GLYPHS.navigation.pipeSeparator} `;
|
|
454
|
+
const ctxBody = joinPrioritizedSegments(ctxParts, sep, width - 3);
|
|
455
|
+
const ctxLine = ' ' + ctxBody;
|
|
456
|
+
lines.push(this.stringToLine(ctxLine, width, { fg: '240', dim: true }));
|
|
386
457
|
}
|
|
387
458
|
if (showExitNotice) {
|
|
388
459
|
const notice = ` !!! Press Ctrl+C again to exit !!! `;
|
|
@@ -403,7 +474,7 @@ export class UIFactory {
|
|
|
403
474
|
for (const ch of dangerWarn) {
|
|
404
475
|
if (col >= width) break;
|
|
405
476
|
const cw = getDisplayWidth(ch);
|
|
406
|
-
line[col] = { char: ch, fg:
|
|
477
|
+
line[col] = { char: ch, fg: t.chrome.bad, bg: '', bold: true, dim: false, underline: false, italic: false, strikethrough: false };
|
|
407
478
|
if (cw === 2 && col + 1 < width) line[col + 1] = { ...line[col], char: '' };
|
|
408
479
|
col += cw;
|
|
409
480
|
}
|
|
@@ -413,33 +484,20 @@ export class UIFactory {
|
|
|
413
484
|
return lines;
|
|
414
485
|
}
|
|
415
486
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
'Entering flow state...',
|
|
426
|
-
'Consulting the void...',
|
|
427
|
-
'Absorbing aesthetics...',
|
|
428
|
-
'Synthesizing vibes...',
|
|
429
|
-
'Transcending...',
|
|
430
|
-
'Dreaming in neon...',
|
|
431
|
-
'Parsing the cosmos...',
|
|
432
|
-
'Loading good vibes...',
|
|
433
|
-
'Meditating...',
|
|
434
|
-
'Catching a vibe...',
|
|
435
|
-
'Harmonizing...',
|
|
436
|
-
'Feeling it...',
|
|
437
|
-
'In the zone...',
|
|
438
|
-
];
|
|
487
|
+
// W6-P1: the rotating "thinking" phrase pool and the honest waiting-state
|
|
488
|
+
// wording (approval/reconnecting/pre-first-token/stalled/thinking) are no
|
|
489
|
+
// longer minted here. They come from the SDK presentation contract's
|
|
490
|
+
// waitingPhrase() (@pellux/goodvibes-sdk/platform/presentation, landed by
|
|
491
|
+
// W4-S1, already adopted by the agent in W4-R4) — see createThinkingFragment
|
|
492
|
+
// below. This renderer still decides WHICH state applies from its own
|
|
493
|
+
// stall/reconnect/approval signals (computeStallInfo/computeRenderStallInfo
|
|
494
|
+
// stay renderer-local per the extraction decision record); only the exact
|
|
495
|
+
// wording is shared.
|
|
439
496
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
497
|
+
// Gradient colors for thinking text — cyan→purple in dark, teal→purple in
|
|
498
|
+
// light (matches splash/brand). Read live per render inside
|
|
499
|
+
// createThinkingFragment via activeUiTones() rather than baked into a static
|
|
500
|
+
// field, so a mode flip re-resolves without a module reload.
|
|
443
501
|
|
|
444
502
|
/**
|
|
445
503
|
* Per-frame stall info from stream metrics — computed from lastDeltaAtMs every render (not
|
|
@@ -478,25 +536,36 @@ export class UIFactory {
|
|
|
478
536
|
: undefined;
|
|
479
537
|
}
|
|
480
538
|
|
|
481
|
-
public static createThinkingFragment(width: number, spinner: string, frame: number = 0, tokenSpeed?: number, toolPreview?: string, inputTokens?: number, outputTokens?: number, elapsedMs?: number, ttftMs?: number, stallInfo?: ThinkingStallInfo): Line[] {
|
|
539
|
+
public static createThinkingFragment(width: number, spinner: string, frame: number = 0, tokenSpeed?: number, toolPreview?: string, inputTokens?: number, outputTokens?: number, elapsedMs?: number, ttftMs?: number, stallInfo?: ThinkingStallInfo, approvalPending?: boolean): Line[] {
|
|
540
|
+
// Live thinking row paints on the transparent terminal bg → read the
|
|
541
|
+
// mode-resolved chrome tones per render (gradient/brand/tool accents flip).
|
|
542
|
+
const tones = activeUiTones();
|
|
482
543
|
// Freeze the whimsical phrase rotation once real silence has gone on
|
|
483
|
-
// long enough to be misleading (THINKING_STALL_FREEZE_MS)
|
|
484
|
-
// honest
|
|
485
|
-
//
|
|
544
|
+
// long enough to be misleading (THINKING_STALL_FREEZE_MS). Decide WHICH
|
|
545
|
+
// honest waiting state applies (renderer-local — this signal computation
|
|
546
|
+
// stays here per the extraction decision record), then defer the exact
|
|
547
|
+
// wording to the SDK presentation contract's waitingPhrase() (W6-P1;
|
|
548
|
+
// mirrors the agent's W4-R4 adoption). Precedence matches the contract:
|
|
549
|
+
// approval > reconnecting > pre-first-token > stalled > thinking.
|
|
486
550
|
const isStalled = stallInfo !== undefined && stallInfo.msSinceLastDelta >= THINKING_STALL_FREEZE_MS;
|
|
487
|
-
let
|
|
488
|
-
if (
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
551
|
+
let state: WaitingState;
|
|
552
|
+
if (approvalPending) state = 'approval';
|
|
553
|
+
else if (stallInfo?.reconnect) state = 'reconnecting';
|
|
554
|
+
else if (isStalled && (outputTokens ?? 0) === 0) state = 'pre-first-token';
|
|
555
|
+
else if (isStalled) state = 'stalled';
|
|
556
|
+
else state = 'thinking';
|
|
557
|
+
const phrase = waitingPhrase(state, {
|
|
558
|
+
reconnectAttempt: stallInfo?.reconnect?.attempt,
|
|
559
|
+
reconnectMaxAttempts: stallInfo?.reconnect?.maxAttempts,
|
|
560
|
+
msSinceLastDelta: stallInfo?.msSinceLastDelta,
|
|
561
|
+
frame,
|
|
562
|
+
});
|
|
563
|
+
// Token-rate and time-to-first-token readouts are meaningless while waiting on the user, and a
|
|
564
|
+
// "tok/s" figure next to an approval prompt reads as if the model were still working — suppress
|
|
565
|
+
// them; keep the elapsed timer since "how long the approval has waited" is honest.
|
|
566
|
+
const speedSuffix = (!approvalPending && tokenSpeed !== undefined && tokenSpeed > 0) ? ` (${Math.round(tokenSpeed)} tok/s)` : '';
|
|
498
567
|
const elapsedSuffix = elapsedMs !== undefined ? ` (${formatElapsed(elapsedMs)})` : '';
|
|
499
|
-
const ttftSuffix = (ttftMs !== undefined && ttftMs > 0) ? ` ttft:${ttftMs}ms` : '';
|
|
568
|
+
const ttftSuffix = (!approvalPending && ttftMs !== undefined && ttftMs > 0) ? ` ttft:${ttftMs}ms` : '';
|
|
500
569
|
const text = ` ${spinner} ${phrase}${speedSuffix}${elapsedSuffix}${ttftSuffix} `;
|
|
501
570
|
|
|
502
571
|
const textWidth = Math.max(1, getDisplayWidth(text) - 1);
|
|
@@ -506,7 +575,7 @@ export class UIFactory {
|
|
|
506
575
|
const gradientPos = raw <= 0.5 ? raw * 2 : (1 - raw) * 2;
|
|
507
576
|
return {
|
|
508
577
|
text: char,
|
|
509
|
-
fg: interpolateColor(
|
|
578
|
+
fg: interpolateColor(tones.accent.gradientStart, tones.accent.gradientEnd, gradientPos),
|
|
510
579
|
bold: true,
|
|
511
580
|
};
|
|
512
581
|
});
|
|
@@ -514,7 +583,7 @@ export class UIFactory {
|
|
|
514
583
|
const inTok = inputTokens ?? 0;
|
|
515
584
|
const outTok = outputTokens ?? 0;
|
|
516
585
|
segments.push({ text: ` in ${fmtNum(inTok)} `, fg: '243', dim: true });
|
|
517
|
-
segments.push({ text: `out ${fmtNum(outTok)}`, fg:
|
|
586
|
+
segments.push({ text: `out ${fmtNum(outTok)}`, fg: tones.accent.brand });
|
|
518
587
|
}
|
|
519
588
|
const line = createEmptyLine(width);
|
|
520
589
|
let col = 1;
|
|
@@ -554,7 +623,7 @@ export class UIFactory {
|
|
|
554
623
|
if (px >= width) break;
|
|
555
624
|
previewLine[px] = {
|
|
556
625
|
char: ch,
|
|
557
|
-
fg:
|
|
626
|
+
fg: tones.state.info,
|
|
558
627
|
bg: '',
|
|
559
628
|
bold: true,
|
|
560
629
|
dim: false,
|
|
@@ -1,134 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
moreBelow: '▼',
|
|
29
|
-
next: '→',
|
|
30
|
-
back: '←',
|
|
31
|
-
pipeSeparator: '│',
|
|
32
|
-
},
|
|
33
|
-
status: {
|
|
34
|
-
success: '✓',
|
|
35
|
-
failure: '✕',
|
|
36
|
-
pending: '•',
|
|
37
|
-
active: '●',
|
|
38
|
-
idle: '◌',
|
|
39
|
-
info: '○',
|
|
40
|
-
warn: '⚠',
|
|
41
|
-
blocked: '⊘',
|
|
42
|
-
skipped: '◇',
|
|
43
|
-
review: '◈',
|
|
44
|
-
retry: '↻',
|
|
45
|
-
handoff: '⇢',
|
|
46
|
-
reference: '↗',
|
|
47
|
-
partial: '◐',
|
|
48
|
-
dualPane: '◆',
|
|
49
|
-
star: '★',
|
|
50
|
-
},
|
|
51
|
-
meter: {
|
|
52
|
-
filled: '█',
|
|
53
|
-
medium: '▓',
|
|
54
|
-
light: '▒',
|
|
55
|
-
empty: '░',
|
|
56
|
-
spark: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'],
|
|
57
|
-
},
|
|
58
|
-
} as const;
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// ui-primitives.ts — glyph registry + tone-token table.
|
|
3
|
+
//
|
|
4
|
+
// W6-P1: these four tables (GLYPHS, UI_TONES, DIFF_TONES, SPINNER_FRAMES) are
|
|
5
|
+
// no longer minted locally. They are the SDK presentation contract
|
|
6
|
+
// (@pellux/goodvibes-sdk/platform/presentation, landed by W4-S1 and already
|
|
7
|
+
// adopted by the agent in W4-R4), consumed here so the TUI and the agent
|
|
8
|
+
// share ONE source (Mike's move-to-SDK ruling — machinery needed by 2+
|
|
9
|
+
// surfaces => SDK). The TUI was the reference these values were lifted from
|
|
10
|
+
// verbatim, so this swap is byte-identical. See
|
|
11
|
+
// docs/decisions/2026-07-05-presentation-contract-sdk-extraction.md in the SDK.
|
|
12
|
+
//
|
|
13
|
+
// Re-exported under the historical names (GLYPHS, UI_TONES) so every existing
|
|
14
|
+
// importer keeps working with no call-site churn. UI_TONES is the dark-mode
|
|
15
|
+
// tone table (== resolveTones('dark')); light is resolved via theme.ts's
|
|
16
|
+
// activeUiTones() / resolveUiTones(), which composes the mode dimension over
|
|
17
|
+
// this dark constant unchanged.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
GLYPHS,
|
|
22
|
+
TONE_TOKENS,
|
|
23
|
+
DIFF_TONES,
|
|
24
|
+
SPINNER_FRAMES,
|
|
25
|
+
} from '@pellux/goodvibes-sdk/platform/presentation';
|
|
26
|
+
|
|
27
|
+
export { GLYPHS, DIFF_TONES, SPINNER_FRAMES };
|
|
59
28
|
|
|
60
29
|
/**
|
|
61
30
|
* UI_TONES — the single chrome/color-token source for src/renderer and
|
|
62
31
|
* src/panels. Concrete (dark-mode) values; resolveUiTones(mode) in theme.ts
|
|
63
|
-
* is the single mode-resolved read path — this object is the 'dark' entry
|
|
64
|
-
|
|
65
|
-
export const UI_TONES = {
|
|
66
|
-
fg: {
|
|
67
|
-
primary: '#e2e8f0',
|
|
68
|
-
secondary: '#cbd5e1',
|
|
69
|
-
muted: '#94a3b8',
|
|
70
|
-
dim: '#475569',
|
|
71
|
-
inverse: '#0f172a',
|
|
72
|
-
/** Empty-state / placeholder foreground (formerly DEFAULT_PANEL_PALETTE.empty literal). */
|
|
73
|
-
empty: '#334155',
|
|
74
|
-
},
|
|
75
|
-
bg: {
|
|
76
|
-
base: '#11131a',
|
|
77
|
-
surface: '#161a22',
|
|
78
|
-
title: '#0f172a',
|
|
79
|
-
section: '#18202b',
|
|
80
|
-
summary: '#1b2430',
|
|
81
|
-
selected: '#223049',
|
|
82
|
-
input: '#1e293b',
|
|
83
|
-
warning: '#2b2116',
|
|
84
|
-
error: '#2a161b',
|
|
85
|
-
success: '#14241b',
|
|
86
|
-
/** Fullscreen/shell footer background. */
|
|
87
|
-
footer: '#111827',
|
|
88
|
-
},
|
|
89
|
-
state: {
|
|
90
|
-
info: '#38bdf8',
|
|
91
|
-
good: '#22c55e',
|
|
92
|
-
warn: '#f59e0b',
|
|
93
|
-
bad: '#ef4444',
|
|
94
|
-
blocked: '#f97316',
|
|
95
|
-
active: '#60a5fa',
|
|
96
|
-
/** Single canonical reasoning/thinking purple (replaces #9945FF and ad-hoc purples). */
|
|
97
|
-
reasoning: '#a855f7',
|
|
98
|
-
},
|
|
99
|
-
accent: {
|
|
100
|
-
browser: '#7dd3fc',
|
|
101
|
-
control: '#22d3ee',
|
|
102
|
-
inspector: '#c4b5fd',
|
|
103
|
-
workflow: '#fbbf24',
|
|
104
|
-
conversation: '#93c5fd',
|
|
105
|
-
/** Neon brand accent — header/splash/thinking gradient only. */
|
|
106
|
-
brand: '#00ffff',
|
|
107
|
-
gradientStart: '#00ffff',
|
|
108
|
-
gradientEnd: '#d000ff',
|
|
109
|
-
},
|
|
110
|
-
/** Canonical border stroke color for fullscreen/panel chrome. */
|
|
111
|
-
border: '#64748b',
|
|
112
|
-
} as const;
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Diff surface accent color shared across the three diff-rendering surfaces —
|
|
116
|
-
* the conversation diff view (diff-view.ts), the file diff panel
|
|
117
|
-
* (diff-panel.ts), and the git panel's inline diff (git-panel.ts). Hunk-header
|
|
118
|
-
* blue has no matching UI_TONES.state/accent role, so WO-204 gives it one
|
|
119
|
-
* named token here (value converged on diff-panel.ts's pre-existing hunk
|
|
120
|
-
* color, the only one of the three surfaces that predates this token; add/del
|
|
121
|
-
* on all three surfaces already converge on UI_TONES.state.good/bad).
|
|
32
|
+
* is the single mode-resolved read path — this object is the 'dark' entry
|
|
33
|
+
* (== resolveTones('dark') from the SDK presentation contract).
|
|
122
34
|
*/
|
|
123
|
-
export const
|
|
124
|
-
// add/del carry diff-panel.ts's shipped colors: the diff panel is the only
|
|
125
|
-
// diff surface users have ever seen (diff-view.ts was unwired from v0.9.6
|
|
126
|
-
// until WO-204), so its look is the reference — the conversation surface
|
|
127
|
-
// adopts it, never the other way around.
|
|
128
|
-
add: '#00ff88',
|
|
129
|
-
del: '#ff4444',
|
|
130
|
-
hunk: '#88aaff',
|
|
131
|
-
} as const;
|
|
132
|
-
|
|
133
|
-
/** Single spinner-frame source — layout.ts and progress.ts both re-export this. */
|
|
134
|
-
export const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] as const;
|
|
35
|
+
export const UI_TONES = TONE_TOKENS;
|