pi-zentui 0.26.1 → 0.27.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.
- package/docs/configuration.md +5 -2
- package/extensions/zentui/config.ts +16 -0
- package/extensions/zentui/index.ts +145 -43
- package/extensions/zentui/minimalist-editor.ts +6 -1
- package/extensions/zentui/settings-command.ts +48 -0
- package/extensions/zentui/ui.ts +192 -41
- package/extensions/zentui/working-line.ts +141 -6
- package/package.json +1 -1
package/docs/configuration.md
CHANGED
|
@@ -225,7 +225,8 @@ Reference only—not a starter file. Prefer the minimal overrides above. Optiona
|
|
|
225
225
|
"contextThresholds": {
|
|
226
226
|
"warning": 70,
|
|
227
227
|
"error": 90
|
|
228
|
-
}
|
|
228
|
+
},
|
|
229
|
+
"separator": "dash"
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
},
|
|
@@ -246,6 +247,7 @@ Reference only—not a starter file. Prefer the minimal overrides above. Optiona
|
|
|
246
247
|
},
|
|
247
248
|
"workingLine": {
|
|
248
249
|
"enabled": false,
|
|
250
|
+
"placement": "above",
|
|
249
251
|
"turnSummary": true,
|
|
250
252
|
"spinner": "star-bloom",
|
|
251
253
|
"spinnerIntervalMs": 100,
|
|
@@ -422,6 +424,7 @@ Reference only—not a starter file. Prefer the minimal overrides above. Optiona
|
|
|
422
424
|
- `components.thinkingSteps` independently owns opt-in **Thinking (Experimental)** display. It defaults to `{ "enabled": false, "mode": "tree" }`; canonical modes are `rail | tree | streaming`. The former persisted `streaming-experimental` value is accepted only as a migration alias and is normalized to `streaming` on save.
|
|
423
425
|
- All three modes decorate Pi's private host renderer and are tested on exact Pi versions 0.80.5, 0.82.1, 0.83.0, 0.84.0, 0.84.4, and 0.85.1. Active Streaming can switch live to Rail or Tree, and Rail and Tree can switch live between each other. Entering Streaming from a structural mode, first enable, and re-enable after live disable require restart; live disable restores native thinking. Disabled mode changes only preconfigure.
|
|
424
426
|
- `components.workingLine.enabled` is the sole Working-line ownership switch. Thinking (Experimental) never enables, configures, or owns the Working line and leaves the existing **Thinking time** option unchanged.
|
|
427
|
+
- `components.workingLine.placement` (`above | border`) selects the default floating row or the Minimalist/Opencode top border. Accent Rail supports only Above. Disabled, native, unavailable, or unsafe/narrow editors and Pi versions without working-row visibility control fall back to Above without changing saved choices. Legacy `input` normalizes to `above`; the prompt remains available for typing.
|
|
425
428
|
- `components.selectorBorders` owns selector-border enablement, fixed `zentui` style, and color source. Disable it for native Pi behavior.
|
|
426
429
|
- `components.footer` owns `native | starship | hidden` style selection, color source, model label, and Starship options. Hidden hides its main segments, retaining only allowed extension statuses; an empty status line occupies no rows.
|
|
427
430
|
- Starship's package-version segment reads the project manifest and is distinct from the runtime segment, which reports the installed toolchain.
|
|
@@ -531,7 +534,7 @@ Set `ZENTUI_DEBUG=1` when launching Pi to log the workaround diagnostic without
|
|
|
531
534
|
|
|
532
535
|
Set `components.editor.style` to `minimalist` or select it in `/zentui`. The rounded frame places viewport counts, Bash state, current/completed turn duration, and explicit session name at top left; cost, model, thinking, context, and optional latest-prompt cache hit rate at top right; viewport count plus Git at bottom left; and configured path at bottom right. Unnamed sessions add no placeholder.
|
|
533
536
|
|
|
534
|
-
Path examples are `src` (`compact`), `zentui/src` (`project`), and `~/Projects/zentui/src` (`full`). Context can render as `11%`, `11%/372k`, or, with the gauge enabled and enough room, `[█░░░░] 11%/372k`. Enable `showCacheHit` to append values such as `Cache 98.2%`; it defaults to `false`, omits missing data, yields before context at narrow widths, and remains independent of Footer. The gauge shortens or disappears before the context text at narrow widths. Session name, timer, cost, cache hit rate, and Git can be hidden independently; model, thinking, and context remain structurally stable.
|
|
537
|
+
Path examples are `src` (`compact`), `zentui/src` (`project`), and `~/Projects/zentui/src` (`full`). Context can render as `11%`, `11%/372k`, or, with the gauge enabled and enough room, `[█░░░░] 11%/372k`. Enable `showCacheHit` to append values such as `Cache 98.2%`; it defaults to `false`, omits missing data, yields before context at narrow widths, and remains independent of Footer. The gauge shortens or disappears before the context text at narrow widths. Session name, timer, cost, cache hit rate, and Git can be hidden independently; model, thinking, and context remain structurally stable. Metadata items are joined with dashes by default (`separator: "dash"`) or dots (`separator: "dot"`).
|
|
535
538
|
|
|
536
539
|
Autocomplete stays inside the frame when Pi output can be split safely. Unknown third-party layouts fail open. Footer visibility remains independently controlled by `components.footer.style`; Minimalist does not remove Pi's header.
|
|
537
540
|
|
|
@@ -54,10 +54,12 @@ export type WorkingLineSpinner =
|
|
|
54
54
|
| "claude-inspired"
|
|
55
55
|
| "pulse";
|
|
56
56
|
export type WorkingLineTextAnimation = "classic" | "kitt" | "disabled";
|
|
57
|
+
export type WorkingLinePlacement = "above" | "border";
|
|
57
58
|
export type ThinkingStepsMode = "rail" | "tree" | "streaming";
|
|
58
59
|
export type ComponentStyleOwner = "editor" | "userMessages" | "selectorBorders" | "footer";
|
|
59
60
|
export type MinimalistPathDisplayMode = "compact" | "project" | "full";
|
|
60
61
|
export type MinimalistContextFormat = "percent" | "percent-total";
|
|
62
|
+
export type MinimalistEditorSeparator = "dash" | "dot";
|
|
61
63
|
export type EditorBorderColorMode = "static" | "adaptive";
|
|
62
64
|
export type CompletionMenuStyle = "native" | "palette";
|
|
63
65
|
export type CompactFooterMaxLines = 1 | 2 | 3 | "unlimited";
|
|
@@ -142,6 +144,7 @@ export type MinimalistEditorStyleConfig = {
|
|
|
142
144
|
showCacheHit: boolean;
|
|
143
145
|
showGit: boolean;
|
|
144
146
|
contextThresholds: ContextThresholds;
|
|
147
|
+
separator: MinimalistEditorSeparator;
|
|
145
148
|
};
|
|
146
149
|
|
|
147
150
|
/** Temporary name retained for existing settings consumers. */
|
|
@@ -258,6 +261,7 @@ export type WorkingLineComponentConfig = {
|
|
|
258
261
|
colorSource: ColorSource;
|
|
259
262
|
messages: WorkingLineMessagesConfig;
|
|
260
263
|
segments: WorkingLineSegmentsConfig;
|
|
264
|
+
placement: WorkingLinePlacement;
|
|
261
265
|
};
|
|
262
266
|
|
|
263
267
|
export type WorkingLineComponentPatch = Partial<
|
|
@@ -498,6 +502,7 @@ const defaultMinimalistStyle: MinimalistEditorStyleConfig = {
|
|
|
498
502
|
showCacheHit: false,
|
|
499
503
|
showGit: true,
|
|
500
504
|
contextThresholds: { warning: 70, error: 90 },
|
|
505
|
+
separator: "dash",
|
|
501
506
|
};
|
|
502
507
|
|
|
503
508
|
const defaultStarshipStyle: StarshipFooterStyleConfig = {
|
|
@@ -557,6 +562,7 @@ const defaultComponents: ComponentsConfig = {
|
|
|
557
562
|
colorSource: "theme",
|
|
558
563
|
messages: { custom: true, values: [...PI_WORKING_LINE_MESSAGES] },
|
|
559
564
|
segments: { tool: true, elapsed: true, thought: true, tokens: true },
|
|
565
|
+
placement: "above",
|
|
560
566
|
},
|
|
561
567
|
selectorBorders: { enabled: true, style: "zentui", colorSource: "theme" },
|
|
562
568
|
footer: {
|
|
@@ -1405,6 +1411,10 @@ function resolveComponents(config: ConfigRecord): ComponentsConfig {
|
|
|
1405
1411
|
showCacheHit: parseBoolean(minimalist.showCacheHit, defaultMinimalistStyle.showCacheHit),
|
|
1406
1412
|
showGit: parseBoolean(minimalist.showGit, defaultMinimalistStyle.showGit),
|
|
1407
1413
|
contextThresholds: minimalistThresholds,
|
|
1414
|
+
separator:
|
|
1415
|
+
minimalist.separator === "dash" || minimalist.separator === "dot"
|
|
1416
|
+
? minimalist.separator
|
|
1417
|
+
: defaultMinimalistStyle.separator,
|
|
1408
1418
|
},
|
|
1409
1419
|
},
|
|
1410
1420
|
},
|
|
@@ -1466,6 +1476,10 @@ function resolveComponents(config: ConfigRecord): ComponentsConfig {
|
|
|
1466
1476
|
workingLine.textAnimation === "disabled"
|
|
1467
1477
|
? workingLine.textAnimation
|
|
1468
1478
|
: defaultComponents.workingLine.textAnimation,
|
|
1479
|
+
placement:
|
|
1480
|
+
workingLine.placement === "border"
|
|
1481
|
+
? workingLine.placement
|
|
1482
|
+
: defaultComponents.workingLine.placement,
|
|
1469
1483
|
colorSource: parseColorSource(
|
|
1470
1484
|
workingLine.colorSource,
|
|
1471
1485
|
defaultComponents.workingLine.colorSource,
|
|
@@ -1903,6 +1917,7 @@ function applyMinimalistStylePatch(
|
|
|
1903
1917
|
if (patch.showCost !== undefined) style.showCost = patch.showCost;
|
|
1904
1918
|
if (patch.showCacheHit !== undefined) style.showCacheHit = patch.showCacheHit;
|
|
1905
1919
|
if (patch.showGit !== undefined) style.showGit = patch.showGit;
|
|
1920
|
+
if (patch.separator !== undefined) style.separator = patch.separator;
|
|
1906
1921
|
if (patch.contextThresholds !== undefined) {
|
|
1907
1922
|
style.contextThresholds = { ...style.contextThresholds, ...patch.contextThresholds };
|
|
1908
1923
|
}
|
|
@@ -1970,6 +1985,7 @@ export function saveWorkingLineComponentPatch(
|
|
|
1970
1985
|
if (patch.textIntervalMs !== undefined) component.textIntervalMs = patch.textIntervalMs;
|
|
1971
1986
|
if (patch.textAnimation !== undefined) component.textAnimation = patch.textAnimation;
|
|
1972
1987
|
if (patch.colorSource !== undefined) component.colorSource = patch.colorSource;
|
|
1988
|
+
if (patch.placement !== undefined) component.placement = patch.placement;
|
|
1973
1989
|
if (patch.messages?.custom !== undefined) component.messages.custom = patch.messages.custom;
|
|
1974
1990
|
if (patch.messages?.values !== undefined) {
|
|
1975
1991
|
component.messages.values = normalizeWorkingLineMessages([...patch.messages.values]);
|
|
@@ -240,6 +240,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
240
240
|
let editorInstalled = false;
|
|
241
241
|
let editorInstallMode: EditorInstallMode = "none";
|
|
242
242
|
let installedEditorFactory: EditorFactory | undefined;
|
|
243
|
+
let activeEditor:
|
|
244
|
+
| {
|
|
245
|
+
editor: PolishedEditor | WrappedPolishedEditor;
|
|
246
|
+
factory: EditorFactory;
|
|
247
|
+
generation: number;
|
|
248
|
+
}
|
|
249
|
+
| undefined;
|
|
243
250
|
let wrappedEditorFactory: EditorFactory | undefined;
|
|
244
251
|
let stopSessionTimer: () => void = () => {};
|
|
245
252
|
let stopMinimalistDurationUpdates: () => void = () => {};
|
|
@@ -330,7 +337,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
330
337
|
Date.now,
|
|
331
338
|
() => interactionMetrics.currentThought(),
|
|
332
339
|
() => invalidateWorkingLinePublisherState(),
|
|
340
|
+
undefined,
|
|
341
|
+
(ctx) => {
|
|
342
|
+
if (!activeTuiContext || ctx.ui !== activeTuiContext.ui) return false;
|
|
343
|
+
if (!ownsInstalledEditorFactory()) {
|
|
344
|
+
activeEditor = undefined;
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
return Boolean(
|
|
348
|
+
activeEditor &&
|
|
349
|
+
sessionLifecycle.isCurrent(activeEditor.generation) &&
|
|
350
|
+
activeEditor.factory === installedEditorFactory &&
|
|
351
|
+
effectiveEditorEnabled() &&
|
|
352
|
+
currentConfig.components.editor.style !== "accent-rail" &&
|
|
353
|
+
activeEditor.editor.canEmbedWorkingLineBorder(),
|
|
354
|
+
);
|
|
355
|
+
},
|
|
333
356
|
);
|
|
357
|
+
workingLine.setRequestRender(() => {
|
|
358
|
+
if (sessionLifecycle.isCurrent()) {
|
|
359
|
+
requestEditorRender?.();
|
|
360
|
+
}
|
|
361
|
+
});
|
|
334
362
|
let workingLineSessionReady = false;
|
|
335
363
|
const workingLineExtensions = new WorkingLineExtensionSegments(
|
|
336
364
|
pi.events,
|
|
@@ -567,6 +595,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
567
595
|
|
|
568
596
|
const refreshInteractiveState = (ctx: ExtensionContext, project = false) => {
|
|
569
597
|
if (!sessionLifecycle.isCurrent() || !ctx.hasUI) return;
|
|
598
|
+
if (activeTuiContext?.ui === ctx.ui) activeTuiContext = ctx;
|
|
570
599
|
if (editorInstalled && !ownsInstalledEditorFactory()) reconcileObservedEditorOwnership(ctx);
|
|
571
600
|
syncFooterState(ctx);
|
|
572
601
|
if (project && needsProjectRefresh()) scheduleProjectRefresh(ctx);
|
|
@@ -768,6 +797,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
768
797
|
};
|
|
769
798
|
|
|
770
799
|
const clearEditorOwnership = () => {
|
|
800
|
+
activeEditor = undefined;
|
|
801
|
+
if (activeTuiContext && workingLineSessionReady) workingLine.reconcile(activeTuiContext);
|
|
771
802
|
setMinimalistDecorationActive(false);
|
|
772
803
|
requestEditorRender = undefined;
|
|
773
804
|
wrappedEditorFactory = undefined;
|
|
@@ -779,6 +810,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
779
810
|
|
|
780
811
|
const trackZentuiEditorFactory = (factory: EditorFactory): boolean => {
|
|
781
812
|
if (!isOwnedEditorFactory(factory)) return false;
|
|
813
|
+
if (activeEditor?.factory !== factory) activeEditor = undefined;
|
|
782
814
|
const baseFactory = getZentuiEditorBaseFactory(factory);
|
|
783
815
|
wrappedEditorFactory = baseFactory;
|
|
784
816
|
installedEditorFactory = factory;
|
|
@@ -799,7 +831,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
799
831
|
|
|
800
832
|
const reconcileObservedEditorOwnership = (ctx: ExtensionContext) => {
|
|
801
833
|
const observed = observeEditorFactory(ctx);
|
|
802
|
-
if (!observed.known)
|
|
834
|
+
if (!observed.known) {
|
|
835
|
+
activeEditor = undefined;
|
|
836
|
+
if (workingLineSessionReady) workingLine.reconcile(ctx);
|
|
837
|
+
return observed;
|
|
838
|
+
}
|
|
803
839
|
if (observed.factory && isOwnedEditorFactory(observed.factory)) {
|
|
804
840
|
trackZentuiEditorFactory(observed.factory);
|
|
805
841
|
} else {
|
|
@@ -820,39 +856,78 @@ export default function (pi: ExtensionAPI) {
|
|
|
820
856
|
return editor;
|
|
821
857
|
};
|
|
822
858
|
|
|
859
|
+
const isActiveEditor = (editor: PolishedEditor | WrappedPolishedEditor, generation: number) =>
|
|
860
|
+
sessionLifecycle.isCurrent(generation) && activeEditor?.editor === editor;
|
|
861
|
+
|
|
862
|
+
const editorBorderCapabilityChanged = (
|
|
863
|
+
editor: PolishedEditor | WrappedPolishedEditor,
|
|
864
|
+
generation: number,
|
|
865
|
+
) => {
|
|
866
|
+
if (!isActiveEditor(editor, generation) || !activeTuiContext || !workingLineSessionReady)
|
|
867
|
+
return;
|
|
868
|
+
workingLine.reconcile(activeTuiContext);
|
|
869
|
+
if (workingLine.isAvailable()) requestEditorRender?.();
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
const editorWorkingLineFrame = (
|
|
873
|
+
editor: PolishedEditor | WrappedPolishedEditor,
|
|
874
|
+
generation: number,
|
|
875
|
+
) => (isActiveEditor(editor, generation) ? workingLine.currentWorkingLineFrame() : undefined);
|
|
876
|
+
|
|
823
877
|
const makeEditorFactory = (ctx: ExtensionContext): ZentuiEditorFactory => {
|
|
824
878
|
const sessionTheme = ctx.ui.theme;
|
|
879
|
+
const generation = sessionLifecycle.currentGeneration();
|
|
825
880
|
const factory = ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => {
|
|
826
|
-
|
|
827
|
-
return markOwnedAccentRailEditor(
|
|
881
|
+
const editor: PolishedEditor = markOwnedAccentRailEditor(
|
|
828
882
|
new PolishedEditor(
|
|
829
883
|
tui,
|
|
830
884
|
theme,
|
|
831
885
|
keybindings,
|
|
832
886
|
sessionTheme,
|
|
833
887
|
getCurrentConfig,
|
|
834
|
-
() => getEditorMeta(ctx),
|
|
888
|
+
() => getEditorMeta(activeTuiContext ?? ctx),
|
|
835
889
|
getThinkingLevel,
|
|
836
|
-
() =>
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
890
|
+
() => {
|
|
891
|
+
const workingLineFrame = editorWorkingLineFrame(editor, generation);
|
|
892
|
+
if (currentConfig.components.editor.style !== "minimalist") {
|
|
893
|
+
return { cwd: "", workingLineFrame };
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
codexQuota: getEditorQuota(),
|
|
897
|
+
cwd: (activeTuiContext ?? ctx).cwd,
|
|
898
|
+
projectRoot: minimalistProjectRoot,
|
|
899
|
+
branch: state.branch,
|
|
900
|
+
dirty: state.dirty,
|
|
901
|
+
ahead: state.ahead,
|
|
902
|
+
behind: state.behind,
|
|
903
|
+
costLabel: state.costLabel,
|
|
904
|
+
modelLabel: modelLabelFor(state, currentConfig.components.editor.modelLabel),
|
|
905
|
+
thinkingLevel: getThinkingLevel(),
|
|
906
|
+
...getEditorContextMetadata(activeTuiContext ?? ctx),
|
|
907
|
+
cacheHitRate: state.usageTotals.latestCacheHitRate,
|
|
908
|
+
sessionName: (activeTuiContext ?? ctx).sessionManager.getSessionName() ?? "",
|
|
909
|
+
agentDurationMs: getAgentDurationMs(),
|
|
910
|
+
agentActive: agentRunActive,
|
|
911
|
+
workingLineFrame,
|
|
912
|
+
};
|
|
913
|
+
},
|
|
914
|
+
(active) => {
|
|
915
|
+
if (isActiveEditor(editor, generation)) setMinimalistDecorationActive(active);
|
|
916
|
+
},
|
|
917
|
+
() => editorBorderCapabilityChanged(editor, generation),
|
|
854
918
|
),
|
|
855
919
|
);
|
|
920
|
+
const observed = observeEditorFactory(ctx);
|
|
921
|
+
if (
|
|
922
|
+
sessionLifecycle.isCurrent(generation) &&
|
|
923
|
+
activeTuiContext?.ui === ctx.ui &&
|
|
924
|
+
observed.known &&
|
|
925
|
+
observed.factory === factory
|
|
926
|
+
) {
|
|
927
|
+
activeEditor = { editor, factory, generation };
|
|
928
|
+
requestEditorRender = () => tui.requestRender();
|
|
929
|
+
}
|
|
930
|
+
return editor;
|
|
856
931
|
}) as ZentuiEditorFactory;
|
|
857
932
|
factory[ZENTUI_EDITOR_FACTORY] = true;
|
|
858
933
|
factory[ZENTUI_EDITOR_OWNER] = editorOwnerToken;
|
|
@@ -864,35 +939,56 @@ export default function (pi: ExtensionAPI) {
|
|
|
864
939
|
baseFactory: EditorFactory,
|
|
865
940
|
): ZentuiEditorFactory => {
|
|
866
941
|
const sessionTheme = ctx.ui.theme;
|
|
942
|
+
const generation = sessionLifecycle.currentGeneration();
|
|
867
943
|
const factory = ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => {
|
|
868
|
-
|
|
869
|
-
return markOwnedAccentRailEditor(
|
|
944
|
+
const editor: WrappedPolishedEditor = markOwnedAccentRailEditor(
|
|
870
945
|
new WrappedPolishedEditor(
|
|
871
946
|
baseFactory(tui, theme, keybindings),
|
|
872
947
|
sessionTheme,
|
|
873
948
|
getCurrentConfig,
|
|
874
|
-
() => getEditorMeta(ctx),
|
|
949
|
+
() => getEditorMeta(activeTuiContext ?? ctx),
|
|
875
950
|
getThinkingLevel,
|
|
876
|
-
() =>
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
951
|
+
() => {
|
|
952
|
+
const workingLineFrame = editorWorkingLineFrame(editor, generation);
|
|
953
|
+
if (currentConfig.components.editor.style !== "minimalist") {
|
|
954
|
+
return { cwd: "", workingLineFrame };
|
|
955
|
+
}
|
|
956
|
+
return {
|
|
957
|
+
codexQuota: getEditorQuota(),
|
|
958
|
+
cwd: (activeTuiContext ?? ctx).cwd,
|
|
959
|
+
projectRoot: minimalistProjectRoot,
|
|
960
|
+
branch: state.branch,
|
|
961
|
+
dirty: state.dirty,
|
|
962
|
+
ahead: state.ahead,
|
|
963
|
+
behind: state.behind,
|
|
964
|
+
costLabel: state.costLabel,
|
|
965
|
+
modelLabel: modelLabelFor(state, currentConfig.components.editor.modelLabel),
|
|
966
|
+
thinkingLevel: getThinkingLevel(),
|
|
967
|
+
...getEditorContextMetadata(activeTuiContext ?? ctx),
|
|
968
|
+
cacheHitRate: state.usageTotals.latestCacheHitRate,
|
|
969
|
+
sessionName: (activeTuiContext ?? ctx).sessionManager.getSessionName() ?? "",
|
|
970
|
+
agentDurationMs: getAgentDurationMs(),
|
|
971
|
+
agentActive: agentRunActive,
|
|
972
|
+
workingLineFrame,
|
|
973
|
+
};
|
|
974
|
+
},
|
|
975
|
+
(active) => {
|
|
976
|
+
if (isActiveEditor(editor, generation)) setMinimalistDecorationActive(active);
|
|
977
|
+
},
|
|
978
|
+
() => editorBorderCapabilityChanged(editor, generation),
|
|
894
979
|
),
|
|
895
980
|
);
|
|
981
|
+
const observed = observeEditorFactory(ctx);
|
|
982
|
+
if (
|
|
983
|
+
sessionLifecycle.isCurrent(generation) &&
|
|
984
|
+
activeTuiContext?.ui === ctx.ui &&
|
|
985
|
+
observed.known &&
|
|
986
|
+
observed.factory === factory
|
|
987
|
+
) {
|
|
988
|
+
activeEditor = { editor, factory, generation };
|
|
989
|
+
requestEditorRender = () => tui.requestRender();
|
|
990
|
+
}
|
|
991
|
+
return editor;
|
|
896
992
|
}) as ZentuiEditorFactory;
|
|
897
993
|
factory[ZENTUI_EDITOR_FACTORY] = true;
|
|
898
994
|
factory[ZENTUI_EDITOR_OWNER] = editorOwnerToken;
|
|
@@ -1141,6 +1237,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1141
1237
|
ok: false,
|
|
1142
1238
|
reason: "the editor could not be reconciled safely; reload Pi to apply this change",
|
|
1143
1239
|
};
|
|
1240
|
+
} finally {
|
|
1241
|
+
if (workingLineSessionReady) workingLine.reconcile(ctx);
|
|
1144
1242
|
}
|
|
1145
1243
|
};
|
|
1146
1244
|
|
|
@@ -1231,6 +1329,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1231
1329
|
const cleanupUi = (ctx?: ExtensionContext) => {
|
|
1232
1330
|
if (!ctx || !sessionLifecycle.isCurrent()) return;
|
|
1233
1331
|
sessionLifecycle.shutdown();
|
|
1332
|
+
activeEditor = undefined;
|
|
1234
1333
|
extensionStatuses.dispose();
|
|
1235
1334
|
codexQuota.stop();
|
|
1236
1335
|
stopSessionTimer();
|
|
@@ -1290,6 +1389,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1290
1389
|
usageTotals.invalidate();
|
|
1291
1390
|
footerTelemetry.reset();
|
|
1292
1391
|
const lifecycleGeneration = sessionLifecycle.start();
|
|
1392
|
+
activeEditor = undefined;
|
|
1293
1393
|
// A new generation must not expose or route extension segments through the previous
|
|
1294
1394
|
// session while asynchronous TUI startup is still pending.
|
|
1295
1395
|
workingLineSessionReady = false;
|
|
@@ -1350,6 +1450,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1350
1450
|
if (currentConfig.components.editor.style !== "minimalist") {
|
|
1351
1451
|
setMinimalistDecorationActive(false);
|
|
1352
1452
|
}
|
|
1453
|
+
workingLine.reconcile(ctx);
|
|
1353
1454
|
reconcileUserMessages();
|
|
1354
1455
|
reconcileFooter(ctx);
|
|
1355
1456
|
reconcileProjectRefresh(ctx, effectiveFooterStyle() !== previousFooterStyle);
|
|
@@ -1404,6 +1505,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1404
1505
|
if (patch.style !== undefined && patch.style !== "minimalist") {
|
|
1405
1506
|
setMinimalistDecorationActive(false);
|
|
1406
1507
|
}
|
|
1508
|
+
workingLine.reconcile(ctx);
|
|
1407
1509
|
syncFooterUsage(ctx);
|
|
1408
1510
|
if (patch.modelLabel !== undefined) syncFooterState(ctx);
|
|
1409
1511
|
reconcileProjectRefresh(ctx);
|
|
@@ -58,6 +58,8 @@ export type MinimalistEditorMetadata = {
|
|
|
58
58
|
sessionName?: string;
|
|
59
59
|
agentDurationMs?: number;
|
|
60
60
|
agentActive?: boolean;
|
|
61
|
+
/** Controller-gated border content; never resolve canonical placement in the renderer. */
|
|
62
|
+
workingLineFrame?: string;
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
export type MinimalistFrameOptions = {
|
|
@@ -182,6 +184,7 @@ function renderTopLeft(
|
|
|
182
184
|
: safeThemeFg(uiTheme, "muted", duration),
|
|
183
185
|
);
|
|
184
186
|
}
|
|
187
|
+
if (metadata.workingLineFrame) parts.push(metadata.workingLineFrame);
|
|
185
188
|
const sessionName = includeSessionName
|
|
186
189
|
? sanitizeEditorMetadataText(metadata.sessionName ?? "")
|
|
187
190
|
: "";
|
|
@@ -209,8 +212,9 @@ function renderTopRight(
|
|
|
209
212
|
): string {
|
|
210
213
|
const source = config.components.editor.colorSource;
|
|
211
214
|
const parts: string[] = [];
|
|
215
|
+
const separator = config.components.editor.styles.minimalist.separator === "dot" ? " · " : " – ";
|
|
212
216
|
const joinParts = (values: string[]) =>
|
|
213
|
-
values.map((part, index) => (index > 0 ? `${renderBorder(
|
|
217
|
+
values.map((part, index) => (index > 0 ? `${renderBorder(separator)}${part}` : part)).join("");
|
|
214
218
|
const cost = config.components.editor.styles.minimalist.showCost
|
|
215
219
|
? sanitizeEditorMetadataText(metadata.costLabel ?? "")
|
|
216
220
|
: "";
|
|
@@ -498,6 +502,7 @@ export function renderMinimalistFrame({
|
|
|
498
502
|
const topFallbacks = [
|
|
499
503
|
joinStyled([topViewport, topOperational], separator),
|
|
500
504
|
topOperational,
|
|
505
|
+
...(metadata.workingLineFrame ? [metadata.workingLineFrame] : []),
|
|
501
506
|
].filter((value, index, values) => value !== topLeft && values.indexOf(value) === index);
|
|
502
507
|
const top = renderLabeledBorder({
|
|
503
508
|
width,
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
MAX_WORKING_LINE_INTERVAL_MS,
|
|
46
46
|
MIN_WORKING_LINE_INTERVAL_MS,
|
|
47
47
|
type MinimalistConfig,
|
|
48
|
+
type MinimalistEditorSeparator,
|
|
48
49
|
type ModelLabelSource,
|
|
49
50
|
type PathDisplayConfig,
|
|
50
51
|
type PolishedCopyFriendlyEditorStyleConfig,
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
type UserMessageStyle,
|
|
58
59
|
type UserMessagesComponentConfig,
|
|
59
60
|
type WorkingLineComponentPatch,
|
|
61
|
+
type WorkingLinePlacement,
|
|
60
62
|
type WorkingLineSpinner,
|
|
61
63
|
type WorkingLineTextAnimation,
|
|
62
64
|
} from "./config";
|
|
@@ -126,6 +128,7 @@ const completionMenuValues: CompletionMenuStyle[] = ["palette", "native"];
|
|
|
126
128
|
const accentRailSurfaceValues = ["filled", "transparent"];
|
|
127
129
|
const minimalistPathDisplayValues = ["compact", "project", "full"];
|
|
128
130
|
const minimalistContextFormatValues = ["percent", "percent-total"];
|
|
131
|
+
const minimalistSeparatorValues: MinimalistEditorSeparator[] = ["dash", "dot"];
|
|
129
132
|
const editorBorderColorModeValues: EditorBorderColorMode[] = ["static", "adaptive"];
|
|
130
133
|
const compactFooterMaxLineValues = ["1", "2", "3", "unlimited"];
|
|
131
134
|
const featureStateValues: FeatureState[] = ["enabled", "disabled"];
|
|
@@ -151,6 +154,11 @@ const speedValues = (presets: readonly { label: string }[]) => [
|
|
|
151
154
|
...presets.map(({ label }) => label),
|
|
152
155
|
"Custom…",
|
|
153
156
|
];
|
|
157
|
+
const workingLinePlacementLabels: Record<WorkingLinePlacement, string> = {
|
|
158
|
+
above: "Above",
|
|
159
|
+
border: "Border",
|
|
160
|
+
};
|
|
161
|
+
const workingLinePlacementValues = Object.values(workingLinePlacementLabels);
|
|
154
162
|
const workingLineTextAnimationValues: WorkingLineTextAnimation[] = ["classic", "kitt", "disabled"];
|
|
155
163
|
const thinkingStepsModeLabels: Record<ThinkingStepsMode, string> = {
|
|
156
164
|
rail: "Rail",
|
|
@@ -728,6 +736,13 @@ function buildMinimalistEditorStyleItems(config: PolishedTuiConfig): SettingItem
|
|
|
728
736
|
currentValue: featureValue(minimalist.showGit),
|
|
729
737
|
values: featureStateValues,
|
|
730
738
|
},
|
|
739
|
+
{
|
|
740
|
+
id: "minimalistSeparator",
|
|
741
|
+
label: "Separator",
|
|
742
|
+
description: "Choose dash (–) or dot (·) between editor metadata items.",
|
|
743
|
+
currentValue: minimalist.separator,
|
|
744
|
+
values: minimalistSeparatorValues,
|
|
745
|
+
},
|
|
731
746
|
];
|
|
732
747
|
}
|
|
733
748
|
|
|
@@ -796,6 +811,16 @@ function buildWorkingLineItems(config: PolishedTuiConfig): SettingItem[] {
|
|
|
796
811
|
currentValue: featureValue(workingLine.enabled),
|
|
797
812
|
values: featureStateValues,
|
|
798
813
|
},
|
|
814
|
+
...(config.components.editor.enabled && config.components.editor.style !== "accent-rail"
|
|
815
|
+
? [
|
|
816
|
+
{
|
|
817
|
+
id: "workingLinePlacement",
|
|
818
|
+
label: "Placement",
|
|
819
|
+
currentValue: workingLinePlacementLabels[workingLine.placement],
|
|
820
|
+
values: workingLinePlacementValues,
|
|
821
|
+
},
|
|
822
|
+
]
|
|
823
|
+
: []),
|
|
799
824
|
{
|
|
800
825
|
id: "workingLineTurnSummary",
|
|
801
826
|
label: "Turn summary",
|
|
@@ -1745,6 +1770,14 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
|
|
|
1745
1770
|
{ contextFormat: newValue as MinimalistConfig["contextFormat"] },
|
|
1746
1771
|
ctx,
|
|
1747
1772
|
);
|
|
1773
|
+
else if (
|
|
1774
|
+
id === "minimalistSeparator" &&
|
|
1775
|
+
(newValue === "dash" || newValue === "dot")
|
|
1776
|
+
)
|
|
1777
|
+
deps.setMinimalist(
|
|
1778
|
+
{ separator: newValue as MinimalistEditorSeparator },
|
|
1779
|
+
ctx,
|
|
1780
|
+
);
|
|
1748
1781
|
else if (enabled !== undefined) {
|
|
1749
1782
|
const key =
|
|
1750
1783
|
id === "minimalistContextGauge"
|
|
@@ -1817,6 +1850,21 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
|
|
|
1817
1850
|
notifyWorkingLineChange("Turn summary", newValue, result, false);
|
|
1818
1851
|
return;
|
|
1819
1852
|
}
|
|
1853
|
+
if (id === "workingLinePlacement") {
|
|
1854
|
+
const editor = deps.getConfig().components.editor;
|
|
1855
|
+
if (!editor.enabled || editor.style === "accent-rail") return;
|
|
1856
|
+
const placement = (
|
|
1857
|
+
Object.entries(workingLinePlacementLabels) as Array<
|
|
1858
|
+
[WorkingLinePlacement, string]
|
|
1859
|
+
>
|
|
1860
|
+
).find(([, label]) => label === newValue)?.[0];
|
|
1861
|
+
if (placement) {
|
|
1862
|
+
const result = deps.setWorkingLineComponent({ placement }, ctx);
|
|
1863
|
+
settingsList.updateValue(id, newValue);
|
|
1864
|
+
notifyWorkingLineChange("Placement", newValue, result);
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1820
1868
|
const selectedWorkingLineSpinner =
|
|
1821
1869
|
id === "workingLineSpinner" ? workingLineSpinnerId(newValue) : undefined;
|
|
1822
1870
|
if (selectedWorkingLineSpinner) {
|