pi-one-ui 0.1.1 → 0.2.1

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 (113) hide show
  1. package/README.md +66 -39
  2. package/extensions/{shell → app/commands}/settings-previews.ts +7 -7
  3. package/extensions/app/config/renderer.ts +13 -63
  4. package/extensions/app/config/shell.ts +24 -138
  5. package/extensions/app/config/store.ts +290 -0
  6. package/extensions/app/host/pi-extension-port.ts +25 -0
  7. package/extensions/app/host/pi-ui-port.ts +52 -0
  8. package/extensions/app/host/tui-capabilities.ts +27 -0
  9. package/extensions/app/overlay/input-router.ts +63 -0
  10. package/extensions/app/overlay/overlay-manager.ts +39 -0
  11. package/extensions/{shell → app/overlay}/selector-border.ts +3 -3
  12. package/extensions/app/overlay/selector-controller.ts +104 -0
  13. package/extensions/app/ownership/layout-registry.ts +65 -0
  14. package/extensions/app/ownership.ts +21 -7
  15. package/extensions/app/panel.ts +315 -143
  16. package/extensions/app/runtime/event-coordinator.ts +84 -0
  17. package/extensions/app/runtime/render-scheduler.ts +56 -0
  18. package/extensions/app/runtime/runtime-state.ts +83 -0
  19. package/extensions/app/runtime/tui-runtime.ts +546 -0
  20. package/extensions/features/{context.ts → context-inspector/index.ts} +30 -23
  21. package/extensions/features/{shell/flush-docked-bash.ts → flush-docked-bash.ts} +1 -4
  22. package/extensions/index.ts +2 -48
  23. package/extensions/layouts/context/index.ts +188 -0
  24. package/extensions/{shell → layouts/context/message}/user-message-styles.ts +2 -2
  25. package/extensions/{shell → layouts/context/message}/user-message.ts +2 -2
  26. package/extensions/{transcript → layouts/context}/renderer/compact-mode.ts +17 -13
  27. package/extensions/layouts/context/renderer/context-refresh.ts +79 -0
  28. package/extensions/{transcript → layouts/context}/renderer/default-mode.ts +3 -3
  29. package/extensions/{transcript → layouts/context}/renderer/index.ts +14 -14
  30. package/extensions/{transcript → layouts/context}/renderer/mouse/hover.ts +2 -2
  31. package/extensions/{transcript → layouts/context}/renderer/mouse/interaction.ts +26 -7
  32. package/extensions/{transcript → layouts/context}/renderer/mouse/layout.ts +1 -1
  33. package/extensions/{transcript → layouts/context}/renderer/mouse/packets.ts +3 -3
  34. package/extensions/{transcript → layouts/context}/renderer/mouse/scroll.ts +12 -7
  35. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-component.ts +2 -2
  36. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-edit-render.ts +1 -1
  37. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-highlight.ts +1 -1
  38. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-layout.ts +1 -1
  39. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-limits.ts +1 -1
  40. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-palette.ts +1 -1
  41. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-presentation.ts +1 -1
  42. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-renderer.ts +1 -1
  43. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-write-render.ts +1 -1
  44. package/extensions/{transcript → layouts/context}/renderer/tool/diff/index.ts +2 -2
  45. package/extensions/{transcript → layouts/context}/renderer/tool/grouping.ts +4 -4
  46. package/extensions/{transcript → layouts/context}/renderer/tool/message-display.ts +6 -4
  47. package/extensions/{transcript → layouts/context}/renderer/tool/names.ts +2 -2
  48. package/extensions/{transcript → layouts/context}/renderer/tool/result.ts +3 -3
  49. package/extensions/{features/agent-summary → layouts/context/summary}/core.ts +1 -1
  50. package/extensions/{features → layouts/context/thinking}/compact-thinking.ts +7 -7
  51. package/extensions/{shell → layouts/editor}/accent-rail-editor.ts +8 -8
  52. package/extensions/{shell → layouts/editor}/completion-menu.ts +3 -6
  53. package/extensions/layouts/editor/controller.ts +613 -0
  54. package/extensions/{shell → layouts/editor}/editor-metadata-format.ts +6 -3
  55. package/extensions/layouts/editor/factory.ts +159 -0
  56. package/extensions/layouts/editor/index.ts +16 -0
  57. package/extensions/{shell → layouts/editor}/minimalist-editor.ts +4 -4
  58. package/extensions/layouts/editor/ownership.ts +103 -0
  59. package/extensions/{shell → layouts/editor}/ui.ts +2 -2
  60. package/extensions/layouts/footer/controller.ts +392 -0
  61. package/extensions/layouts/footer/data.ts +41 -0
  62. package/extensions/{shell → layouts/footer}/extension-status.ts +2 -2
  63. package/extensions/{shell → layouts/footer}/footer-layout.ts +1 -1
  64. package/extensions/{shell → layouts/footer}/footer.ts +11 -8
  65. package/extensions/layouts/footer/index.ts +24 -0
  66. package/extensions/layouts/header/index.ts +5 -0
  67. package/extensions/{features/shell → layouts/header}/startup-header.ts +105 -19
  68. package/extensions/layouts/working-line/controller.ts +291 -0
  69. package/extensions/{shell → layouts/working-line}/interaction-summary.ts +3 -3
  70. package/extensions/{shell → layouts/working-line}/working-line-spinners.ts +1 -1
  71. package/extensions/{shell → layouts/working-line}/working-line.ts +4 -4
  72. package/extensions/{shell → services}/live-context.ts +29 -1
  73. package/extensions/services/project-refresh.ts +374 -0
  74. package/extensions/{shell → services}/project-state.ts +5 -5
  75. package/extensions/{shell/state.ts → services/session-state.ts} +75 -5
  76. package/extensions/{shell → shared}/format.ts +4 -4
  77. package/extensions/tools/component-tree.ts +6 -6
  78. package/package.json +12 -4
  79. package/extensions/shell/index.ts +0 -1659
  80. package/extensions/shell/project-refresh.ts +0 -130
  81. package/extensions/shell/settings-command.ts +0 -2144
  82. package/extensions/transcript/index.ts +0 -65
  83. package/extensions/transcript/renderer/transcript-refresh.ts +0 -79
  84. /package/extensions/{shell → app/ownership}/prototype-patch-registry.ts +0 -0
  85. /package/extensions/{shell → app/runtime}/session-lifecycle.ts +0 -0
  86. /package/extensions/features/{shell/aliases.ts → aliases.ts} +0 -0
  87. /package/extensions/features/{shell → legacy}/working-message.ts +0 -0
  88. /package/extensions/features/{reference → session-reference}/index.ts +0 -0
  89. /package/extensions/features/{reference → session-reference}/session.ts +0 -0
  90. /package/extensions/features/{reference/subagent.ts → subagent-autocomplete.ts} +0 -0
  91. /package/extensions/{shell → layouts/context/message}/user-message-osc.ts +0 -0
  92. /package/extensions/{transcript → layouts/context}/renderer/markdown-enhance.ts +0 -0
  93. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/ATTRIBUTION.md +0 -0
  94. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/ansi-utils.ts +0 -0
  95. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-header.ts +0 -0
  96. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-inline.ts +0 -0
  97. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-parse.ts +0 -0
  98. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-text.ts +0 -0
  99. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/line-width-safety.ts +0 -0
  100. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/shiki-highlight.ts +0 -0
  101. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/write-execution.ts +0 -0
  102. /package/extensions/{transcript → layouts/context}/renderer/tool/show-more-hint.ts +0 -0
  103. /package/extensions/{features/agent-summary → layouts/context/summary}/index.ts +0 -0
  104. /package/extensions/{shell → layouts/editor}/accent-rail-layout-patch.ts +0 -0
  105. /package/extensions/{shell → layouts/editor}/editor-transfer.ts +0 -0
  106. /package/extensions/{shell → layouts/footer}/footer-format.ts +0 -0
  107. /package/extensions/{shell → layouts/working-line}/working-line-messages.ts +0 -0
  108. /package/extensions/{shell/git.ts → services/git-data.ts} +0 -0
  109. /package/extensions/{shell/package-version.ts → services/package-data.ts} +0 -0
  110. /package/extensions/{shell/runtime.ts → services/runtime-data.ts} +0 -0
  111. /package/extensions/{shell → services}/telemetry.ts +0 -0
  112. /package/extensions/{shell → shared}/icons.ts +0 -0
  113. /package/extensions/{shell → shared}/style.ts +0 -0
