pi-one-ui 0.7.2 → 0.9.0
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/CHANGELOG.md +52 -1
- package/README.en.md +5 -8
- package/README.md +6 -7
- package/extensions/app/config/footer.ts +21 -5
- package/extensions/app/config/renderer.ts +1 -12
- package/extensions/app/config/shell.ts +62 -60
- package/extensions/app/runtime/prototype-patch-registry.ts +13 -9
- package/extensions/app/runtime/tui-runtime.ts +19 -12
- package/extensions/app/settings/controller.ts +2 -2
- package/extensions/layouts/context/index.ts +2 -2
- package/extensions/layouts/context/message/user-message-osc.ts +1 -1
- package/extensions/layouts/context/message/user-message-styles.ts +9 -6
- package/extensions/layouts/context/message/user-message.ts +5 -5
- package/extensions/layouts/context/renderer/compact-mode.ts +2 -2
- package/extensions/layouts/context/renderer/default-mode.ts +10 -10
- package/extensions/layouts/context/renderer/index.ts +0 -1
- package/extensions/layouts/context/renderer/mouse/hover.ts +0 -4
- package/extensions/layouts/context/renderer/mouse/interaction.ts +106 -95
- package/extensions/layouts/context/renderer/mouse/layout.ts +0 -4
- package/extensions/layouts/context/renderer/mouse/scroll.ts +2 -183
- package/extensions/layouts/context/renderer/tool/diff/diff-edit-render.ts +1 -1
- package/extensions/layouts/context/renderer/tool/diff/index.ts +1 -1
- package/extensions/layouts/context/renderer/tool/grouping.ts +3 -3
- package/extensions/layouts/context/renderer/tool/message-display.ts +7 -7
- package/extensions/layouts/context/renderer/tool/result.ts +16 -16
- package/extensions/layouts/context/renderer/tool/toggle-render-cache.ts +9 -9
- package/extensions/layouts/editor/controller.ts +17 -17
- package/extensions/layouts/editor/editor-metadata-format.ts +5 -5
- package/extensions/layouts/editor/factory.ts +5 -5
- package/extensions/layouts/editor/minimalist-editor.ts +8 -8
- package/extensions/layouts/editor/ownership.ts +24 -20
- package/extensions/layouts/editor/ui.ts +10 -15
- package/extensions/layouts/footer/controller.ts +4 -4
- package/extensions/layouts/footer/data.ts +2 -2
- package/extensions/layouts/footer/extension-status.ts +4 -4
- package/extensions/layouts/footer/footer.ts +5 -2
- package/extensions/layouts/overlay/selector-border.ts +4 -4
- package/extensions/layouts/overlay/selector-controller.ts +1 -1
- package/extensions/layouts/overlay/settings-previews.ts +2 -2
- package/extensions/layouts/working-line/interaction-summary.ts +3 -1
- package/extensions/layouts/working-line/working-line.ts +4 -4
- package/extensions/services/package-data.ts +1 -1
- package/extensions/shared/style.ts +66 -56
- package/extensions/tools/patch-keys.ts +22 -25
- package/package.json +7 -7
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Global registry slot for prototype patches. The key is only shared by module
|
|
3
|
+
* instances of the same process, so it has to stay stable across `/reload`.
|
|
4
|
+
*/
|
|
5
|
+
export const POLISHED_PROTOTYPE_PATCH_REGISTRY = Symbol.for(
|
|
6
|
+
"pi-one-ui.prototype-patch-registry",
|
|
3
7
|
);
|
|
4
8
|
|
|
5
9
|
type PrototypePatchAdapter =
|
|
@@ -35,7 +39,7 @@ type PatchRegistry = Map<PrototypePatchAdapter, PatchRecord>;
|
|
|
35
39
|
type PatchTarget = Record<PropertyKey, unknown>;
|
|
36
40
|
|
|
37
41
|
function existingRegistry(target: PatchTarget): PatchRegistry | undefined {
|
|
38
|
-
const existing = target[
|
|
42
|
+
const existing = target[POLISHED_PROTOTYPE_PATCH_REGISTRY];
|
|
39
43
|
return existing instanceof Map ? (existing as PatchRegistry) : undefined;
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -43,7 +47,7 @@ function registryFor(target: PatchTarget): PatchRegistry {
|
|
|
43
47
|
const existing = existingRegistry(target);
|
|
44
48
|
if (existing) return existing;
|
|
45
49
|
const registry: PatchRegistry = new Map();
|
|
46
|
-
Object.defineProperty(target,
|
|
50
|
+
Object.defineProperty(target, POLISHED_PROTOTYPE_PATCH_REGISTRY, {
|
|
47
51
|
value: registry,
|
|
48
52
|
configurable: true,
|
|
49
53
|
});
|
|
@@ -100,7 +104,7 @@ function createCleanup(
|
|
|
100
104
|
if (current !== record) return;
|
|
101
105
|
restorePredecessor(target, record);
|
|
102
106
|
registry.delete(adapter);
|
|
103
|
-
if (registry.size === 0) delete target[
|
|
107
|
+
if (registry.size === 0) delete target[POLISHED_PROTOTYPE_PATCH_REGISTRY];
|
|
104
108
|
};
|
|
105
109
|
}
|
|
106
110
|
|
|
@@ -124,7 +128,7 @@ export function installPrototypePatch(
|
|
|
124
128
|
);
|
|
125
129
|
const predecessor = target[method];
|
|
126
130
|
if (typeof predecessor !== "function") {
|
|
127
|
-
if (registry.size === 0) delete target[
|
|
131
|
+
if (registry.size === 0) delete target[POLISHED_PROTOTYPE_PATCH_REGISTRY];
|
|
128
132
|
throw new TypeError(
|
|
129
133
|
`Cannot patch ${method}: predecessor is not a function`,
|
|
130
134
|
);
|
|
@@ -135,7 +139,7 @@ export function installPrototypePatch(
|
|
|
135
139
|
predecessorDescriptor,
|
|
136
140
|
wrapper: () => undefined,
|
|
137
141
|
};
|
|
138
|
-
const wrapper: PrototypeMethod = function
|
|
142
|
+
const wrapper: PrototypeMethod = function polishedPrototypeWrapper(
|
|
139
143
|
this: unknown,
|
|
140
144
|
...args: unknown[]
|
|
141
145
|
): unknown {
|
|
@@ -152,7 +156,7 @@ export function installPrototypePatch(
|
|
|
152
156
|
try {
|
|
153
157
|
installWrapper(target, nextRecord);
|
|
154
158
|
} catch (error) {
|
|
155
|
-
if (registry.size === 0) delete target[
|
|
159
|
+
if (registry.size === 0) delete target[POLISHED_PROTOTYPE_PATCH_REGISTRY];
|
|
156
160
|
throw error;
|
|
157
161
|
}
|
|
158
162
|
record = nextRecord;
|
|
@@ -179,5 +183,5 @@ export function removePrototypePatch(
|
|
|
179
183
|
deactivateRecord(record);
|
|
180
184
|
restorePredecessor(target, record);
|
|
181
185
|
registry.delete(adapter);
|
|
182
|
-
if (registry.size === 0) delete target[
|
|
186
|
+
if (registry.size === 0) delete target[POLISHED_PROTOTYPE_PATCH_REGISTRY];
|
|
183
187
|
}
|
|
@@ -20,6 +20,7 @@ import registerHeader, {
|
|
|
20
20
|
import { SelectorController } from "../../layouts/overlay/selector-controller.ts";
|
|
21
21
|
import { WorkingLineLayoutController } from "../../layouts/working-line/controller.ts";
|
|
22
22
|
import {
|
|
23
|
+
LEGACY_TURN_SUMMARY_ENTRY_TYPE,
|
|
23
24
|
renderTurnSummaryEntry,
|
|
24
25
|
TURN_SUMMARY_ENTRY_TYPE,
|
|
25
26
|
} from "../../layouts/working-line/interaction-summary.ts";
|
|
@@ -192,18 +193,24 @@ export function createLayoutRuntime(
|
|
|
192
193
|
});
|
|
193
194
|
const state = sessionState.state;
|
|
194
195
|
if (typeof pi.registerEntryRenderer === "function") {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
196
|
+
const renderTurnSummary = (
|
|
197
|
+
entry: { data?: unknown },
|
|
198
|
+
entryOptions: unknown,
|
|
199
|
+
theme: Parameters<typeof renderTurnSummaryEntry>[2],
|
|
200
|
+
) => {
|
|
201
|
+
const options = (entryOptions ?? {}) as Record<string, unknown>;
|
|
202
|
+
return renderTurnSummaryEntry(
|
|
203
|
+
entry,
|
|
204
|
+
{
|
|
205
|
+
...options,
|
|
206
|
+
workingLineHigh: currentConfig.colors.workingLineHigh,
|
|
207
|
+
},
|
|
208
|
+
theme,
|
|
209
|
+
);
|
|
210
|
+
};
|
|
211
|
+
pi.registerEntryRenderer(TURN_SUMMARY_ENTRY_TYPE, renderTurnSummary);
|
|
212
|
+
// 旧 session 的摘要条目继续可渲染。
|
|
213
|
+
pi.registerEntryRenderer(LEGACY_TURN_SUMMARY_ENTRY_TYPE, renderTurnSummary);
|
|
207
214
|
}
|
|
208
215
|
const refresh = (): void => {
|
|
209
216
|
if (sessionLifecycle.isCurrent()) {
|
|
@@ -6,7 +6,7 @@ import { type Config, normalizeConfig } from "../config/renderer.ts";
|
|
|
6
6
|
import {
|
|
7
7
|
mergeConfig,
|
|
8
8
|
mutateComponentsRecord,
|
|
9
|
-
type
|
|
9
|
+
type PolishedTuiConfig,
|
|
10
10
|
} from "../config/shell.ts";
|
|
11
11
|
import {
|
|
12
12
|
type ConfigRecord,
|
|
@@ -19,7 +19,7 @@ import { applyPresetRecord, isPreset } from "./presets.ts";
|
|
|
19
19
|
export type SettingsChange = Readonly<{ id: string; value: string }>;
|
|
20
20
|
|
|
21
21
|
export type SettingsSnapshot = Readonly<{
|
|
22
|
-
shell:
|
|
22
|
+
shell: PolishedTuiConfig;
|
|
23
23
|
renderer: Config;
|
|
24
24
|
panel: PanelOverlayConfig;
|
|
25
25
|
}>;
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
import { type Config, config } from "../../app/config/renderer.ts";
|
|
7
7
|
import {
|
|
8
8
|
hasUnsupportedComponentStyle,
|
|
9
|
-
type
|
|
9
|
+
type PolishedTuiConfig,
|
|
10
10
|
} from "../../app/config/shell.ts";
|
|
11
11
|
import { installUserMessageStyle } from "./message/user-message.ts";
|
|
12
12
|
import registerContextRenderer, {
|
|
@@ -22,7 +22,7 @@ export type ContextRuntimeController = RendererRuntimeController & {
|
|
|
22
22
|
applyConfig(ctx: ExtensionContext, previous: Config): void;
|
|
23
23
|
};
|
|
24
24
|
export type ContextExtensionOptions = {
|
|
25
|
-
readonly getConfig: () =>
|
|
25
|
+
readonly getConfig: () => PolishedTuiConfig;
|
|
26
26
|
readonly services?: RendererExtensionOptions["services"];
|
|
27
27
|
};
|
|
28
28
|
|
|
@@ -192,7 +192,7 @@ function filterUserMessageTerminalText(
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// Removing an unterminated introducer prevents it from consuming
|
|
195
|
-
//
|
|
195
|
+
// pi-one-ui's prompt markers. Preserve visible trailing payload, except
|
|
196
196
|
// for a recognized partial OSC 133 command prefix.
|
|
197
197
|
const payloadEnd = boundary?.index ?? text.length;
|
|
198
198
|
const payload = text.slice(payloadStart, payloadEnd);
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
truncateToWidth,
|
|
6
6
|
visibleWidth,
|
|
7
7
|
} from "@earendil-works/pi-tui";
|
|
8
|
-
import type {
|
|
8
|
+
import type { PolishedTuiConfig } from "../../../app/config/shell.ts";
|
|
9
9
|
import {
|
|
10
10
|
EDITOR_ACCENT_FALLBACK,
|
|
11
11
|
EDITOR_BORDER_FALLBACK,
|
|
@@ -17,7 +17,7 @@ export type UserMessageStyleRenderInput = {
|
|
|
17
17
|
text: string;
|
|
18
18
|
width: number;
|
|
19
19
|
theme?: Theme;
|
|
20
|
-
config:
|
|
20
|
+
config: PolishedTuiConfig;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
function themeFg(
|
|
@@ -70,7 +70,7 @@ function fillLine(content: string, width: number): string {
|
|
|
70
70
|
|
|
71
71
|
function accent(
|
|
72
72
|
theme: Theme | undefined,
|
|
73
|
-
config:
|
|
73
|
+
config: PolishedTuiConfig,
|
|
74
74
|
text: string,
|
|
75
75
|
): string {
|
|
76
76
|
return theme
|
|
@@ -85,7 +85,7 @@ function accent(
|
|
|
85
85
|
|
|
86
86
|
function border(
|
|
87
87
|
theme: Theme | undefined,
|
|
88
|
-
config:
|
|
88
|
+
config: PolishedTuiConfig,
|
|
89
89
|
text: string,
|
|
90
90
|
): string {
|
|
91
91
|
return theme
|
|
@@ -98,7 +98,10 @@ function border(
|
|
|
98
98
|
: text;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
function renderRail(
|
|
101
|
+
function renderRail(
|
|
102
|
+
theme: Theme | undefined,
|
|
103
|
+
config: PolishedTuiConfig,
|
|
104
|
+
): string {
|
|
102
105
|
return `${accent(theme, config, config.icons.rail)} `;
|
|
103
106
|
}
|
|
104
107
|
|
|
@@ -202,7 +205,7 @@ function renderLabeled({
|
|
|
202
205
|
return [top, ...body.map(side), bottom];
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
export function userMessageStyleCacheKey(config:
|
|
208
|
+
export function userMessageStyleCacheKey(config: PolishedTuiConfig): string {
|
|
206
209
|
const messages = config.components.userMessages;
|
|
207
210
|
switch (messages.style) {
|
|
208
211
|
case "framed":
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
UserMessageComponent,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
6
|
-
import type {
|
|
6
|
+
import type { PolishedTuiConfig } from "../../../app/config/shell.ts";
|
|
7
7
|
import {
|
|
8
8
|
installPrototypePatch,
|
|
9
9
|
removePrototypePatch,
|
|
@@ -79,11 +79,11 @@ function getCachedMarkdownText(instance: object): string | undefined {
|
|
|
79
79
|
return text;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function
|
|
82
|
+
function renderPolishedUserMessage(
|
|
83
83
|
instance: PatchableUserMessagePrototype,
|
|
84
84
|
width: number,
|
|
85
85
|
theme: Theme | undefined,
|
|
86
|
-
config:
|
|
86
|
+
config: PolishedTuiConfig,
|
|
87
87
|
): string[] | undefined {
|
|
88
88
|
if (!isRecord(instance)) return undefined;
|
|
89
89
|
|
|
@@ -171,7 +171,7 @@ export function removeUserMessageStyle(): void {
|
|
|
171
171
|
|
|
172
172
|
export function installUserMessageStyle(
|
|
173
173
|
getTheme: () => Theme | undefined,
|
|
174
|
-
getConfig: () =>
|
|
174
|
+
getConfig: () => PolishedTuiConfig,
|
|
175
175
|
): Cleanup {
|
|
176
176
|
const prototype = UserMessageComponent.prototype;
|
|
177
177
|
const cleanupInvalidate = installPrototypePatch(
|
|
@@ -195,7 +195,7 @@ export function installUserMessageStyle(
|
|
|
195
195
|
const width = args[0];
|
|
196
196
|
if (typeof width !== "number") return renderPredecessor();
|
|
197
197
|
try {
|
|
198
|
-
const lines =
|
|
198
|
+
const lines = renderPolishedUserMessage(
|
|
199
199
|
receiver as PatchableUserMessagePrototype,
|
|
200
200
|
width,
|
|
201
201
|
getTheme(),
|
|
@@ -690,7 +690,7 @@ function compactEditWriteLines(
|
|
|
690
690
|
outputText,
|
|
691
691
|
theme,
|
|
692
692
|
isError,
|
|
693
|
-
state.
|
|
693
|
+
state.oneuiIoView,
|
|
694
694
|
component.args,
|
|
695
695
|
component,
|
|
696
696
|
true, // 外层 Box(1,1) 已 pad
|
|
@@ -1380,7 +1380,7 @@ export function installCompactMode(
|
|
|
1380
1380
|
uiRef = ctx?.ui;
|
|
1381
1381
|
resetRounds();
|
|
1382
1382
|
// 始终保持补丁在链上:mode=on/off 走 passThrough,仍写入 lastMessage
|
|
1383
|
-
// 与 tracked 集合,这样 /
|
|
1383
|
+
// 与 tracked 集合,这样 /oneui 切回 compact 时不必 /reload。
|
|
1384
1384
|
patch.assertAssistantOwnership();
|
|
1385
1385
|
if (config.mode === "compact") syncGlobalExpanded(ctx);
|
|
1386
1386
|
else hoveredAssistantComponent = undefined;
|
|
@@ -236,8 +236,8 @@ function renderExpandedTaskResult(
|
|
|
236
236
|
return formatted ? new Text(`↳ ${formatted}`, 0, 0) : undefined;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
/** 用
|
|
240
|
-
function
|
|
239
|
+
/** 用 pi-one-ui call/result 包装任意工具定义。 */
|
|
240
|
+
function createPolishedTool(
|
|
241
241
|
originalTool: any,
|
|
242
242
|
writeExecutionMetadata: WriteExecutionMetadataStore,
|
|
243
243
|
): any {
|
|
@@ -400,7 +400,7 @@ function createCcstyleTool(
|
|
|
400
400
|
true, // mode=on:贴左,由外层 Box(1,1) 提供 1 格 padding
|
|
401
401
|
);
|
|
402
402
|
}
|
|
403
|
-
if (context?.state) context.state.
|
|
403
|
+
if (context?.state) context.state.oneuiIoView = undefined;
|
|
404
404
|
let cachedWidth: number | undefined;
|
|
405
405
|
let cachedLine: string | undefined;
|
|
406
406
|
let cachedHoveredLine: string | undefined;
|
|
@@ -425,7 +425,7 @@ function createCcstyleTool(
|
|
|
425
425
|
cachedWidth = undefined;
|
|
426
426
|
cachedLine = undefined;
|
|
427
427
|
cachedHoveredLine = undefined;
|
|
428
|
-
context?.state?.
|
|
428
|
+
context?.state?.oneuiExpandedIoView?.invalidate?.();
|
|
429
429
|
},
|
|
430
430
|
};
|
|
431
431
|
},
|
|
@@ -465,7 +465,7 @@ function shouldGloballyStyleTool(
|
|
|
465
465
|
const builtInDefinition = component.builtInToolDefinition;
|
|
466
466
|
const definition = extensionDefinition ?? builtInDefinition;
|
|
467
467
|
const toolName = String(component.toolName || definition?.name || "");
|
|
468
|
-
const
|
|
468
|
+
const usePolishedToolStyle =
|
|
469
469
|
patch.mode() === "on" &&
|
|
470
470
|
!DEDICATED_RENDERER_TOOLS.has(toolName) &&
|
|
471
471
|
!preservesOriginalRenderer(
|
|
@@ -473,8 +473,8 @@ function shouldGloballyStyleTool(
|
|
|
473
473
|
toolName,
|
|
474
474
|
builtInDefinition,
|
|
475
475
|
);
|
|
476
|
-
component[COMPONENT_TOOL_RENDER_MODE] =
|
|
477
|
-
return
|
|
476
|
+
component[COMPONENT_TOOL_RENDER_MODE] = usePolishedToolStyle;
|
|
477
|
+
return usePolishedToolStyle;
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
function getGloballyStyledTool(
|
|
@@ -607,7 +607,7 @@ function installGlobalToolRendering(
|
|
|
607
607
|
active: true,
|
|
608
608
|
enabled: () => config.mode === "on",
|
|
609
609
|
mode: () => config.mode,
|
|
610
|
-
wrap: (tool: any) =>
|
|
610
|
+
wrap: (tool: any) => createPolishedTool(tool, writeExecutionMetadata),
|
|
611
611
|
byDefinition: new WeakMap(),
|
|
612
612
|
byName: new Map(),
|
|
613
613
|
downstream,
|
|
@@ -626,8 +626,8 @@ function installGlobalToolRendering(
|
|
|
626
626
|
getRenderShell: function (this: any, ...args: any[]) {
|
|
627
627
|
if (!patch.active)
|
|
628
628
|
return patch.downstream.getRenderShell.apply(this, args);
|
|
629
|
-
const
|
|
630
|
-
const shell =
|
|
629
|
+
const usePolishedToolStyle = shouldGloballyStyleTool(this, patch);
|
|
630
|
+
const shell = usePolishedToolStyle
|
|
631
631
|
? this.expanded
|
|
632
632
|
? "default"
|
|
633
633
|
: "self"
|
|
@@ -243,7 +243,6 @@ export default function (
|
|
|
243
243
|
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
244
244
|
hooks.toggleRenderCache.setTheme(ctx.ui.theme);
|
|
245
245
|
setMessageDisplayTheme(ctx.ui.theme);
|
|
246
|
-
ctx.ui.setStatus("ccstyle", undefined);
|
|
247
246
|
// Collect the resumed context before syncing compact patches and expansion state.
|
|
248
247
|
syncCompactMode(ctx);
|
|
249
248
|
// compact-thinking 的 session_start 处理在本 handler 之后执行(在其之上再装
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
type ToolIoSection,
|
|
13
13
|
} from "../tool/result.ts";
|
|
14
14
|
import { type ComponentRowHit, componentAtLocalRow } from "./layout.ts";
|
|
15
|
-
import { setScrollButtonHovered } from "./scroll.ts";
|
|
16
15
|
|
|
17
16
|
type SharedToolHoverState = { toolCallId: string | null };
|
|
18
17
|
|
|
@@ -132,7 +131,6 @@ export function cachedFullscreenComponentAtRow(
|
|
|
132
131
|
|
|
133
132
|
/** fullscreen 鼠标悬停目标。 */
|
|
134
133
|
export type FullscreenHoverTarget =
|
|
135
|
-
| { kind: "button" }
|
|
136
134
|
| { kind: "group"; component: ToolGroupComponent }
|
|
137
135
|
| { kind: "thinking"; component: ThinkingPreviewBlock }
|
|
138
136
|
| { kind: "message"; component: any }
|
|
@@ -169,7 +167,5 @@ export function applyFullscreenHover(
|
|
|
169
167
|
const nextView = target?.kind === "tool" ? target.view : null;
|
|
170
168
|
const nextSection = target?.kind === "tool" ? target.section : null;
|
|
171
169
|
if (setHoveredToolIo(nextView, nextSection)) changed = true;
|
|
172
|
-
const nextButton = target?.kind === "button";
|
|
173
|
-
if (setScrollButtonHovered(nextButton)) changed = true;
|
|
174
170
|
if (changed) tui.requestRender?.();
|
|
175
171
|
}
|