@timurproko/a1 0.2.5-dev.683 → 0.2.5-dev.698

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.
Files changed (62) hide show
  1. package/dist/app/session-shell/prompt-suggestion-controller.d.ts +2 -0
  2. package/dist/app/session-shell/prompt-suggestion-controller.js +10 -0
  3. package/dist/app/session-shell/session-shell-root.js +15 -2
  4. package/dist/app/session-shell/session-shell.js +71 -15
  5. package/dist/app/session-shell/share-operation.d.ts +3 -0
  6. package/dist/app/session-shell/share-operation.js +11 -0
  7. package/dist/composition/settings-route-host.js +1 -1
  8. package/dist/contracts/presentation/index.d.ts +5 -0
  9. package/dist/contracts/presentation/index.js +2 -0
  10. package/dist/features/owned-ui/project-trust-dialog.js +7 -5
  11. package/dist/features/owned-ui/reference-screen-app.d.ts +2 -2
  12. package/dist/features/owned-ui/reference-screen-app.js +10 -6
  13. package/dist/features/owned-ui/settings-app.js +10 -5
  14. package/dist/integrations/pi/components/index.d.ts +3 -2
  15. package/dist/integrations/pi/components/index.js +2 -1
  16. package/dist/integrations/pi/components/modal-frame.d.ts +11 -1
  17. package/dist/integrations/pi/components/modal-frame.js +29 -1
  18. package/dist/integrations/pi/components/models-dialog.js +17 -17
  19. package/dist/integrations/pi/components/share-operation-dialog.d.ts +6 -0
  20. package/dist/integrations/pi/components/share-operation-dialog.js +40 -0
  21. package/dist/integrations/pi/components/shell-editor-autocomplete.js +15 -6
  22. package/dist/integrations/pi/components/shell-extension-ui.d.ts +3 -0
  23. package/dist/integrations/pi/components/shell-extension-ui.js +8 -4
  24. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +0 -4
  25. package/dist/integrations/pi/components/shell-selectors-dialogs.js +56 -34
  26. package/dist/integrations/pi/components/shell-session-info-reference.js +35 -2
  27. package/dist/integrations/pi/components/shell-shared-facade.d.ts +3 -1
  28. package/dist/integrations/pi/components/shell-shared-facade.js +13 -1
  29. package/dist/integrations/pi/components/skills-dialog.js +19 -6
  30. package/dist/integrations/pi/components/theme.d.ts +8 -1
  31. package/dist/integrations/pi/components/theme.js +28 -1
  32. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
  33. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +20 -6
  34. package/dist/integrations/pi/components/upstream/components/extension-editor.js +2 -2
  35. package/dist/integrations/pi/components/upstream/components/extension-input.js +4 -5
  36. package/dist/integrations/pi/components/upstream/components/extension-selector.js +2 -3
  37. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -2
  38. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +4 -15
  39. package/dist/integrations/pi/components/upstream/components/session-selector.d.ts +2 -0
  40. package/dist/integrations/pi/components/upstream/components/session-selector.js +136 -109
  41. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +8 -5
  42. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +63 -26
  43. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +29 -20
  44. package/dist/integrations/pi/components/upstream/components/tree-selector.js +245 -210
  45. package/dist/integrations/pi/components/upstream/components/trust-selector.js +13 -15
  46. package/dist/integrations/pi/engine/adapter.js +21 -2
  47. package/dist/integrations/pi/engine/workflow-runner.js +2 -1
  48. package/dist/native/darwin-arm64/manifest.json +1 -1
  49. package/dist/native/linux-x64/manifest.json +1 -1
  50. package/dist/native/win32-x64/manifest.json +2 -2
  51. package/dist/native/win32-x64/process-guardian.exe +0 -0
  52. package/dist/ui/apps/host.d.ts +4 -3
  53. package/dist/ui/apps/host.js +9 -4
  54. package/dist/ui/components/dialog-panel.js +6 -7
  55. package/dist/ui/components/index.d.ts +1 -1
  56. package/dist/ui/components/index.js +1 -1
  57. package/dist/ui/components/list-view.d.ts +3 -3
  58. package/dist/ui/components/list-view.js +8 -9
  59. package/dist/ui/components/shortcut-hints.d.ts +2 -0
  60. package/dist/ui/components/shortcut-hints.js +17 -1
  61. package/dist/ui/components/theme.d.ts +1 -1
  62. package/package.json +1 -1
@@ -31,6 +31,8 @@ export declare class ContextualPromptSuggestionController {
31
31
  invalidate(): void;
32
32
  /** Retires only a generating request; a prepared or available suggestion survives the draft edit. */
33
33
  abortPending(): void;
34
+ /** Reasserts retained ghost text after an editor mutation makes the prompt eligible again. */
35
+ restoreAvailable(): void;
34
36
  dispose(): void;
35
37
  }
36
38
  export declare function samePromptSuggestionIdentity(left: OwnedUiPromptSuggestionIdentity, right: OwnedUiPromptSuggestionIdentity): boolean;
@@ -100,6 +100,16 @@ export class ContextualPromptSuggestionController {
100
100
  this.#finish(request, "cancelled");
101
101
  request.abort.abort();
102
102
  }
