@timurproko/a1 0.2.5-dev.698 → 0.2.5
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/session-shell.js +9 -8
- package/dist/composition/owned-ui.js +2 -0
- package/dist/contracts/presentation/index.d.ts +6 -1
- package/dist/contracts/presentation/index.js +4 -2
- package/dist/features/owned-ui/resources/release-notes.json +4 -0
- package/dist/features/owned-ui/settings-app.d.ts +1 -1
- package/dist/features/owned-ui/settings-app.js +334 -63
- package/dist/integrations/pi/components/models-dialog.js +2 -2
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +40 -2
- package/dist/integrations/pi/components/skills-dialog.js +2 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +4 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +48 -12
- package/dist/integrations/pi/components/upstream/components/session-selector.js +14 -14
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +5 -4
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +6 -4
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +7 -6
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +24 -18
- 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.d.ts +15 -0
- package/dist/ui/components/dialog-panel.js +50 -3
- package/dist/ui/components/index.d.ts +3 -3
- package/dist/ui/components/index.js +2 -2
- package/dist/ui/components/line-input.d.ts +2 -0
- package/dist/ui/components/line-input.js +2 -2
- package/dist/ui/components/prompt-input.d.ts +2 -2
- package/dist/ui/components/prompt-input.js +5 -5
- package/dist/ui/components/shortcut-hints.d.ts +8 -3
- package/dist/ui/components/shortcut-hints.js +25 -5
- package/dist/ui/components/shortcuts.d.ts +1 -1
- package/dist/ui/components/shortcuts.js +4 -0
- package/dist/ui/components/value-menu.js +3 -1
- package/dist/ui/settings/manager.d.ts +1 -0
- package/dist/ui/settings/manager.js +9 -3
- package/docs/releases/0.2.5.md +35 -0
- package/package.json +1 -1
|
@@ -161,8 +161,12 @@ export function createPiShellEditor(options) {
|
|
|
161
161
|
const resources = commands
|
|
162
162
|
.filter(command => command.name !== collapsed.collapsedCommand?.name && command.source !== "builtin" && !builtInNames.has(command.name))
|
|
163
163
|
.map(command => autocompleteCommand(command));
|
|
164
|
-
const
|
|
165
|
-
|
|
164
|
+
const catalog = [...builtIns, ...resources];
|
|
165
|
+
const combined = new CombinedAutocompleteProvider(catalog, options.cwd ?? process.cwd());
|
|
166
|
+
const tunnelAware = tunnelSkills.length === 0 ? combined : createSkillsTunnelProvider(combined, tunnelSkills);
|
|
167
|
+
autocompleteProvider = options.keybindingProfile === "a1"
|
|
168
|
+
? createDelimiterReadyCommandProvider(tunnelAware, catalog)
|
|
169
|
+
: tunnelAware;
|
|
166
170
|
editor.setAutocompleteProvider(autocompleteProvider);
|
|
167
171
|
};
|
|
168
172
|
setAutocompleteCommands(options.autocompleteCommands ?? []);
|
|
@@ -345,6 +349,40 @@ export function createPiShellEditor(options) {
|
|
|
345
349
|
cancelPendingPastes: () => editorUx?.cancelPendingPastes(),
|
|
346
350
|
};
|
|
347
351
|
}
|
|
352
|
+
/** Bare A1 leaves an applied catalog command ready for `:`, a user-entered space, or submission. */
|
|
353
|
+
function createDelimiterReadyCommandProvider(base, commands) {
|
|
354
|
+
const commandNames = new Set(commands.map(command => command.name));
|
|
355
|
+
return {
|
|
356
|
+
...(base.triggerCharacters === undefined ? {} : { triggerCharacters: base.triggerCharacters }),
|
|
357
|
+
getSuggestions: (lines, cursorLine, cursorCol, options) => base.getSuggestions(lines, cursorLine, cursorCol, options),
|
|
358
|
+
applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
|
|
359
|
+
const result = base.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
360
|
+
const currentLine = lines[cursorLine] ?? "";
|
|
361
|
+
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
362
|
+
const beforePrefix = currentLine.slice(0, cursorCol - prefix.length);
|
|
363
|
+
const completedLine = result.lines[result.cursorLine] ?? "";
|
|
364
|
+
const spacerIndex = result.cursorCol - 1;
|
|
365
|
+
const expectedCommand = `${beforePrefix}/${item.value}`;
|
|
366
|
+
const isCatalogCommand = commandNames.has(item.value)
|
|
367
|
+
&& prefix.startsWith("/")
|
|
368
|
+
&& !prefix.slice(1).includes("/")
|
|
369
|
+
&& !prefix.includes(" ")
|
|
370
|
+
&& textBeforeCursor.trimStart() === prefix;
|
|
371
|
+
if (!isCatalogCommand
|
|
372
|
+
|| result.cursorLine !== cursorLine
|
|
373
|
+
|| spacerIndex < 0
|
|
374
|
+
|| completedLine[spacerIndex] !== " "
|
|
375
|
+
|| completedLine.slice(0, spacerIndex) !== expectedCommand)
|
|
376
|
+
return result;
|
|
377
|
+
const nextLines = [...result.lines];
|
|
378
|
+
nextLines[result.cursorLine] = completedLine.slice(0, spacerIndex) + completedLine.slice(spacerIndex + 1);
|
|
379
|
+
return { ...result, lines: nextLines, cursorCol: spacerIndex };
|
|
380
|
+
},
|
|
381
|
+
...(base.shouldTriggerFileCompletion === undefined ? {} : {
|
|
382
|
+
shouldTriggerFileCompletion: (lines, cursorLine, cursorCol) => base.shouldTriggerFileCompletion(lines, cursorLine, cursorCol),
|
|
383
|
+
}),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
348
386
|
function autocompleteCommand(command, addition) {
|
|
349
387
|
const argumentOptions = addition?.argumentOptions ?? command.argumentOptions;
|
|
350
388
|
return {
|
|
@@ -27,8 +27,7 @@ class SkillRow {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* The A1-owned searchable Skills dialog
|
|
31
|
-
* search input, spacer, list, spacer, hint footer, border) built from public pi-tui components; its
|
|
30
|
+
* The A1-owned searchable Skills dialog uses the model selector's public pi-tui composition; its
|
|
32
31
|
* content follows the v2 skills extension: an accent bold title, bare skill-name rows, the selected
|
|
33
32
|
* skill's description cut to one line under the rows, the pinned scroll counter on overflow,
|
|
34
33
|
* and the two empty states. Enter applies the selected skill with no arguments; the query is never appended.
|
|
@@ -57,6 +56,7 @@ class SkillsSelectorComponent extends Container {
|
|
|
57
56
|
this.addChild(new Spacer(1));
|
|
58
57
|
const bindings = getKeybindings();
|
|
59
58
|
this.addChild(new Text(renderPiModalShortcutHints([
|
|
59
|
+
{ key: "Type", action: "search" },
|
|
60
60
|
{ key: "↑↓", action: "navigate" },
|
|
61
61
|
{ key: bindings.getKeys("tui.select.confirm").join("/"), action: "select" },
|
|
62
62
|
DIALOG_CLOSE_SHORTCUT_HINT,
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* private upstream keybinding constructor dependency; bare A1 injects the shared Settings/agent input
|
|
6
6
|
* frame, transient contextual-suggestion branch, and explicit body geometry for selection and
|
|
7
7
|
* above-prompt autocomplete. Bare A1 also clears a sole top-level slash-command search on Escape. Bare
|
|
8
|
-
* A1 also completes a selected tunnel command with `:` and reopens the menu on its
|
|
8
|
+
* A1 also completes a selected or exact completed tunnel command with `:` and reopens the menu on its
|
|
9
|
+
* tunnel rows, and synchronously retains then refreshes an exact top-level command menu after Tab
|
|
10
|
+
* applies its row.
|
|
9
11
|
* Deviations: owned-shared-input-frame, above-prompt-autocomplete-placement,
|
|
10
|
-
* clear-command-search-on-escape, command-tunnel-colon-completion.
|
|
12
|
+
* clear-command-search-on-escape, command-tunnel-colon-completion, keep-command-menu-open-after-tab.
|
|
11
13
|
*/
|
|
12
14
|
import { type EditorOptions, type EditorTheme, type TUI } from "@earendil-works/pi-tui";
|
|
13
15
|
import type { AppKeybinding, KeybindingsManager } from "../adjacent/core/keybindings.js";
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* private upstream keybinding constructor dependency; bare A1 injects the shared Settings/agent input
|
|
6
6
|
* frame, transient contextual-suggestion branch, and explicit body geometry for selection and
|
|
7
7
|
* above-prompt autocomplete. Bare A1 also clears a sole top-level slash-command search on Escape. Bare
|
|
8
|
-
* A1 also completes a selected tunnel command with `:` and reopens the menu on its
|
|
8
|
+
* A1 also completes a selected or exact completed tunnel command with `:` and reopens the menu on its
|
|
9
|
+
* tunnel rows, and synchronously retains then refreshes an exact top-level command menu after Tab
|
|
10
|
+
* applies its row.
|
|
9
11
|
* Deviations: owned-shared-input-frame, above-prompt-autocomplete-placement,
|
|
10
|
-
* clear-command-search-on-escape, command-tunnel-colon-completion.
|
|
12
|
+
* clear-command-search-on-escape, command-tunnel-colon-completion, keep-command-menu-open-after-tab.
|
|
11
13
|
*/
|
|
12
14
|
import { CURSOR_MARKER, Editor, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
13
15
|
const PROMPT_GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
@@ -78,7 +80,10 @@ export function createOwnedEditorClass(Base) {
|
|
|
78
80
|
handleInput(data) {
|
|
79
81
|
if (this.onExtensionShortcut?.(data))
|
|
80
82
|
return;
|
|
81
|
-
|
|
83
|
+
const tabbedCommand = this.keybindings.matches(data, "tui.input.tab") && this.isTopLevelCommandSearch()
|
|
84
|
+
? retainedAutocomplete(this)
|
|
85
|
+
: undefined;
|
|
86
|
+
if (data === ":" && this.#completeCommandTunnel())
|
|
82
87
|
return;
|
|
83
88
|
if (this.#promptSuggestion !== null
|
|
84
89
|
&& !this.isShowingAutocomplete()
|
|
@@ -133,19 +138,41 @@ export function createOwnedEditorClass(Base) {
|
|
|
133
138
|
}
|
|
134
139
|
}
|
|
135
140
|
super.handleInput(data);
|
|
141
|
+
if (tabbedCommand !== undefined)
|
|
142
|
+
this.#retainCompletedCommandMenu(tabbedCommand);
|
|
143
|
+
}
|
|
144
|
+
/** Keep the current menu visible while refreshing it for the exact completed command. */
|
|
145
|
+
#retainCompletedCommandMenu(completion) {
|
|
146
|
+
const text = this.getText();
|
|
147
|
+
const cursor = this.getCursor();
|
|
148
|
+
if (text !== `/${completion.value}` || cursor.line !== 0 || cursor.col !== text.length)
|
|
149
|
+
return;
|
|
150
|
+
Reflect.set(this, "autocompleteList", completion.list);
|
|
151
|
+
Reflect.set(this, "autocompleteState", completion.state);
|
|
152
|
+
Reflect.set(this, "autocompletePrefix", text);
|
|
153
|
+
triggerAutocomplete(this);
|
|
136
154
|
}
|
|
137
155
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
156
|
+
* Complete either a selected tunnel row or an exact command left by Tab to `/<command>:` and
|
|
157
|
+
* reopen the menu on the tunnel rows. Public setText records the undo snapshot; every other colon
|
|
158
|
+
* stays ordinary text.
|
|
141
159
|
*/
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
160
|
+
#completeCommandTunnel() {
|
|
161
|
+
const tunnels = this.#commandTunnels();
|
|
162
|
+
let command;
|
|
163
|
+
if (this.isTopLevelCommandSearch()) {
|
|
164
|
+
const selected = selectedAutocompleteValue(this);
|
|
165
|
+
if (selected !== undefined && tunnels.includes(selected))
|
|
166
|
+
command = selected;
|
|
167
|
+
}
|
|
168
|
+
else if (!this.isShowingAutocomplete()) {
|
|
169
|
+
const text = this.getText();
|
|
170
|
+
const cursor = this.getCursor();
|
|
171
|
+
command = tunnels.find(candidate => text === `/${candidate}` && cursor.line === 0 && cursor.col === text.length);
|
|
172
|
+
}
|
|
173
|
+
if (command === undefined)
|
|
147
174
|
return false;
|
|
148
|
-
this.setText(`/${
|
|
175
|
+
this.setText(`/${command}:`);
|
|
149
176
|
triggerAutocomplete(this);
|
|
150
177
|
return true;
|
|
151
178
|
}
|
|
@@ -200,6 +227,15 @@ export function createOwnedEditorClass(Base) {
|
|
|
200
227
|
}
|
|
201
228
|
export class OwnedEditor extends createOwnedEditorClass(Editor) {
|
|
202
229
|
}
|
|
230
|
+
/** Capture the active selected command list so exact Tab application can retain it synchronously. */
|
|
231
|
+
function retainedAutocomplete(editor) {
|
|
232
|
+
const value = selectedAutocompleteValue(editor);
|
|
233
|
+
const list = Reflect.get(editor, "autocompleteList");
|
|
234
|
+
const state = Reflect.get(editor, "autocompleteState");
|
|
235
|
+
if (value === undefined || typeof list !== "object" || list === null || (state !== "regular" && state !== "force"))
|
|
236
|
+
return undefined;
|
|
237
|
+
return { value, list, state };
|
|
238
|
+
}
|
|
203
239
|
/** The value of the row the open autocomplete list highlights, read from either editor's list. */
|
|
204
240
|
function selectedAutocompleteValue(editor) {
|
|
205
241
|
const list = Reflect.get(editor, "autocompleteList");
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* progressive partial results, cancellation, focus, and disposal while remapping public helpers, owned
|
|
7
7
|
* keybindings/theme, canonical path handling, reverse-Tab scope cycling, and the shared bare-A1 modal
|
|
8
8
|
* frame with standalone title, standard outer rules, stable filter/status row, progressive result
|
|
9
|
-
* paging, aligned result columns, Session Tree selection roles,
|
|
10
|
-
*
|
|
11
|
-
* muted metadata, and ordinary text
|
|
9
|
+
* paging, aligned result columns, Session Tree selection roles, quiet in-field query syntax guidance,
|
|
10
|
+
* and one-line bottom dynamic feedback and searchable-dialog shortcut footer. Selected session rows
|
|
11
|
+
* use the blue selection surface, success-green primary title, muted metadata, and ordinary text
|
|
12
|
+
* weight.
|
|
12
13
|
* Deviations: owned-modal-shortcut-hints, owned-resume-session-dialog,
|
|
13
14
|
* owned-standard-dialog-selection.
|
|
14
15
|
*/
|
|
@@ -183,23 +184,19 @@ class SessionSelectorHeader {
|
|
|
183
184
|
return [withCloseHint(theme.fg(color, this.statusMessage.message), width)];
|
|
184
185
|
}
|
|
185
186
|
const pathState = this.showPath ? "(on)" : "(off)";
|
|
186
|
-
const
|
|
187
|
+
const hints = [
|
|
188
|
+
{ key: "Type", action: "search" },
|
|
189
|
+
{ key: "↑↓", action: "navigate" },
|
|
190
|
+
shortcutHint("tui.select.confirm", "select", this.keybindings),
|
|
187
191
|
shortcutHint("tui.input.tab", "scope", this.keybindings),
|
|
188
|
-
{ action: "re:<pattern> regex" },
|
|
189
|
-
{ action: '"phrase" exact' },
|
|
190
|
-
]);
|
|
191
|
-
const hint2Parts = [
|
|
192
192
|
shortcutHint("app.session.toggleSort", "sort", this.keybindings),
|
|
193
193
|
shortcutHint("app.session.toggleNamedFilter", "named", this.keybindings),
|
|
194
194
|
shortcutHint("app.session.delete", "delete", this.keybindings),
|
|
195
195
|
shortcutHint("app.session.togglePath", `path ${pathState}`, this.keybindings),
|
|
196
196
|
];
|
|
197
197
|
if (this.showRenameHint)
|
|
198
|
-
|
|
199
|
-
return [
|
|
200
|
-
truncateToWidth(hint1, width, "…"),
|
|
201
|
-
withCloseHint(renderPiModalShortcutHints(hint2Parts), width),
|
|
202
|
-
];
|
|
198
|
+
hints.push(shortcutHint("app.session.rename", "rename", this.keybindings));
|
|
199
|
+
return [withCloseHint(renderPiModalShortcutHints(hints), width)];
|
|
203
200
|
}
|
|
204
201
|
}
|
|
205
202
|
class SessionSelectorFooter {
|
|
@@ -320,7 +317,10 @@ class SessionList {
|
|
|
320
317
|
constructor(sessions, showCwd, sortMode, nameFilter, keybindings, currentSessionFilePath) {
|
|
321
318
|
this.allSessions = sessions;
|
|
322
319
|
this.filteredSessions = [];
|
|
323
|
-
this.searchInput = new Input(
|
|
320
|
+
this.searchInput = new Input({
|
|
321
|
+
placeholder: 're:<pattern> regex, "phrase" exact',
|
|
322
|
+
placeholderStyle: text => text === "r" ? text : `\u001b[2m${text}\u001b[22m`,
|
|
323
|
+
});
|
|
324
324
|
this.showCwd = showCwd;
|
|
325
325
|
this.sortMode = sortMode;
|
|
326
326
|
this.nameFilter = nameFilter;
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* levels, and rendering exclusive radio-style default markers before level names, an item-adjacent
|
|
9
9
|
* active checkmark, and aligned muted descriptions. Bare A1 persists defaults immediately on Space,
|
|
10
10
|
* closes through the implicit Ctrl+C-capable selection-cancel action, and uses the shared compact
|
|
11
|
-
* semantic shortcut row without advertising that alias. All list and border
|
|
12
|
-
* and its explicit color mode. Selected rows use the shared bare-A1 blue
|
|
13
|
-
* the rendered item span, with the existing accent arrow, normal-text
|
|
14
|
-
* current/default/description roles. The comparison profile retains the public
|
|
11
|
+
* semantic shortcut row with Type/search guidance without advertising that alias. All list and border
|
|
12
|
+
* colors use the owned theme and its explicit color mode. Selected rows use the shared bare-A1 blue
|
|
13
|
+
* selection surface only around the rendered item span, with the existing accent arrow, normal-text
|
|
14
|
+
* level, and preserved current/default/description roles. The comparison profile retains the public
|
|
15
|
+
* pinned component.
|
|
15
16
|
* Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading,
|
|
16
17
|
* owned-thinking-selector-controls, owned-dialog-ctrl-c-cancel, owned-standard-dialog-selection.
|
|
17
18
|
*/
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* levels, and rendering exclusive radio-style default markers before level names, an item-adjacent
|
|
9
9
|
* active checkmark, and aligned muted descriptions. Bare A1 persists defaults immediately on Space,
|
|
10
10
|
* closes through the implicit Ctrl+C-capable selection-cancel action, and uses the shared compact
|
|
11
|
-
* semantic shortcut row without advertising that alias. All list and border
|
|
12
|
-
* and its explicit color mode. Selected rows use the shared bare-A1 blue
|
|
13
|
-
* the rendered item span, with the existing accent arrow, normal-text
|
|
14
|
-
* current/default/description roles. The comparison profile retains the public
|
|
11
|
+
* semantic shortcut row with Type/search guidance without advertising that alias. All list and border
|
|
12
|
+
* colors use the owned theme and its explicit color mode. Selected rows use the shared bare-A1 blue
|
|
13
|
+
* selection surface only around the rendered item span, with the existing accent arrow, normal-text
|
|
14
|
+
* level, and preserved current/default/description roles. The comparison profile retains the public
|
|
15
|
+
* pinned component.
|
|
15
16
|
* Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading,
|
|
16
17
|
* owned-thinking-selector-controls, owned-dialog-ctrl-c-cancel, owned-standard-dialog-selection.
|
|
17
18
|
*/
|
|
@@ -96,6 +97,7 @@ export class OwnedThinkingSelectorComponent extends Container {
|
|
|
96
97
|
this.addChild(this.selectListContainer);
|
|
97
98
|
this.addChild(new Spacer(1));
|
|
98
99
|
this.addChild(new Text(renderPiModalShortcutHints([
|
|
100
|
+
{ key: "Type", action: "search" },
|
|
99
101
|
{ key: this.keyDisplayText("tui.select.confirm"), action: "select" },
|
|
100
102
|
{ key: "Space", action: "default" },
|
|
101
103
|
DIALOG_CLOSE_SHORTCUT_HINT,
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* Models-style filter status with Tab/Shift+Tab directional cycling and concise all-first presentation
|
|
7
7
|
* excluding internal bookkeeping, standard search input, blue item-bounded menu-arrow selection that
|
|
8
8
|
* preserves per-entry semantic foregrounds and has no path bullets, accent entry labels, bracketed
|
|
9
|
-
* timestamps,
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* timestamps, numeric result counters without duplicate label-time status, and a stateful time on/off
|
|
10
|
+
* shortcut, semantic role colors with session naming for the system root, standard
|
|
11
|
+
* paging/first-last/non-root containing-branch folding keys with a permanently expanded system session
|
|
12
|
+
* entry even behind hidden metadata, single-character ellipses on both clipped edges with
|
|
13
|
+
* bracket-delimiter preservation and selected-fragment highlighting, and Models-ordered semantic
|
|
14
|
+
* Type/search shortcut footers without a redundant select hint.
|
|
14
15
|
* Deviations: owned-modal-shortcut-hints, owned-session-tree-dialog.
|
|
15
16
|
*/
|
|
16
17
|
import { type Component, Container, type Focusable } from "@earendil-works/pi-tui";
|
|
@@ -57,10 +58,10 @@ declare class TreeList implements Component {
|
|
|
57
58
|
invalidate(): void;
|
|
58
59
|
getSearchQuery(): string;
|
|
59
60
|
getFilterMode(): FilterMode;
|
|
61
|
+
isLabelTimestampVisible(): boolean;
|
|
60
62
|
getSelectedNode(): SessionTreeNode | undefined;
|
|
61
63
|
copySelected(): void;
|
|
62
64
|
updateNodeLabel(entryId: string, label: string | undefined, labelTimestamp?: string): void;
|
|
63
|
-
private getStatusLabels;
|
|
64
65
|
render(width: number): string[];
|
|
65
66
|
private getEntryDisplayText;
|
|
66
67
|
private formatLabelTimestamp;
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* Models-style filter status with Tab/Shift+Tab directional cycling and concise all-first presentation
|
|
7
7
|
* excluding internal bookkeeping, standard search input, blue item-bounded menu-arrow selection that
|
|
8
8
|
* preserves per-entry semantic foregrounds and has no path bullets, accent entry labels, bracketed
|
|
9
|
-
* timestamps,
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* timestamps, numeric result counters without duplicate label-time status, and a stateful time on/off
|
|
10
|
+
* shortcut, semantic role colors with session naming for the system root, standard
|
|
11
|
+
* paging/first-last/non-root containing-branch folding keys with a permanently expanded system session
|
|
12
|
+
* entry even behind hidden metadata, single-character ellipses on both clipped edges with
|
|
13
|
+
* bracket-delimiter preservation and selected-fragment highlighting, and Models-ordered semantic
|
|
14
|
+
* Type/search shortcut footers without a redundant select hint.
|
|
14
15
|
* Deviations: owned-modal-shortcut-hints, owned-session-tree-dialog.
|
|
15
16
|
*/
|
|
16
17
|
import { Container, Input, matchesKey, Spacer, sliceByColumn, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
@@ -528,6 +529,9 @@ class TreeList {
|
|
|
528
529
|
getFilterMode() {
|
|
529
530
|
return this.filterMode;
|
|
530
531
|
}
|
|
532
|
+
isLabelTimestampVisible() {
|
|
533
|
+
return this.showLabelTimestamps;
|
|
534
|
+
}
|
|
531
535
|
getSelectedNode() {
|
|
532
536
|
return this.filteredNodes[this.selectedIndex]?.node;
|
|
533
537
|
}
|
|
@@ -550,16 +554,10 @@ class TreeList {
|
|
|
550
554
|
}
|
|
551
555
|
}
|
|
552
556
|
}
|
|
553
|
-
getStatusLabels() {
|
|
554
|
-
return this.showLabelTimestamps ? " label time" : "";
|
|
555
|
-
}
|
|
556
557
|
render(width) {
|
|
557
558
|
const lines = [];
|
|
558
559
|
if (this.filteredNodes.length === 0) {
|
|
559
560
|
lines.push(truncateToWidth(theme.fg("muted", " No entries found"), width));
|
|
560
|
-
const status = this.getStatusLabels().trim();
|
|
561
|
-
if (status)
|
|
562
|
-
lines.push(truncateToWidth(theme.fg("muted", ` ${status}`), width));
|
|
563
561
|
return lines;
|
|
564
562
|
}
|
|
565
563
|
const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisibleLines / 2), this.filteredNodes.length - this.maxVisibleLines));
|
|
@@ -632,7 +630,7 @@ class TreeList {
|
|
|
632
630
|
renderedRows.push({ gutter, body, anchorCol, bodyWidth: visibleWidth(body), rightClipSuffix, isSelected });
|
|
633
631
|
}
|
|
634
632
|
lines.push(...renderHorizontalViewport(renderedRows, width));
|
|
635
|
-
lines.push(truncateToWidth(theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filteredNodes.length})
|
|
633
|
+
lines.push(truncateToWidth(theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filteredNodes.length})`), width));
|
|
636
634
|
return lines;
|
|
637
635
|
}
|
|
638
636
|
getEntryDisplayText(node) {
|
|
@@ -1096,12 +1094,19 @@ class TreeFilter {
|
|
|
1096
1094
|
}
|
|
1097
1095
|
/** Component that renders tree help as semantic rows with chunk-aware wrapping */
|
|
1098
1096
|
class TreeHelp {
|
|
1097
|
+
treeList;
|
|
1098
|
+
constructor(treeList) {
|
|
1099
|
+
this.treeList = treeList;
|
|
1100
|
+
}
|
|
1099
1101
|
invalidate() { }
|
|
1100
1102
|
render(width) {
|
|
1101
1103
|
const items = [
|
|
1102
|
-
...TREE_HELP_ITEMS.map(({ keys, label, labelFirst }) => {
|
|
1103
|
-
const key = formatHelpKeys(keys);
|
|
1104
|
-
|
|
1104
|
+
...TREE_HELP_ITEMS.map(({ keys, label, labelFirst, displayKey }) => {
|
|
1105
|
+
const key = displayKey ?? formatHelpKeys(keys);
|
|
1106
|
+
const action = keys.includes("app.tree.toggleLabelTimestamp")
|
|
1107
|
+
? `time (${this.treeList.isLabelTimestampVisible() ? "on" : "off"})`
|
|
1108
|
+
: label;
|
|
1109
|
+
return renderPiModalShortcutHints([key ? { key, action, ...(labelFirst === undefined ? {} : { actionFirst: labelFirst }) } : { action }]);
|
|
1105
1110
|
}),
|
|
1106
1111
|
renderPiModalShortcutHints([DIALOG_CLOSE_SHORTCUT_HINT]),
|
|
1107
1112
|
];
|
|
@@ -1131,7 +1136,7 @@ class TreeHelp {
|
|
|
1131
1136
|
}
|
|
1132
1137
|
}
|
|
1133
1138
|
const TREE_HELP_ITEMS = [
|
|
1134
|
-
{ keys: [],
|
|
1139
|
+
{ keys: [], displayKey: "Type", label: "search" },
|
|
1135
1140
|
{ keys: ["tui.select.up", "tui.select.down"], label: "navigate" },
|
|
1136
1141
|
{ keys: ["app.tree.filter.cycleForward"], label: "filter" },
|
|
1137
1142
|
{ keys: ["tui.select.pageUp", "tui.select.pageDown"], label: "page" },
|
|
@@ -1139,7 +1144,7 @@ const TREE_HELP_ITEMS = [
|
|
|
1139
1144
|
{ keys: ["owned.tree.collapse", "owned.tree.expand"], label: "branch" },
|
|
1140
1145
|
{ keys: ["app.message.copy"], label: "copy" },
|
|
1141
1146
|
{ keys: ["app.tree.editLabel"], label: "label" },
|
|
1142
|
-
{ keys: ["app.tree.toggleLabelTimestamp"], label: "
|
|
1147
|
+
{ keys: ["app.tree.toggleLabelTimestamp"], label: "time" },
|
|
1143
1148
|
];
|
|
1144
1149
|
function formatHelpKeys(keybindings) {
|
|
1145
1150
|
const keys = [];
|
|
@@ -1235,7 +1240,7 @@ export class TreeSelectorComponent extends Container {
|
|
|
1235
1240
|
treeContainer = new Container();
|
|
1236
1241
|
labelInputContainer = new Container();
|
|
1237
1242
|
footerContainer = new Container();
|
|
1238
|
-
treeHelp
|
|
1243
|
+
treeHelp;
|
|
1239
1244
|
labelInput = null;
|
|
1240
1245
|
onLabelChangeCallback;
|
|
1241
1246
|
onCopy;
|
|
@@ -1261,6 +1266,7 @@ export class TreeSelectorComponent extends Container {
|
|
|
1261
1266
|
this.treeList.onLabelEdit = (entryId, currentLabel) => this.showLabelInput(entryId, currentLabel);
|
|
1262
1267
|
this.searchInput = new TreeSearchInput(this.treeList);
|
|
1263
1268
|
this.treeFilter = new TreeFilter(this.treeList);
|
|
1269
|
+
this.treeHelp = new TreeHelp(this.treeList);
|
|
1264
1270
|
this.titleText = new Text(theme.fg("accent", theme.bold("Session Tree")), 0, 0);
|
|
1265
1271
|
this.restoreTreeContent();
|
|
1266
1272
|
const header = addPiModalHeader(this, new DynamicBorder(), this.titleText);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-07T15:54:44.546Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "51b0b2dc1c9ae577f2586c4217066929278726c2c15308d9144eb6f538e118ff",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-07T15:54:41.381Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "ef62679e83bc1fad3d38f9f21675d9256f08d20e67fd4791571032115ca1b0e9",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-07T15:55:19.867Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "0c34b684eab04e6d34b7feb5d1f5668f036e55da0af02b3b23516470bc9a4130",
|
|
12
12
|
"size": 288256
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LineInput } from "./line-input.js";
|
|
1
2
|
import { type ShortcutHintEntry } from "./shortcut-hints.js";
|
|
2
3
|
import type { UiTheme } from "./theme.js";
|
|
3
4
|
/**
|
|
@@ -7,11 +8,13 @@ import type { UiTheme } from "./theme.js";
|
|
|
7
8
|
*/
|
|
8
9
|
export interface DialogRow {
|
|
9
10
|
readonly label: string;
|
|
11
|
+
readonly labelSuffix?: string;
|
|
10
12
|
readonly value: string;
|
|
11
13
|
/** What this part does, shown while it is the one in hand. */
|
|
12
14
|
readonly description?: string;
|
|
13
15
|
}
|
|
14
16
|
export interface DialogPanelState {
|
|
17
|
+
readonly title: string;
|
|
15
18
|
readonly rows: readonly DialogRow[];
|
|
16
19
|
/** The row in hand. */
|
|
17
20
|
readonly index: number;
|
|
@@ -26,6 +29,16 @@ export interface DialogPanelFrame {
|
|
|
26
29
|
/** Column the values start at, so a label stays a label. */
|
|
27
30
|
readonly valueColumn: number;
|
|
28
31
|
}
|
|
32
|
+
export interface SteppedDialogPanelState {
|
|
33
|
+
readonly title: string;
|
|
34
|
+
readonly step: number;
|
|
35
|
+
readonly steps: number;
|
|
36
|
+
readonly description: string;
|
|
37
|
+
readonly input?: LineInput;
|
|
38
|
+
readonly rows: readonly DialogRow[];
|
|
39
|
+
readonly index: number;
|
|
40
|
+
readonly hint: readonly ShortcutHintEntry[];
|
|
41
|
+
}
|
|
29
42
|
/** The column values start at: past the widest label, capped so one long name cannot push them off. */
|
|
30
43
|
export declare function dialogValueColumn(rows: readonly DialogRow[]): number;
|
|
31
44
|
/**
|
|
@@ -33,5 +46,7 @@ export declare function dialogValueColumn(rows: readonly DialogRow[]): number;
|
|
|
33
46
|
* one line below the panel's top.
|
|
34
47
|
*/
|
|
35
48
|
export declare function renderDialogPanel(state: DialogPanelState, width: number, theme: UiTheme): readonly string[];
|
|
49
|
+
/** Owned stepped-selector composition for structured settings with nested choices. */
|
|
50
|
+
export declare function renderSteppedDialogPanel(state: SteppedDialogPanelState, width: number, theme: UiTheme): readonly string[];
|
|
36
51
|
/** The row a pointer report lands on, or null when it is not on one. */
|
|
37
52
|
export declare function dialogRowAt(frame: DialogPanelFrame, row: number, column: number, valueWidth: number): number | null;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LineInput, renderInputRow } from "./line-input.js";
|
|
2
|
+
import { renderShortcutHints, renderShortcutHintsWithClose, shortcutHintsText } from "./shortcut-hints.js";
|
|
2
3
|
import { displayWidth, truncateToWidth } from "./text.js";
|
|
3
4
|
const LABEL_COLUMN_CAP = 30;
|
|
5
|
+
const STEPPED_LABEL_COLUMN_CAP = 46;
|
|
4
6
|
/** The column values start at: past the widest label, capped so one long name cannot push them off. */
|
|
5
7
|
export function dialogValueColumn(rows) {
|
|
6
8
|
const widest = Math.min(LABEL_COLUMN_CAP, Math.max(0, ...rows.map(row => displayWidth(row.label))));
|
|
@@ -35,14 +37,59 @@ export function renderDialogPanel(state, width, theme) {
|
|
|
35
37
|
const plainHint = typeof state.hint === "string" ? state.hint : " ".repeat(displayWidth(hint));
|
|
36
38
|
return [
|
|
37
39
|
rule,
|
|
38
|
-
|
|
40
|
+
contentRow(theme.bold(theme.fg("accent", state.title)), state.title),
|
|
41
|
+
contentRow(theme.fg("dim", description), description),
|
|
39
42
|
"",
|
|
40
|
-
|
|
43
|
+
...rows,
|
|
41
44
|
"",
|
|
42
45
|
contentRow(hint, plainHint),
|
|
43
46
|
rule,
|
|
44
47
|
];
|
|
45
48
|
}
|
|
49
|
+
/** Owned stepped-selector composition for structured settings with nested choices. */
|
|
50
|
+
export function renderSteppedDialogPanel(state, width, theme) {
|
|
51
|
+
const rule = theme.fg("border", "─".repeat(Math.max(0, width)));
|
|
52
|
+
const contentPadding = Math.min(1, Math.max(0, width));
|
|
53
|
+
const contentWidth = Math.max(0, width - contentPadding);
|
|
54
|
+
const inset = " ".repeat(contentPadding);
|
|
55
|
+
const contentRow = (painted, raw) => `${inset}${pad(truncateToWidth(painted, contentWidth), contentWidth, raw)}`;
|
|
56
|
+
const labelColumn = Math.min(STEPPED_LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(`${row.label}${row.labelSuffix ?? ""}`))));
|
|
57
|
+
const rows = state.rows.map((row, index) => {
|
|
58
|
+
const selected = index === state.index;
|
|
59
|
+
const completeLabel = `${row.label}${row.labelSuffix ?? ""}`;
|
|
60
|
+
const padding = " ".repeat(Math.max(0, labelColumn - displayWidth(completeLabel)));
|
|
61
|
+
const suffix = row.value.length === 0 ? "" : ` ${row.value}`;
|
|
62
|
+
const cursor = selected ? "→ " : " ";
|
|
63
|
+
const raw = `${cursor}${completeLabel}${padding}${suffix}`;
|
|
64
|
+
const label = `${theme.fg("text", row.label)}${theme.fg("muted", row.labelSuffix ?? "")}${padding}`;
|
|
65
|
+
const content = selected
|
|
66
|
+
? `${theme.fg("accent", cursor)}${label}${theme.fg("muted", suffix)}`
|
|
67
|
+
: `${cursor}${label}${theme.fg("muted", suffix)}`;
|
|
68
|
+
return contentRow(selected ? theme.highlight(content) : content, raw);
|
|
69
|
+
});
|
|
70
|
+
const input = state.input === undefined
|
|
71
|
+
? []
|
|
72
|
+
: [...renderInputRow(state.input, width, { ruled: false, promptGlyph: "> ", theme }).lines, ""];
|
|
73
|
+
const step = `(step ${state.step}/${state.steps})`;
|
|
74
|
+
const title = `${state.title} ${step}`;
|
|
75
|
+
const paintedTitle = `${theme.bold(theme.fg("accent", state.title))} ${theme.fg("dim", step)}`;
|
|
76
|
+
return [
|
|
77
|
+
rule,
|
|
78
|
+
contentRow(paintedTitle, title),
|
|
79
|
+
contentRow(theme.fg("dim", state.description), state.description),
|
|
80
|
+
"",
|
|
81
|
+
...input,
|
|
82
|
+
...rows,
|
|
83
|
+
"",
|
|
84
|
+
renderSteppedHint(state.hint, width, theme),
|
|
85
|
+
rule,
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
function renderSteppedHint(entries, width, theme) {
|
|
89
|
+
const raw = shortcutHintsText(entries, 1, " · ");
|
|
90
|
+
const painted = renderShortcutHints(entries, theme, 1, theme.fg("dim", " · "));
|
|
91
|
+
return `${truncateToWidth(painted, width)}${" ".repeat(Math.max(0, width - displayWidth(raw)))}`;
|
|
92
|
+
}
|
|
46
93
|
/** The row a pointer report lands on, or null when it is not on one. */
|
|
47
94
|
export function dialogRowAt(frame, row, column, valueWidth) {
|
|
48
95
|
const at = row - frame.firstRow;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { dialogRowAt, dialogValueColumn, renderDialogPanel } from "./dialog-panel.js";
|
|
2
|
-
export type { DialogPanelFrame, DialogPanelState, DialogRow } from "./dialog-panel.js";
|
|
1
|
+
export { dialogRowAt, dialogValueColumn, renderDialogPanel, renderSteppedDialogPanel } from "./dialog-panel.js";
|
|
2
|
+
export type { DialogPanelFrame, DialogPanelState, DialogRow, SteppedDialogPanelState } from "./dialog-panel.js";
|
|
3
3
|
export { FrameContractError, assertPaneRect, finalizeFrame, validateFrame } from "./frame.js";
|
|
4
4
|
export type { PaneRect } from "./frame.js";
|
|
5
5
|
export { humanizeLabel, humanizeTitle } from "./label.js";
|
|
@@ -23,7 +23,7 @@ export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selecti
|
|
|
23
23
|
export type { SelectionCopyPrompt, SelectionCopyRow, SelectionCopySnapshot } from "./selection-copy.js";
|
|
24
24
|
export { ScrollbarRails, isThumbRow, scrollForThumbRow, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarReservesSpace, scrollbarSelectionRows, scrollbarWheelRows, } from "./scrollbar.js";
|
|
25
25
|
export type { RailPointer, RailPosition, ScrollbarAppearance, ScrollbarGeometry, ScrollbarInput, ScrollbarPresentation, ScrollbarPresentationInput, ScrollbarSpeed, ScrollbarStyle, } from "./scrollbar.js";
|
|
26
|
-
export { renderShortcutHints, renderShortcutHintsWithClose } from "./shortcut-hints.js";
|
|
26
|
+
export { assertOwnedShortcutHintConventions, renderShortcutHints, renderShortcutHintsWithClose, shortcutHintsText } from "./shortcut-hints.js";
|
|
27
27
|
export type { ShortcutHintEntry } from "./shortcut-hints.js";
|
|
28
28
|
export { GLOBAL_SCOPE, ShortcutRegistry, assembleShortcuts, assertNoShortcutConflicts } from "./shortcuts.js";
|
|
29
29
|
export type { ShortcutConflict, ShortcutDeclaration, ShortcutRegistryResult } from "./shortcuts.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { dialogRowAt, dialogValueColumn, renderDialogPanel } from "./dialog-panel.js";
|
|
1
|
+
export { dialogRowAt, dialogValueColumn, renderDialogPanel, renderSteppedDialogPanel } from "./dialog-panel.js";
|
|
2
2
|
export { FrameContractError, assertPaneRect, finalizeFrame, validateFrame } from "./frame.js";
|
|
3
3
|
export { humanizeLabel, humanizeTitle } from "./label.js";
|
|
4
4
|
export { LineInput, caretCell, handleLineInputKey, renderInputRow, wordLeft, wordRight } from "./line-input.js";
|
|
@@ -11,7 +11,7 @@ export { progressStatusFrame, progressStatusText } from "./progress-status.js";
|
|
|
11
11
|
export { FrameCache, RENDER_REVISION_KINDS, RenderRevisionTracker, ZERO_REVISIONS, normalizeRevisions, revisionsEqual } from "./revision.js";
|
|
12
12
|
export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
|
|
13
13
|
export { ScrollbarRails, isThumbRow, scrollForThumbRow, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarReservesSpace, scrollbarSelectionRows, scrollbarWheelRows, } from "./scrollbar.js";
|
|
14
|
-
export { renderShortcutHints, renderShortcutHintsWithClose } from "./shortcut-hints.js";
|
|
14
|
+
export { assertOwnedShortcutHintConventions, renderShortcutHints, renderShortcutHintsWithClose, shortcutHintsText } from "./shortcut-hints.js";
|
|
15
15
|
export { GLOBAL_SCOPE, ShortcutRegistry, assembleShortcuts, assertNoShortcutConflicts } from "./shortcuts.js";
|
|
16
16
|
export { backgroundSgrSpan, heldNativeHyperlinkStyle, hyperlinkSgrSpan, hyperlinkTargetAtColumn, nativeHyperlinkStyle, overlaySpan } from "./spans.js";
|
|
17
17
|
export { readVisibleHyperlinks } from "./visible-hyperlinks.js";
|
|
@@ -58,6 +58,8 @@ export interface InputRowOptions {
|
|
|
58
58
|
readonly placeholder?: string;
|
|
59
59
|
/** Rules above and below, in the prompt's own grey. Default true. */
|
|
60
60
|
readonly ruled?: boolean;
|
|
61
|
+
/** Prompt marker including any following spacing. Defaults to the shared prompt glyph. */
|
|
62
|
+
readonly promptGlyph?: string;
|
|
61
63
|
readonly theme?: Pick<UiTheme, "fg">;
|
|
62
64
|
}
|
|
63
65
|
export interface InputRow {
|