pi-one-ui 0.1.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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/extensions/app/config/renderer.ts +452 -0
  4. package/extensions/app/config/shell.ts +2484 -0
  5. package/extensions/app/ownership.ts +18 -0
  6. package/extensions/app/panel.ts +533 -0
  7. package/extensions/app/presets.ts +36 -0
  8. package/extensions/features/agent-summary/core.ts +194 -0
  9. package/extensions/features/agent-summary/index.ts +37 -0
  10. package/extensions/features/compact-thinking.ts +1262 -0
  11. package/extensions/features/context.ts +857 -0
  12. package/extensions/features/reference/index.ts +643 -0
  13. package/extensions/features/reference/session.ts +278 -0
  14. package/extensions/features/reference/subagent.ts +218 -0
  15. package/extensions/features/shell/aliases.ts +28 -0
  16. package/extensions/features/shell/flush-docked-bash.ts +39 -0
  17. package/extensions/features/shell/startup-header.ts +357 -0
  18. package/extensions/features/shell/working-message.ts +226 -0
  19. package/extensions/index.ts +52 -0
  20. package/extensions/shell/accent-rail-editor.ts +112 -0
  21. package/extensions/shell/accent-rail-layout-patch.ts +530 -0
  22. package/extensions/shell/completion-menu.ts +186 -0
  23. package/extensions/shell/editor-metadata-format.ts +287 -0
  24. package/extensions/shell/editor-transfer.ts +73 -0
  25. package/extensions/shell/extension-status.ts +102 -0
  26. package/extensions/shell/footer-format.ts +388 -0
  27. package/extensions/shell/footer-layout.ts +144 -0
  28. package/extensions/shell/footer.ts +1035 -0
  29. package/extensions/shell/format.ts +577 -0
  30. package/extensions/shell/git.ts +434 -0
  31. package/extensions/shell/icons.ts +261 -0
  32. package/extensions/shell/index.ts +1659 -0
  33. package/extensions/shell/interaction-summary.ts +1194 -0
  34. package/extensions/shell/live-context.ts +82 -0
  35. package/extensions/shell/minimalist-editor.ts +548 -0
  36. package/extensions/shell/package-version.ts +675 -0
  37. package/extensions/shell/project-refresh.ts +130 -0
  38. package/extensions/shell/project-state.ts +60 -0
  39. package/extensions/shell/prototype-patch-registry.ts +183 -0
  40. package/extensions/shell/runtime.ts +915 -0
  41. package/extensions/shell/selector-border.ts +109 -0
  42. package/extensions/shell/session-lifecycle.ts +60 -0
  43. package/extensions/shell/settings-command.ts +2144 -0
  44. package/extensions/shell/settings-previews.ts +137 -0
  45. package/extensions/shell/state.ts +83 -0
  46. package/extensions/shell/style.ts +480 -0
  47. package/extensions/shell/telemetry.ts +90 -0
  48. package/extensions/shell/ui.ts +1326 -0
  49. package/extensions/shell/user-message-osc.ts +279 -0
  50. package/extensions/shell/user-message-styles.ts +266 -0
  51. package/extensions/shell/user-message.ts +226 -0
  52. package/extensions/shell/working-line-messages.ts +24 -0
  53. package/extensions/shell/working-line-spinners.ts +54 -0
  54. package/extensions/shell/working-line.ts +1603 -0
  55. package/extensions/tools/ansi-color.ts +44 -0
  56. package/extensions/tools/ansi-text.ts +52 -0
  57. package/extensions/tools/component-tree.ts +37 -0
  58. package/extensions/tools/format.ts +28 -0
  59. package/extensions/tools/fullscreen-detect.ts +11 -0
  60. package/extensions/tools/patch-keys.ts +158 -0
  61. package/extensions/tools/sgr-mouse.ts +59 -0
  62. package/extensions/tools/tool-loading-icon.ts +20 -0
  63. package/extensions/tools/tool-result-sanitize.ts +27 -0
  64. package/extensions/transcript/index.ts +65 -0
  65. package/extensions/transcript/renderer/compact-mode.ts +1443 -0
  66. package/extensions/transcript/renderer/default-mode.ts +735 -0
  67. package/extensions/transcript/renderer/index.ts +286 -0
  68. package/extensions/transcript/renderer/markdown-enhance.ts +325 -0
  69. package/extensions/transcript/renderer/mouse/hover.ts +175 -0
  70. package/extensions/transcript/renderer/mouse/interaction.ts +1016 -0
  71. package/extensions/transcript/renderer/mouse/layout.ts +207 -0
  72. package/extensions/transcript/renderer/mouse/packets.ts +96 -0
  73. package/extensions/transcript/renderer/mouse/scroll.ts +211 -0
  74. package/extensions/transcript/renderer/tool/diff/ATTRIBUTION.md +28 -0
  75. package/extensions/transcript/renderer/tool/diff/ansi-utils.ts +128 -0
  76. package/extensions/transcript/renderer/tool/diff/diff-component.ts +112 -0
  77. package/extensions/transcript/renderer/tool/diff/diff-edit-render.ts +255 -0
  78. package/extensions/transcript/renderer/tool/diff/diff-header.ts +184 -0
  79. package/extensions/transcript/renderer/tool/diff/diff-highlight.ts +131 -0
  80. package/extensions/transcript/renderer/tool/diff/diff-inline.ts +350 -0
  81. package/extensions/transcript/renderer/tool/diff/diff-layout.ts +870 -0
  82. package/extensions/transcript/renderer/tool/diff/diff-limits.ts +189 -0
  83. package/extensions/transcript/renderer/tool/diff/diff-palette.ts +436 -0
  84. package/extensions/transcript/renderer/tool/diff/diff-parse.ts +536 -0
  85. package/extensions/transcript/renderer/tool/diff/diff-presentation.ts +79 -0
  86. package/extensions/transcript/renderer/tool/diff/diff-renderer.ts +29 -0
  87. package/extensions/transcript/renderer/tool/diff/diff-text.ts +57 -0
  88. package/extensions/transcript/renderer/tool/diff/diff-write-render.ts +629 -0
  89. package/extensions/transcript/renderer/tool/diff/index.ts +147 -0
  90. package/extensions/transcript/renderer/tool/diff/line-width-safety.ts +100 -0
  91. package/extensions/transcript/renderer/tool/diff/shiki-highlight.ts +104 -0
  92. package/extensions/transcript/renderer/tool/diff/write-execution.ts +124 -0
  93. package/extensions/transcript/renderer/tool/grouping.ts +619 -0
  94. package/extensions/transcript/renderer/tool/message-display.ts +183 -0
  95. package/extensions/transcript/renderer/tool/names.ts +195 -0
  96. package/extensions/transcript/renderer/tool/result.ts +842 -0
  97. package/extensions/transcript/renderer/tool/show-more-hint.ts +42 -0
  98. package/extensions/transcript/renderer/transcript-refresh.ts +79 -0
  99. package/package.json +58 -0
  100. package/themes/cc-dark.json +91 -0
  101. package/themes/cc-light.json +91 -0