@@ -1,26 +1,30 @@
1
- import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
1
+ import {
2
+ type ExtensionCommandContext,
3
+ type ExtensionContext,
4
+ getSettingsListTheme,
5
+ } from "@earendil-works/pi-coding-agent";
2
6
  import {
3
7
  matchesKey,
4
8
  type SettingItem,
5
9
  SettingsList,
6
10
  truncateToWidth,
7
11
  } from "@earendil-works/pi-tui";
8
- import type { ShellRuntimeController } from "../shell/index.ts";
9
12
  import {
10
13
  renderEditorSettingsPreview,
11
14
  renderUserMessageSettingsPreview,
12
- } from "../shell/settings-previews.ts";
13
- import type { TranscriptRuntimeController } from "../transcript/index.ts";
15
+ } from "./commands/settings-previews.ts";
14
16
  import {
15
17
  type CompactStyleMode,
18
+ type Config as ContextConfig,
16
19
  DIFF_INDICATOR_MODES,
17
20
  DIFF_VIEW_MODES,
18
- type Config as RendererConfig,
19
21
  config as rendererConfig,
20
- updateConfig as updateRendererConfig,
22
+ updateConfig as updateContextConfig,
21
23
  } from "./config/renderer.ts";
22
24
  import {
25
+ type EditorComponentConfig,
23
26
  type EditorStyle,
27
+ type FooterComponentConfig,
24
28
  type FooterStyle,
25
29
  loadConfig as loadShellConfig,
26
30
  saveEditorComponentPatch,
@@ -28,8 +32,11 @@ import {
28
32
  saveUserMessagesComponentPatch,
29
33
  saveWorkingLineComponentPatch,
30
34
  type UserMessageStyle,
35
+ type UserMessagesComponentConfig,
36
+ type WorkingLineComponentPatch,
31
37
  type WorkingLineSpinner,
32
38
  } from "./config/shell.ts";
39
+ import { overlayManager } from "./overlay/overlay-manager.ts";
33
40
  import { ownerFor } from "./ownership.ts";
34
41
  import { applyPreset, PRESET_VALUES, type Preset } from "./presets.ts";
35
42
 
@@ -56,8 +63,11 @@ const WORKING_SPINNERS: WorkingLineSpinner[] = [
56
63
  const PREVIEW_LINES = ["0", "1", "3", "5", "10"];
57
64
 
58
65
  const SECTIONS = [
59
- { id: "shell", label: "Shell" },
60
- { id: "renderer", label: "Renderer" },
66
+ { id: "header", label: "Header" },
67
+ { id: "context", label: "Context" },
68
+ { id: "workingLine", label: "WorkingLine" },
69
+ { id: "editor", label: "Editor" },
70
+ { id: "footer", label: "Footer" },
61
71
  { id: "features", label: "Features" },
62
72
  { id: "presets", label: "Presets" },
63
73
  ] as const;
@@ -75,12 +85,38 @@ function renderTabs(theme: any, active: number, width: number): string {
75
85
  return truncateToWidth(text, Math.max(0, width));
76
86
  }
77
87
 
88
+ /** Adds a stable chrome frame around the settings content. */
89
+ function renderPanelFrame(theme: any, width: number, rows: string[]): string[] {
90
+ const safeWidth = Math.max(1, width);
91
+ const border = (text: string) => theme.fg("border", text);
92
+ if (safeWidth === 1)
93
+ return [border("│"), ...rows.map(() => border("│")), border("│")];
94
+
95
+ const innerWidth = safeWidth - 2;
96
+ const content = rows.map(
97
+ (row) =>
98
+ `${border("│")}${truncateToWidth(row, innerWidth, "", true)}${border("│")}`,
99
+ );
100
+ if (safeWidth === 2) return [border("╭╮"), ...content, border("╰╯")];
101
+ return [
102
+ border(`╭${"─".repeat(innerWidth)}╮`),
103
+ ...content,
104
+ border(`╰${"─".repeat(innerWidth)}╯`),
105
+ ];
106
+ }
107
+
78
108
  function sectionDescription(section: SectionId): string {
79
109
  switch (section) {
80
- case "shell":
81
- return `${ownerFor("editor")} owns the editor, user messages, working line, and footer.`;
82
- case "renderer":
83
- return `${ownerFor("toolRenderer")} owns tool cards, rich diffs, thinking, and transcript layout.`;
110
+ case "header":
111
+ return "Header owns startup branding and startup guidance.";
112
+ case "context":
113
+ return `${ownerFor("toolRenderer")} owns messages, tool cards, diffs, thinking, and summaries.`;
114
+ case "workingLine":
115
+ return "WorkingLine is the sole owner of Pi's unkeyed working row.";
116
+ case "editor":
117
+ return `${ownerFor("editor")} owns the input editor factory and its styles.`;
118
+ case "footer":
119
+ return `${ownerFor("footer")} owns footer rendering and project status segments.`;
84
120
  case "features":
85
121
  return "Feature switches are saved immediately and applied after /reload.";
86
122
  case "presets":
@@ -88,13 +124,16 @@ function sectionDescription(section: SectionId): string {
88
124
  }
89
125
  }
90
126
 
91
- function shellItems(): SettingItem[] {
127
+ /**
128
+ * Builds settings owned by the Editor layout.
129
+ */
130
+ function editorItems(): SettingItem[] {
92
131
  const config = loadShellConfig();
93
132
  return [
94
133
  {
95
134
  id: "editorEnabled",
96
135
  label: "Editor",
97
- description: "Enable Zentui's custom input editor.",
136
+ description: "Enable the custom input editor.",
98
137
  currentValue: onOff(config.components.editor.enabled),
99
138
  values: ["on", "off"],
100
139
  },
@@ -105,24 +144,19 @@ function shellItems(): SettingItem[] {
105
144
  currentValue: config.components.editor.style,
106
145
  values: EDITOR_STYLES,
107
146
  },
108
- {
109
- id: "userMessagesEnabled",
110
- label: "User messages",
111
- description: "Enable custom rendering for previous user messages.",
112
- currentValue: onOff(config.components.userMessages.enabled),
113
- values: ["on", "off"],
114
- },
115
- {
116
- id: "userMessagesStyle",
117
- label: "Message style",
118
- description: "Choose the previous user message treatment.",
119
- currentValue: config.components.userMessages.style,
120
- values: MESSAGE_STYLES,
121
- },
147
+ ];
148
+ }
149
+
150
+ /**
151
+ * Builds settings owned by the WorkingLine layout.
152
+ */
153
+ function workingLineItems(): SettingItem[] {
154
+ const config = loadShellConfig();
155
+ return [
122
156
  {
123
157
  id: "workingLineEnabled",
124
158
  label: "Working line",
125
- description: "Zentui is the sole owner of Pi's working row.",
159
+ description: "Enable the sole owner of Pi's working row.",
126
160
  currentValue: onOff(config.components.workingLine.enabled),
127
161
  values: ["on", "off"],
128
162
  },
@@ -133,6 +167,15 @@ function shellItems(): SettingItem[] {
133
167
  currentValue: config.components.workingLine.spinner,
134
168
  values: WORKING_SPINNERS,
135
169
  },
170
+ ];
171
+ }
172
+
173
+ /**
174
+ * Builds settings owned by the Footer layout.
175
+ */
176
+ function footerItems(): SettingItem[] {
177
+ const config = loadShellConfig();
178
+ return [
136
179
  {
137
180
  id: "footerStyle",
138
181
  label: "Footer",
@@ -143,10 +186,28 @@ function shellItems(): SettingItem[] {
143
186
  ];
144
187
  }
145
188
 
146
- function rendererItems(config: RendererConfig): SettingItem[] {
189
+ /**
190
+ * Builds settings owned by the Context layout.
191
+ */
192
+ function contextItems(config: ContextConfig): SettingItem[] {
193
+ const shellConfig = loadShellConfig();
147
194
  return [
148
195
  {
149
- id: "rendererMode",
196
+ id: "userMessagesEnabled",
197
+ label: "User messages",
198
+ description: "Enable custom rendering for previous user messages.",
199
+ currentValue: onOff(shellConfig.components.userMessages.enabled),
200
+ values: ["on", "off"],
201
+ },
202
+ {
203
+ id: "userMessagesStyle",
204
+ label: "Message style",
205
+ description: "Choose the previous user message treatment.",
206
+ currentValue: shellConfig.components.userMessages.style,
207
+ values: MESSAGE_STYLES,
208
+ },
209
+ {
210
+ id: "contextMode",
150
211
  label: "Tool style",
151
212
  description:
152
213
  "on = rich cards, compact = one summary per assistant message, off = native.",
@@ -191,7 +252,25 @@ function rendererItems(config: RendererConfig): SettingItem[] {
191
252
  ];
192
253
  }
193
254
 
194
- function featureItems(config: RendererConfig): SettingItem[] {
255
+ /**
256
+ * Builds settings owned by the Header layout.
257
+ */
258
+ function headerItems(config: ContextConfig): SettingItem[] {
259
+ return [
260
+ {
261
+ id: "showStartupHeader",
262
+ label: "Startup header",
263
+ description: "Show the custom startup header on new sessions.",
264
+ currentValue: onOff(config.showStartupHeader),
265
+ values: ["on", "off"],
266
+ },
267
+ ];
268
+ }
269
+
270
+ /**
271
+ * Builds non-visual feature switches.
272
+ */
273
+ function featureItems(config: ContextConfig): SettingItem[] {
195
274
  return [
196
275
  {
197
276
  id: "enableSessionReference",
@@ -228,13 +307,6 @@ function featureItems(config: RendererConfig): SettingItem[] {
228
307
  currentValue: onOff(config.enableAliases),
229
308
  values: ["on", "off"],
230
309
  },
231
- {
232
- id: "showStartupHeader",
233
- label: "Startup header",
234
- description: "Show CC Style's startup header on new sessions.",
235
- currentValue: onOff(config.showStartupHeader),
236
- values: ["on", "off"],
237
- },
238
310
  ];
239
311
  }
240
312
 
@@ -244,10 +316,10 @@ function presetItems(): SettingItem[] {
244
316
  label: preset,
245
317
  description:
246
318
  preset === "balanced"
247
- ? "Zentui shell + rich CC Style tool renderer."
319
+ ? "Zentui shell + rich CC Style context renderer."
248
320
  : preset === "compact"
249
- ? "Zentui shell + compact CC Style transcript."
250
- : "Disable custom shell and transcript rendering.",
321
+ ? "Zentui shell + compact CC Style context."
322
+ : "Disable custom shell and context rendering.",
251
323
  currentValue: "apply",
252
324
  values: ["apply"],
253
325
  }));
@@ -255,10 +327,16 @@ function presetItems(): SettingItem[] {
255
327
 
256
328
  function itemsFor(section: SectionId): SettingItem[] {
257
329
  switch (section) {
258
- case "shell":
259
- return shellItems();
260
- case "renderer":
261
- return rendererItems(rendererConfig);
330
+ case "header":
331
+ return headerItems(rendererConfig);
332
+ case "context":
333
+ return contextItems(rendererConfig);
334
+ case "workingLine":
335
+ return workingLineItems();
336
+ case "editor":
337
+ return editorItems();
338
+ case "footer":
339
+ return footerItems();
262
340
  case "features":
263
341
  return featureItems(rendererConfig);
264
342
  case "presets":
@@ -270,12 +348,38 @@ function isOn(value: string): boolean {
270
348
  return value === "on";
271
349
  }
272
350
 
351
+ export type TuiPanelRuntime = {
352
+ setEditorComponent: (
353
+ patch: Partial<EditorComponentConfig>,
354
+ ctx: ExtensionContext,
355
+ ) => { applied: boolean; reason?: string };
356
+ setUserMessagesComponent: (
357
+ patch: Partial<UserMessagesComponentConfig>,
358
+ ctx: ExtensionContext,
359
+ ) => void;
360
+ setWorkingLineComponent: (
361
+ patch: WorkingLineComponentPatch,
362
+ ctx: ExtensionContext,
363
+ ) => { applied: boolean; reason?: string };
364
+ setFooterComponent: (
365
+ patch: Partial<FooterComponentConfig>,
366
+ ctx: ExtensionContext,
367
+ ) => void;
368
+ setContextMode: (mode: CompactStyleMode, ctx: ExtensionContext) => void;
369
+ updateContextConfig: (
370
+ patch: Partial<ContextConfig>,
371
+ ctx: ExtensionContext,
372
+ ) => void;
373
+ };
374
+
273
375
  type UnifiedPanelDeps = {
274
- shell?: ShellRuntimeController;
275
- renderer?: TranscriptRuntimeController;
376
+ runtime?: TuiPanelRuntime;
276
377
  };
277
378
 
278
- function renderShellPreview(theme: any, width: number): string[] {
379
+ /**
380
+ * Renders the Editor layout preview for the settings panel.
381
+ */
382
+ function renderEditorPreview(theme: any, width: number): string[] {
279
383
  const config = loadShellConfig();
280
384
  const previewWidth = Math.max(20, Math.min(72, width - 2));
281
385
  return [
@@ -284,6 +388,16 @@ function renderShellPreview(theme: any, width: number): string[] {
284
388
  ...renderEditorSettingsPreview(config, theme, previewWidth).map(
285
389
  (line) => ` ${line}`,
286
390
  ),
391
+ ];
392
+ }
393
+
394
+ /**
395
+ * Renders the Context layout User Message preview for the settings panel.
396
+ */
397
+ function renderContextPreview(theme: any, width: number): string[] {
398
+ const config = loadShellConfig();
399
+ const previewWidth = Math.max(20, Math.min(72, width - 2));
400
+ return [
287
401
  "",
288
402
  theme.fg("muted", " User message preview"),
289
403
  ...renderUserMessageSettingsPreview(config, theme, previewWidth).map(
@@ -299,6 +413,14 @@ function saveNotice(ctx: any, label: string, value: string): void {
299
413
  );
300
414
  }
301
415
 
416
+ /**
417
+ * Persists one panel setting and reconciles its owning runtime layout.
418
+ *
419
+ * @param id Stable settings item identifier.
420
+ * @param value Selected settings value.
421
+ * @param ctx Active Pi extension context.
422
+ * @param deps Live runtime controllers available to the panel.
423
+ */
302
424
  function updateSetting(
303
425
  id: string,
304
426
  value: string,
@@ -306,22 +428,22 @@ function updateSetting(
306
428
  deps: UnifiedPanelDeps,
307
429
  ): void {
308
430
  if (id === "editorEnabled") {
309
- if (deps.shell)
310
- deps.shell.setEditorComponent({ enabled: isOn(value) }, ctx);
431
+ if (deps.runtime)
432
+ deps.runtime.setEditorComponent({ enabled: isOn(value) }, ctx);
311
433
  else saveEditorComponentPatch({ enabled: isOn(value) });
312
434
  saveNotice(ctx, "Editor", value);
313
435
  return;
314
436
  }
315
437
  if (id === "editorStyle" && EDITOR_STYLES.includes(value as EditorStyle)) {
316
- if (deps.shell)
317
- deps.shell.setEditorComponent({ style: value as EditorStyle }, ctx);
438
+ if (deps.runtime)
439
+ deps.runtime.setEditorComponent({ style: value as EditorStyle }, ctx);
318
440
  else saveEditorComponentPatch({ style: value as EditorStyle });
319
441
  saveNotice(ctx, "Editor style", value);
320
442
  return;
321
443
  }
322
444
  if (id === "userMessagesEnabled") {
323
- if (deps.shell)
324
- deps.shell.setUserMessagesComponent({ enabled: isOn(value) }, ctx);
445
+ if (deps.runtime)
446
+ deps.runtime.setUserMessagesComponent({ enabled: isOn(value) }, ctx);
325
447
  else saveUserMessagesComponentPatch({ enabled: isOn(value) });
326
448
  saveNotice(ctx, "User messages", value);
327
449
  return;
@@ -330,8 +452,8 @@ function updateSetting(
330
452
  id === "userMessagesStyle" &&
331
453
  MESSAGE_STYLES.includes(value as UserMessageStyle)
332
454
  ) {
333
- if (deps.shell)
334
- deps.shell.setUserMessagesComponent(
455
+ if (deps.runtime)
456
+ deps.runtime.setUserMessagesComponent(
335
457
  { style: value as UserMessageStyle },
336
458
  ctx,
337
459
  );
@@ -340,8 +462,8 @@ function updateSetting(
340
462
  return;
341
463
  }
342
464
  if (id === "workingLineEnabled") {
343
- if (deps.shell)
344
- deps.shell.setWorkingLineComponent({ enabled: isOn(value) }, ctx);
465
+ if (deps.runtime)
466
+ deps.runtime.setWorkingLineComponent({ enabled: isOn(value) }, ctx);
345
467
  else saveWorkingLineComponentPatch({ enabled: isOn(value) });
346
468
  saveNotice(ctx, "Working line", value);
347
469
  return;
@@ -350,8 +472,8 @@ function updateSetting(
350
472
  id === "workingLineSpinner" &&
351
473
  WORKING_SPINNERS.includes(value as WorkingLineSpinner)
352
474
  ) {
353
- if (deps.shell)
354
- deps.shell.setWorkingLineComponent(
475
+ if (deps.runtime)
476
+ deps.runtime.setWorkingLineComponent(
355
477
  { spinner: value as WorkingLineSpinner },
356
478
  ctx,
357
479
  );
@@ -361,22 +483,23 @@ function updateSetting(
361
483
  return;
362
484
  }
363
485
  if (id === "footerStyle" && FOOTER_STYLES.includes(value as FooterStyle)) {
364
- if (deps.shell)
365
- deps.shell.setFooterComponent({ style: value as FooterStyle }, ctx);
486
+ if (deps.runtime)
487
+ deps.runtime.setFooterComponent({ style: value as FooterStyle }, ctx);
366
488
  else saveFooterComponentPatch({ style: value as FooterStyle });
367
489
  saveNotice(ctx, "Footer", value);
368
490
  return;
369
491
  }
370
- if (id === "rendererMode") {
371
- if (deps.renderer) deps.renderer.setMode(value as CompactStyleMode, ctx);
372
- else updateRendererConfig({ mode: value as CompactStyleMode });
492
+ if (id === "contextMode") {
493
+ if (deps.runtime)
494
+ deps.runtime.setContextMode(value as CompactStyleMode, ctx);
495
+ else updateContextConfig({ mode: value as CompactStyleMode });
373
496
  saveNotice(ctx, "Tool style", value);
374
497
  return;
375
498
  }
376
499
  if (id === "diffViewMode" && DIFF_VIEW_MODES.includes(value as never)) {
377
- const patch = { diffViewMode: value as RendererConfig["diffViewMode"] };
378
- if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
379
- else updateRendererConfig(patch);
500
+ const patch = { diffViewMode: value as ContextConfig["diffViewMode"] };
501
+ if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
502
+ else updateContextConfig(patch);
380
503
  saveNotice(ctx, "Diff layout", value);
381
504
  return;
382
505
  }
@@ -385,31 +508,31 @@ function updateSetting(
385
508
  DIFF_INDICATOR_MODES.includes(value as never)
386
509
  ) {
387
510
  const patch = {
388
- diffIndicatorMode: value as RendererConfig["diffIndicatorMode"],
511
+ diffIndicatorMode: value as ContextConfig["diffIndicatorMode"],
389
512
  };
390
- if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
391
- else updateRendererConfig(patch);
513
+ if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
514
+ else updateContextConfig(patch);
392
515
  saveNotice(ctx, "Diff indicator", value);
393
516
  return;
394
517
  }
395
518
  if (id === "diffWordWrap") {
396
519
  const patch = { diffWordWrap: isOn(value) };
397
- if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
398
- else updateRendererConfig(patch);
520
+ if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
521
+ else updateContextConfig(patch);
399
522
  saveNotice(ctx, "Diff word wrap", value);
400
523
  return;
401
524
  }
402
525
  if (id === "thinkingPreviewLines") {
403
526
  const patch = { previewLines: Number(value) };
404
- if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
405
- else updateRendererConfig(patch);
527
+ if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
528
+ else updateContextConfig(patch);
406
529
  saveNotice(ctx, "Thinking preview", value);
407
530
  return;
408
531
  }
409
532
  if (id === "dimThinkingText") {
410
533
  const patch = { dimThinkingText: isOn(value) };
411
- if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
412
- else updateRendererConfig(patch);
534
+ if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
535
+ else updateContextConfig(patch);
413
536
  saveNotice(ctx, "Thinking text", value);
414
537
  return;
415
538
  }
@@ -421,9 +544,9 @@ function updateSetting(
421
544
  id === "enableAliases" ||
422
545
  id === "showStartupHeader"
423
546
  ) {
424
- const patch = { [id]: isOn(value) } as Partial<RendererConfig>;
425
- if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
426
- else updateRendererConfig(patch);
547
+ const patch = { [id]: isOn(value) } as Partial<ContextConfig>;
548
+ if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
549
+ else updateContextConfig(patch);
427
550
  saveNotice(ctx, id, value);
428
551
  return;
429
552
  }
@@ -431,8 +554,8 @@ function updateSetting(
431
554
  const preset = id.slice("preset:".length);
432
555
  if (PRESET_VALUES.includes(preset as Preset)) {
433
556
  applyPreset(preset as Preset);
434
- if (deps.renderer) {
435
- deps.renderer.setMode(
557
+ if (deps.runtime) {
558
+ deps.runtime.setContextMode(
436
559
  preset === "compact" ? "compact" : preset === "native" ? "off" : "on",
437
560
  ctx,
438
561
  );
@@ -445,8 +568,14 @@ function updateSetting(
445
568
 
446
569
  let unifiedPanelOpen = false;
447
570
 
571
+ /**
572
+ * Opens the unified settings panel as a managed overlay.
573
+ *
574
+ * @param ctx Active TUI extension context.
575
+ * @param deps Runtime controllers used for live updates.
576
+ */
448
577
  export async function showOneUiPanel(
449
- ctx: any,
578
+ ctx: ExtensionCommandContext,
450
579
  deps: UnifiedPanelDeps = {},
451
580
  ): Promise<void> {
452
581
  if (
@@ -464,68 +593,111 @@ export async function showOneUiPanel(
464
593
 
465
594
  unifiedPanelOpen = true;
466
595
  try {
467
- await ctx.ui.custom(
468
- (tui: any, theme: any, _keybindings: any, done: () => void) => {
469
- let activeIndex = 0;
470
- let list: SettingsList;
471
- const createList = () => {
472
- list = new SettingsList(
473
- itemsFor(SECTIONS[activeIndex].id),
474
- 10,
475
- getSettingsListTheme(),
476
- (id, value) => {
477
- updateSetting(id, value, ctx, deps);
478
- list.updateValue(id, value);
479
- tui.requestRender();
480
- },
481
- done,
482
- );
483
- };
484
- createList();
485
-
486
- const switchSection = (delta: number) => {
487
- activeIndex =
488
- (activeIndex + delta + SECTIONS.length) % SECTIONS.length;
596
+ let panelHandle: { focus: () => void } | undefined;
597
+ await overlayManager.run(() =>
598
+ ctx.ui.custom(
599
+ (tui, theme, _keybindings, done) => {
600
+ let activeIndex = 0;
601
+ let list: SettingsList;
602
+ const createList = () => {
603
+ list = new SettingsList(
604
+ itemsFor(SECTIONS[activeIndex].id),
605
+ 10,
606
+ getSettingsListTheme(),
607
+ (id, value) => {
608
+ // Replacing Pi's editor synchronously moves focus away from
609
+ // this overlay. Restore the overlay handle after the editor
610
+ // has been reconciled so the panel remains interactive.
611
+ if (id === "editorEnabled") {
612
+ try {
613
+ updateSetting(id, value, ctx, deps);
614
+ list.updateValue(id, value);
615
+ } catch (error) {
616
+ ctx.ui?.notify?.(
617
+ `Could not update Editor setting: ${error instanceof Error ? error.message : String(error)}`,
618
+ "error",
619
+ );
620
+ } finally {
621
+ panelHandle?.focus();
622
+ tui.requestRender();
623
+ }
624
+ return;
625
+ }
626
+ updateSetting(id, value, ctx, deps);
627
+ list.updateValue(id, value);
628
+ tui.requestRender();
629
+ },
630
+ () => done(undefined),
631
+ );
632
+ };
489
633
  createList();
490
- tui.requestRender();
491
- };
492
634
 
493
- return {
494
- render: (width: number) => [
495
- renderTabs(theme, activeIndex, width),
496
- theme.fg("dim", "─".repeat(Math.max(0, width))),
497
- theme.fg(
498
- "muted",
499
- ` ${sectionDescription(SECTIONS[activeIndex].id)}`,
500
- ),
501
- "",
502
- ...list.render(width),
503
- ...(SECTIONS[activeIndex].id === "shell"
504
- ? renderShellPreview(theme, width)
505
- : []),
506
- "",
507
- truncateToWidth(
508
- theme.fg(
509
- "dim",
510
- " Tab/Shift+Tab switch sections · Enter/Space change · Esc close",
511
- ),
512
- width,
513
- ),
514
- ],
515
- invalidate: () => list.invalidate(),
516
- handleInput: (data: string) => {
517
- if (data === "\x1b[Z" || matchesKey(data, "shift+tab")) {
518
- switchSection(-1);
519
- return;
520
- }
521
- if (matchesKey(data, "tab")) {
522
- switchSection(1);
523
- return;
524
- }
525
- list.handleInput(data);
635
+ const switchSection = (delta: number) => {
636
+ activeIndex =
637
+ (activeIndex + delta + SECTIONS.length) % SECTIONS.length;
638
+ createList();
639
+ tui.requestRender();
640
+ };
641
+
642
+ return {
643
+ render: (width: number) => {
644
+ const contentWidth = Math.max(0, width - 2);
645
+ return renderPanelFrame(theme, width, [
646
+ renderTabs(theme, activeIndex, contentWidth),
647
+ theme.fg("dim", "─".repeat(contentWidth)),
648
+ theme.fg(
649
+ "muted",
650
+ ` ${sectionDescription(SECTIONS[activeIndex].id)}`,
651
+ ),
652
+ "",
653
+ ...list.render(contentWidth),
654
+ ...(SECTIONS[activeIndex].id === "editor"
655
+ ? renderEditorPreview(theme, contentWidth)
656
+ : SECTIONS[activeIndex].id === "context"
657
+ ? renderContextPreview(theme, contentWidth)
658
+ : []),
659
+ "",
660
+ truncateToWidth(
661
+ theme.fg(
662
+ "dim",
663
+ " Tab/Shift+Tab switch sections · Enter/Space change · Esc close",
664
+ ),
665
+ contentWidth,
666
+ ),
667
+ ]);
668
+ },
669
+ invalidate: () => list.invalidate(),
670
+ handleInput: (data: string) => {
671
+ if (data === "\x1b[Z" || matchesKey(data, "shift+tab")) {
672
+ switchSection(-1);
673
+ return;
674
+ }
675
+ if (matchesKey(data, "tab")) {
676
+ switchSection(1);
677
+ return;
678
+ }
679
+ list.handleInput(data);
680
+ },
681
+ };
682
+ },
683
+ {
684
+ overlay: true,
685
+ overlayOptions: {
686
+ anchor: "top-center",
687
+ width: "85%",
688
+ maxHeight: "90%",
689
+ margin: {
690
+ top: 10,
691
+ right: 1,
692
+ bottom: 1,
693
+ left: 1,
694
+ },
695
+ },
696
+ onHandle: (handle: { focus: () => void }) => {
697
+ panelHandle = handle;
526
698
  },
527
- };
528
- },
699
+ },
700
+ ),
529
701
  );
530
702
  } finally {
531
703
  unifiedPanelOpen = false;