@timurproko/a1 0.1.8-dev.563 → 0.1.8-dev.573
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -11
- package/dist/app/session-shell/prompt-chips.js +20 -28
- package/dist/app/session-shell/session-shell-root.d.ts +2 -0
- package/dist/app/session-shell/session-shell-root.js +60 -17
- package/dist/app/session-shell/session-shell.js +35 -1
- package/dist/app/session-shell/session-viewport-controller.d.ts +1 -0
- package/dist/app/session-shell/session-viewport-controller.js +52 -30
- package/dist/cli/dispatch.d.ts +3 -1
- package/dist/cli/dispatch.js +56 -56
- package/dist/cli/package-messages.d.ts +4 -3
- package/dist/cli/package-messages.js +23 -11
- package/dist/contracts/owned-ui/index.d.ts +2 -0
- package/dist/contracts/owned-ui/index.js +1 -0
- package/dist/contracts/owned-ui/prompt-chips.d.ts +15 -0
- package/dist/contracts/owned-ui/prompt-chips.js +59 -0
- package/dist/contracts/owned-ui/validation.js +1 -1
- package/dist/integrations/pi/components/index.d.ts +1 -0
- package/dist/integrations/pi/components/index.js +1 -0
- package/dist/integrations/pi/components/modal-frame.d.ts +41 -0
- package/dist/integrations/pi/components/modal-frame.js +110 -0
- package/dist/integrations/pi/components/models-dialog.js +19 -5
- package/dist/integrations/pi/components/shell-footer-status.d.ts +4 -3
- package/dist/integrations/pi/components/shell-footer-status.js +53 -15
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +38 -2
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -1
- package/dist/integrations/pi/components/skills-dialog.js +3 -3
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +1 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +5 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +3 -2
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +7 -6
- package/dist/integrations/pi/components/upstream/components/extension-input.js +9 -8
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +10 -9
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +3 -3
- package/dist/integrations/pi/components/upstream/components/session-selector.js +13 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +15 -12
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +13 -6
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +9 -9
- package/dist/integrations/pi/engine/compaction-progress.d.ts +3 -7
- package/dist/integrations/pi/engine/compaction-progress.js +11 -10
- package/dist/integrations/pi/engine/repository-pr.d.ts +3 -3
- package/dist/integrations/pi/engine/repository-pr.js +6 -6
- package/dist/integrations/pi/engine/session-runtime.js +5 -4
- package/dist/integrations/pi/engine/settings-bridge.js +1 -1
- package/dist/integrations/pi/engine/settings-port.js +1 -1
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +11 -2
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +6 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.js +34 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +3 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/dialog-panel.js +10 -6
- package/dist/ui/components/index.d.ts +2 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/mouse.d.ts +2 -2
- package/dist/ui/components/mouse.js +2 -2
- package/dist/ui/components/progress-status.d.ts +9 -2
- package/dist/ui/components/progress-status.js +24 -3
- package/dist/ui/components/transcript-viewport.d.ts +9 -3
- package/dist/ui/components/transcript-viewport.js +116 -136
- package/docs/architecture/ui-reference-provenance.md +8 -5
- package/docs/openspec-archive-automation.md +2 -2
- package/docs/validation.md +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { Input, Key, getKeybindings, matchesKey, truncateToWidth, } from "@earendil-works/pi-tui";
|
|
2
|
+
import { DynamicBorder } from "../startup-public.js";
|
|
3
|
+
import { PiModalFrame } from "./modal-frame.js";
|
|
2
4
|
import { piTheme, renderPiModalShortcutHints } from "./theme.js";
|
|
3
5
|
const MAX_VISIBLE_ROWS = 10;
|
|
4
6
|
const MODELS_TITLE = "Models";
|
|
7
|
+
class ModalTitleLine {
|
|
8
|
+
#text = "";
|
|
9
|
+
setText(text) { this.#text = text; }
|
|
10
|
+
invalidate() { }
|
|
11
|
+
render(width) { return [truncateToWidth(this.#text, width)]; }
|
|
12
|
+
}
|
|
5
13
|
function fullModelId(model) {
|
|
6
14
|
return `${model.provider}/${model.id}`;
|
|
7
15
|
}
|
|
@@ -27,6 +35,12 @@ function keyLabel(action) {
|
|
|
27
35
|
*/
|
|
28
36
|
export class ModelsDialogComponent {
|
|
29
37
|
#input = new Input();
|
|
38
|
+
#title = new ModalTitleLine();
|
|
39
|
+
#body = {
|
|
40
|
+
invalidate: () => this.#input.invalidate(),
|
|
41
|
+
render: width => this.#renderBody(width),
|
|
42
|
+
};
|
|
43
|
+
#frame = new PiModalFrame(new DynamicBorder(), [this.#title, this.#body], new DynamicBorder());
|
|
30
44
|
#callbacks;
|
|
31
45
|
#models = [];
|
|
32
46
|
#activeModelId;
|
|
@@ -182,15 +196,16 @@ export class ModelsDialogComponent {
|
|
|
182
196
|
this.#selectedIndex = 0;
|
|
183
197
|
}
|
|
184
198
|
render(width) {
|
|
199
|
+
const theme = piTheme();
|
|
200
|
+
this.#title.setText(theme.fg("accent", theme.bold(MODELS_TITLE)) + (this.dirty ? theme.fg("warning", " (unsaved)") : ""));
|
|
201
|
+
return this.#frame.render(width);
|
|
202
|
+
}
|
|
203
|
+
#renderBody(width) {
|
|
185
204
|
const theme = piTheme();
|
|
186
205
|
const lines = [];
|
|
187
206
|
const push = (line = "") => { lines.push(truncateToWidth(line, width)); };
|
|
188
|
-
const border = theme.fg("border", "─".repeat(Math.max(1, width)));
|
|
189
207
|
const rows = this.#rows();
|
|
190
208
|
this.#clampSelection(rows);
|
|
191
|
-
push(border);
|
|
192
|
-
push();
|
|
193
|
-
push(theme.fg("accent", theme.bold(MODELS_TITLE)) + (this.dirty ? theme.fg("warning", " (unsaved)") : ""));
|
|
194
209
|
push(theme.fg("dim", "Filter: ")
|
|
195
210
|
+ theme.fg(this.#filter === "all" ? "accent" : "dim", "all")
|
|
196
211
|
+ theme.fg("dim", " | ")
|
|
@@ -229,7 +244,6 @@ export class ModelsDialogComponent {
|
|
|
229
244
|
if (this.#refreshStatus !== undefined)
|
|
230
245
|
push(theme.fg(this.#refreshStatus.kind, ` ${this.#refreshStatus.message}`));
|
|
231
246
|
push(renderPiModalShortcutHints(this.#hints()));
|
|
232
|
-
push(border);
|
|
233
247
|
return lines;
|
|
234
248
|
}
|
|
235
249
|
#hints() {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { OwnedUiSessionViewModel } from "../../../contracts/owned-ui/index.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type KeybindingsConfig } from "./upstream/adjacent/core/keybindings.js";
|
|
3
|
+
import { type PiShellViewComponentPort, type PiShellStatusPort, type PiShellProgressStatusPresentation, type PiShellQueuedInputPort, type PiShellHeaderPort, type PiShellResourceEntry, type PiShellLoadedResourcesPort, type PiShellHeaderOptions, type PiShellEditorOptions } from "./shell-shared-facade.js";
|
|
3
4
|
export declare function createPiShellHeader(options?: PiShellHeaderOptions): PiShellHeaderPort;
|
|
4
5
|
export declare function createPiShellLoadedResources(resources: readonly PiShellResourceEntry[], initialExpanded?: boolean): PiShellLoadedResourcesPort;
|
|
5
|
-
export declare function createPiShellStatus(view: OwnedUiSessionViewModel,
|
|
6
|
+
export declare function createPiShellStatus(view: OwnedUiSessionViewModel, progressStatus: PiShellProgressStatusPresentation, runtime?: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">): PiShellStatusPort;
|
|
6
7
|
export declare function createPiShellFooter(view: OwnedUiSessionViewModel, cwd: string, profile?: "pi" | "a1", showLevel?: () => boolean): PiShellViewComponentPort;
|
|
7
|
-
export declare function createPiQueuedInputStatus(submissions: readonly string[], presentation?: "pinned" | "custom-viewport"): PiShellQueuedInputPort;
|
|
8
|
+
export declare function createPiQueuedInputStatus(submissions: readonly string[], presentation?: "pinned" | "custom-viewport", getKeybindings?: () => KeybindingsConfig): PiShellQueuedInputPort;
|
|
@@ -2,7 +2,7 @@ import { rawKeyHint, VERSION } from "../startup-public.js";
|
|
|
2
2
|
import { Spacer, Text, } from "@earendil-works/pi-tui";
|
|
3
3
|
import { SessionFooter } from "./upstream/components/session-footer.js";
|
|
4
4
|
import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
5
|
-
import { WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
|
|
5
|
+
import { StatusIndicator, WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
|
|
6
6
|
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
7
7
|
import { createTuiFacade, ensureTheme, } from "./shell-shared-facade.js";
|
|
8
8
|
export function createPiShellHeader(options = {}) {
|
|
@@ -70,15 +70,15 @@ export function createPiShellLoadedResources(resources, initialExpanded = false)
|
|
|
70
70
|
invalidate() { },
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
-
export function createPiShellStatus(view,
|
|
73
|
+
export function createPiShellStatus(view, progressStatus, runtime) {
|
|
74
74
|
ensureTheme();
|
|
75
75
|
const statusUi = createTuiFacade(runtime ?? { getColumns: () => 80, getRows: () => 24, requestRender() { } });
|
|
76
76
|
let workingOverride;
|
|
77
77
|
let outputPad = PINNED_PI_LAYOUT.outputPad;
|
|
78
78
|
let progressPresentation = "pinned";
|
|
79
79
|
let placement = statusPlacement(view, workingOverride);
|
|
80
|
-
const liveStatusText = () =>
|
|
81
|
-
let component = statusComponent(view, statusUi, outputPad, liveStatusText, placement);
|
|
80
|
+
const liveStatusText = () => progressStatus.text(liveWorkingText(view, workingOverride, progressPresentation), progressPresentation);
|
|
81
|
+
let component = statusComponent(view, statusUi, outputPad, liveStatusText, placement, progressPresentation, progressStatus);
|
|
82
82
|
let signature = statusSignature(view, workingOverride, outputPad, placement, progressPresentation);
|
|
83
83
|
const rebuild = () => {
|
|
84
84
|
const nextPlacement = statusPlacement(view, workingOverride);
|
|
@@ -86,7 +86,10 @@ export function createPiShellStatus(view, formatProgressStatus, runtime) {
|
|
|
86
86
|
if (nextSignature === signature)
|
|
87
87
|
return;
|
|
88
88
|
// Performance: progress ticks change only the live message; the spinner keeps its frame and timer.
|
|
89
|
-
|
|
89
|
+
const componentMatchesPresentation = progressPresentation === "custom-viewport"
|
|
90
|
+
? component instanceof OwnedWorkingStatusIndicator
|
|
91
|
+
: component instanceof WorkingStatusIndicator;
|
|
92
|
+
if (placement === "live" && nextPlacement === "live" && component instanceof StatusIndicator && componentMatchesPresentation
|
|
90
93
|
&& statusSignature(view, workingOverride, outputPad, nextPlacement, progressPresentation, false)
|
|
91
94
|
=== statusSignature(view, workingOverride, outputPad, placement, progressPresentation, false)) {
|
|
92
95
|
component.setMessage(liveStatusText());
|
|
@@ -97,7 +100,7 @@ export function createPiShellStatus(view, formatProgressStatus, runtime) {
|
|
|
97
100
|
component.dispose();
|
|
98
101
|
placement = nextPlacement;
|
|
99
102
|
signature = nextSignature;
|
|
100
|
-
component = statusComponent(view, statusUi, outputPad, liveStatusText, placement);
|
|
103
|
+
component = statusComponent(view, statusUi, outputPad, liveStatusText, placement, progressPresentation, progressStatus);
|
|
101
104
|
};
|
|
102
105
|
return {
|
|
103
106
|
render: width => component?.render(width) ?? [],
|
|
@@ -138,14 +141,27 @@ export function createPiShellFooter(view, cwd, profile = "pi", showLevel = () =>
|
|
|
138
141
|
dispose: () => footer.dispose(),
|
|
139
142
|
};
|
|
140
143
|
}
|
|
141
|
-
export function createPiQueuedInputStatus(submissions, presentation = "pinned") {
|
|
142
|
-
|
|
144
|
+
export function createPiQueuedInputStatus(submissions, presentation = "pinned", getKeybindings) {
|
|
145
|
+
let renderedText = queuedInputText(submissions, presentation, getKeybindings?.());
|
|
146
|
+
const text = new Text(renderedText, 1, 0);
|
|
147
|
+
const refresh = () => {
|
|
148
|
+
const next = queuedInputText(submissions, presentation, getKeybindings?.());
|
|
149
|
+
if (next === renderedText)
|
|
150
|
+
return;
|
|
151
|
+
renderedText = next;
|
|
152
|
+
text.setText(next);
|
|
153
|
+
};
|
|
143
154
|
return {
|
|
144
|
-
render
|
|
155
|
+
render(width) {
|
|
156
|
+
if (submissions.length === 0)
|
|
157
|
+
return [];
|
|
158
|
+
refresh();
|
|
159
|
+
return text.render(width);
|
|
160
|
+
},
|
|
145
161
|
invalidate: () => text.invalidate(),
|
|
146
162
|
update(next) {
|
|
147
163
|
submissions = next;
|
|
148
|
-
|
|
164
|
+
refresh();
|
|
149
165
|
},
|
|
150
166
|
};
|
|
151
167
|
}
|
|
@@ -168,9 +184,23 @@ function liveWorkingText(view, workingOverride, progressPresentation) {
|
|
|
168
184
|
? `${message}(${progress}%)`
|
|
169
185
|
: message;
|
|
170
186
|
}
|
|
171
|
-
|
|
187
|
+
class OwnedWorkingStatusIndicator extends StatusIndicator {
|
|
188
|
+
constructor(ui, message, frame) {
|
|
189
|
+
let phase = -1;
|
|
190
|
+
super("working", ui, spinner => {
|
|
191
|
+
phase += 1;
|
|
192
|
+
return piTheme().fg("accent", spinner);
|
|
193
|
+
}, text => frame(text, phase, {
|
|
194
|
+
muted: value => piTheme().fg("muted", value),
|
|
195
|
+
accent: value => piTheme().fg("accent", value),
|
|
196
|
+
}), message);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function statusComponent(view, ui, outputPad, liveStatusText, placement, progressPresentation, progressStatus) {
|
|
172
200
|
if (placement === "live") {
|
|
173
|
-
return
|
|
201
|
+
return progressPresentation === "custom-viewport"
|
|
202
|
+
? new OwnedWorkingStatusIndicator(ui, liveStatusText(), progressStatus.frame)
|
|
203
|
+
: new WorkingStatusIndicator(ui, liveStatusText());
|
|
174
204
|
}
|
|
175
205
|
if (placement === "dock") {
|
|
176
206
|
if (view.lifecycle === "failed") {
|
|
@@ -184,7 +214,7 @@ function statusSignature(view, workingOverride, outputPad, placement, progressPr
|
|
|
184
214
|
const message = withMessage ? `${workingOverride ?? ""}\u0000${view.status.workingMessage ?? ""}\u0000${view.status.workingProgress ?? ""}` : "";
|
|
185
215
|
return `${placement}\u0000${outputPad}\u0000${view.lifecycle}\u0000${progressPresentation}\u0000${message}\u0000${view.status.diagnostics.at(-1) ?? ""}`;
|
|
186
216
|
}
|
|
187
|
-
function queuedInputText(submissions, presentation) {
|
|
217
|
+
function queuedInputText(submissions, presentation, bindings) {
|
|
188
218
|
if (submissions.length === 0)
|
|
189
219
|
return "";
|
|
190
220
|
const theme = piTheme();
|
|
@@ -192,11 +222,19 @@ function queuedInputText(submissions, presentation) {
|
|
|
192
222
|
return submissions.map(submission => theme.fg("muted", `Steering: ${submission.replaceAll("\n", " ⏎ ")}`)).join("\n");
|
|
193
223
|
}
|
|
194
224
|
const messages = submissions.map(submission => theme.fg("dim", `Steering: ${submission.replaceAll("\n", " ⏎ ")}`));
|
|
195
|
-
const
|
|
225
|
+
const keys = KeybindingsManager.fromOwnedBindings(bindings).getKeys("app.message.dequeue");
|
|
226
|
+
const label = keys.length === 0 ? "Unbound" : keys.map(displayKeybinding).join("/");
|
|
227
|
+
const dequeueHint = theme.fg("dim", `↳ ${label} to edit all queued messages`);
|
|
196
228
|
// Compatibility: the custom viewport matches Pi's interactive queue presentation while the
|
|
197
229
|
// comparison shell remains byte-for-byte compatible with its pinned fixture.
|
|
198
230
|
return ["", ...messages, dequeueHint].join("\n");
|
|
199
231
|
}
|
|
232
|
+
function displayKeybinding(key) {
|
|
233
|
+
return key.split("+").map(part => {
|
|
234
|
+
const display = process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part;
|
|
235
|
+
return display.charAt(0).toUpperCase() + display.slice(1);
|
|
236
|
+
}).join("+");
|
|
237
|
+
}
|
|
200
238
|
function compactHeaderText() {
|
|
201
239
|
const theme = piTheme();
|
|
202
240
|
const instructions = [
|
|
@@ -231,7 +269,7 @@ function expandedHeaderText(bindings) {
|
|
|
231
269
|
rawKeyHint("!", "to run bash"),
|
|
232
270
|
rawKeyHint("!!", "to run bash (no context)"),
|
|
233
271
|
rawKeyHint("alt+enter", "to queue follow-up"),
|
|
234
|
-
rawKeyHint("alt+up", "to edit all queued messages"),
|
|
272
|
+
rawKeyHint(keys === undefined ? "alt+up" : keys.getKeys("app.message.dequeue").join("/") || "unbound", "to edit all queued messages"),
|
|
235
273
|
rawKeyHint(process.platform === "win32" ? "alt+v" : "ctrl+v", "to paste image (with text fallback)"),
|
|
236
274
|
rawKeyHint("drop files", "to attach"),
|
|
237
275
|
].join("\n");
|
|
@@ -3,6 +3,7 @@ import { DaxnutsComponent, } from "./upstream/components/daxnuts.js";
|
|
|
3
3
|
import { EarendilAnnouncementComponent, } from "./upstream/components/earendil-announcement.js";
|
|
4
4
|
import { Box, Container, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
5
5
|
import { ScopedModelsSelectorComponent, } from "./upstream/components/scoped-models-selector.js";
|
|
6
|
+
import { adoptPiModalFrame, adoptPiModalHeader } from "./modal-frame.js";
|
|
6
7
|
import { ModelsDialogComponent, } from "./models-dialog.js";
|
|
7
8
|
import { TrustSelectorComponent, } from "./upstream/components/trust-selector.js";
|
|
8
9
|
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
@@ -72,6 +73,7 @@ export function createPiShellSettingsSelector(options) {
|
|
|
72
73
|
export function createPiShellModelSelector(options) {
|
|
73
74
|
ensureTheme();
|
|
74
75
|
const selector = new ModelSelectorComponent(createTuiFacade(options.runtime), options.currentModel, options.modelRuntime, options.scopedModels, options.onSelect, options.onCancel, options.initialSearchInput, options.onSelectAsDefault, options.defaultModel);
|
|
76
|
+
adoptPiModalFrame(selector, { topIndex: 0, bottomIndex: selector.children.length - 1 });
|
|
75
77
|
return componentPort(selector);
|
|
76
78
|
}
|
|
77
79
|
function toPiScopedModel(model) {
|
|
@@ -137,11 +139,20 @@ export function createPiShellUserMessageSelector(messages, onSelect, onCancel, i
|
|
|
137
139
|
ensureTheme();
|
|
138
140
|
const selector = new UserMessageSelectorComponent(messages.map(message => ({ id: message.id, text: message.label })), onSelect, onCancel, initialSelectedId);
|
|
139
141
|
const list = selector.getMessageList();
|
|
142
|
+
adoptPiModalFrame(selector, { topIndex: 4, bottomIndex: selector.children.length - 1 });
|
|
140
143
|
return componentPort(selector, data => list.handleInput(data));
|
|
141
144
|
}
|
|
142
145
|
export function createPiShellLoginDialog(runtime, providerId, onComplete, providerName, title) {
|
|
143
146
|
ensureTheme();
|
|
144
147
|
const dialog = new LoginDialogComponent(createTuiFacade(runtime), providerId, onComplete, providerName, title);
|
|
148
|
+
const preInsetTitle = dialog.children[1];
|
|
149
|
+
const header = adoptPiModalHeader(dialog, 0, 1);
|
|
150
|
+
adoptPiModalFrame(dialog, {
|
|
151
|
+
topIndex: 0,
|
|
152
|
+
bottomIndex: dialog.children.length - 1,
|
|
153
|
+
header,
|
|
154
|
+
preInsetContent: [preInsetTitle],
|
|
155
|
+
});
|
|
145
156
|
return {
|
|
146
157
|
...componentPort(dialog),
|
|
147
158
|
showAuth: (url, instructions) => dialog.showAuth(url, instructions),
|
|
@@ -169,6 +180,11 @@ export function createPiShellEarendilAnnouncement() {
|
|
|
169
180
|
export function createPiShellOperationLoader(runtime, message) {
|
|
170
181
|
ensureTheme();
|
|
171
182
|
const loader = new BorderedLoader(createTuiFacade(runtime), piTheme(), message, { cancellable: true });
|
|
183
|
+
adoptPiModalFrame(loader, {
|
|
184
|
+
topIndex: 0,
|
|
185
|
+
bottomIndex: loader.children.length - 1,
|
|
186
|
+
preInsetContent: loader.children[3] === undefined ? [] : [loader.children[3]],
|
|
187
|
+
});
|
|
172
188
|
return { ...componentPort(loader), signal: loader.signal };
|
|
173
189
|
}
|
|
174
190
|
export function createPiShellReloadBox() {
|
|
@@ -177,9 +193,10 @@ export function createPiShellReloadBox() {
|
|
|
177
193
|
const borderColor = (text) => piTheme().fg("border", text);
|
|
178
194
|
container.addChild(new DynamicBorder(borderColor));
|
|
179
195
|
container.addChild(new Spacer(1));
|
|
180
|
-
container.addChild(new Text(piTheme().fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."),
|
|
196
|
+
container.addChild(new Text(piTheme().fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 0, 0));
|
|
181
197
|
container.addChild(new Spacer(1));
|
|
182
198
|
container.addChild(new DynamicBorder(borderColor));
|
|
199
|
+
adoptPiModalFrame(container, { topIndex: 0, bottomIndex: container.children.length - 1 });
|
|
183
200
|
return componentPort(container);
|
|
184
201
|
}
|
|
185
202
|
export function createPiShellAuthProviderSelector(mode, providers, onSelect, onCancel, initialSearchInput) {
|
|
@@ -194,22 +211,41 @@ export function createPiShellAuthProviderSelector(mode, providers, onSelect, onC
|
|
|
194
211
|
if (selected)
|
|
195
212
|
onSelect(selected.id);
|
|
196
213
|
}, onCancel, initialSearchInput);
|
|
214
|
+
const preInsetContent = [selector.children[2], selector.children[6]];
|
|
215
|
+
const header = adoptPiModalHeader(selector, 0, 2);
|
|
216
|
+
adoptPiModalFrame(selector, {
|
|
217
|
+
topIndex: 0,
|
|
218
|
+
bottomIndex: selector.children.length - 1,
|
|
219
|
+
header,
|
|
220
|
+
preInsetContent,
|
|
221
|
+
});
|
|
197
222
|
return componentPort(selector);
|
|
198
223
|
}
|
|
199
224
|
export function createPiShellExtensionSelector(title, options, onSelect, onCancel) {
|
|
200
225
|
ensureTheme();
|
|
201
|
-
|
|
226
|
+
const selector = new ExtensionSelectorComponent(title, [...options], onSelect, onCancel);
|
|
227
|
+
const preInsetContent = [selector.children[2], selector.children[4], selector.children[6]];
|
|
228
|
+
const header = adoptPiModalHeader(selector, 0, 2);
|
|
229
|
+
adoptPiModalFrame(selector, {
|
|
230
|
+
topIndex: 0,
|
|
231
|
+
bottomIndex: selector.children.length - 1,
|
|
232
|
+
header,
|
|
233
|
+
preInsetContent,
|
|
234
|
+
});
|
|
235
|
+
return componentPort(selector);
|
|
202
236
|
}
|
|
203
237
|
export function createPiShellThemeSelector(currentTheme, onSelect, onCancel, onPreview) {
|
|
204
238
|
ensureTheme();
|
|
205
239
|
const selector = new ThemeSelectorComponent(currentTheme, onSelect, onCancel, onPreview);
|
|
206
240
|
const list = selector.getSelectList();
|
|
241
|
+
adoptPiModalFrame(selector, { topIndex: 0, bottomIndex: selector.children.length - 1 });
|
|
207
242
|
return componentPort(selector, data => list.handleInput(data));
|
|
208
243
|
}
|
|
209
244
|
export function createPiShellShowImagesSelector(currentValue, onSelect, onCancel) {
|
|
210
245
|
ensureTheme();
|
|
211
246
|
const selector = new ShowImagesSelectorComponent(currentValue, onSelect, onCancel);
|
|
212
247
|
const list = selector.getSelectList();
|
|
248
|
+
adoptPiModalFrame(selector, { topIndex: 0, bottomIndex: selector.children.length - 1 });
|
|
213
249
|
return componentPort(selector, data => list.handleInput(data));
|
|
214
250
|
}
|
|
215
251
|
export function createPiShellDialog(dialog, handlers = {}) {
|
|
@@ -80,11 +80,19 @@ export interface PiShellViewComponentPort extends PiShellComponentPort {
|
|
|
80
80
|
update(view: OwnedUiSessionViewModel): void;
|
|
81
81
|
}
|
|
82
82
|
export type PiShellStatusPlacement = "live" | "dock" | "hidden";
|
|
83
|
+
export type PiShellProgressPresentationMode = "pinned" | "custom-viewport";
|
|
84
|
+
export interface PiShellProgressStatusPresentation {
|
|
85
|
+
readonly text: (message: string, mode: PiShellProgressPresentationMode) => string;
|
|
86
|
+
readonly frame: (message: string, phase: number, styles: {
|
|
87
|
+
readonly muted: (text: string) => string;
|
|
88
|
+
readonly accent: (text: string) => string;
|
|
89
|
+
}) => string;
|
|
90
|
+
}
|
|
83
91
|
export interface PiShellStatusPort extends PiShellViewComponentPort {
|
|
84
92
|
setWorkingOverride(message: string | undefined): void;
|
|
85
93
|
setOutputPad(padding: 0 | 1): void;
|
|
86
94
|
/** Bare A1 shows engine-measured progress beside the working word; the pinned route keeps the bare word. */
|
|
87
|
-
setProgressPresentation(presentation:
|
|
95
|
+
setProgressPresentation(presentation: PiShellProgressPresentationMode): void;
|
|
88
96
|
/** Semantic row placement; callers must not inspect rendered text. */
|
|
89
97
|
placement(): PiShellStatusPlacement;
|
|
90
98
|
renderDock(width: number): readonly string[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DynamicBorder } from "../startup-public.js";
|
|
2
2
|
import { Container, getKeybindings, Input, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { addPiModalHeader, adoptPiModalFrame } from "./modal-frame.js";
|
|
3
4
|
import { PINNED_PI_LAYOUT, piTheme, renderPiModalShortcutHints } from "./theme.js";
|
|
4
5
|
import { componentPort, ensureTheme, piShellTruncateToWidth, piShellVisibleWidth } from "./shell-shared-facade.js";
|
|
5
6
|
import { SKILL_COMMAND_PREFIX, skillMatchesQuery } from "./skills-command.js";
|
|
@@ -36,9 +37,7 @@ class SkillsSelectorComponent extends Container {
|
|
|
36
37
|
this.#onSelect = options.onSelect;
|
|
37
38
|
this.#onCancel = options.onCancel;
|
|
38
39
|
this.#searchInput.onSubmit = () => this.#selectCurrent();
|
|
39
|
-
this
|
|
40
|
-
this.addChild(new Spacer(1));
|
|
41
|
-
this.addChild(new Text(piTheme().fg("accent", piTheme().bold("Skills")), 0, 0));
|
|
40
|
+
const header = addPiModalHeader(this, new DynamicBorder(), new Text(piTheme().fg("accent", piTheme().bold("Skills")), 0, 0));
|
|
42
41
|
this.addChild(new Spacer(1));
|
|
43
42
|
this.addChild(this.#searchInput);
|
|
44
43
|
this.addChild(new Spacer(1));
|
|
@@ -51,6 +50,7 @@ class SkillsSelectorComponent extends Container {
|
|
|
51
50
|
{ key: bindings.getKeys("tui.select.cancel").join("/"), action: "cancel" },
|
|
52
51
|
]), 0, 0));
|
|
53
52
|
this.addChild(new DynamicBorder());
|
|
53
|
+
adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
|
|
54
54
|
this.#updateList();
|
|
55
55
|
}
|
|
56
56
|
// Protocol: focus propagates to the search input so the terminal cursor follows the query.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Component } from "@earendil-works/pi-tui";
|
|
2
|
-
import type
|
|
2
|
+
import { type OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
|
|
3
3
|
export interface PiShellSubmittedPromptComposer {
|
|
4
4
|
readonly layout: (width: number, source?: Date | number | null) => {
|
|
5
5
|
readonly contentWidth: number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getMarkdownTheme } from "../startup-public.js";
|
|
2
2
|
import { Box, Markdown, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { protectPromptChipWrapping } from "../../../contracts/owned-ui/index.js";
|
|
3
4
|
import { piTheme } from "./theme.js";
|
|
4
5
|
/** Branch summaries share the block kind but do not participate in prompt navigation. */
|
|
5
6
|
export function isPiPromptStyleCompaction(block) {
|
|
@@ -18,11 +19,13 @@ export function createPiSubmittedPromptComponent(block, composer, heading) {
|
|
|
18
19
|
const timestamp = numericTimestamp(block);
|
|
19
20
|
// Invariant: presentation-only chrome stays outside the summary's Markdown parser and stored text.
|
|
20
21
|
const header = heading === undefined ? undefined : new Text(heading, 0, 0);
|
|
21
|
-
const
|
|
22
|
+
const chipWrapping = block.kind === "user" ? protectPromptChipWrapping(block.text) : undefined;
|
|
23
|
+
const markdown = new Markdown(chipWrapping?.text ?? block.text, 0, 0, getMarkdownTheme(), { color: content => piTheme().fg("userMessageText", content) }, { preserveOrderedListMarkers: true, preserveBackslashEscapes: true });
|
|
22
24
|
const content = {
|
|
23
25
|
render(width) {
|
|
24
26
|
const layout = composer.layout(width, timestamp);
|
|
25
|
-
const
|
|
27
|
+
const renderedBodyRows = markdown.render(layout.contentWidth);
|
|
28
|
+
const bodyRows = chipWrapping === undefined ? renderedBodyRows : renderedBodyRows.map(row => chipWrapping.restore(row));
|
|
26
29
|
const rows = header === undefined ? bodyRows : [
|
|
27
30
|
...header.render(layout.contentWidth).map(row => piTheme().fg("userMessageText", row)),
|
|
28
31
|
...(bodyRows.length === 0 ? [] : ["", ...bodyRows]),
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Provenance: @earendil-works/pi-coding-agent 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
|
|
3
3
|
* packages/coding-agent/src/core/keybindings.ts.
|
|
4
4
|
* Modifications: Mechanical source port with Node import prefixes, public package-root agent-directory
|
|
5
|
-
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling
|
|
6
|
-
* selection.
|
|
5
|
+
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, unbound model
|
|
6
|
+
* selection, and cross-platform Alt+Up queued-message restoration.
|
|
7
7
|
* Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut,
|
|
8
8
|
* owned-input-keybinding-aliases.
|
|
9
9
|
*/
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Provenance: @earendil-works/pi-coding-agent 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
|
|
3
3
|
* packages/coding-agent/src/core/keybindings.ts.
|
|
4
4
|
* Modifications: Mechanical source port with Node import prefixes, public package-root agent-directory
|
|
5
|
-
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling
|
|
6
|
-
* selection.
|
|
5
|
+
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, unbound model
|
|
6
|
+
* selection, and cross-platform Alt+Up queued-message restoration.
|
|
7
7
|
* Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut,
|
|
8
8
|
* owned-input-keybinding-aliases.
|
|
9
9
|
*/
|
|
@@ -184,6 +184,7 @@ const OWNED_INPUT_KEYBINDINGS = {
|
|
|
184
184
|
...KEYBINDINGS,
|
|
185
185
|
"app.thinking.cycle": { ...KEYBINDINGS["app.thinking.cycle"], defaultKeys: "ctrl+l" },
|
|
186
186
|
"app.model.select": { ...KEYBINDINGS["app.model.select"], defaultKeys: [] },
|
|
187
|
+
"app.message.dequeue": { ...KEYBINDINGS["app.message.dequeue"], defaultKeys: "alt+up" },
|
|
187
188
|
"tui.editor.deleteWordBackward": {
|
|
188
189
|
...KEYBINDINGS["tui.editor.deleteWordBackward"],
|
|
189
190
|
defaultKeys: [...KEYBINDINGS["tui.editor.deleteWordBackward"].defaultKeys, "ctrl+backspace"],
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/extension-editor.ts.
|
|
4
4
|
* Modifications: Mechanical port: remap pi-tui to the root public singleton, use owned
|
|
5
5
|
* keybindings/theme and external-editor seams, preserve editor layout, hints, focus, submission,
|
|
6
|
-
* cancellation, and external-editor lifecycle; bare A1 uses the shared semantic modal shortcut row
|
|
6
|
+
* cancellation, and external-editor lifecycle; bare A1 uses the shared semantic modal shortcut row and
|
|
7
|
+
* compact padded modal frame.
|
|
7
8
|
* Deviations: owned-modal-shortcut-hints.
|
|
8
9
|
*/
|
|
9
10
|
import { Container, type TUI } from "@earendil-works/pi-tui";
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/extension-editor.ts.
|
|
4
4
|
* Modifications: Mechanical port: remap pi-tui to the root public singleton, use owned
|
|
5
5
|
* keybindings/theme and external-editor seams, preserve editor layout, hints, focus, submission,
|
|
6
|
-
* cancellation, and external-editor lifecycle; bare A1 uses the shared semantic modal shortcut row
|
|
6
|
+
* cancellation, and external-editor lifecycle; bare A1 uses the shared semantic modal shortcut row and
|
|
7
|
+
* compact padded modal frame.
|
|
7
8
|
* Deviations: owned-modal-shortcut-hints.
|
|
8
9
|
*/
|
|
9
10
|
import { Container, Editor, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
10
11
|
import { DynamicBorder, getSelectListTheme } from "../../../startup-public.js";
|
|
11
12
|
import { editInExternalEditor } from "../external-editor.js";
|
|
13
|
+
import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
|
|
12
14
|
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
13
15
|
export class ExtensionEditorComponent extends Container {
|
|
14
16
|
#editor;
|
|
@@ -34,12 +36,10 @@ export class ExtensionEditorComponent extends Container {
|
|
|
34
36
|
|| process.env.EDITOR
|
|
35
37
|
|| (process.platform === "win32" ? "notepad" : "nano");
|
|
36
38
|
const { description, ...editorOptions } = options ?? {};
|
|
37
|
-
this
|
|
38
|
-
this.addChild(new Spacer(1));
|
|
39
|
-
this.addChild(new Text(piTheme().fg("accent", title), 1, 0));
|
|
39
|
+
const header = addPiModalHeader(this, new DynamicBorder(), new Text(piTheme().fg("accent", title), 0, 0));
|
|
40
40
|
if (description) {
|
|
41
41
|
this.addChild(new Spacer(1));
|
|
42
|
-
this.addChild(new Text(piTheme().fg("text", description),
|
|
42
|
+
this.addChild(new Text(piTheme().fg("text", description), 0, 0));
|
|
43
43
|
}
|
|
44
44
|
this.addChild(new Spacer(1));
|
|
45
45
|
this.#editor = new Editor(tui, {
|
|
@@ -57,9 +57,10 @@ export class ExtensionEditorComponent extends Container {
|
|
|
57
57
|
{ key: this.#keybindings.getKeys("tui.select.cancel").join("/"), action: "cancel" },
|
|
58
58
|
{ key: this.#keybindings.getKeys("app.editor.external").join("/"), action: "external editor" },
|
|
59
59
|
]);
|
|
60
|
-
this.addChild(new Text(hint,
|
|
60
|
+
this.addChild(new Text(hint, 0, 0));
|
|
61
61
|
this.addChild(new Spacer(1));
|
|
62
62
|
this.addChild(new DynamicBorder());
|
|
63
|
+
adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
|
|
63
64
|
}
|
|
64
65
|
handleInput(data) {
|
|
65
66
|
if (this.#keybindings.matches(data, "tui.select.cancel")) {
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Provenance: @earendil-works/pi-coding-agent 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/extension-input.ts.
|
|
4
4
|
* Modifications: Mechanical port: remap public imports, use ECMAScript private fields, and route the
|
|
5
|
-
* bare-A1 instruction row through the shared semantic shortcut
|
|
6
|
-
* timeout, focus, submission, cancellation, and
|
|
5
|
+
* bare-A1 instruction row and chrome through the shared semantic shortcut and compact padded
|
|
6
|
+
* modal-frame components while preserving input, timeout, focus, submission, cancellation, and
|
|
7
|
+
* disposal behavior.
|
|
7
8
|
* Deviations: owned-modal-shortcut-hints.
|
|
8
9
|
*/
|
|
9
10
|
import { DynamicBorder } from "../../../startup-public.js";
|
|
10
11
|
import { Container, getKeybindings, Input, Spacer, Text } from "@earendil-works/pi-tui";
|
|
12
|
+
import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
|
|
11
13
|
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
12
14
|
import { CountdownTimer } from "./countdown-timer.js";
|
|
13
15
|
/** Bare-A1 extension text input using the common modal shortcut row. */
|
|
@@ -30,13 +32,11 @@ export class ExtensionInputComponent extends Container {
|
|
|
30
32
|
this.#onCancel = onCancel;
|
|
31
33
|
this.#baseTitle = title;
|
|
32
34
|
const theme = piTheme();
|
|
33
|
-
this.
|
|
34
|
-
this
|
|
35
|
-
this.#titleText = new Text(theme.fg("accent", title), 1, 0);
|
|
36
|
-
this.addChild(this.#titleText);
|
|
35
|
+
this.#titleText = new Text(theme.fg("accent", title), 0, 0);
|
|
36
|
+
const header = addPiModalHeader(this, new DynamicBorder(), this.#titleText);
|
|
37
37
|
if (opts?.description) {
|
|
38
38
|
this.addChild(new Spacer(1));
|
|
39
|
-
this.addChild(new Text(theme.fg("text", opts.description),
|
|
39
|
+
this.addChild(new Text(theme.fg("text", opts.description), 0, 0));
|
|
40
40
|
}
|
|
41
41
|
this.addChild(new Spacer(1));
|
|
42
42
|
this.#countdown = opts?.timeout !== undefined && opts.timeout > 0 && opts.tui !== undefined
|
|
@@ -53,9 +53,10 @@ export class ExtensionInputComponent extends Container {
|
|
|
53
53
|
this.addChild(new Text(renderPiModalShortcutHints([
|
|
54
54
|
{ key: keys.getKeys("tui.select.confirm").join("/"), action: "submit" },
|
|
55
55
|
{ key: keys.getKeys("tui.select.cancel").join("/"), action: "cancel" },
|
|
56
|
-
]),
|
|
56
|
+
]), 0, 0));
|
|
57
57
|
this.addChild(new Spacer(1));
|
|
58
58
|
this.addChild(new DynamicBorder());
|
|
59
|
+
adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
|
|
59
60
|
}
|
|
60
61
|
handleInput(keyData) {
|
|
61
62
|
const keys = getKeybindings();
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Provenance: @earendil-works/pi-coding-agent 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/extension-selector.ts.
|
|
4
4
|
* Modifications: Mechanical port: remap public imports, use ECMAScript private fields, and route the
|
|
5
|
-
* bare-A1 instruction row through the shared semantic shortcut
|
|
6
|
-
* timeout, navigation, selection, cancellation, and
|
|
5
|
+
* bare-A1 instruction row and chrome through the shared semantic shortcut and compact padded
|
|
6
|
+
* modal-frame components while preserving options, timeout, navigation, selection, cancellation, and
|
|
7
|
+
* disposal behavior.
|
|
7
8
|
* Deviations: owned-modal-shortcut-hints.
|
|
8
9
|
*/
|
|
9
10
|
import { DynamicBorder } from "../../../startup-public.js";
|
|
10
11
|
import { Container, getKeybindings, Spacer, Text } from "@earendil-works/pi-tui";
|
|
12
|
+
import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
|
|
11
13
|
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
12
14
|
import { CountdownTimer } from "./countdown-timer.js";
|
|
13
15
|
/** Bare-A1 extension selector using the common modal shortcut row. */
|
|
@@ -29,13 +31,11 @@ export class ExtensionSelectorComponent extends Container {
|
|
|
29
31
|
this.#baseTitle = title;
|
|
30
32
|
this.#onToggleToolsExpanded = opts?.onToggleToolsExpanded;
|
|
31
33
|
const theme = piTheme();
|
|
32
|
-
this.
|
|
33
|
-
this
|
|
34
|
-
this.#titleText = new Text(theme.fg("accent", theme.bold(title)), 1, 0);
|
|
35
|
-
this.addChild(this.#titleText);
|
|
34
|
+
this.#titleText = new Text(theme.fg("accent", theme.bold(title)), 0, 0);
|
|
35
|
+
const header = addPiModalHeader(this, new DynamicBorder(), this.#titleText);
|
|
36
36
|
if (opts?.description) {
|
|
37
37
|
this.addChild(new Spacer(1));
|
|
38
|
-
this.addChild(new Text(theme.fg("text", opts.description),
|
|
38
|
+
this.addChild(new Text(theme.fg("text", opts.description), 0, 0));
|
|
39
39
|
}
|
|
40
40
|
this.addChild(new Spacer(1));
|
|
41
41
|
this.#countdown = opts?.timeout !== undefined && opts.timeout > 0 && opts.tui !== undefined
|
|
@@ -51,9 +51,10 @@ export class ExtensionSelectorComponent extends Container {
|
|
|
51
51
|
{ key: "↑↓", action: "navigate" },
|
|
52
52
|
{ key: keys.getKeys("tui.select.confirm").join("/"), action: "select" },
|
|
53
53
|
{ key: keys.getKeys("tui.select.cancel").join("/"), action: "cancel" },
|
|
54
|
-
]),
|
|
54
|
+
]), 0, 0));
|
|
55
55
|
this.addChild(new Spacer(1));
|
|
56
56
|
this.addChild(new DynamicBorder());
|
|
57
|
+
adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
|
|
57
58
|
this.#updateList();
|
|
58
59
|
}
|
|
59
60
|
#updateList() {
|
|
@@ -63,7 +64,7 @@ export class ExtensionSelectorComponent extends Container {
|
|
|
63
64
|
const option = this.#options[index];
|
|
64
65
|
this.#listContainer.addChild(new Text(index === this.#selectedIndex
|
|
65
66
|
? theme.fg("accent", "→ ") + theme.fg("accent", option)
|
|
66
|
-
: ` ${theme.fg("text", option)}`,
|
|
67
|
+
: ` ${theme.fg("text", option)}`, 0, 0));
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
handleInput(keyData) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* search, bulk/provider/reorder actions, dirty state, Ctrl+S persistence, refresh status,
|
|
6
6
|
* cancellation, and focus while remapping theme and public helper imports; local key labels preserve
|
|
7
7
|
* pinned platform formatting before layout without changing binding identities, and bare A1 uses the
|
|
8
|
-
* shared semantic modal shortcut row.
|
|
8
|
+
* shared semantic modal shortcut row and compact padded modal frame.
|
|
9
9
|
* Deviations: owned-modal-shortcut-hints.
|
|
10
10
|
*/
|
|
11
11
|
interface ScopedModel {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Container, fuzzyFilter, getKeybindings, Input, Key, matchesKey, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
2
2
|
import { DynamicBorder } from "../../../startup-public.js";
|
|
3
3
|
import { getModelSearchText } from "../model-search.js";
|
|
4
|
+
import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
|
|
4
5
|
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
5
6
|
// Rationale: upstream's capitalized hint formatter is private to the package; the same mapping lives here.
|
|
6
7
|
function keyDisplayText(keybinding) {
|
|
@@ -110,9 +111,7 @@ export class ScopedModelsSelectorComponent extends Container {
|
|
|
110
111
|
this.enabledIds = config.enabledModelIds === null ? null : [...config.enabledModelIds];
|
|
111
112
|
this.filteredItems = this.buildItems();
|
|
112
113
|
// Header
|
|
113
|
-
this
|
|
114
|
-
this.addChild(new Spacer(1));
|
|
115
|
-
this.addChild(new Text(theme.fg("accent", theme.bold("Model Configuration")), 0, 0));
|
|
114
|
+
const header = addPiModalHeader(this, new DynamicBorder(), new Text(theme.fg("accent", theme.bold("Model Configuration")), 0, 0));
|
|
116
115
|
const saveKey = keyDisplayText("app.models.save");
|
|
117
116
|
this.addChild(new Text(theme.fg("muted", "Session-only.")
|
|
118
117
|
+ (saveKey ? ` ${renderPiModalShortcutHints([{ key: saveKey, action: "to save to settings." }])}` : ""), 0, 0));
|
|
@@ -133,6 +132,7 @@ export class ScopedModelsSelectorComponent extends Container {
|
|
|
133
132
|
this.footerText = new Text(this.getFooterText(), 0, 0);
|
|
134
133
|
this.addChild(this.footerText);
|
|
135
134
|
this.addChild(new DynamicBorder());
|
|
135
|
+
adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
|
|
136
136
|
this.updateList();
|
|
137
137
|
}
|
|
138
138
|
updateModels(models, enabledModelIds) {
|