@pellux/goodvibes-tui 1.1.0 → 1.7.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 +20 -11
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +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/conversation-rendering.ts +19 -0
- 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/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +36 -0
- package/src/input/command-registry.ts +38 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- 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 +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- 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 +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- 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-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +15 -7
- 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 +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-feed-routes.ts +61 -23
- package/src/input/handler-feed.ts +47 -23
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +6 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +24 -27
- 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 +9 -43
- 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-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -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 +151 -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 +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -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 +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +13 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- 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/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/turn-injection.ts +114 -0
- package/src/runtime/bootstrap-command-context.ts +21 -1
- package/src/runtime/bootstrap-command-parts.ts +34 -7
- package/src/runtime/bootstrap-core.ts +14 -4
- package/src/runtime/bootstrap-shell.ts +29 -16
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- 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
package/src/panels/qr-panel.ts
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
-
import { BasePanel } from './base-panel.ts';
|
|
4
|
-
import {
|
|
5
|
-
buildKeyboardHints,
|
|
6
|
-
buildPanelLine,
|
|
7
|
-
buildPanelTitle,
|
|
8
|
-
DEFAULT_PANEL_PALETTE,
|
|
9
|
-
extendPalette,
|
|
10
|
-
} from './polish.ts';
|
|
11
|
-
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
12
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
13
|
-
import { renderQrMatrix, generateQrMatrix } from '../renderer/qr-renderer.ts';
|
|
14
|
-
import { encodeConnectionPayload } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
15
|
-
import type { UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
|
|
16
|
-
import type { UiControlPlaneSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
17
|
-
|
|
18
|
-
// Fixed-width placeholder — deliberately NOT derived from the real secret's
|
|
19
|
-
// length, so masking doesn't leak how long the token/password is.
|
|
20
|
-
const SECRET_MASK = '••••••••••••';
|
|
21
|
-
|
|
22
|
-
// Domain accents only; base chrome (header/headerBg/label/info) comes from
|
|
23
|
-
// DEFAULT_PANEL_PALETTE. qrFg/qrBg stay pure black/white — scanner contrast
|
|
24
|
-
// requirements override theming.
|
|
25
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
26
|
-
token: '#a78bfa',
|
|
27
|
-
qrFg: '#000000',
|
|
28
|
-
qrBg: '#ffffff',
|
|
29
|
-
} as const);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Connection info passed to the QR panel.
|
|
33
|
-
* Populated at construction; updated when the token is regenerated.
|
|
34
|
-
*/
|
|
35
|
-
export interface QrPanelConnectionInfo {
|
|
36
|
-
/** Full connection URL (e.g. http://192.168.1.x:3141) */
|
|
37
|
-
readonly url: string;
|
|
38
|
-
/** Auth token */
|
|
39
|
-
readonly token: string;
|
|
40
|
-
/** Username associated with the companion session */
|
|
41
|
-
readonly username: string;
|
|
42
|
-
/** Bootstrap password for companion authentication */
|
|
43
|
-
readonly password?: string;
|
|
44
|
-
/** SDK/surface version (defaults to '0.0.0' if omitted) */
|
|
45
|
-
readonly version?: string;
|
|
46
|
-
/** Surface identifier (defaults to 'tui' if omitted) */
|
|
47
|
-
readonly surface?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Callback used by the panel to regenerate the companion token.
|
|
52
|
-
* Returns updated connection info.
|
|
53
|
-
*/
|
|
54
|
-
export type RegenerateTokenFn = () => QrPanelConnectionInfo;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Callback used by the panel to copy text to the clipboard.
|
|
58
|
-
*/
|
|
59
|
-
export type CopyToClipboardFn = (text: string) => void;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* QrPanel - displays a QR code for companion app pairing.
|
|
63
|
-
*
|
|
64
|
-
* Shows connection URL, truncated token, and username above the QR code.
|
|
65
|
-
* Supports `r` to regenerate the token and `c` to copy the token.
|
|
66
|
-
*
|
|
67
|
-
* QR matrix generation uses the SDK's `generateQrMatrix` via `encodeConnectionPayload`.
|
|
68
|
-
*/
|
|
69
|
-
export class QrPanel extends BasePanel {
|
|
70
|
-
private connectionInfo: QrPanelConnectionInfo;
|
|
71
|
-
private readonly regenerateToken: RegenerateTokenFn | undefined;
|
|
72
|
-
private readonly copyToClipboard: CopyToClipboardFn | undefined;
|
|
73
|
-
private readonly controlPlaneReadModel: UiReadModel<UiControlPlaneSnapshot> | undefined;
|
|
74
|
-
private readonly localUserAuthManager: Pick<UserAuthManager, 'inspect'> | undefined;
|
|
75
|
-
private readonly unsub: (() => void) | null;
|
|
76
|
-
private lastStatus = '';
|
|
77
|
-
/** v toggles whether the token/password render in the clear. */
|
|
78
|
-
private revealed = false;
|
|
79
|
-
/** Set when r is pressed while a companion session is live — confirmed with y/Enter, cancelled with n/Esc. */
|
|
80
|
-
private confirmRegenerate: ConfirmState<null> | null = null;
|
|
81
|
-
|
|
82
|
-
public constructor(
|
|
83
|
-
connectionInfo: QrPanelConnectionInfo,
|
|
84
|
-
regenerateToken?: RegenerateTokenFn,
|
|
85
|
-
copyToClipboard?: CopyToClipboardFn,
|
|
86
|
-
controlPlaneReadModel?: UiReadModel<UiControlPlaneSnapshot>,
|
|
87
|
-
localUserAuthManager?: Pick<UserAuthManager, 'inspect'>,
|
|
88
|
-
) {
|
|
89
|
-
super('qr-code', 'QR Code', 'Q', 'session');
|
|
90
|
-
this.connectionInfo = connectionInfo;
|
|
91
|
-
this.regenerateToken = regenerateToken;
|
|
92
|
-
this.copyToClipboard = copyToClipboard;
|
|
93
|
-
this.controlPlaneReadModel = controlPlaneReadModel;
|
|
94
|
-
this.localUserAuthManager = localUserAuthManager;
|
|
95
|
-
this.unsub = controlPlaneReadModel ? controlPlaneReadModel.subscribe(() => this.markDirty()) : null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
public override onDestroy(): void {
|
|
99
|
-
this.unsub?.();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
private hasLiveCompanionSession(): boolean {
|
|
103
|
-
return (this.localUserAuthManager?.inspect().sessionCount ?? 0) > 0;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private doRegenerate(): void {
|
|
107
|
-
if (!this.regenerateToken) return;
|
|
108
|
-
this.connectionInfo = this.regenerateToken();
|
|
109
|
-
this.lastStatus = 'Token regenerated.';
|
|
110
|
-
this.markDirty();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
public handleInput(key: string): boolean {
|
|
114
|
-
if (this.confirmRegenerate) {
|
|
115
|
-
const result = handleConfirmInput(this.confirmRegenerate, key);
|
|
116
|
-
if (result === 'confirmed') {
|
|
117
|
-
this.confirmRegenerate = null;
|
|
118
|
-
this.doRegenerate();
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
if (result === 'cancelled') {
|
|
122
|
-
this.confirmRegenerate = null;
|
|
123
|
-
this.lastStatus = 'Regeneration cancelled.';
|
|
124
|
-
this.markDirty();
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
return true; // absorbed
|
|
128
|
-
}
|
|
129
|
-
if (key === 'r') {
|
|
130
|
-
if (!this.regenerateToken) {
|
|
131
|
-
this.lastStatus = 'Regeneration not available.';
|
|
132
|
-
this.markDirty();
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
if (this.hasLiveCompanionSession()) {
|
|
136
|
-
this.confirmRegenerate = {
|
|
137
|
-
subject: null,
|
|
138
|
-
label: 'pairing token — a companion session is live and will be disconnected',
|
|
139
|
-
verb: 'Regenerate',
|
|
140
|
-
};
|
|
141
|
-
this.markDirty();
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
this.doRegenerate();
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
if (key === 'v') {
|
|
148
|
-
this.revealed = !this.revealed;
|
|
149
|
-
this.markDirty();
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
if (key === 'c') {
|
|
153
|
-
if (this.copyToClipboard) {
|
|
154
|
-
this.copyToClipboard(this.connectionInfo.token);
|
|
155
|
-
this.lastStatus = 'Token copied to clipboard.';
|
|
156
|
-
} else {
|
|
157
|
-
this.lastStatus = 'Clipboard not available.';
|
|
158
|
-
}
|
|
159
|
-
this.markDirty();
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
public render(width: number, height: number): Line[] {
|
|
166
|
-
this.needsRender = false;
|
|
167
|
-
const lines: Line[] = [];
|
|
168
|
-
|
|
169
|
-
const { url, token, username, password } = this.connectionInfo;
|
|
170
|
-
const valueWidth = Math.max(0, width - 12);
|
|
171
|
-
const displayToken = this.revealed ? token : SECRET_MASK;
|
|
172
|
-
const displayPassword = password !== undefined ? (this.revealed ? password : SECRET_MASK) : undefined;
|
|
173
|
-
|
|
174
|
-
// ── Title + purpose: tell the operator exactly what this code is for ────
|
|
175
|
-
lines.push(buildPanelTitle(width, 'Companion Pairing', C));
|
|
176
|
-
lines.push(
|
|
177
|
-
buildPanelLine(width, [
|
|
178
|
-
[' Scan with the GoodVibes companion app to pair this session.', C.label],
|
|
179
|
-
]),
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// ── Connection info header ─────────────────────────────────────────────
|
|
183
|
-
// Token and (if present) bootstrap password are masked by default — they
|
|
184
|
-
// are credentials, not display text — and only render in the clear while
|
|
185
|
-
// `revealed` is toggled on via v.
|
|
186
|
-
lines.push(createEmptyLine(width));
|
|
187
|
-
lines.push(
|
|
188
|
-
buildPanelLine(width, [
|
|
189
|
-
[' URL ', C.label],
|
|
190
|
-
[truncateDisplay(url, valueWidth), C.info],
|
|
191
|
-
]),
|
|
192
|
-
);
|
|
193
|
-
lines.push(
|
|
194
|
-
buildPanelLine(width, [
|
|
195
|
-
[' Token ', C.label],
|
|
196
|
-
[truncateDisplay(displayToken, valueWidth), C.token],
|
|
197
|
-
]),
|
|
198
|
-
);
|
|
199
|
-
lines.push(
|
|
200
|
-
buildPanelLine(width, [
|
|
201
|
-
[' Username ', C.label],
|
|
202
|
-
[truncateDisplay(username, valueWidth), C.value],
|
|
203
|
-
]),
|
|
204
|
-
);
|
|
205
|
-
if (displayPassword !== undefined) {
|
|
206
|
-
lines.push(
|
|
207
|
-
buildPanelLine(width, [
|
|
208
|
-
[' Password ', C.label],
|
|
209
|
-
[truncateDisplay(displayPassword, valueWidth), C.value],
|
|
210
|
-
]),
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ── Connected companions ──────────────────────────────────────────────
|
|
215
|
-
if (this.controlPlaneReadModel) {
|
|
216
|
-
const connected = this.controlPlaneReadModel.getSnapshot().activeClientIds.length;
|
|
217
|
-
lines.push(
|
|
218
|
-
buildPanelLine(width, [
|
|
219
|
-
[' Companions ', C.label],
|
|
220
|
-
[`connected: ${connected}`, connected > 0 ? C.good : C.dim],
|
|
221
|
-
]),
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
lines.push(createEmptyLine(width));
|
|
225
|
-
|
|
226
|
-
// ── QR code ────────────────────────────────────────────────────────────
|
|
227
|
-
const payload = encodeConnectionPayload({
|
|
228
|
-
url: this.connectionInfo.url,
|
|
229
|
-
token: this.connectionInfo.token,
|
|
230
|
-
username: this.connectionInfo.username,
|
|
231
|
-
...(this.connectionInfo.password !== undefined ? { password: this.connectionInfo.password } : {}),
|
|
232
|
-
version: this.connectionInfo.version ?? '0.0.0',
|
|
233
|
-
surface: this.connectionInfo.surface ?? 'tui',
|
|
234
|
-
});
|
|
235
|
-
const matrix = generateQrMatrix(payload);
|
|
236
|
-
const qrLines = renderQrMatrix(matrix.modules, width, { fg: C.qrFg, bg: C.qrBg });
|
|
237
|
-
for (const qrLine of qrLines) {
|
|
238
|
-
lines.push(qrLine);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
lines.push(createEmptyLine(width));
|
|
242
|
-
|
|
243
|
-
// ── Footer: confirm overlay (regenerate while a companion is live) takes
|
|
244
|
-
// priority over the ephemeral status message ──────────────────────────
|
|
245
|
-
const footerLines: Line[] = [];
|
|
246
|
-
if (this.confirmRegenerate) {
|
|
247
|
-
footerLines.push(...renderConfirmLines(width, this.confirmRegenerate));
|
|
248
|
-
} else if (this.lastStatus) {
|
|
249
|
-
footerLines.push(
|
|
250
|
-
buildPanelLine(width, [
|
|
251
|
-
[` ${this.lastStatus} `, C.label],
|
|
252
|
-
]),
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// ── Hints ── only advertise actions that are actually wired ─────────────
|
|
257
|
-
const hints: Array<{ keys: string; label: string }> = [];
|
|
258
|
-
if (this.regenerateToken) hints.push({ keys: 'r', label: 'regenerate token' });
|
|
259
|
-
if (this.copyToClipboard) hints.push({ keys: 'c', label: 'copy token' });
|
|
260
|
-
hints.push({ keys: 'v', label: this.revealed ? 'hide token' : 'reveal token' });
|
|
261
|
-
const hintsLine = hints.length > 0
|
|
262
|
-
? buildKeyboardHints(width, hints, C)
|
|
263
|
-
: buildPanelLine(width, [[' Pairing is read-only in this surface.', C.dim]]);
|
|
264
|
-
footerLines.push(hintsLine);
|
|
265
|
-
|
|
266
|
-
// Push the footer at the bottom if we have room, otherwise append it
|
|
267
|
-
// directly after the QR code.
|
|
268
|
-
const remaining = height - lines.length - footerLines.length;
|
|
269
|
-
if (remaining > 0) {
|
|
270
|
-
for (let i = 0; i < remaining; i++) lines.push(createEmptyLine(width));
|
|
271
|
-
}
|
|
272
|
-
lines.push(...footerLines);
|
|
273
|
-
|
|
274
|
-
// Clamp to the exact requested height — the QR matrix's row count is
|
|
275
|
-
// driven by payload size, not by `height`, so it can legitimately
|
|
276
|
-
// overflow a small pane; never hand back more (or fewer) lines than asked.
|
|
277
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
278
|
-
return lines.slice(0, height);
|
|
279
|
-
}
|
|
280
|
-
}
|