@timurproko/a1 0.1.8-dev.572 → 0.1.8-dev.581
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/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 +63 -14
- package/dist/app/session-shell/session-shell.js +30 -2
- package/dist/app/session-shell/session-viewport-controller.d.ts +1 -0
- package/dist/app/session-shell/session-viewport-controller.js +5 -1
- 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-selectors-dialogs.js +38 -2
- 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/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 +2 -6
- package/dist/integrations/pi/engine/compaction-progress.js +10 -10
- 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.js +6 -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 +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/dialog-panel.js +10 -6
- package/dist/ui/components/mouse.d.ts +2 -2
- package/dist/ui/components/mouse.js +2 -2
- package/dist/ui/components/transcript-viewport.d.ts +6 -0
- package/dist/ui/components/transcript-viewport.js +186 -27
- package/docs/architecture/ui-reference-provenance.md +9 -0
- package/docs/local-worktree-cleanup.md +3 -1
- package/docs/openspec-archive-automation.md +4 -0
- package/docs/validation.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Container, Spacer } from "@earendil-works/pi-tui";
|
|
2
|
+
export const PI_MODAL_CONTENT_PADDING_X = 1;
|
|
3
|
+
/** The semantic top rule/title pair used while a modal assembles its frame. */
|
|
4
|
+
export class PiModalHeader {
|
|
5
|
+
rule;
|
|
6
|
+
title;
|
|
7
|
+
constructor(rule, title) {
|
|
8
|
+
this.rule = rule;
|
|
9
|
+
this.title = title;
|
|
10
|
+
}
|
|
11
|
+
invalidate() {
|
|
12
|
+
this.rule.invalidate();
|
|
13
|
+
this.title.invalidate();
|
|
14
|
+
}
|
|
15
|
+
render(width) {
|
|
16
|
+
return [...this.rule.render(width), ...this.title.render(width)];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Complete framed-modal chrome. Rules remain frame-wide; every semantic content component renders
|
|
21
|
+
* in the same one-cell-smaller rectangle and receives the same left inset.
|
|
22
|
+
*/
|
|
23
|
+
export class PiModalFrame {
|
|
24
|
+
#topRule;
|
|
25
|
+
#content;
|
|
26
|
+
#bottomRule;
|
|
27
|
+
#fullWidthContent;
|
|
28
|
+
#preInsetContent;
|
|
29
|
+
constructor(topRule, content, bottomRule, fullWidthContent = [], preInsetContent = []) {
|
|
30
|
+
this.#topRule = topRule;
|
|
31
|
+
this.#content = content;
|
|
32
|
+
this.#bottomRule = bottomRule;
|
|
33
|
+
this.#fullWidthContent = new Set(fullWidthContent);
|
|
34
|
+
this.#preInsetContent = new Set(preInsetContent);
|
|
35
|
+
}
|
|
36
|
+
invalidate() {
|
|
37
|
+
this.#topRule.invalidate();
|
|
38
|
+
for (const component of this.#content)
|
|
39
|
+
component.invalidate();
|
|
40
|
+
this.#bottomRule.invalidate();
|
|
41
|
+
}
|
|
42
|
+
render(width) {
|
|
43
|
+
const padding = Math.min(PI_MODAL_CONTENT_PADDING_X, Math.max(0, width));
|
|
44
|
+
const contentWidth = Math.max(0, width - padding);
|
|
45
|
+
const rows = [...this.#topRule.render(width)];
|
|
46
|
+
for (const component of this.#content) {
|
|
47
|
+
if (this.#fullWidthContent.has(component) || this.#preInsetContent.has(component)) {
|
|
48
|
+
rows.push(...component.render(width));
|
|
49
|
+
}
|
|
50
|
+
else if (component instanceof Spacer) {
|
|
51
|
+
rows.push(...component.render(contentWidth));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
rows.push(...component.render(contentWidth).map(row => `${" ".repeat(padding)}${row}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
rows.push(...this.#bottomRule.render(width));
|
|
58
|
+
return rows;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Add compact titled-modal chrome while a component assembles its semantic body. */
|
|
62
|
+
export function addPiModalHeader(container, rule, title) {
|
|
63
|
+
const header = new PiModalHeader(rule, title);
|
|
64
|
+
container.addChild(header);
|
|
65
|
+
return header;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Replace explicitly identified structural modal children with the shared padded frame. Callers
|
|
69
|
+
* identify semantic rules/components; rendered text is never inspected or rewritten.
|
|
70
|
+
*/
|
|
71
|
+
export function adoptPiModalFrame(container, adoption) {
|
|
72
|
+
const { topIndex, bottomIndex, header, fullWidthContent = [], preInsetContent = [] } = adoption;
|
|
73
|
+
if (topIndex < 0 || bottomIndex <= topIndex || bottomIndex >= container.children.length) {
|
|
74
|
+
throw new TypeError("invalid modal frame child positions");
|
|
75
|
+
}
|
|
76
|
+
const top = container.children[topIndex];
|
|
77
|
+
const bottom = container.children[bottomIndex];
|
|
78
|
+
const contentStart = topIndex + 1;
|
|
79
|
+
let topRule = top;
|
|
80
|
+
const content = [];
|
|
81
|
+
if (header !== undefined) {
|
|
82
|
+
if (top !== header)
|
|
83
|
+
throw new TypeError("modal header is not at the declared top position");
|
|
84
|
+
topRule = header.rule;
|
|
85
|
+
content.push(header.title);
|
|
86
|
+
}
|
|
87
|
+
content.push(...container.children.slice(contentStart, bottomIndex));
|
|
88
|
+
const frame = new PiModalFrame(topRule, content, bottom, fullWidthContent, preInsetContent);
|
|
89
|
+
container.children.splice(topIndex, bottomIndex - topIndex + 1, frame);
|
|
90
|
+
return frame;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Adopt a public component's semantic rule/title children at the bare-A1 boundary. Only an optional
|
|
94
|
+
* structural Spacer between those explicitly identified children is accepted; rendered text is
|
|
95
|
+
* never inspected or rewritten. The pinned comparison profile does not call this adapter.
|
|
96
|
+
*/
|
|
97
|
+
export function adoptPiModalHeader(container, ruleIndex, titleIndex) {
|
|
98
|
+
if (ruleIndex < 0 || titleIndex <= ruleIndex || titleIndex >= container.children.length) {
|
|
99
|
+
throw new TypeError("invalid modal rule/title child positions");
|
|
100
|
+
}
|
|
101
|
+
const between = container.children.slice(ruleIndex + 1, titleIndex);
|
|
102
|
+
if (between.length > 1 || between.some(child => !(child instanceof Spacer))) {
|
|
103
|
+
throw new TypeError("modal rule and title are not separated by an optional single Spacer");
|
|
104
|
+
}
|
|
105
|
+
const rule = container.children[ruleIndex];
|
|
106
|
+
const title = container.children[titleIndex];
|
|
107
|
+
const header = new PiModalHeader(rule, title);
|
|
108
|
+
container.children.splice(ruleIndex, titleIndex - ruleIndex + 1, header);
|
|
109
|
+
return header;
|
|
110
|
+
}
|
|
@@ -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() {
|
|
@@ -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 = {}) {
|
|
@@ -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]),
|
|
@@ -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) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Modifications: Source-synchronized session selector port: preserve threaded/current/all scope,
|
|
5
5
|
* search, sort, named/path filters, rename, delete confirmation, active-session protection, loading
|
|
6
6
|
* progress, cancellation, focus, and disposal while remapping public helpers, owned keybindings/theme,
|
|
7
|
-
* canonical path handling, and the shared bare-A1 modal shortcut row.
|
|
7
|
+
* canonical path handling, and the shared bare-A1 modal shortcut row and compact padded modal frame.
|
|
8
8
|
* Deviations: owned-modal-shortcut-hints.
|
|
9
9
|
*/
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
@@ -14,6 +14,7 @@ import * as os from "node:os";
|
|
|
14
14
|
import { Container, getKeybindings, Input, Spacer, Text, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
15
15
|
import { KeybindingsManager } from "../adjacent/core/keybindings.js";
|
|
16
16
|
import { DynamicBorder } from "../../../startup-public.js";
|
|
17
|
+
import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
|
|
17
18
|
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
18
19
|
const theme = new Proxy({}, {
|
|
19
20
|
get(_target, property) {
|
|
@@ -665,15 +666,21 @@ export class SessionSelectorComponent extends Container {
|
|
|
665
666
|
buildBaseLayout(content, options) {
|
|
666
667
|
this.clear();
|
|
667
668
|
this.addChild(new Spacer(1));
|
|
668
|
-
|
|
669
|
+
const showHeader = options?.showHeader ?? true;
|
|
670
|
+
const modalHeader = showHeader
|
|
671
|
+
? addPiModalHeader(this, new DynamicBorder((s) => theme.fg("accent", s)), this.header)
|
|
672
|
+
: undefined;
|
|
673
|
+
if (!showHeader)
|
|
674
|
+
this.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
669
675
|
this.addChild(new Spacer(1));
|
|
670
|
-
if (options?.showHeader ?? true) {
|
|
671
|
-
this.addChild(this.header);
|
|
672
|
-
this.addChild(new Spacer(1));
|
|
673
|
-
}
|
|
674
676
|
this.addChild(content);
|
|
675
677
|
this.addChild(new Spacer(1));
|
|
676
678
|
this.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
679
|
+
const frame = { topIndex: 1, bottomIndex: this.children.length - 1 };
|
|
680
|
+
if (modalHeader === undefined)
|
|
681
|
+
adoptPiModalFrame(this, frame);
|
|
682
|
+
else
|
|
683
|
+
adoptPiModalFrame(this, { ...frame, header: modalHeader });
|
|
677
684
|
}
|
|
678
685
|
constructor(currentSessionsLoader, allSessionsLoader, onSelect, onCancel, onExit, requestRender, options, currentSessionFilePath) {
|
|
679
686
|
super();
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/thinking-selector.ts.
|
|
4
4
|
* Modifications: Preserve the searchable thinking-level selector, current/default semantics,
|
|
5
5
|
* selection, save, cancellation, and focus while accepting the active bare-A1 cycle-key label from the
|
|
6
|
-
* shell, styling the title with the established bold semantic accent treatment, placing
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* shell, styling the title with the established bold semantic accent treatment, placing it directly
|
|
7
|
+
* below the top rule through the shared compact padded modal frame and its muted hint directly below
|
|
8
|
+
* it, deduplicating levels, and rendering aligned muted descriptions after adjacent active and
|
|
9
|
+
* bracketed default markers. All list and border colors use the owned theme and its explicit color
|
|
10
|
+
* mode, and the footer uses the shared bare-A1 modal shortcut row. The comparison profile retains the
|
|
11
|
+
* public pinned component.
|
|
11
12
|
* Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading.
|
|
12
13
|
*/
|
|
13
14
|
import { Container, type Focusable, SelectList } from "@earendil-works/pi-tui";
|
|
@@ -16,7 +17,7 @@ export type ThinkingSelectorLevel = "off" | "minimal" | "low" | "medium" | "high
|
|
|
16
17
|
export declare class OwnedThinkingSelectorComponent extends Container implements Focusable {
|
|
17
18
|
private searchInput;
|
|
18
19
|
private selectList;
|
|
19
|
-
private
|
|
20
|
+
private selectListContainer;
|
|
20
21
|
private allItems;
|
|
21
22
|
private onSelect;
|
|
22
23
|
private onCancel;
|