@@ -0,0 +1,18 @@
1
+ export const UI_OWNERSHIP = {
2
+ editor: "shell",
3
+ userMessage: "shell",
4
+ footer: "shell",
5
+ workingLine: "shell",
6
+ selector: "shell",
7
+ toolRenderer: "transcript",
8
+ diffRenderer: "transcript",
9
+ thinking: "transcript",
10
+ agentSummary: "features",
11
+ } as const;
12
+
13
+ export type UiSlot = keyof typeof UI_OWNERSHIP;
14
+ export type UiOwner = (typeof UI_OWNERSHIP)[UiSlot];
15
+
16
+ export function ownerFor(slot: UiSlot): UiOwner {
17
+ return UI_OWNERSHIP[slot];
18
+ }
@@ -0,0 +1,533 @@
1
+ import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ matchesKey,
4
+ type SettingItem,
5
+ SettingsList,
6
+ truncateToWidth,
7
+ } from "@earendil-works/pi-tui";
8
+ import type { ShellRuntimeController } from "../shell/index.ts";
9
+ import {
10
+ renderEditorSettingsPreview,
11
+ renderUserMessageSettingsPreview,
12
+ } from "../shell/settings-previews.ts";
13
+ import type { TranscriptRuntimeController } from "../transcript/index.ts";
14
+ import {
15
+ type CompactStyleMode,
16
+ DIFF_INDICATOR_MODES,
17
+ DIFF_VIEW_MODES,
18
+ type Config as RendererConfig,
19
+ config as rendererConfig,
20
+ updateConfig as updateRendererConfig,
21
+ } from "./config/renderer.ts";
22
+ import {
23
+ type EditorStyle,
24
+ type FooterStyle,
25
+ loadConfig as loadShellConfig,
26
+ saveEditorComponentPatch,
27
+ saveFooterComponentPatch,
28
+ saveUserMessagesComponentPatch,
29
+ saveWorkingLineComponentPatch,
30
+ type UserMessageStyle,
31
+ type WorkingLineSpinner,
32
+ } from "./config/shell.ts";
33
+ import { ownerFor } from "./ownership.ts";
34
+ import { applyPreset, PRESET_VALUES, type Preset } from "./presets.ts";
35
+
36
+ const EDITOR_STYLES: EditorStyle[] = [
37
+ "opencode",
38
+ "opencode-copy-friendly",
39
+ "accent-rail",
40
+ "minimalist",
41
+ ];
42
+ const MESSAGE_STYLES: UserMessageStyle[] = [
43
+ "framed",
44
+ "framed-copy-friendly",
45
+ "compact",
46
+ "labeled",
47
+ ];
48
+ const FOOTER_STYLES: FooterStyle[] = ["starship", "native", "hidden"];
49
+ const WORKING_SPINNERS: WorkingLineSpinner[] = [
50
+ "braille",
51
+ "star-bloom",
52
+ "pinwheel",
53
+ "claude-inspired",
54
+ "pulse",
55
+ ];
56
+ const PREVIEW_LINES = ["0", "1", "3", "5", "10"];
57
+
58
+ const SECTIONS = [
59
+ { id: "shell", label: "Shell" },
60
+ { id: "renderer", label: "Renderer" },
61
+ { id: "features", label: "Features" },
62
+ { id: "presets", label: "Presets" },
63
+ ] as const;
64
+ type SectionId = (typeof SECTIONS)[number]["id"];
65
+
66
+ function onOff(value: boolean): string {
67
+ return value ? "on" : "off";
68
+ }
69
+
70
+ function renderTabs(theme: any, active: number, width: number): string {
71
+ const text = SECTIONS.map((section, index) => {
72
+ const label = index === active ? theme.bold(section.label) : section.label;
73
+ return index === active ? theme.fg("text", label) : theme.fg("dim", label);
74
+ }).join(theme.fg("dim", " / "));
75
+ return truncateToWidth(text, Math.max(0, width));
76
+ }
77
+
78
+ function sectionDescription(section: SectionId): string {
79
+ 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.`;
84
+ case "features":
85
+ return "Feature switches are saved immediately and applied after /reload.";
86
+ case "presets":
87
+ return "Presets change ownership defaults while preserving detailed options.";
88
+ }
89
+ }
90
+
91
+ function shellItems(): SettingItem[] {
92
+ const config = loadShellConfig();
93
+ return [
94
+ {
95
+ id: "editorEnabled",
96
+ label: "Editor",
97
+ description: "Enable Zentui's custom input editor.",
98
+ currentValue: onOff(config.components.editor.enabled),
99
+ values: ["on", "off"],
100
+ },
101
+ {
102
+ id: "editorStyle",
103
+ label: "Editor style",
104
+ description: "Choose the input editor treatment.",
105
+ currentValue: config.components.editor.style,
106
+ values: EDITOR_STYLES,
107
+ },
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
+ },
122
+ {
123
+ id: "workingLineEnabled",
124
+ label: "Working line",
125
+ description: "Zentui is the sole owner of Pi's working row.",
126
+ currentValue: onOff(config.components.workingLine.enabled),
127
+ values: ["on", "off"],
128
+ },
129
+ {
130
+ id: "workingLineSpinner",
131
+ label: "Working spinner",
132
+ description: "Select the working line animation preset.",
133
+ currentValue: config.components.workingLine.spinner,
134
+ values: WORKING_SPINNERS,
135
+ },
136
+ {
137
+ id: "footerStyle",
138
+ label: "Footer",
139
+ description: "Choose Starship, native, or hidden footer rendering.",
140
+ currentValue: config.components.footer.style,
141
+ values: FOOTER_STYLES,
142
+ },
143
+ ];
144
+ }
145
+
146
+ function rendererItems(config: RendererConfig): SettingItem[] {
147
+ return [
148
+ {
149
+ id: "rendererMode",
150
+ label: "Tool style",
151
+ description:
152
+ "on = rich cards, compact = one summary per assistant message, off = native.",
153
+ currentValue: config.mode,
154
+ values: ["on", "compact", "off"],
155
+ },
156
+ {
157
+ id: "diffViewMode",
158
+ label: "Diff layout",
159
+ description: "Choose automatic, side-by-side, or unified diff layout.",
160
+ currentValue: config.diffViewMode,
161
+ values: [...DIFF_VIEW_MODES],
162
+ },
163
+ {
164
+ id: "diffIndicatorMode",
165
+ label: "Diff indicator",
166
+ description: "Choose bars, classic +/- gutters, or no indicator.",
167
+ currentValue: config.diffIndicatorMode,
168
+ values: [...DIFF_INDICATOR_MODES],
169
+ },
170
+ {
171
+ id: "diffWordWrap",
172
+ label: "Diff word wrap",
173
+ description: "Wrap long diff lines to the available terminal width.",
174
+ currentValue: onOff(config.diffWordWrap),
175
+ values: ["on", "off"],
176
+ },
177
+ {
178
+ id: "thinkingPreviewLines",
179
+ label: "Thinking preview",
180
+ description: "Number of thinking body lines shown before expansion.",
181
+ currentValue: String(config.previewLines),
182
+ values: PREVIEW_LINES,
183
+ },
184
+ {
185
+ id: "dimThinkingText",
186
+ label: "Dim thinking text",
187
+ description: "Use the dim theme color for thinking body text.",
188
+ currentValue: onOff(config.dimThinkingText),
189
+ values: ["on", "off"],
190
+ },
191
+ ];
192
+ }
193
+
194
+ function featureItems(config: RendererConfig): SettingItem[] {
195
+ return [
196
+ {
197
+ id: "enableSessionReference",
198
+ label: "Session reference",
199
+ description: "Enable @ session search and context injection.",
200
+ currentValue: onOff(config.enableSessionReference),
201
+ values: ["on", "off"],
202
+ },
203
+ {
204
+ id: "enableSubagentAutocomplete",
205
+ label: "Subagent autocomplete",
206
+ description: "Enable @ subagent completion and delegation hints.",
207
+ currentValue: onOff(config.enableSubagentAutocomplete),
208
+ values: ["on", "off"],
209
+ },
210
+ {
211
+ id: "enableContextCommand",
212
+ label: "Context command",
213
+ description: "Keep the /context inspection command enabled.",
214
+ currentValue: onOff(config.enableContextCommand),
215
+ values: ["on", "off"],
216
+ },
217
+ {
218
+ id: "enableAgentSummary",
219
+ label: "Agent summary",
220
+ description: "Show per-turn tool statistics after an agent turn.",
221
+ currentValue: onOff(config.enableAgentSummary),
222
+ values: ["on", "off"],
223
+ },
224
+ {
225
+ id: "enableAliases",
226
+ label: "Command aliases",
227
+ description: "Register /clear and /exit aliases.",
228
+ currentValue: onOff(config.enableAliases),
229
+ values: ["on", "off"],
230
+ },
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
+ ];
239
+ }
240
+
241
+ function presetItems(): SettingItem[] {
242
+ return PRESET_VALUES.map((preset) => ({
243
+ id: `preset:${preset}`,
244
+ label: preset,
245
+ description:
246
+ preset === "balanced"
247
+ ? "Zentui shell + rich CC Style tool renderer."
248
+ : preset === "compact"
249
+ ? "Zentui shell + compact CC Style transcript."
250
+ : "Disable custom shell and transcript rendering.",
251
+ currentValue: "apply",
252
+ values: ["apply"],
253
+ }));
254
+ }
255
+
256
+ function itemsFor(section: SectionId): SettingItem[] {
257
+ switch (section) {
258
+ case "shell":
259
+ return shellItems();
260
+ case "renderer":
261
+ return rendererItems(rendererConfig);
262
+ case "features":
263
+ return featureItems(rendererConfig);
264
+ case "presets":
265
+ return presetItems();
266
+ }
267
+ }
268
+
269
+ function isOn(value: string): boolean {
270
+ return value === "on";
271
+ }
272
+
273
+ type UnifiedPanelDeps = {
274
+ shell?: ShellRuntimeController;
275
+ renderer?: TranscriptRuntimeController;
276
+ };
277
+
278
+ function renderShellPreview(theme: any, width: number): string[] {
279
+ const config = loadShellConfig();
280
+ const previewWidth = Math.max(20, Math.min(72, width - 2));
281
+ return [
282
+ "",
283
+ theme.fg("muted", " Editor preview"),
284
+ ...renderEditorSettingsPreview(config, theme, previewWidth).map(
285
+ (line) => ` ${line}`,
286
+ ),
287
+ "",
288
+ theme.fg("muted", " User message preview"),
289
+ ...renderUserMessageSettingsPreview(config, theme, previewWidth).map(
290
+ (line) => ` ${line}`,
291
+ ),
292
+ ];
293
+ }
294
+
295
+ function saveNotice(ctx: any, label: string, value: string): void {
296
+ ctx.ui.notify(
297
+ `${label}: ${value} saved; live preview updated when supported.`,
298
+ "info",
299
+ );
300
+ }
301
+
302
+ function updateSetting(
303
+ id: string,
304
+ value: string,
305
+ ctx: any,
306
+ deps: UnifiedPanelDeps,
307
+ ): void {
308
+ if (id === "editorEnabled") {
309
+ if (deps.shell)
310
+ deps.shell.setEditorComponent({ enabled: isOn(value) }, ctx);
311
+ else saveEditorComponentPatch({ enabled: isOn(value) });
312
+ saveNotice(ctx, "Editor", value);
313
+ return;
314
+ }
315
+ if (id === "editorStyle" && EDITOR_STYLES.includes(value as EditorStyle)) {
316
+ if (deps.shell)
317
+ deps.shell.setEditorComponent({ style: value as EditorStyle }, ctx);
318
+ else saveEditorComponentPatch({ style: value as EditorStyle });
319
+ saveNotice(ctx, "Editor style", value);
320
+ return;
321
+ }
322
+ if (id === "userMessagesEnabled") {
323
+ if (deps.shell)
324
+ deps.shell.setUserMessagesComponent({ enabled: isOn(value) }, ctx);
325
+ else saveUserMessagesComponentPatch({ enabled: isOn(value) });
326
+ saveNotice(ctx, "User messages", value);
327
+ return;
328
+ }
329
+ if (
330
+ id === "userMessagesStyle" &&
331
+ MESSAGE_STYLES.includes(value as UserMessageStyle)
332
+ ) {
333
+ if (deps.shell)
334
+ deps.shell.setUserMessagesComponent(
335
+ { style: value as UserMessageStyle },
336
+ ctx,
337
+ );
338
+ else saveUserMessagesComponentPatch({ style: value as UserMessageStyle });
339
+ saveNotice(ctx, "Message style", value);
340
+ return;
341
+ }
342
+ if (id === "workingLineEnabled") {
343
+ if (deps.shell)
344
+ deps.shell.setWorkingLineComponent({ enabled: isOn(value) }, ctx);
345
+ else saveWorkingLineComponentPatch({ enabled: isOn(value) });
346
+ saveNotice(ctx, "Working line", value);
347
+ return;
348
+ }
349
+ if (
350
+ id === "workingLineSpinner" &&
351
+ WORKING_SPINNERS.includes(value as WorkingLineSpinner)
352
+ ) {
353
+ if (deps.shell)
354
+ deps.shell.setWorkingLineComponent(
355
+ { spinner: value as WorkingLineSpinner },
356
+ ctx,
357
+ );
358
+ else
359
+ saveWorkingLineComponentPatch({ spinner: value as WorkingLineSpinner });
360
+ saveNotice(ctx, "Working spinner", value);
361
+ return;
362
+ }
363
+ if (id === "footerStyle" && FOOTER_STYLES.includes(value as FooterStyle)) {
364
+ if (deps.shell)
365
+ deps.shell.setFooterComponent({ style: value as FooterStyle }, ctx);
366
+ else saveFooterComponentPatch({ style: value as FooterStyle });
367
+ saveNotice(ctx, "Footer", value);
368
+ return;
369
+ }
370
+ if (id === "rendererMode") {
371
+ if (deps.renderer) deps.renderer.setMode(value as CompactStyleMode, ctx);
372
+ else updateRendererConfig({ mode: value as CompactStyleMode });
373
+ saveNotice(ctx, "Tool style", value);
374
+ return;
375
+ }
376
+ 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);
380
+ saveNotice(ctx, "Diff layout", value);
381
+ return;
382
+ }
383
+ if (
384
+ id === "diffIndicatorMode" &&
385
+ DIFF_INDICATOR_MODES.includes(value as never)
386
+ ) {
387
+ const patch = {
388
+ diffIndicatorMode: value as RendererConfig["diffIndicatorMode"],
389
+ };
390
+ if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
391
+ else updateRendererConfig(patch);
392
+ saveNotice(ctx, "Diff indicator", value);
393
+ return;
394
+ }
395
+ if (id === "diffWordWrap") {
396
+ const patch = { diffWordWrap: isOn(value) };
397
+ if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
398
+ else updateRendererConfig(patch);
399
+ saveNotice(ctx, "Diff word wrap", value);
400
+ return;
401
+ }
402
+ if (id === "thinkingPreviewLines") {
403
+ const patch = { previewLines: Number(value) };
404
+ if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
405
+ else updateRendererConfig(patch);
406
+ saveNotice(ctx, "Thinking preview", value);
407
+ return;
408
+ }
409
+ if (id === "dimThinkingText") {
410
+ const patch = { dimThinkingText: isOn(value) };
411
+ if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
412
+ else updateRendererConfig(patch);
413
+ saveNotice(ctx, "Thinking text", value);
414
+ return;
415
+ }
416
+ if (
417
+ id === "enableSessionReference" ||
418
+ id === "enableSubagentAutocomplete" ||
419
+ id === "enableContextCommand" ||
420
+ id === "enableAgentSummary" ||
421
+ id === "enableAliases" ||
422
+ id === "showStartupHeader"
423
+ ) {
424
+ const patch = { [id]: isOn(value) } as Partial<RendererConfig>;
425
+ if (deps.renderer) deps.renderer.updateConfig(patch, ctx);
426
+ else updateRendererConfig(patch);
427
+ saveNotice(ctx, id, value);
428
+ return;
429
+ }
430
+ if (id.startsWith("preset:")) {
431
+ const preset = id.slice("preset:".length);
432
+ if (PRESET_VALUES.includes(preset as Preset)) {
433
+ applyPreset(preset as Preset);
434
+ if (deps.renderer) {
435
+ deps.renderer.setMode(
436
+ preset === "compact" ? "compact" : preset === "native" ? "off" : "on",
437
+ ctx,
438
+ );
439
+ }
440
+ saveNotice(ctx, "Preset", preset);
441
+ }
442
+ return;
443
+ }
444
+ }
445
+
446
+ let unifiedPanelOpen = false;
447
+
448
+ export async function showOneUiPanel(
449
+ ctx: any,
450
+ deps: UnifiedPanelDeps = {},
451
+ ): Promise<void> {
452
+ if (
453
+ ctx?.mode !== "tui" ||
454
+ !ctx?.hasUI ||
455
+ typeof ctx.ui?.custom !== "function"
456
+ ) {
457
+ ctx.ui?.notify?.("/oneui requires TUI mode", "warning");
458
+ return;
459
+ }
460
+ if (unifiedPanelOpen) {
461
+ ctx.ui.notify("/oneui panel is already open", "info");
462
+ return;
463
+ }
464
+
465
+ unifiedPanelOpen = true;
466
+ 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;
489
+ createList();
490
+ tui.requestRender();
491
+ };
492
+
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);
526
+ },
527
+ };
528
+ },
529
+ );
530
+ } finally {
531
+ unifiedPanelOpen = false;
532
+ }
533
+ }
@@ -0,0 +1,36 @@
1
+ import { updateConfig as updateRendererConfig } from "./config/renderer.ts";
2
+ import {
3
+ saveEditorComponentPatch,
4
+ saveFooterComponentPatch,
5
+ saveUserMessagesComponentPatch,
6
+ saveWorkingLineComponentPatch,
7
+ } from "./config/shell.ts";
8
+
9
+ export type Preset = "balanced" | "compact" | "native";
10
+
11
+ export const PRESET_VALUES: Preset[] = ["balanced", "compact", "native"];
12
+
13
+ export function isPreset(value: string): value is Preset {
14
+ return PRESET_VALUES.includes(value as Preset);
15
+ }
16
+
17
+ /** Apply only ownership-related values; detailed options remain user-editable. */
18
+ export function applyPreset(preset: Preset): void {
19
+ if (preset === "native") {
20
+ saveEditorComponentPatch({ enabled: false });
21
+ saveUserMessagesComponentPatch({ enabled: false });
22
+ saveWorkingLineComponentPatch({ enabled: false });
23
+ saveFooterComponentPatch({ style: "native" });
24
+ updateRendererConfig({ mode: "off", enableWorkingMessage: false });
25
+ return;
26
+ }
27
+
28
+ saveEditorComponentPatch({ enabled: true });
29
+ saveUserMessagesComponentPatch({ enabled: true });
30
+ saveWorkingLineComponentPatch({ enabled: true });
31
+ saveFooterComponentPatch({ style: "starship" });
32
+ updateRendererConfig({
33
+ mode: preset === "compact" ? "compact" : "on",
34
+ enableWorkingMessage: false,
35
+ });
36
+ }