103
+ /** Reasserts retained ghost text after an editor mutation makes the prompt eligible again. */
104
+ restoreAvailable() {
105
+ if (this.#disposed || !this.#enabled || this.#state.status !== "available")
106
+ return;
107
+ const reason = this.options.surface.presentationBlockReason?.(this.#state.identity)
108
+ ?? (this.options.surface.canPresent(this.#state.identity) ? null : "presentation-unavailable");
109
+ if (reason !== null || !this.options.surface.present(this.#state.text))
110
+ return;
111
+ this.options.surface.requestRender();
112
+ }
103
113
  dispose() {
104
114
  this.#disposed = true;
105
115
  this.invalidate();
@@ -9,7 +9,7 @@ import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES } from
9
9
  import { createPiExtensionUiBridge } from "../../integrations/pi/components/shell-extension-ui.js";
10
10
  import { createPiShellEditor } from "../../integrations/pi/components/shell-editor-autocomplete.js";
11
11
  import { createPiQueuedInputStatus, createPiShellFooter, createPiShellHeader, createPiShellLoadedResources, createPiShellStatus, } from "../../integrations/pi/components/shell-footer-status.js";
12
- import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
12
+ import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
13
13
  import { createPiShellChangelog, createPiShellCollapsedChangelog, createPiShellHotkeys, createPiShellSessionInfo, renderPiShellCommandMessage, renderPiShellPackageUpdateNotice, renderPiShellReleaseUpdateBanner, renderPiShellReleaseUpdateNotice, renderPiShellStatusText, } from "../../integrations/pi/components/shell-presenters-info.js";
14
14
  import { createPiShellTranscriptComponent, isPiPromptStyleCompaction, paintPiSubmittedPromptTimestamp, renderPiShellStartupDiagnostic, renderPiShellTranscriptBlock, } from "../../integrations/pi/components/shell-presenters-transcript.js";
15
15
  import { onPiThemeChange, PINNED_PI_LAYOUT, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
@@ -333,6 +333,10 @@ export class OwnedUiSessionShellRoot {
333
333
  }
334
334
  const promptAccepted = this.#view.lifecycle !== "busy" && view.lifecycle === "busy";
335
335
  this.#view = view;
336
+ // Invariant: the idle new-session confirmation yields to the first truthful live status;
337
+ // ordinary dock notices deliberately remain visible below Working during a run.
338
+ if (promptAccepted && this.#dockNotice?.kind === "new-session")
339
+ this.#dockNotice = undefined;
336
340
  if (promptAccepted && this.#imageSubmissionsSending > 0) {
337
341
  this.#imageSubmissionsSending = 0;
338
342
  this.#syncWorkingOverride();
@@ -657,7 +661,10 @@ export class OwnedUiSessionShellRoot {
657
661
  // Compatibility: Pi pads its status text by one cell regardless of the output pad setting.
658
662
  return ["", ...renderPiShellStatusText(notice.message, width, PINNED_PI_LAYOUT.outputPad)];
659
663
  }
660
- return renderPiShellCommandMessage({ kind: notice.kind, message: notice.message }, width, this.#outputPad);
664
+ return renderPiShellCommandMessage({
665
+ kind: notice.kind === "new-session" ? "accent" : notice.kind,
666
+ message: notice.message,
667
+ }, width, this.#outputPad);
661
668
  }
662
669
  dismissNotice() {
663
670
  if (this.#dockNotice === undefined)
@@ -960,6 +967,12 @@ export class OwnedUiSessionShellRoot {
960
967
  }
961
968
  if (result.outcome === "completed" && (result.command === "quit" || result.command === "compact"))
962
969
  return;
970
+ if (result.outcome === "completed" && result.command === "new" && this.#customViewport) {
971
+ // Rationale: bare A1 keeps the replacement session visually empty while preserving Pi's accent notice shape.
972
+ this.#dockNotice = { kind: "new-session", message: result.message };
973
+ this.#invalidateChrome();
974
+ return;
975
+ }
963
976
  if (result.outcome === "completed" && (result.command === "new" || result.command === "name" || result.command === "debug")) {
964
977
  this.#lastWorkflowStatusId = undefined;
965
978
  const presentation = { kind: result.command, message: result.message, ...(result.detail === undefined ? {} : { detail: result.detail }) };
@@ -14,7 +14,7 @@ import { createPiExtensionUiBridge } from "../../integrations/pi/components/shel
14
14
  import { createPiShellEditor } from "../../integrations/pi/components/shell-editor-autocomplete.js";
15
15
  import { SKILLS_COMMAND_NAME, findSkillByArgument, rewriteSkillsTunnelSubmission, skillPrompt, skillsFromCommands, } from "../../integrations/pi/components/skills-command.js";
16
16
  import { createPiQueuedInputStatus, createPiShellFooter, createPiShellHeader, createPiShellLoadedResources, createPiShellStatus, } from "../../integrations/pi/components/shell-footer-status.js";
17
- import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellSkillsSelector, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
17
+ import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellSkillsSelector, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
18
18
  import { createPiShellChangelog, createPiShellHotkeys, createPiShellSessionInfo, renderPiShellPackageUpdateNotice, renderPiShellStatusText, } from "../../integrations/pi/components/shell-presenters-info.js";
19
19
  import { createPiShellTranscriptComponent, renderPiShellStartupDiagnostic, renderPiShellTranscriptBlock, } from "../../integrations/pi/components/shell-presenters-transcript.js";
20
20
  import { onPiThemeChange, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
@@ -188,6 +188,11 @@ export class OwnedUiSessionShell {
188
188
  onEditorChange: text => {
189
189
  this.#editorRevision++;
190
190
  promptSuggestionController?.abortPending();
191
+ if (text.length === 0) {
192
+ // Concurrency: the editor synchronizes deletion-owned autocomplete after onChange.
193
+ // Reassert retained ghost text in the microtask before Pi emits its scheduled frame.
194
+ queueMicrotask(() => promptSuggestionController?.restoreAvailable());
195
+ }
191
196
  this.root.reconcilePromptImageLimitNotice(text);
192
197
  },
193
198
  onPromptSuggestionAccepted: () => promptSuggestionController?.accept(),
@@ -358,8 +363,15 @@ export class OwnedUiSessionShell {
358
363
  ? this.runtime.addPreInputListener(data => {
359
364
  if (inputPresentation?.coordination === false)
360
365
  this.#streamPresentation.noteImmediatePresentation();
361
- // Compatibility: route boundary keys before Pi.
366
+ // Compatibility: route modal paging and boundary keys before Pi's fullscreen handlers.
362
367
  const isDefault = this.root.usesDefaultInputSurface();
368
+ if (!this.runtime.hasOverlay() && !isDefault
369
+ && (this.root.editor.matchesTerminalKey(data, "pageUp")
370
+ || this.root.editor.matchesTerminalKey(data, "pageDown"))) {
371
+ this.root.handleInput(data);
372
+ this.runtime.requestRender();
373
+ return { consume: true };
374
+ }
363
375
  if (!this.runtime.hasOverlay() && (this.root.editor.matchesTerminalKey(data, "home")
364
376
  || this.root.editor.matchesTerminalKey(data, "end")
365
377
  || !isDefault && (this.root.editor.matchesTerminalKey(data, "ctrl+home")
@@ -1065,12 +1077,14 @@ export class OwnedUiSessionShell {
1065
1077
  },
1066
1078
  onCancel: close,
1067
1079
  onSelect: entryId => {
1068
- close();
1069
1080
  if (entryId === context.currentLeafId) {
1081
+ close();
1070
1082
  this.root.appendWorkflowStatus("Already at this point");
1071
1083
  this.runtime.requestRender();
1072
1084
  return;
1073
1085
  }
1086
+ if (context.skipSummaryPrompt)
1087
+ close();
1074
1088
  void this.#completeTreeSelection(entryId, context.skipSummaryPrompt);
1075
1089
  },
1076
1090
  });
@@ -1206,6 +1220,19 @@ export class OwnedUiSessionShell {
1206
1220
  async runWorkflow(request) {
1207
1221
  if (request.command === "quit")
1208
1222
  return this.shutdown();
1223
+ if (this.#customViewport && request.command === "name" && request.argument.trim().length === 0) {
1224
+ void this.#extensionBridge.input("Session Name", "Enter name").then(value => {
1225
+ if (this.#disposed || value === undefined || value.trim().length === 0)
1226
+ return;
1227
+ return this.runWorkflow({ ...request, argument: value });
1228
+ }).catch(error => {
1229
+ if (this.#disposed)
1230
+ return;
1231
+ this.root.appendWorkflowResult({ command: "name", outcome: "failed", message: error instanceof Error ? error.message : String(error) });
1232
+ this.runtime.requestRender();
1233
+ });
1234
+ return { outcome: "completed", diagnostic: null };
1235
+ }
1209
1236
  const copyGeneration = request.command === "copy" ? this.backend.sessionBindingGeneration : undefined;
1210
1237
  if (request.command === "login" && request.selection !== undefined) {
1211
1238
  const setup = this.backend.pinnedAmbientAuthentication(request.selection);
@@ -1281,13 +1308,15 @@ export class OwnedUiSessionShell {
1281
1308
  }
1282
1309
  this.root.resetExtensionUi();
1283
1310
  }
1284
- const shareSurface = request.command === "share"
1285
- ? createPiShellOperationLoader({
1311
+ const shareDialog = request.command === "share"
1312
+ ? await import("./share-operation.js")
1313
+ : undefined;
1314
+ const shareSurface = shareDialog === undefined ? undefined
1315
+ : (this.#customViewport ? shareDialog.createPiShellShareOperationDialog : shareDialog.createPiShellOperationLoader)({
1286
1316
  getColumns: () => this.runtime.viewport().columns,
1287
1317
  getRows: () => this.runtime.viewport().rows,
1288
1318
  requestRender: () => this.runtime.requestRender(),
1289
- }, "Creating gist...")
1290
- : undefined;
1319
+ }, "Creating gist…");
1291
1320
  const operationSurface = shareSurface ?? (request.command === "reload" ? createPiShellReloadBox() : undefined);
1292
1321
  const now = this.#reloadPresentation?.now ?? Date.now;
1293
1322
  const shownAt = now();
@@ -1343,7 +1372,17 @@ export class OwnedUiSessionShell {
1343
1372
  this.root.editor.reloadKeybindings();
1344
1373
  this.#installAutocompleteCommands();
1345
1374
  }
1346
- this.root.appendWorkflowResult(result);
1375
+ if (request.command === "tree" && result.outcome === "completed") {
1376
+ if (result.detail && !this.root.editor.getText().trim())
1377
+ this.root.editor.setText(result.detail);
1378
+ this.root.resumeViewportFollowing();
1379
+ }
1380
+ if (shareDialog !== undefined && this.#customViewport && result.outcome === "completed" && result.detail !== undefined) {
1381
+ this.root.appendWorkflowStatus(shareDialog.renderPiShellShareResult(result.message, result.detail));
1382
+ }
1383
+ else {
1384
+ this.root.appendWorkflowResult(result);
1385
+ }
1347
1386
  this.runtime.requestRender();
1348
1387
  return workflowAdapterResult(result);
1349
1388
  }
@@ -1782,6 +1821,19 @@ export class OwnedUiSessionShell {
1782
1821
  const removeInterruptWatch = this.runtime.addInputListener(data => {
1783
1822
  if (!data.includes(INTERRUPT))
1784
1823
  return undefined;
1824
+ // Protocol: route hosts decide whether one interrupt closes their app. Raw-input forwarding
1825
+ // is required because Pi handles Ctrl+C before the fullscreen overlay receives normal input.
1826
+ const consumedBySurface = surface.handleInput(INTERRUPT);
1827
+ if (surface.isClosed()) {
1828
+ armedAt = 0;
1829
+ closeSurface();
1830
+ return { consume: true };
1831
+ }
1832
+ if (consumedBySurface) {
1833
+ armedAt = 0;
1834
+ this.runtime.requestRender();
1835
+ return { consume: true };
1836
+ }
1785
1837
  const now = Date.now();
1786
1838
  if (armedAt !== 0 && now - armedAt <= INTERRUPT_CHORD_MS) {
1787
1839
  armedAt = 0;
@@ -1970,7 +2022,7 @@ export class OwnedUiSessionShell {
1970
2022
  }
1971
2023
  return this.#submitSkillPrompt(skillPrompt(skill.name, separator < 0 ? "" : trimmed.slice(separator + 1)), text);
1972
2024
  }
1973
- // Rationale: the engine expands "/skill:<name> args" itself; history keeps the form the user typed so recall restores the invocation.
2025
+ // Rationale: engine expands skill arguments; history retains typed input for recall.
1974
2026
  #submitSkillPrompt(prompt, typed) {
1975
2027
  this.#rememberInput(typed, "slash");
1976
2028
  this.root.resumeViewportFollowing();
@@ -1981,29 +2033,33 @@ export class OwnedUiSessionShell {
1981
2033
  text: prompt,
1982
2034
  });
1983
2035
  }
2036
+ #treeSummary() {
2037
+ return new Promise(resolve => {
2038
+ this.root.setInputSurface(createPiShellExtensionSelector("Summarize Branch?", ["No summary", "Summarize", "Summarize with custom prompt"], resolve, () => resolve(undefined)), true, "opaque");
2039
+ this.runtime.requestRender();
2040
+ });
2041
+ }
1984
2042
  async #completeTreeSelection(entryId, skipSummaryPrompt) {
1985
2043
  let summarize = false;
1986
2044
  let customInstructions;
1987
2045
  if (!skipSummaryPrompt) {
1988
2046
  while (true) {
1989
- const choice = await this.#extensionBridge.context.select("Summarize branch?", [
1990
- "No summary",
1991
- "Summarize",
1992
- "Summarize with custom prompt",
1993
- ]);
2047
+ const choice = await this.#treeSummary();
1994
2048
  if (choice === undefined) {
1995
2049
  await this.showTreeSelector(entryId);
1996
2050
  return;
1997
2051
  }
1998
2052
  summarize = choice !== "No summary";
1999
2053
  if (choice === "Summarize with custom prompt") {
2000
- customInstructions = await this.#extensionBridge.context.editor("Custom summarization instructions", "");
2054
+ customInstructions = await this.#extensionBridge.input("Custom Summarization Instructions", "", { retainSurfaceOnSettle: true });
2001
2055
  if (customInstructions === undefined)
2002
2056
  continue;
2003
2057
  }
2004
2058
  break;
2005
2059
  }
2006
2060
  }
2061
+ this.root.setInputSurface(null);
2062
+ this.runtime.requestRender();
2007
2063
  const result = await this.runWorkflow({
2008
2064
  command: "tree",
2009
2065
  argument: "",
@@ -0,0 +1,3 @@
1
+ import { createPiShellOperationLoader, createPiShellShareOperationDialog } from "../../integrations/pi/components/index.js";
2
+ export { createPiShellOperationLoader, createPiShellShareOperationDialog };
3
+ export declare function renderPiShellShareResult(message: string, gistUrl: string): string;
@@ -0,0 +1,11 @@
1
+ import { createPiShellOperationLoader, createPiShellShareOperationDialog, piShellHyperlink, piTheme, } from "../../integrations/pi/components/index.js";
2
+ import { nativeHyperlinkStyle } from "../../ui/components/index.js";
3
+ export { createPiShellOperationLoader, createPiShellShareOperationDialog };
4
+ export function renderPiShellShareResult(message, gistUrl) {
5
+ const theme = piTheme();
6
+ const match = /^Share URL: (\S+)$/u.exec(message);
7
+ const linked = match === null
8
+ ? theme.fg("dim", message)
9
+ : `${theme.fg("dim", "Share URL: ")}${piShellHyperlink(match[1])}`;
10
+ return nativeHyperlinkStyle(`${linked}\n${theme.fg("dim", "Gist: ")}${piShellHyperlink(gistUrl)}`, text => theme.fg("mdLink", text));
11
+ }
@@ -131,7 +131,7 @@ function pinnedTheme() {
131
131
  bold: (text) => piTheme().bold(text),
132
132
  plain: (text) => text,
133
133
  disabled: (text) => faint(piTheme().fg("dim", text)),
134
- highlight: (text) => `\u001b[48;2;82;82;82m\u001b[97m${text}\u001b[39m\u001b[49m`,
134
+ highlight: (text) => piTheme().bg("selectedBg", text),
135
135
  panel: (text) => `\u001b[48;2;55;55;55m${text}\u001b[49m`,
136
136
  };
137
137
  }
@@ -4,6 +4,11 @@ export interface SemanticShortcutHint {
4
4
  readonly action: string;
5
5
  readonly actionFirst?: boolean;
6
6
  }
7
+ /** Canonical dismissal guidance for every A1-rendered dialog surface. */
8
+ export declare const DIALOG_CLOSE_SHORTCUT_HINT: Readonly<{
9
+ key: string;
10
+ action: string;
11
+ }>;
7
12
  export interface ShortcutHintRoles {
8
13
  key(label: string): string;
9
14
  action(name: string): string;
@@ -1,3 +1,5 @@
1
+ /** Canonical dismissal guidance for every A1-rendered dialog surface. */
2
+ export const DIALOG_CLOSE_SHORTCUT_HINT = Object.freeze({ key: "esc", action: "close" });
1
3
  const SHORTCUT_KEY_NAMES = Object.freeze({
2
4
  alt: "Alt", backspace: "Backspace", cmd: "Cmd", ctrl: "Ctrl", delete: "Delete", down: "Down", end: "End",
3
5
  enter: "Enter", esc: "Esc", escape: "Escape", home: "Home", insert: "Insert", left: "Left", meta: "Meta",
@@ -1,7 +1,8 @@
1
- const ACCENT = "\u001b[38;2;138;190;183m";
2
- const BORDER = "\u001b[38;2;95;135;255m";
3
- const MUTED = "\u001b[38;2;128;128;128m";
4
- const DIM = "\u001b[38;2;102;102;102m";
1
+ import { DIALOG_CLOSE_SHORTCUT_HINT, displayShortcutKeyLabel } from "../../contracts/presentation/index.js";
2
+ const ACCENT = "\u001b[38;2;167;152;215m";
3
+ const BORDER = "\u001b[38;2;97;133;204m";
4
+ const MUTED = "\u001b[38;2;157;165;169m";
5
+ const DIM = "\u001b[38;2;126;136;142m";
5
6
  const RESET_FG = "\u001b[39m";
6
7
  const BOLD = "\u001b[1m";
7
8
  const RESET_BOLD = "\u001b[22m";
@@ -13,7 +14,8 @@ export function renderProjectTrustDialog(cwd, options, selected, terminalWidth,
13
14
  const title = ` ${BOLD}${ACCENT}Trust project folder?${RESET_FG}${RESET_BOLD}`;
14
15
  const path = ` ${MUTED}${sanitize(cwd)}${RESET_FG}`;
15
16
  const choices = options.map((label, index) => choice(label, selected === index));
16
- const hint = ` ${DIM}↑/↓${MUTED} to navigate ${DIM}Enter${MUTED} to select ${DIM}Esc${MUTED} to exit${RESET_FG}`;
17
+ const closeKey = displayShortcutKeyLabel(DIALOG_CLOSE_SHORTCUT_HINT.key);
18
+ const hint = ` ${DIM}↑↓${MUTED} navigate ${DIM}Enter${MUTED} select ${DIM}${closeKey}${MUTED} ${DIALOG_CLOSE_SHORTCUT_HINT.action}${RESET_FG}`;
17
19
  const explanation = wrap(EXPLANATION, Math.max(1, width - 2)).map(line => ` ${line}`);
18
20
  const preferred = [rule, title, path, "", ...explanation, "", ...choices, "", hint, rule];
19
21
  if (preferred.length <= terminalRows)
@@ -29,8 +29,8 @@ export interface ReferenceScreenOptions {
29
29
  * Presents one read-only document full screen between two accent rules: the bold
30
30
  * title leads the scrolled rows, the shared scrollbar rail runs beside them, and
31
31
  * the hint line closes the frame. Rows come from a provider per content width and
32
- * are cached until the width changes; the screen closes on Escape and leaves the
33
- * interrupt chord to its host.
32
+ * are cached until the width changes; Escape closes locally, while an opted-in
33
+ * host closes on Ctrl+C before dispatching it into the screen.
34
34
  */
35
35
  export declare class ReferenceScreenApp implements UiApp {
36
36
  #private;
@@ -1,4 +1,5 @@
1
- import { GLOBAL_SCOPE, PLAIN_THEME, RAIL_COLUMNS, ShortcutRegistry, ScrollbarRails, assertNoShortcutConflicts, displayWidth, layoutList, padToWidth, renderGroupHeader, renderShortcutHints, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarWheelRows, truncateToWidth, withScrollbarRail, } from "../../ui/components/index.js";
1
+ import { DIALOG_CLOSE_SHORTCUT_HINT } from "../../contracts/presentation/index.js";
2
+ import { GLOBAL_SCOPE, PLAIN_THEME, RAIL_COLUMNS, ShortcutRegistry, ScrollbarRails, assertNoShortcutConflicts, displayWidth, layoutList, padToWidth, renderGroupHeader, renderShortcutHintsWithClose, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarWheelRows, truncateToWidth, withScrollbarRail, } from "../../ui/components/index.js";
2
3
  const SCOPE = "reference-screen";
3
4
  /** Rows above the document: the top rule. */
4
5
  const HEADER_ROWS = 1;
@@ -9,14 +10,17 @@ const SCROLL_LINGER_MS = 900;
9
10
  const SCROLL_LINGER_REPAINT_MS = 925;
10
11
  const LOADING_NOTICE = "Loading…";
11
12
  export const REFERENCE_SCREEN_SHORTCUTS = new ShortcutRegistry();
12
- // Compatibility: Ctrl+C remains the A1 interrupt chord here rather than a close key.
13
- REFERENCE_SCREEN_SHORTCUTS.declare({ key: "escape", scope: GLOBAL_SCOPE, description: "Close", section: "Screen", hint: { keys: "esc", does: "close" } }, "close");
14
13
  REFERENCE_SCREEN_SHORTCUTS.declare({ key: "up", scope: SCOPE, description: "Scroll up", section: "Navigate", hint: { keys: "↑↓", does: "scroll" } }, "up");
15
14
  REFERENCE_SCREEN_SHORTCUTS.declare({ key: "down", scope: SCOPE, description: "Scroll down", section: "Navigate", hint: { keys: "↑↓", does: "scroll" } }, "down");
16
15
  REFERENCE_SCREEN_SHORTCUTS.declare({ key: "pageUp", scope: SCOPE, description: "Up a page", section: "Navigate" }, "page-up");
17
16
  REFERENCE_SCREEN_SHORTCUTS.declare({ key: "pageDown", scope: SCOPE, description: "Down a page", section: "Navigate" }, "page-down");
18
17
  REFERENCE_SCREEN_SHORTCUTS.declare({ key: "home", scope: SCOPE, description: "First row", section: "Navigate" }, "first");
19
18
  REFERENCE_SCREEN_SHORTCUTS.declare({ key: "end", scope: SCOPE, description: "Last row", section: "Navigate" }, "last");
19
+ // Compatibility: Ctrl+C remains the A1 interrupt chord here rather than a visible close key.
20
+ REFERENCE_SCREEN_SHORTCUTS.declare({
21
+ key: "escape", scope: GLOBAL_SCOPE, description: "Close", section: "Screen",
22
+ hint: { keys: DIALOG_CLOSE_SHORTCUT_HINT.key, does: DIALOG_CLOSE_SHORTCUT_HINT.action },
23
+ }, "close");
20
24
  assertNoShortcutConflicts(REFERENCE_SCREEN_SHORTCUTS.assemble());
21
25
  const KEYS = {
22
26
  "\u001b[A": "up",
@@ -36,8 +40,8 @@ const KEYS = {
36
40
  * Presents one read-only document full screen between two accent rules: the bold
37
41
  * title leads the scrolled rows, the shared scrollbar rail runs beside them, and
38
42
  * the hint line closes the frame. Rows come from a provider per content width and
39
- * are cached until the width changes; the screen closes on Escape and leaves the
40
- * interrupt chord to its host.
43
+ * are cached until the width changes; Escape closes locally, while an opted-in
44
+ * host closes on Ctrl+C before dispatching it into the screen.
41
45
  */
42
46
  export class ReferenceScreenApp {
43
47
  id;
@@ -135,7 +139,7 @@ export class ReferenceScreenApp {
135
139
  const withRail = withScrollbarRail(body.slice(0, bodyHeight), geometry, contentWidth, theme, { presentation });
136
140
  const hint = this.#interruptArmed
137
141
  ? theme.fg("dim", " press ctrl+c again to exit a1")
138
- : renderShortcutHints(REFERENCE_SCREEN_SHORTCUTS.hintEntries(SCOPE), theme, 1);
142
+ : renderShortcutHintsWithClose(REFERENCE_SCREEN_SHORTCUTS.hintEntries(SCOPE), theme, rect.width, 1);
139
143
  // Compatibility: the v2 reference screen frames its document between two border-coloured rules.
140
144
  const rule = theme.fg("border", "─".repeat(rect.width));
141
145
  // Invariant: dialog chrome shares the title's one-cell inset; document content keeps its own layout.
@@ -1,4 +1,5 @@
1
- import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, dialogValueColumn, numericValues, RAIL_COLUMNS, renderDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderListRow, renderNote, renderShortcutHints, dialogRowAt, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, isThumbRow, layoutList, moveSelection, rowKey, scrollForSelection, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, ScrollbarRails, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
1
+ import { DIALOG_CLOSE_SHORTCUT_HINT } from "../../contracts/presentation/index.js";
2
+ import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, dialogValueColumn, numericValues, RAIL_COLUMNS, renderDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderListRow, renderNote, renderShortcutHintsWithClose, dialogRowAt, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, isThumbRow, layoutList, moveSelection, rowKey, scrollForSelection, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, ScrollbarRails, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
2
3
  import { SETTINGS_APP_ID, SETTINGS_ROUTE } from "./settings-route.js";
3
4
  export { SETTINGS_APP_ID, SETTINGS_ROUTE } from "./settings-route.js";
4
5
  const SCOPE = SETTINGS_APP_ID;
@@ -35,13 +36,16 @@ SETTINGS_SHORTCUTS.declare({ key: "enter", scope: SCOPE, description: "Change va
35
36
  SETTINGS_SHORTCUTS.declare({ key: "space", scope: SCOPE, description: "Change value", section: "Change", hint: { keys: "Enter/Space", does: "to change" } }, "activate");
36
37
  SETTINGS_SHORTCUTS.declare({ key: "left", scope: SCOPE, description: "Previous value", section: "Change", hint: { keys: "←→", does: "to adjust" } }, "previous-value");
37
38
  SETTINGS_SHORTCUTS.declare({ key: "right", scope: SCOPE, description: "Next value", section: "Change", hint: { keys: "←→", does: "to adjust" } }, "next-value");
38
- SETTINGS_SHORTCUTS.declare({ key: "escape", scope: GLOBAL_SCOPE, description: "Close", section: "Screen", hint: { keys: "Esc", does: "to cancel" } }, "close");
39
39
  SETTINGS_SHORTCUTS.declare({ key: "enter", scope: DIALOG_SCOPE, description: "Change this part", section: "Parts", hint: { keys: "Enter/Space", does: "to change" } }, "part-change");
40
40
  SETTINGS_SHORTCUTS.declare({ key: "space", scope: DIALOG_SCOPE, description: "Change this part", section: "Parts", hint: { keys: "Enter/Space", does: "to change" } }, "part-change");
41
41
  SETTINGS_SHORTCUTS.declare({ key: "left", scope: DIALOG_SCOPE, description: "Change this part", section: "Parts" }, "part-change");
42
42
  SETTINGS_SHORTCUTS.declare({ key: "right", scope: DIALOG_SCOPE, description: "Change this part", section: "Parts" }, "part-change");
43
43
  SETTINGS_SHORTCUTS.declare({ key: "up", scope: DIALOG_SCOPE, description: "Previous part", section: "Parts" }, "part-previous");
44
44
  SETTINGS_SHORTCUTS.declare({ key: "down", scope: DIALOG_SCOPE, description: "Next part", section: "Parts" }, "part-next");
45
+ SETTINGS_SHORTCUTS.declare({
46
+ key: "escape", scope: GLOBAL_SCOPE, description: "Close", section: "Screen",
47
+ hint: { keys: DIALOG_CLOSE_SHORTCUT_HINT.key, does: DIALOG_CLOSE_SHORTCUT_HINT.action },
48
+ }, "close");
45
49
  assertNoShortcutConflicts(SETTINGS_SHORTCUTS.assemble());
46
50
  const KEYS = {
47
51
  "\u001b[A": "up",
@@ -451,8 +455,9 @@ export class SettingsApp {
451
455
  return;
452
456
  }
453
457
  const current = shown === null ? 0 : Math.max(0, entry.choices.indexOf(shown));
454
- // Compatibility: pinned SelectList opens on the value currently in effect.
455
- this.#menu = { entry, current, anchorKey: `${entry.backend}:${entry.id}`, choices: entry.choices, index: current };
458
+ // Invariant: the opening press targeted the setting value, not an option. Keep the
459
+ // effective value as the keyboard origin without painting it as pointer-picked.
460
+ this.#menu = { entry, current, anchorKey: `${entry.backend}:${entry.id}`, choices: entry.choices, index: -1 };
456
461
  }
457
462
  // Rationale: a structured setting opens as its own flag list rather than a value menu.
458
463
  #openStructured(entry) {
@@ -797,7 +802,7 @@ export class SettingsApp {
797
802
  ? notice
798
803
  : this.#interruptArmed ? "press ctrl+c again to exit a1" : null;
799
804
  const status = plainStatus === null
800
- ? truncateToWidth(renderShortcutHints(SETTINGS_SHORTCUTS.hintEntries(SCOPE), theme, width > 0 ? 1 : 0), width)
805
+ ? renderShortcutHintsWithClose(SETTINGS_SHORTCUTS.hintEntries(SCOPE), theme, width, width > 0 ? 1 : 0)
801
806
  : truncateToWidth(`${width > 0 ? " " : ""}${theme.fg("dim", plainStatus)}`, width);
802
807
  const input = this.#filter;
803
808
  if (input === null)
@@ -2,10 +2,11 @@ export { adaptPiAssistantMessage, adaptPiToolExecution, adaptPiUserMessage } fro
2
2
  export { loadHistoryEditor, type HistoryEditorConstructor } from "./history-editor-loader.js";
3
3
  export { PiComponentConformanceError, runPiComponentConformance } from "./conformance.js";
4
4
  export type { PiComponentConformanceReport, PiComponentConformanceResult } from "./conformance.js";
5
- export { OWNED_BUILTIN_SLASH_COMMANDS, PINNED_PI_BUILTIN_SLASH_COMMANDS, componentFromPort, componentPort, createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellCollapsedChangelog, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellShowImagesSelector, createPiShellSkillsSelector, createPiShellStatus, createPiShellThemeSelector, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, createTuiFacade, ensureTheme, formatSessionTokens, isAutocompleteProvider, isPiPromptStyleCompaction, isRecord, paintPiSubmittedPromptTimestamp, piShellHyperlink, piShellTruncateToWidth, piShellVisibleWidth, renderPiShellChangelogLines, renderPiShellCommandMessage, renderPiShellHotkeysLines, renderPiShellPackageUpdateNotice, renderPiShellReleaseUpdateBanner, renderPiShellReleaseUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, validatedAssistantMessage, } from "./shell-components.js";
5
+ export { createPiShellOperationLoader, createPiShellShareOperationDialog, type PiShellOperationLoaderPort, } from "./share-operation-dialog.js";
6
+ export { OWNED_BUILTIN_SLASH_COMMANDS, PINNED_PI_BUILTIN_SLASH_COMMANDS, componentFromPort, componentPort, createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellCollapsedChangelog, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellShowImagesSelector, createPiShellSkillsSelector, createPiShellStatus, createPiShellThemeSelector, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, createTuiFacade, ensureTheme, formatSessionTokens, isAutocompleteProvider, isPiPromptStyleCompaction, isRecord, paintPiSubmittedPromptTimestamp, piShellHyperlink, piShellTruncateToWidth, piShellVisibleWidth, renderPiShellChangelogLines, renderPiShellCommandMessage, renderPiShellHotkeysLines, renderPiShellPackageUpdateNotice, renderPiShellReleaseUpdateBanner, renderPiShellReleaseUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, validatedAssistantMessage, } from "./shell-components.js";
6
7
  export { renderPiShellHotkeySections } from "./shell-hotkey-sections.js";
7
8
  export type { PiShellHotkeySection } from "./shell-hotkey-sections.js";
8
- export type { PiExtensionUiBridge, PiExtensionUiBridgeHost, PiShellAuthProviderOption, PiShellAutocompleteCommand, PiShellClipboardContent, PiShellCommandMessagePresentation, PiShellComponentPort, PiShellEditorBodyGeometry, PiShellEditorOptions, PiShellEditorPointerEvent, PiShellEditorPort, PiShellEditorTextRange, PiShellExtensionRendererResolver, PiShellHeaderOptions, PiShellHeaderPort, PiShellHotkeysPresentation, PiShellImageAssetResolver, PiShellLoadedResourcesPort, PiShellLoginDialogPort, PiShellModelSelectorOptions, PiShellModelsDialogOptions, PiShellModelsDialogPort, PiShellOperationLoaderPort, PiShellPasteReservation, PiShellQueuedInputPort, PiShellResourceEntry, PiShellResourceSection, PiShellScopedModelDescriptor, PiShellScopedModelsSelectorOptions, PiShellScopedModelsSelectorPort, PiShellSelectorOption, PiShellSelectorOptions, PiShellSessionInfoPresentation, PiShellSettingsSelectorOptions, PiShellStartupNotice, PiShellStatusPlacement, PiShellStatusPort, PiShellSubmittedPromptComposer, PiShellTranscriptComponentPort, PiShellViewComponentPort, } from "./shell-components.js";
9
+ export type { PiExtensionUiBridge, PiExtensionUiBridgeHost, PiShellAuthProviderOption, PiShellAutocompleteCommand, PiShellClipboardContent, PiShellCommandMessagePresentation, PiShellComponentPort, PiShellEditorBodyGeometry, PiShellEditorOptions, PiShellEditorPointerEvent, PiShellEditorPort, PiShellEditorTextRange, PiShellExtensionRendererResolver, PiShellHeaderOptions, PiShellHeaderPort, PiShellHotkeysPresentation, PiShellImageAssetResolver, PiShellLoadedResourcesPort, PiShellLoginDialogPort, PiShellModelSelectorOptions, PiShellModelsDialogOptions, PiShellModelsDialogPort, PiShellPasteReservation, PiShellQueuedInputPort, PiShellResourceEntry, PiShellResourceSection, PiShellScopedModelDescriptor, PiShellScopedModelsSelectorOptions, PiShellScopedModelsSelectorPort, PiShellSelectorOption, PiShellSelectorOptions, PiShellSessionInfoPresentation, PiShellSettingsSelectorOptions, PiShellStartupNotice, PiShellStatusPlacement, PiShellStatusPort, PiShellSubmittedPromptComposer, PiShellTranscriptComponentPort, PiShellViewComponentPort, } from "./shell-components.js";
9
10
  export { PINNED_PI_LAYOUT, applyConfiguredPiTheme, applyPiTheme, applyPiThemeInstance, currentPiThemeName, detectPiTerminalBackgroundFromEnv, detectPiTerminalBackgroundTheme, detectPiTerminalThemeForAuto, ensurePiTheme, getAvailablePiThemes, loadPiTheme, onPiThemeChange, parsePiAutoThemeSetting, piTheme, resolvePiThemeSetting, stopPiThemeWatcher, } from "./theme.js";
10
11
  export type { PiColorMode, PiTerminalTheme, PiTerminalThemeDetection, PiTerminalThemeDetector, PiThemeBackground, PiThemeResult } from "./theme.js";
11
12
  export { OwnedPiThemeController } from "./upstream/theme/theme-controller.js";
@@ -1,7 +1,8 @@
1
1
  export { adaptPiAssistantMessage, adaptPiToolExecution, adaptPiUserMessage } from "./components.js";
2
2
  export { loadHistoryEditor } from "./history-editor-loader.js";
3
3
  export { PiComponentConformanceError, runPiComponentConformance } from "./conformance.js";
4
- export { OWNED_BUILTIN_SLASH_COMMANDS, PINNED_PI_BUILTIN_SLASH_COMMANDS, componentFromPort, componentPort, createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellCollapsedChangelog, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellShowImagesSelector, createPiShellSkillsSelector, createPiShellStatus, createPiShellThemeSelector, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, createTuiFacade, ensureTheme, formatSessionTokens, isAutocompleteProvider, isPiPromptStyleCompaction, isRecord, paintPiSubmittedPromptTimestamp, piShellHyperlink, piShellTruncateToWidth, piShellVisibleWidth, renderPiShellChangelogLines, renderPiShellCommandMessage, renderPiShellHotkeysLines, renderPiShellPackageUpdateNotice, renderPiShellReleaseUpdateBanner, renderPiShellReleaseUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, validatedAssistantMessage, } from "./shell-components.js";
4
+ export { createPiShellOperationLoader, createPiShellShareOperationDialog, } from "./share-operation-dialog.js";
5
+ export { OWNED_BUILTIN_SLASH_COMMANDS, PINNED_PI_BUILTIN_SLASH_COMMANDS, componentFromPort, componentPort, createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellCollapsedChangelog, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellShowImagesSelector, createPiShellSkillsSelector, createPiShellStatus, createPiShellThemeSelector, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, createTuiFacade, ensureTheme, formatSessionTokens, isAutocompleteProvider, isPiPromptStyleCompaction, isRecord, paintPiSubmittedPromptTimestamp, piShellHyperlink, piShellTruncateToWidth, piShellVisibleWidth, renderPiShellChangelogLines, renderPiShellCommandMessage, renderPiShellHotkeysLines, renderPiShellPackageUpdateNotice, renderPiShellReleaseUpdateBanner, renderPiShellReleaseUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, validatedAssistantMessage, } from "./shell-components.js";
5
6
  export { renderPiShellHotkeySections } from "./shell-hotkey-sections.js";
6
7
  export { PINNED_PI_LAYOUT, applyConfiguredPiTheme, applyPiTheme, applyPiThemeInstance, currentPiThemeName, detectPiTerminalBackgroundFromEnv, detectPiTerminalBackgroundTheme, detectPiTerminalThemeForAuto, ensurePiTheme, getAvailablePiThemes, loadPiTheme, onPiThemeChange, parsePiAutoThemeSetting, piTheme, resolvePiThemeSetting, stopPiThemeWatcher, } from "./theme.js";
7
8
  export { OwnedPiThemeController } from "./upstream/theme/theme-controller.js";
@@ -1,5 +1,15 @@
1
- import { Container, type Component } from "@earendil-works/pi-tui";
1
+ import { Container, type Component, type TUI } from "@earendil-works/pi-tui";
2
2
  export declare const PI_MODAL_CONTENT_PADDING_X = 1;
3
+ /** Adds the standard navigation/select/close footer before a dependency component's bottom rule. */
4
+ export declare function addPiModalSelectFooter(container: Container): void;
5
+ /** Owned cancellable operation frame with the canonical dialog close guidance. */
6
+ export declare class PiCancellableOperationFrame extends Container {
7
+ #private;
8
+ constructor(tui: TUI, message: string);
9
+ get signal(): AbortSignal;
10
+ handleInput(data: string): void;
11
+ dispose(): void;
12
+ }
3
13
  /** The semantic top rule/title pair used while a modal assembles its frame. */
4
14
  export declare class PiModalHeader implements Component {
5
15
  readonly rule: Component;
@@ -1,5 +1,33 @@
1
- import { Container, Spacer } from "@earendil-works/pi-tui";
1
+ import { CancellableLoader, Container, Spacer, Text } from "@earendil-works/pi-tui";
2
+ import { DynamicBorder } from "../startup-public.js";
3
+ import { DIALOG_CLOSE_SHORTCUT_HINT, piTheme, renderPiModalShortcutHints } from "./theme.js";
2
4
  export const PI_MODAL_CONTENT_PADDING_X = 1;
5
+ /** Adds the standard navigation/select/close footer before a dependency component's bottom rule. */
6
+ export function addPiModalSelectFooter(container) {
7
+ container.children.splice(container.children.length - 1, 0, new Text(renderPiModalShortcutHints([
8
+ { key: "↑↓", action: "navigate" }, { key: "enter", action: "select" }, DIALOG_CLOSE_SHORTCUT_HINT,
9
+ ]), 0, 0));
10
+ }
11
+ /** Owned cancellable operation frame with the canonical dialog close guidance. */
12
+ export class PiCancellableOperationFrame extends Container {
13
+ #loader;
14
+ constructor(tui, message) {
15
+ super();
16
+ const theme = piTheme();
17
+ const border = (text) => theme.fg("border", text);
18
+ this.#loader = new CancellableLoader(tui, text => theme.fg("accent", text), text => theme.fg("muted", text), message);
19
+ this.addChild(new DynamicBorder(border));
20
+ this.addChild(this.#loader);
21
+ this.addChild(new Spacer(1));
22
+ this.addChild(new Text(renderPiModalShortcutHints([DIALOG_CLOSE_SHORTCUT_HINT]), 0, 0));
23
+ this.addChild(new Spacer(1));
24
+ this.addChild(new DynamicBorder(border));
25
+ adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1 });
26
+ }
27
+ get signal() { return this.#loader.signal; }
28
+ handleInput(data) { this.#loader.handleInput(data); }
29
+ dispose() { this.#loader.dispose(); }
30
+ }
3
31
  /** The semantic top rule/title pair used while a modal assembles its frame. */
4
32
  export class PiModalHeader {
5
33
  rule;
@@ -1,7 +1,16 @@
1
- import { Input, Key, getKeybindings, matchesKey, truncateToWidth, } from "@earendil-works/pi-tui";
1
+ import { Input, getKeybindings, matchesKey, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
2
2
  import { DynamicBorder } from "../startup-public.js";
3
3
  import { PiModalFrame } from "./modal-frame.js";
4
- import { piTheme, renderPiModalShortcutHints } from "./theme.js";
4
+ import { DIALOG_CLOSE_SHORTCUT_HINT, piTheme, renderPiModalListRow, renderPiModalShortcutHints, } from "./theme.js";
5
+ function renderHintsWithClose(entries, width) {
6
+ const body = renderPiModalShortcutHints(entries.slice(0, -1));
7
+ const close = renderPiModalShortcutHints([DIALOG_CLOSE_SHORTCUT_HINT]);
8
+ const closeWidth = visibleWidth(close);
9
+ if (width <= closeWidth)
10
+ return truncateToWidth(close, width, "");
11
+ const clippedBody = truncateToWidth(body, Math.max(0, width - closeWidth - 2), "…");
12
+ return visibleWidth(clippedBody) === 0 ? close : `${clippedBody} ${close}`;
13
+ }
5
14
  const MAX_VISIBLE_ROWS = 10;
6
15
  const MODELS_TITLE = "Models";
7
16
  const REFRESHING_TITLE_MIN_DURATION_MS = 1_000;
@@ -166,7 +175,7 @@ export class ModelsDialogComponent {
166
175
  this.#selectedIndex = this.#selectedIndex === rows.length - 1 ? 0 : this.#selectedIndex + 1;
167
176
  return;
168
177
  }
169
- if (kb.matches(data, "tui.input.tab")) {
178
+ if (matchesKey(data, "shift+tab") || kb.matches(data, "tui.input.tab")) {
170
179
  this.#filter = this.#filter === "all" ? "scoped" : "all";
171
180
  this.#restoreSelection(preferredId);
172
181
  return;
@@ -221,15 +230,6 @@ export class ModelsDialogComponent {
221
230
  this.#setScope(next, selected.fullId);
222
231
  return;
223
232
  }
224
- if (matchesKey(data, Key.ctrl("c"))) {
225
- if (this.#input.getValue().length > 0) {
226
- this.#input.setValue("");
227
- this.#selectedIndex = 0;
228
- return;
229
- }
230
- this.#callbacks.onCancel();
231
- return;
232
- }
233
233
  if (kb.matches(data, "tui.select.cancel")) {
234
234
  this.#callbacks.onCancel();
235
235
  return;
@@ -305,11 +305,11 @@ export class ModelsDialogComponent {
305
305
  const scoped = this.#scopeIds.includes(row.fullId);
306
306
  // Invariant: arrow, scope marker, model id, [provider], then the active checkmark, in that order.
307
307
  const prefix = selected ? theme.fg("accent", "→ ") : " ";
308
- const marker = scoped ? theme.fg("success", "●") : theme.fg("dim", "○");
309
- const label = selected ? theme.fg("accent", row.model.id) : row.model.id;
308
+ const marker = scoped ? theme.fg("accent", "●") : theme.fg("dim", "○");
309
+ const label = selected ? theme.fg("text", row.model.id) : row.model.id;
310
310
  const provider = theme.fg("muted", `[${row.model.provider}]`);
311
311
  const active = row.fullId === this.#activeModelId ? ` ${theme.fg("success", "✓")}` : "";
312
- push(`${prefix}${marker} ${label} ${provider}${active}`);
312
+ push(renderPiModalListRow(`${prefix}${marker} ${label} ${provider}${active}`, width, selected));
313
313
  }
314
314
  if (startIndex > 0 || endIndex < rows.length)
315
315
  push(theme.fg("muted", ` (${this.#selectedIndex + 1}/${rows.length})`));
@@ -322,7 +322,7 @@ export class ModelsDialogComponent {
322
322
  push();
323
323
  if (this.#refreshStatus !== undefined)
324
324
  push(theme.fg(this.#refreshStatus.kind, ` ${this.#refreshStatus.message}`));
325
- push(renderPiModalShortcutHints(this.#hints()));
325
+ push(renderHintsWithClose(this.#hints(), width));
326
326
  return lines;
327
327
  }
328
328
  #hints() {
@@ -336,7 +336,7 @@ export class ModelsDialogComponent {
336
336
  ...(confirm.length === 0 ? [] : [{ key: confirm, action: "switch" }]),
337
337
  { key: "space", action: "scope" },
338
338
  ...(save.length === 0 ? [] : [{ key: save, action: "save" }]),
339
- { key: "esc", action: "close" },
339
+ DIALOG_CLOSE_SHORTCUT_HINT,
340
340
  ];
341
341
  }
342
342
  #emptyText() {
@@ -0,0 +1,6 @@
1
+ import { type PiShellComponentPort, type PiShellEditorOptions } from "./shell-shared-facade.js";
2
+ export interface PiShellOperationLoaderPort extends PiShellComponentPort {
3
+ readonly signal: AbortSignal;
4
+ }
5
+ export declare function createPiShellShareOperationDialog(runtime: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">, message: string): PiShellOperationLoaderPort;
6
+ export declare function createPiShellOperationLoader(runtime: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">, message: string): PiShellOperationLoaderPort;