@timurproko/a1 0.2.6-dev.717 → 0.2.6-dev.727

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 (143) hide show
  1. package/bin/cli.js +13 -2
  2. package/bin/ui.js +13 -0
  3. package/dist/app/session-shell/prompt-suggestion-controller.d.ts +5 -4
  4. package/dist/app/session-shell/prompt-suggestion-controller.js +148 -58
  5. package/dist/app/session-shell/session-shell-root.d.ts +2 -1
  6. package/dist/app/session-shell/session-shell-root.js +13 -6
  7. package/dist/app/session-shell/session-shell.js +45 -3
  8. package/dist/app/session-shell/session-viewport-controller.js +33 -21
  9. package/dist/cli/dispatch.js +5 -3
  10. package/dist/cli/session-context.d.ts +5 -1
  11. package/dist/cli/session-context.js +25 -8
  12. package/dist/composition/owned-ui.d.ts +3 -0
  13. package/dist/composition/owned-ui.js +66 -6
  14. package/dist/composition/settings-route-host.js +2 -1
  15. package/dist/contracts/owned-ui/index.d.ts +4 -2
  16. package/dist/contracts/owned-ui/index.js +1 -1
  17. package/dist/contracts/owned-ui/model.d.ts +4 -0
  18. package/dist/contracts/owned-ui/suggestion-diagnostics.d.ts +5 -1
  19. package/dist/contracts/owned-ui/suggestion-diagnostics.js +3 -1
  20. package/dist/contracts/owned-ui/validation.js +7 -0
  21. package/dist/features/launch/index.d.ts +1 -1
  22. package/dist/features/launch/session-context.d.ts +17 -2
  23. package/dist/features/launch/session-context.js +9 -5
  24. package/dist/features/owned-ui/settings-app.js +21 -39
  25. package/dist/features/prompt-suggestions/diagnostics.js +4 -1
  26. package/dist/foundation/lifecycle/index.d.ts +2 -2
  27. package/dist/foundation/lifecycle/index.js +1 -1
  28. package/dist/foundation/lifecycle/session-repository-context.d.ts +19 -2
  29. package/dist/foundation/lifecycle/session-repository-context.js +434 -66
  30. package/dist/foundation/process-containment/index.d.ts +1 -0
  31. package/dist/foundation/process-containment/index.js +1 -0
  32. package/dist/foundation/process-containment/native-process-inspector.d.ts +3 -0
  33. package/dist/foundation/process-containment/native-process-inspector.js +21 -0
  34. package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
  35. package/dist/foundation/release/progress-palette.generated.js +2 -2
  36. package/dist/foundation/startup/startup-descriptor.js +2 -2
  37. package/dist/integrations/pi/components/components.js +2 -2
  38. package/dist/integrations/pi/components/index.d.ts +2 -2
  39. package/dist/integrations/pi/components/index.js +1 -1
  40. package/dist/integrations/pi/components/modal-frame.js +3 -4
  41. package/dist/integrations/pi/components/models-dialog.js +5 -5
  42. package/dist/integrations/pi/components/prompt-input-port.d.ts +1 -0
  43. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
  44. package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
  45. package/dist/integrations/pi/components/share-operation-dialog.js +3 -3
  46. package/dist/integrations/pi/components/shell-editor-autocomplete.js +8 -7
  47. package/dist/integrations/pi/components/shell-extension-ui.d.ts +2 -0
  48. package/dist/integrations/pi/components/shell-extension-ui.js +20 -10
  49. package/dist/integrations/pi/components/shell-hotkey-sections.js +2 -2
  50. package/dist/integrations/pi/components/shell-presenters-info.js +10 -10
  51. package/dist/integrations/pi/components/shell-presenters-transcript.js +10 -10
  52. package/dist/integrations/pi/components/shell-selectors-dialogs.js +5 -6
  53. package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
  54. package/dist/integrations/pi/components/skills-dialog.js +3 -3
  55. package/dist/integrations/pi/components/submitted-prompt-adapter.js +2 -3
  56. package/dist/integrations/pi/components/theme.d.ts +9 -0
  57. package/dist/integrations/pi/components/theme.js +30 -0
  58. package/dist/integrations/pi/components/tool-result-adapter.js +6 -1
  59. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
  60. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
  61. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  62. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  63. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  64. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  65. package/dist/integrations/pi/components/upstream/components/extension-editor.js +6 -6
  66. package/dist/integrations/pi/components/upstream/components/extension-input.js +4 -4
  67. package/dist/integrations/pi/components/upstream/components/extension-selector.js +4 -4
  68. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  69. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +5 -5
  70. package/dist/integrations/pi/components/upstream/components/owned-editor.js +8 -5
  71. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  72. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  73. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +3 -3
  74. package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
  75. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  76. package/dist/integrations/pi/components/upstream/components/session-selector.js +9 -9
  77. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +3 -2
  78. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +6 -3
  79. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
  80. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  81. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  82. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  83. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  84. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +2 -2
  85. package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +4 -0
  86. package/dist/integrations/pi/components/upstream/components/tool-execution.js +11 -1
  87. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +2 -2
  88. package/dist/integrations/pi/components/upstream/components/tree-selector.js +27 -18
  89. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  90. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  91. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +4 -4
  92. package/dist/integrations/pi/components/upstream/history/editor-core.js +11 -7
  93. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  94. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  95. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  96. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  97. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  98. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  99. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  100. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  101. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +31 -0
  102. package/dist/integrations/pi/components/upstream/program-status-reporter.js +89 -0
  103. package/dist/integrations/pi/components/upstream/theme/system-theme.d.ts +1 -1
  104. package/dist/integrations/pi/components/upstream/theme/system-theme.js +1 -1
  105. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
  106. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +18 -1
  107. package/dist/integrations/pi/components/upstream/theme/theme.js +207 -10
  108. package/dist/integrations/pi/engine/pi-settings-metadata.json +1 -1
  109. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  110. package/dist/integrations/pi/engine/session-events.js +10 -2
  111. package/dist/integrations/pi/engine/transcript-projection.js +2 -1
  112. package/dist/integrations/pi/startup-public.js +306 -290
  113. package/dist/integrations/pi/startup-public.manifest.json +289 -265
  114. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -1
  115. package/dist/integrations/pi/tui-runtime/adapter.js +7 -0
  116. package/dist/integrations/pi/tui-runtime/contracts.d.ts +3 -1
  117. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  118. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  119. package/dist/native/darwin-arm64/manifest.json +1 -1
  120. package/dist/native/linux-x64/manifest.json +1 -1
  121. package/dist/native/win32-x64/manifest.json +2 -2
  122. package/dist/native/win32-x64/process-guardian.exe +0 -0
  123. package/dist/product-identity.d.ts +2 -2
  124. package/dist/product-identity.js +2 -2
  125. package/dist/product-identity.json +3 -1
  126. package/dist/runtime-payload-inventory.json +3092 -2878
  127. package/dist/ui/components/prompt-input.d.ts +2 -0
  128. package/dist/ui/components/prompt-input.js +5 -1
  129. package/dist/ui/components/text-selection.d.ts +1 -1
  130. package/dist/ui/components/text-selection.js +3 -1
  131. package/dist/ui/components/theme.d.ts +2 -0
  132. package/dist/ui/components/transcript-viewport.d.ts +6 -4
  133. package/dist/ui/components/transcript-viewport.js +94 -16
  134. package/dist/ui/components/value-menu.d.ts +2 -0
  135. package/dist/ui/components/value-menu.js +13 -8
  136. package/dist/ui/settings/declarations.d.ts +26 -14
  137. package/dist/ui/settings/declarations.js +24 -11
  138. package/dist/ui/settings/migrations.js +16 -0
  139. package/docs/architecture/project-structure.md +4 -3
  140. package/docs/architecture/prompt-suggestions.md +14 -10
  141. package/docs/local-worktree-cleanup.md +11 -5
  142. package/docs/openspec-archive-automation.md +6 -5
  143. package/package.json +3 -3
@@ -306,14 +306,14 @@ export class SessionViewportController {
306
306
  if (this.#viewport.clearSelection())
307
307
  this.#requestRender();
308
308
  }
309
- else {
309
+ else if (this.#viewport.hasSelection) {
310
310
  const copySelection = this.#viewport.captureSelectedText();
311
- if (copySelection !== null) {
312
- this.#viewport.clearSelection();
313
- this.#stopSelectionAutoScroll();
314
- this.#requestRender();
315
- return { data: "", consumed: true, copySelection };
316
- }
311
+ this.#viewport.clearSelection();
312
+ this.#stopSelectionAutoScroll();
313
+ this.#requestRender();
314
+ return copySelection === null
315
+ ? { data: "", consumed: true }
316
+ : { data: "", consumed: true, copySelection };
317
317
  }
318
318
  }
319
319
  const previousPrompt = SHIFT_UP_INPUTS.has(data) || this.#editor.matchesTerminalKey(data, "alt+home");
@@ -412,12 +412,19 @@ export class SessionViewportController {
412
412
  }
413
413
  if (this.#dockPointerSuppressed || this.#tailPointerSuppressed)
414
414
  return true;
415
+ const pendingEditorClick = this.#pendingEditorClick;
416
+ if (pendingEditorClick !== undefined) {
417
+ if (event.column === pendingEditorClick.column && event.row === pendingEditorClick.row)
418
+ return true;
419
+ this.#pendingEditorClick = undefined;
420
+ this.#viewport.pressSelection(pendingEditorClick.column, pendingEditorClick.row, now, { continueMultiClick: false });
421
+ this.#viewport.extendSelection(event.column, event.row, now, false);
422
+ this.#updateSelectionAutoScroll(event.column, event.row, hits.viewportHeight, frame.rows.length);
423
+ activity = true;
424
+ return true;
425
+ }
415
426
  if (this.#viewport.selectionActive) {
416
427
  this.#viewport.extendSelection(event.column, event.row, now, false);
417
- if (this.#pendingEditorClick !== undefined
418
- && (event.column !== this.#pendingEditorClick.column || event.row !== this.#pendingEditorClick.row)) {
419
- this.#pendingEditorClick = undefined;
420
- }
421
428
  this.#updateSelectionAutoScroll(event.column, event.row, hits.viewportHeight, frame.rows.length);
422
429
  activity = true;
423
430
  return true;
@@ -500,11 +507,13 @@ export class SessionViewportController {
500
507
  }
501
508
  if (event.row >= 1 && event.row <= frame.rows.length && event.column <= frame.descriptor.width) {
502
509
  if (editorFrame !== undefined && event.row >= editorFrame.rowStart && event.row <= editorFrame.rowEnd) {
510
+ // Invariant: no-drag prompt clicks remain visually editor-owned. Distinct
511
+ // motion promotes from this exact origin into complete-frame selection.
503
512
  this.#pendingEditorClick = { column: event.column, row: event.row };
504
513
  }
505
- // Invariant: frame selection includes transient and dock rows without changing
506
- // semantic ownership; no-drag editor presses replay on release.
507
- this.#viewport.pressSelection(event.column, event.row, now);
514
+ else {
515
+ this.#viewport.pressSelection(event.column, event.row, now);
516
+ }
508
517
  repaint = true;
509
518
  return true;
510
519
  }
@@ -525,19 +534,22 @@ export class SessionViewportController {
525
534
  repaint = true;
526
535
  return true;
527
536
  }
528
- if (this.#viewport.releaseSelection()) {
529
- this.#stopSelectionAutoScroll();
530
- const pendingEditorClick = this.#pendingEditorClick;
537
+ const pendingEditorClick = this.#pendingEditorClick;
538
+ if (pendingEditorClick !== undefined) {
531
539
  this.#pendingEditorClick = undefined;
532
- if (pendingEditorClick !== undefined && this.#editorPointerFrame !== undefined) {
533
- this.#viewport.clearSelection();
540
+ if (this.#editorPointerFrame !== undefined) {
534
541
  const editorRow = pendingEditorClick.row - this.#editorPointerFrame.rowStart + 1;
535
542
  this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: editorRow });
536
543
  this.#editor.handlePointer({ kind: "release", button: 0, column: pendingEditorClick.column, row: editorRow });
537
544
  }
538
- else if (this.#copyOnSelect) {
545
+ forceRepaint = true;
546
+ repaint = true;
547
+ return true;
548
+ }
549
+ if (this.#viewport.releaseSelection()) {
550
+ this.#stopSelectionAutoScroll();
551
+ if (this.#copyOnSelect)
539
552
  completedCopy = this.#viewport.captureSelectedText() ?? undefined;
540
- }
541
553
  // Platform: restore OSC 8 links only after the held-button selection paint has
542
554
  // ended, then overwrite any terminal-cached hover cells immediately.
543
555
  forceRepaint = true;
@@ -18,6 +18,7 @@ export function cliUsage(capabilities) {
18
18
  "remove <source>",
19
19
  "uninstall <source>",
20
20
  "list",
21
+ "session worktrees",
21
22
  "session link-worktree <path>",
22
23
  "session unlink-worktree",
23
24
  ]);
@@ -47,6 +48,7 @@ export function cliHelp(capabilities) {
47
48
  ` ${command} list`,
48
49
  "",
49
50
  "Session context:",
51
+ ` ${command} session worktrees`,
50
52
  ` ${command} session link-worktree <path>`,
51
53
  ` ${command} session unlink-worktree`,
52
54
  "",
@@ -127,12 +129,12 @@ function parseSessionContext(rest) {
127
129
  return { kind: "error", message: PRODUCT_TEXT.diagnostic("session link-worktree accepts one path.") };
128
130
  return { kind: "session-context", request: { action, path: value } };
129
131
  }
130
- if (action === "unlink-worktree") {
132
+ if (action === "unlink-worktree" || action === "worktrees") {
131
133
  if (value !== undefined)
132
- return { kind: "error", message: PRODUCT_TEXT.diagnostic("session unlink-worktree does not accept arguments.") };
134
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`session ${action} does not accept arguments.`) };
133
135
  return { kind: "session-context", request: { action } };
134
136
  }
135
- return { kind: "error", message: PRODUCT_TEXT.diagnostic("session accepts link-worktree <path> or unlink-worktree.") };
137
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic("session accepts worktrees, link-worktree <path>, or unlink-worktree.") };
136
138
  }
137
139
  /**
138
140
  * Stable update is the empty form. `--develop` selects the moving development
@@ -2,8 +2,12 @@ import { type SessionContextRequest } from "../features/launch/index.js";
2
2
  export type { SessionContextRequest } from "../features/launch/index.js";
3
3
  export interface SessionContextCommandOptions {
4
4
  readonly environment?: NodeJS.ProcessEnv;
5
+ readonly inspectProcess?: (pid: number) => Promise<{
6
+ readonly pid: number;
7
+ readonly startIdentity: string;
8
+ } | null>;
5
9
  readonly stdout?: (message: string) => void;
6
10
  readonly stderr?: (message: string) => void;
7
11
  }
8
- /** Apply repository presentation context only when invoked by an active Pi session shell tool. */
12
+ /** Apply repository context only when invoked by an exact live A1 shell-tool runtime. */
9
13
  export declare function runSessionContextCommand(request: SessionContextRequest, options?: SessionContextCommandOptions): Promise<number>;
@@ -1,29 +1,46 @@
1
1
  import { updateSessionRepositoryContext, } from "../features/launch/index.js";
2
- import { PRODUCT_TEXT } from "../product-identity.js";
3
- /** Apply repository presentation context only when invoked by an active Pi session shell tool. */
2
+ import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../product-identity.js";
3
+ /** Apply repository context only when invoked by an exact live A1 shell-tool runtime. */
4
4
  export async function runSessionContextCommand(request, options = {}) {
5
5
  const environment = options.environment ?? process.env;
6
6
  const stdout = options.stdout ?? (message => process.stdout.write(message));
7
7
  const stderr = options.stderr ?? (message => process.stderr.write(message));
8
8
  const identity = readIdentity(environment);
9
9
  if (identity === null) {
10
- stderr(`${PRODUCT_TEXT.diagnostic("session context requires an active A1 shell-tool session.")}\n`);
10
+ stderr(`${PRODUCT_TEXT.diagnostic("session context requires an active A1 shell-tool session runtime.")}\n`);
11
11
  return 2;
12
12
  }
13
13
  try {
14
- const cwd = await updateSessionRepositoryContext(identity, request);
15
- stdout(cwd === null ? "Session worktree context cleared.\n" : `Session worktree context linked: ${cwd}\n`);
14
+ if (options.inspectProcess === undefined)
15
+ throw Object.assign(new Error("native process identity inspection is unavailable"), { code: "process-inspection-unavailable" });
16
+ const outcome = await updateSessionRepositoryContext({ sessionId: identity.sessionId, sessionFile: identity.sessionFile }, request, { runtimeId: identity.runtimeId, inspectProcess: options.inspectProcess });
17
+ if (outcome.kind === "unlinked")
18
+ stdout("Session worktree context cleared.\n");
19
+ if (outcome.kind === "linked")
20
+ stdout(`Session worktree context linked: ${outcome.cwd}\n`);
21
+ if (outcome.kind === "worktrees") {
22
+ for (const entry of outcome.entries)
23
+ stdout(`${entry.status}: ${entry.cwd} (${entry.branch})\n`);
24
+ }
16
25
  return 0;
17
26
  }
18
27
  catch (error) {
19
- stderr(`${PRODUCT_TEXT.diagnostic(`could not update session context: ${error instanceof Error ? error.message : String(error)}`)}\n`);
28
+ const code = errorCode(error);
29
+ const detail = error instanceof Error ? error.message : String(error);
30
+ stderr(`${PRODUCT_TEXT.diagnostic(`could not update session context${code === null ? "" : ` (${code})`}: ${detail}`)}\n`);
20
31
  return 1;
21
32
  }
22
33
  }
23
34
  function readIdentity(environment) {
24
35
  const sessionId = environment.PI_SESSION_ID;
25
36
  const sessionFile = environment.PI_SESSION_FILE;
26
- if (!sessionId || !sessionFile)
37
+ const runtimeId = environment[PRODUCT_IDENTITY.environment.sessionRuntimeId];
38
+ if (!sessionId || !sessionFile || !runtimeId)
27
39
  return null;
28
- return { sessionId, sessionFile };
40
+ return { sessionId, sessionFile, runtimeId };
41
+ }
42
+ function errorCode(error) {
43
+ if (error instanceof Error && "code" in error && typeof error.code === "string")
44
+ return error.code;
45
+ return null;
29
46
  }
@@ -1,4 +1,5 @@
1
1
  import type { AvailableRelease, StartupReleaseCheckOptions } from "../foundation/release/latest-release.js";
2
+ import type { NativeProcessIdentity } from "../foundation/lifecycle/model.js";
2
3
  import type { SessionSelection } from "../foundation/lifecycle/session-selection.js";
3
4
  import type { PiEngineAdapter } from "../integrations/pi/engine/adapter.js";
4
5
  import type { PiProjectTrustPreflightPrompt } from "../integrations/pi/engine/project-trust-preflight.js";
@@ -34,6 +35,8 @@ export interface OwnedUiCompositionOptions {
34
35
  readonly releaseNotes?: ReleaseNoteCatalog;
35
36
  /** Startup release-availability seam; defaults to the throttled registry check. */
36
37
  readonly checkForNewerRelease?: (options: StartupReleaseCheckOptions) => Promise<AvailableRelease | null>;
38
+ /** Exact native process inspection injected by the shipped entry; absent SDK seams do not publish claims. */
39
+ readonly inspectProcess?: (pid: number) => Promise<NativeProcessIdentity | null>;
37
40
  }
38
41
  export interface OwnedUiComposition {
39
42
  readonly application: OwnedUiApplicationPort;
@@ -6,8 +6,7 @@ import { PromptImageSidecar } from "../features/prompt-history/image-sidecar.js"
6
6
  import { resolvePromptHistoryPath } from "../features/prompt-history/paths.js";
7
7
  import { resolvePromptHistoryDataDir } from "../features/launch/profile-paths.js";
8
8
  import { resolveProductPaths } from "../foundation/lifecycle/paths.js";
9
- import { readSessionRepositoryContext } from "../foundation/lifecycle/session-repository-context.js";
10
- import { applyConfiguredPiTheme, getAvailablePiThemes } from "../integrations/pi/components/upstream/theme/theme.js";
9
+ import { applyConfiguredPiTheme, getAvailablePiThemes, setPiAccentColor, setPiPackageBorderProjectionEnabled } from "../integrations/pi/components/upstream/theme/theme.js";
11
10
  import { createPiEngineAdapter } from "../integrations/pi/engine/adapter.js";
12
11
  import { ClipboardDiagnosticCapture } from "../app/session-shell/clipboard-diagnostics.js";
13
12
  import { OwnedUiSessionShell } from "../app/session-shell/session-shell.js";
@@ -16,12 +15,46 @@ import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/presentat
16
15
  import { createOwnedRouteHost } from "./settings-route-host.js";
17
16
  import { renderPiShellChangelogLines } from "../integrations/pi/components/shell-presenters-info.js";
18
17
  import { nativeHyperlinkStyle } from "../ui/components/spans.js";
18
+ const SESSION_RUNTIME_REFRESH_MS = 30_000;
19
19
  export async function composeOwnedUiApplication(options = {}) {
20
20
  return (await composeOwnedUi(options)).application;
21
21
  }
22
22
  export async function composeOwnedUi(options = {}) {
23
23
  const cwd = options.cwd ?? process.cwd();
24
24
  const ownedSurfaces = options.ownedSurfaces !== "off";
25
+ const sessionRuntimeId = ownedSurfaces ? process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] : undefined;
26
+ const inspectSessionProcess = options.inspectProcess;
27
+ let sessionRuntimeIdentity = null;
28
+ let activeSessionIdentity = null;
29
+ let sessionRuntimeRefreshTimer = null;
30
+ let sessionRuntimeRefresh = Promise.resolve();
31
+ let sessionRuntimeDisposed = false;
32
+ const runtimeIdentity = async () => {
33
+ if (sessionRuntimeId === undefined || inspectSessionProcess === undefined)
34
+ return null;
35
+ sessionRuntimeIdentity ??= inspectSessionProcess(process.pid).catch(() => null);
36
+ return await sessionRuntimeIdentity;
37
+ };
38
+ const queueSessionRuntimeRefresh = () => {
39
+ if (sessionRuntimeDisposed || activeSessionIdentity === null || sessionRuntimeId === undefined || inspectSessionProcess === undefined)
40
+ return;
41
+ sessionRuntimeRefresh = sessionRuntimeRefresh.then(async () => {
42
+ const identity = activeSessionIdentity;
43
+ if (sessionRuntimeDisposed || identity === null)
44
+ return;
45
+ const owner = await runtimeIdentity();
46
+ if (owner === null)
47
+ return;
48
+ const { registerSessionRepositoryRuntime } = await import("../foundation/lifecycle/session-repository-context.js");
49
+ await registerSessionRepositoryRuntime(identity, sessionRuntimeId, owner, { inspectProcess: inspectSessionProcess });
50
+ }).catch(() => undefined);
51
+ };
52
+ const armSessionRuntimeRefresh = () => {
53
+ if (sessionRuntimeRefreshTimer !== null)
54
+ return;
55
+ sessionRuntimeRefreshTimer = setInterval(queueSessionRuntimeRefresh, SESSION_RUNTIME_REFRESH_MS);
56
+ sessionRuntimeRefreshTimer.unref();
57
+ };
25
58
  const adapter = options.createPiAdapter
26
59
  ? await options.createPiAdapter()
27
60
  : await createPiEngineAdapter({
@@ -30,7 +63,16 @@ export async function composeOwnedUi(options = {}) {
30
63
  settingsProductMode: ownedSurfaces ? "bare" : "comparison",
31
64
  announceStartupChangelog: !ownedSurfaces,
32
65
  ...(ownedSurfaces ? {
33
- repositoryContextReader: async (sessionId, sessionFile, signal) => await readSessionRepositoryContext({ sessionId, sessionFile }, { signal }),
66
+ repositoryContextReader: async (sessionId, sessionFile, signal) => {
67
+ const owner = await runtimeIdentity();
68
+ if (owner === null || sessionRuntimeId === undefined || inspectSessionProcess === undefined)
69
+ return null;
70
+ activeSessionIdentity = { sessionId, sessionFile };
71
+ const { activateSessionRepositoryContext } = await import("../foundation/lifecycle/session-repository-context.js");
72
+ const context = await activateSessionRepositoryContext(activeSessionIdentity, sessionRuntimeId, owner, { signal, inspectProcess: inspectSessionProcess });
73
+ armSessionRuntimeRefresh();
74
+ return context;
75
+ },
34
76
  } : {}),
35
77
  ...(options.sessionPath === undefined ? {} : { sessionPath: options.sessionPath }),
36
78
  ...(options.sessionSelection === undefined ? {} : { sessionSelection: options.sessionSelection }),
@@ -67,10 +109,15 @@ export async function composeOwnedUi(options = {}) {
67
109
  : await import("../features/owned-ui/release-note-state.js").then(module => module.claimReleaseNote({
68
110
  configDir: productPaths.configDir, profileId: options.profileId, version: currentReleaseNote.version,
69
111
  }));
70
- // Compatibility: bare A1 intentionally ships one visual target while its UI is being completed:
71
- // dark, regardless of terminal detection or a previously stored Pi theme. The
72
- // comparison profile keeps Pi's configured theme behavior and settings surface.
112
+ // Compatibility: bare A1 intentionally ships one base visual target while its UI is being completed:
113
+ // dark, regardless of terminal detection or a previously stored Pi theme. Its owned accent projects
114
+ // over that base; comparison keeps Pi's configured theme and unmodified semantic accent.
115
+ setPiPackageBorderProjectionEnabled(ownedSurfaces);
116
+ setPiAccentColor(settings !== null && ownedSurfaces ? settings.value("accentColor") : "purple");
73
117
  applyConfiguredPiTheme(ownedSurfaces ? "dark" : adapter.configuredTheme());
118
+ const unsubscribeAccent = settings === null || !ownedSurfaces
119
+ ? () => { }
120
+ : settings.onChange(current => setPiAccentColor(current.value("accentColor")));
74
121
  // Rationale: Routes open only after shell construction.
75
122
  const references = {
76
123
  changelog: async (input) => {
@@ -167,6 +214,7 @@ export async function composeOwnedUi(options = {}) {
167
214
  });
168
215
  }
169
216
  catch (error) {
217
+ unsubscribeAccent();
170
218
  await releaseNoteClaim?.release();
171
219
  clipboardDiagnostics?.dispose();
172
220
  suggestionDiagnostics?.dispose();
@@ -211,6 +259,18 @@ export async function composeOwnedUi(options = {}) {
211
259
  await releaseNoteAcknowledgement?.catch(() => undefined);
212
260
  }
213
261
  finally {
262
+ unsubscribeAccent();
263
+ sessionRuntimeDisposed = true;
264
+ if (sessionRuntimeRefreshTimer !== null)
265
+ clearInterval(sessionRuntimeRefreshTimer);
266
+ sessionRuntimeRefreshTimer = null;
267
+ await sessionRuntimeRefresh;
268
+ const owner = await runtimeIdentity();
269
+ if (sessionRuntimeId !== undefined && owner !== null) {
270
+ await import("../foundation/lifecycle/session-repository-context.js")
271
+ .then(module => module.releaseSessionRepositoryRuntime(sessionRuntimeId, owner))
272
+ .catch(() => undefined);
273
+ }
214
274
  await releaseNoteClaim?.release();
215
275
  suggestionDiagnostics?.dispose();
216
276
  clipboardDiagnostics?.dispose();
@@ -1,6 +1,6 @@
1
1
  import { CHANGELOG_ROUTE, HOTKEYS_ROUTE, HOTKEYS_TITLE } from "../features/owned-ui/reference-routes.js";
2
2
  import { SETTINGS_APP_ID, SETTINGS_ROUTE } from "../features/owned-ui/settings-route.js";
3
- import { piTheme } from "../integrations/pi/components/upstream/theme/theme.js";
3
+ import { piTheme, renderPiAccentPreview } from "../integrations/pi/components/upstream/theme/theme.js";
4
4
  import { faint } from "../ui/components/text.js";
5
5
  /** Declares settings and lazy document routes. */
6
6
  export function createOwnedRouteHost(settings, references) {
@@ -130,6 +130,7 @@ function pinnedTheme() {
130
130
  fg: (token, text) => piTheme().fg(token, text),
131
131
  bold: (text) => piTheme().bold(text),
132
132
  plain: (text) => text,
133
+ accentPreview: (color, text) => renderPiAccentPreview(color, text),
133
134
  disabled: (text) => faint(piTheme().fg("dim", text)),
134
135
  highlight: (text) => piTheme().bg("selectedBg", text),
135
136
  panel: (text) => `\u001b[48;2;55;55;55m${text}\u001b[49m`,
@@ -1,3 +1,5 @@
1
+ /** Stable stored choices for bare A1's semantic UI accent. */
2
+ export type UiAccentColor = "purple" | "blue" | "cyan" | "green" | "orange" | "pink";
1
3
  export { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiExtensionUiPort, } from "./extension-ui.js";
2
4
  export type { OwnedUiExtensionAutocompleteItem, OwnedUiExtensionAutocompleteProvider, OwnedUiExtensionAutocompleteProviderFactory, OwnedUiExtensionAutocompleteSuggestions, OwnedUiExtensionComponent, OwnedUiExtensionComponentFactory, OwnedUiExtensionComponentFactoryContext, OwnedUiExtensionCustomFactory, OwnedUiExtensionCustomOptions, OwnedUiExtensionDialogOptions, OwnedUiExtensionEditorComponent, OwnedUiExtensionEditorFactory, OwnedUiExtensionEditorThemePort, OwnedUiExtensionEntryRenderInput, OwnedUiExtensionFooterDataPort, OwnedUiExtensionFooterFactory, OwnedUiExtensionInputListener, OwnedUiExtensionKeybindingsPort, OwnedUiExtensionMessageRenderInput, OwnedUiExtensionNotificationSeverity, OwnedUiExtensionOverlayAnchor, OwnedUiExtensionOverlayHandle, OwnedUiExtensionOverlayMargin, OwnedUiExtensionOverlayOptions, OwnedUiExtensionRenderCallback, OwnedUiExtensionRendererPort, OwnedUiExtensionRuntimePort, OwnedUiExtensionSelectListThemePort, OwnedUiExtensionSizeValue, OwnedUiExtensionTerminalPort, OwnedUiExtensionThemeBackground, OwnedUiExtensionThemeColor, OwnedUiExtensionThemeInfo, OwnedUiExtensionThemePort, OwnedUiExtensionToolRenderContext, OwnedUiExtensionUiCallback, OwnedUiExtensionUiPort, OwnedUiExtensionUiProperty, OwnedUiExtensionWidgetOptions, OwnedUiExtensionWidgetPlacement, OwnedUiExtensionWorkingIndicatorOptions, } from "./extension-ui.js";
3
5
  export { OWNED_UI_CONTRACT_VERSION } from "./model.js";
@@ -7,8 +9,8 @@ export { DEFAULT_PROMPT_IMAGE_LIMIT, ImageAttachmentError, MAX_CLIPBOARD_TEXT_BY
7
9
  export { CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, normalizePromptSuggestionCandidate } from "./prompt-suggestions.js";
8
10
  export { canonicalPromptChipMatches, protectPromptChipWrapping, replaceCanonicalPromptChips } from "./prompt-chips.js";
9
11
  export type { PromptChipTextMatch, PromptChipWrapProtection } from "./prompt-chips.js";
10
- export { SUGGESTION_DECISION_REASONS, SUGGESTION_DIAGNOSTIC_EVENTS } from "./suggestion-diagnostics.js";
11
- export type { SuggestionDecision, SuggestionDecisionReason, SuggestionDiagnosticEvent, SuggestionDiagnosticObserver, SuggestionDiagnosticRecord, } from "./suggestion-diagnostics.js";
12
+ export { SUGGESTION_ATTEMPT_TRIGGERS, SUGGESTION_DECISION_REASONS, SUGGESTION_DIAGNOSTIC_EVENTS } from "./suggestion-diagnostics.js";
13
+ export type { SuggestionAttemptTrigger, SuggestionDecision, SuggestionDecisionReason, SuggestionDiagnosticEvent, SuggestionDiagnosticObserver, SuggestionDiagnosticRecord, } from "./suggestion-diagnostics.js";
12
14
  export { PROMPT_HISTORY_EDITOR_REPLACEMENT, PROMPT_HISTORY_MAX_ENTRY_BYTES, PROMPT_HISTORY_MAX_PENDING, PROMPT_HISTORY_MAX_TEXT_BYTES, assertPromptHistorySubmission, } from "./prompt-history.js";
13
15
  export type { PromptHistoryFailure, PromptHistoryImageSidecarAttachment, PromptHistoryImageSidecarPort, PromptHistoryImageSidecarRecord, PromptHistoryKind, PromptHistoryPort, PromptHistoryResult, PromptHistorySnapshot, PromptHistorySubmission, } from "./prompt-history.js";
14
16
  export { assertOwnedUiCommand, assertOwnedUiCustomization, assertOwnedUiDiagnostics, assertOwnedUiEditorState, assertOwnedUiEvent, assertOwnedUiPromptSuggestionIdentity, assertOwnedUiPromptSuggestionRequest, assertOwnedUiPromptSuggestionResult, assertOwnedUiPromptSuggestionState, assertOwnedUiSessionViewModel, assertOwnedUiSnapshot, assertOwnedUiStatusView, assertOwnedUiTerminalSurface, assertOwnedUiTranscriptBlock, } from "./validation.js";
@@ -4,6 +4,6 @@ export { acceptsTranscriptUpdate, transcriptToolState } from "./transcript-lifec
4
4
  export { DEFAULT_PROMPT_IMAGE_LIMIT, ImageAttachmentError, MAX_CLIPBOARD_TEXT_BYTES, MAX_IMAGE_DATA_BYTES, MAX_PROMPT_IMAGES, assertImageEncodedSize, assertPromptImages, } from "./image-attachments.js";
5
5
  export { CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, normalizePromptSuggestionCandidate } from "./prompt-suggestions.js";
6
6
  export { canonicalPromptChipMatches, protectPromptChipWrapping, replaceCanonicalPromptChips } from "./prompt-chips.js";
7
- export { SUGGESTION_DECISION_REASONS, SUGGESTION_DIAGNOSTIC_EVENTS } from "./suggestion-diagnostics.js";
7
+ export { SUGGESTION_ATTEMPT_TRIGGERS, SUGGESTION_DECISION_REASONS, SUGGESTION_DIAGNOSTIC_EVENTS } from "./suggestion-diagnostics.js";
8
8
  export { PROMPT_HISTORY_EDITOR_REPLACEMENT, PROMPT_HISTORY_MAX_ENTRY_BYTES, PROMPT_HISTORY_MAX_PENDING, PROMPT_HISTORY_MAX_TEXT_BYTES, assertPromptHistorySubmission, } from "./prompt-history.js";
9
9
  export { assertOwnedUiCommand, assertOwnedUiCustomization, assertOwnedUiDiagnostics, assertOwnedUiEditorState, assertOwnedUiEvent, assertOwnedUiPromptSuggestionIdentity, assertOwnedUiPromptSuggestionRequest, assertOwnedUiPromptSuggestionResult, assertOwnedUiPromptSuggestionState, assertOwnedUiSessionViewModel, assertOwnedUiSnapshot, assertOwnedUiStatusView, assertOwnedUiTerminalSurface, assertOwnedUiTranscriptBlock, } from "./validation.js";
@@ -336,6 +336,10 @@ export type OwnedUiEvent = {
336
336
  readonly model: OwnedUiModelInfo | null;
337
337
  readonly assistantMessageCount: number;
338
338
  readonly successful: boolean;
339
+ readonly stopReason: string | null;
340
+ readonly toolContinuation: boolean;
341
+ /** Pi 1.1 distinguishes user cancellation from an ordinary or failed settlement. */
342
+ readonly aborted: boolean;
339
343
  } | {
340
344
  readonly type: "editor-state";
341
345
  readonly sessionId: OwnedUiSessionId;
@@ -2,12 +2,16 @@ import type { OwnedUiPromptSuggestionReasoning } from "./model.js";
2
2
  export declare const SUGGESTION_DECISION_REASONS: readonly ["disabled", "disposed", "early-conversation", "no-model", "failed-response", "incomplete-response", "tool-continuation", "replacement-input", "modal", "draft", "not-ready", "not-focused", "autocomplete", "prompt-mode", "presentation-unavailable", "ineligible", "stale-identity"];
3
3
  export type SuggestionDecisionReason = typeof SUGGESTION_DECISION_REASONS[number];
4
4
  export type SuggestionDecision = SuggestionDecisionReason | null;
5
- export declare const SUGGESTION_DIAGNOSTIC_EVENTS: readonly ["skipped", "started", "displayed", "empty", "rejected", "provider-failure", "unavailable", "timeout", "cancelled", "stale-result", "presentation-blocked", "late-result-discarded"];
5
+ export declare const SUGGESTION_DIAGNOSTIC_EVENTS: readonly ["skipped", "started", "displayed", "empty", "rejected", "provider-failure", "unavailable", "timeout", "cancelled", "stale-result", "presentation-blocked", "presentation-deferred", "retry-exhausted", "late-result-discarded"];
6
6
  export type SuggestionDiagnosticEvent = typeof SUGGESTION_DIAGNOSTIC_EVENTS[number];
7
+ export declare const SUGGESTION_ATTEMPT_TRIGGERS: readonly ["prefetch", "settlement", "retry"];
8
+ export type SuggestionAttemptTrigger = typeof SUGGESTION_ATTEMPT_TRIGGERS[number];
7
9
  /** Metadata only: never carry a session path, prompt, candidate, or raw error. */
8
10
  export interface SuggestionDiagnosticRecord {
9
11
  readonly event: SuggestionDiagnosticEvent;
10
12
  readonly reason?: SuggestionDecisionReason;
13
+ readonly attempt?: number;
14
+ readonly trigger?: SuggestionAttemptTrigger;
11
15
  readonly session: number;
12
16
  readonly run: number;
13
17
  readonly response: number;
@@ -5,5 +5,7 @@ export const SUGGESTION_DECISION_REASONS = [
5
5
  ];
6
6
  export const SUGGESTION_DIAGNOSTIC_EVENTS = [
7
7
  "skipped", "started", "displayed", "empty", "rejected", "provider-failure", "unavailable",
8
- "timeout", "cancelled", "stale-result", "presentation-blocked", "late-result-discarded",
8
+ "timeout", "cancelled", "stale-result", "presentation-blocked", "presentation-deferred",
9
+ "retry-exhausted", "late-result-discarded",
9
10
  ];
11
+ export const SUGGESTION_ATTEMPT_TRIGGERS = ["prefetch", "settlement", "retry"];
@@ -133,6 +133,13 @@ export function assertOwnedUiEvent(event) {
133
133
  assertOwnedUiModelInfo(event.model);
134
134
  if (typeof event.successful !== "boolean")
135
135
  throw new TypeError("owned-UI settlement success state is invalid");
136
+ if (event.stopReason === undefined)
137
+ throw new TypeError("owned-UI settlement stop reason is missing");
138
+ assertOptionalText(event.stopReason, "owned-UI settlement stop reason", MAX_LABEL_LENGTH);
139
+ if (typeof event.toolContinuation !== "boolean")
140
+ throw new TypeError("owned-UI settlement tool-continuation state is invalid");
141
+ if (typeof event.aborted !== "boolean")
142
+ throw new TypeError("owned-UI settlement aborted state is invalid");
136
143
  return;
137
144
  case "editor-state":
138
145
  assertOwnedUiEditorState(event.editor);
@@ -9,6 +9,6 @@ export type { LaunchProfilePathOptions, LaunchProfilePaths } from "./profile-pat
9
9
  export { prepareInteractiveLaunch } from "./prepare-launch.js";
10
10
  export type { PrepareInteractiveLaunchOptions, PreparedInteractiveLaunch } from "./prepare-launch.js";
11
11
  export { updateSessionRepositoryContext } from "./session-context.js";
12
- export type { SessionContextRequest } from "./session-context.js";
12
+ export type { SessionContextOutcome, SessionContextRequest, SessionContextRuntime } from "./session-context.js";
13
13
  export { runSelectedInteractiveRuntime, selectInteractiveRuntime } from "./runtime-selection.js";
14
14
  export type { InteractiveRuntimeRunners, InteractiveRuntimeSelection, OwnedUiProfileId } from "./runtime-selection.js";
@@ -1,8 +1,23 @@
1
- import { type SessionRepositoryIdentity } from "../../foundation/lifecycle/index.js";
1
+ import { type NativeProcessIdentity, type SessionRepositoryIdentity, type SessionWorktreeInventoryEntry } from "../../foundation/lifecycle/index.js";
2
2
  export type SessionContextRequest = {
3
3
  readonly action: "link-worktree";
4
4
  readonly path: string;
5
5
  } | {
6
6
  readonly action: "unlink-worktree";
7
+ } | {
8
+ readonly action: "worktrees";
9
+ };
10
+ export type SessionContextOutcome = {
11
+ readonly kind: "linked";
12
+ readonly cwd: string;
13
+ } | {
14
+ readonly kind: "unlinked";
15
+ } | {
16
+ readonly kind: "worktrees";
17
+ readonly entries: readonly SessionWorktreeInventoryEntry[];
7
18
  };
8
- export declare function updateSessionRepositoryContext(identity: SessionRepositoryIdentity, request: SessionContextRequest): Promise<string | null>;
19
+ export interface SessionContextRuntime {
20
+ readonly runtimeId: string;
21
+ readonly inspectProcess: (pid: number) => Promise<NativeProcessIdentity | null>;
22
+ }
23
+ export declare function updateSessionRepositoryContext(identity: SessionRepositoryIdentity, request: SessionContextRequest, runtime: SessionContextRuntime): Promise<SessionContextOutcome>;
@@ -1,8 +1,12 @@
1
- import { clearSessionRepositoryContext, setSessionRepositoryContext, } from "../../foundation/lifecycle/index.js";
2
- export async function updateSessionRepositoryContext(identity, request) {
1
+ import { clearSessionRepositoryContext, listSessionRepositoryWorktrees, setSessionRepositoryContext, } from "../../foundation/lifecycle/index.js";
2
+ export async function updateSessionRepositoryContext(identity, request, runtime) {
3
+ const options = { runtimeId: runtime.runtimeId, inspectProcess: runtime.inspectProcess };
3
4
  if (request.action === "unlink-worktree") {
4
- await clearSessionRepositoryContext(identity);
5
- return null;
5
+ await clearSessionRepositoryContext(identity, options);
6
+ return { kind: "unlinked" };
6
7
  }
7
- return (await setSessionRepositoryContext(identity, request.path)).cwd;
8
+ if (request.action === "worktrees") {
9
+ return { kind: "worktrees", entries: await listSessionRepositoryWorktrees(identity, options) };
10
+ }
11
+ return { kind: "linked", cwd: (await setSessionRepositoryContext(identity, request.path, options)).cwd };
8
12
  }
@@ -343,25 +343,27 @@ export class SettingsApp {
343
343
  this.#apply(menu.entry, value);
344
344
  return { consumed: true };
345
345
  }
346
- if (this.#structured !== null) {
347
- // Invariant: the structured surface is keyboard-only. It owns every pointer report so
348
- // neither its selection nor the Settings list behind it can react.
349
- return { consumed: true, render: false };
350
- }
351
346
  if (event.kind === "wheel-up" || event.kind === "wheel-down") {
352
347
  // Invariant: the whole list pane owns wheel scrolling, including blank space beside
353
- // short labels. It must not depend on finding an item under the pointer.
348
+ // short labels. A fixed structured dialog keeps that content interaction available.
354
349
  const screenRow = event.row - 1;
355
350
  const wheelBottom = this.#filter === null
356
351
  ? this.#bodyTopForFrame + this.#bodyHeightForFrame
357
352
  : this.#panelTopForFrame + SETTINGS_SEARCH_INPUT_ROWS + SETTINGS_STATUS_ROWS;
358
- if (screenRow < 0 || screenRow >= wheelBottom)
353
+ if (screenRow >= 0 && screenRow < wheelBottom) {
354
+ const distance = scrollbarWheelRows(this.#scrollbarSpeed());
355
+ if (this.#filter !== null)
356
+ this.#scrollBeforeFilter = null;
357
+ this.#scroll = Math.max(0, this.#scroll + (event.kind === "wheel-down" ? distance : -distance));
358
+ return { consumed: true };
359
+ }
360
+ if (this.#structured === null)
359
361
  return { consumed: false };
360
- const distance = scrollbarWheelRows(this.#scrollbarSpeed());
361
- if (this.#filter !== null)
362
- this.#scrollBeforeFilter = null;
363
- this.#scroll = Math.max(0, this.#scroll + (event.kind === "wheel-down" ? distance : -distance));
364
- return { consumed: true };
362
+ }
363
+ if (this.#structured !== null) {
364
+ // Invariant: the structured surface is keyboard-only. Non-content-wheel pointer reports
365
+ // cannot change its selection or act on the Settings list behind it.
366
+ return { consumed: true, render: false };
365
367
  }
366
368
  const rail = this.#railPointer(event);
367
369
  if (rail.owned)
@@ -623,9 +625,7 @@ export class SettingsApp {
623
625
  const failure = changeFailure(outcome);
624
626
  if (failure === null) {
625
627
  this.#recordUndo({ kind: "structured", sequence, entry: open.entry, previous, forward });
626
- this.#notice = outcome.status === "deferred" && outcome.application !== null
627
- ? `${labelOf(open.entry)} is stored and applies ${applicationLabel(outcome.application)}`
628
- : null;
628
+ this.#notice = null;
629
629
  return;
630
630
  }
631
631
  // Concurrency: only roll back the dialog if no later press has moved the record on.
@@ -766,9 +766,7 @@ export class SettingsApp {
766
766
  // Concurrency: a later press may have moved on; only the last request clears itself.
767
767
  if (this.#pending.get(key) === value)
768
768
  this.#pending.delete(key);
769
- this.#notice = outcome.status === "deferred" && outcome.application !== null
770
- ? `${labelOf(entry)} is stored and applies ${applicationLabel(outcome.application)}`
771
- : null;
769
+ this.#notice = null;
772
770
  });
773
771
  }
774
772
  #shownValue(entry) {
@@ -825,9 +823,7 @@ export class SettingsApp {
825
823
  this.#notice = `Could not restore ${labelOf(record.entry)}: ${failure}`;
826
824
  }
827
825
  else {
828
- this.#notice = outcome.status === "deferred" && outcome.application !== null
829
- ? `${labelOf(record.entry)} is restored and applies ${applicationLabel(outcome.application)}`
830
- : null;
826
+ this.#notice = null;
831
827
  }
832
828
  this.#undoActive = false;
833
829
  this.#drainUndo();
@@ -925,7 +921,7 @@ export class SettingsApp {
925
921
  ? CONFIGURE
926
922
  : shown === null
927
923
  ? describeRaw(entry.rawValue)
928
- : this.#pending.has(key) ? displayValue(shown) : effectiveDisplay(entry, shown);
924
+ : displayValue(shown);
929
925
  const range = rangeOf(entry);
930
926
  return {
931
927
  key,
@@ -943,6 +939,9 @@ export class SettingsApp {
943
939
  }
944
940
  const state = {
945
941
  choices: menu.choices.map(choice => displayValue(choice)),
942
+ ...(menu.entry.id === "accentColor" ? {
943
+ previews: menu.choices.map(choice => theme.accentPreview?.(String(choice), "■") ?? ""),
944
+ } : {}),
946
945
  current: this.#shownValue(menu.entry) === null ? null : displayValue(this.#shownValue(menu.entry)),
947
946
  index: menu.index,
948
947
  };
@@ -1098,23 +1097,6 @@ function displayValue(value) {
1098
1097
  return value ? "yes" : "no";
1099
1098
  return String(value);
1100
1099
  }
1101
- function effectiveDisplay(entry, stored) {
1102
- const effective = entry.effectiveValue;
1103
- if (effective === stored)
1104
- return displayValue(stored);
1105
- const shownEffective = typeof effective === "string" || typeof effective === "number" || typeof effective === "boolean"
1106
- ? displayValue(effective)
1107
- : describeRaw(effective);
1108
- return `${displayValue(stored)} (effective ${shownEffective}; ${applicationLabel(entry.application)})`;
1109
- }
1110
- function applicationLabel(application) {
1111
- switch (application) {
1112
- case "live": return "live";
1113
- case "next-session": return "in the next session";
1114
- case "next-start": return "on the next start";
1115
- case "current-exit": return "when the current session exits";
1116
- }
1117
- }
1118
1100
  function describeRaw(value) {
1119
1101
  if (value === null || value === undefined)
1120
1102
  return "unset";