pi-one-ui 0.4.0 → 0.5.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.
@@ -19,7 +19,6 @@ import {
19
19
  renderStyleForSourceOrFallback,
20
20
  safeThemeFg,
21
21
  } from "../../shared/style.ts";
22
- import { renderCompletionPalette } from "./completion-menu.ts";
23
22
  import { renderEditorMetadataFormat } from "./editor-metadata-format.ts";
24
23
  import {
25
24
  type MinimalistEditorMetadata,
@@ -91,34 +90,6 @@ export type EditorMeta = {
91
90
  sessionName?: string;
92
91
  };
93
92
 
94
- export type PolishedEditorFrameOptions = {
95
- width: number;
96
- editorLines: string[];
97
- autocompleteLines?: string[];
98
- viewport?: ViewportCounts;
99
- uiTheme: Theme;
100
- config: ZentuiConfig;
101
- modelMeta: EditorMeta;
102
- thinkingLevel?: string;
103
- rightStatus?: string;
104
- borderColor?: (text: string) => string;
105
- };
106
-
107
- type PolishedFrameOptions = {
108
- width: number;
109
- baseRendered: string[];
110
- autocompleteSource: AutocompleteEditorInternals;
111
- autocompleteCapture?: AutocompleteCapture;
112
- uiTheme: Theme;
113
- config: ZentuiConfig;
114
- modelMeta: EditorMeta;
115
- thinkingLevel: string | undefined;
116
- rightStatus?: string;
117
- ownedFrame?: PolishedFrameSplit;
118
- trustedBaseFrame?: boolean;
119
- borderColor?: (text: string) => string;
120
- };
121
-
122
93
  type PolishedFrameResult = {
123
94
  lines: string[];
124
95
  decorated: boolean;
@@ -316,6 +287,29 @@ export function renderWithAutocompleteCapture<T>(
316
287
  }
317
288
  }
318
289
 
290
+ /**
291
+ * off 模式的原生边框覆盖:仅在 style=off 且用户显式配置了 colors.editorBorder
292
+ * 时返回按 colorSource 解释的边框渲染函数;未配置或 on 模式返回 undefined
293
+ * (保持 Pi 原生 effort/主题变色不动)。
294
+ */
295
+ export function offEditorBorderColor(
296
+ config: ZentuiConfig,
297
+ uiTheme: Theme,
298
+ ): ((text: string) => string) | undefined {
299
+ const editor = config.components.editor;
300
+ if (editor.style !== "off") return undefined;
301
+ const spec = config.colors.editorBorder;
302
+ if (typeof spec !== "string" || spec.trim() === "") return undefined;
303
+ return (text) =>
304
+ renderStyleForSourceOrFallback(
305
+ uiTheme,
306
+ editor.colorSource,
307
+ spec,
308
+ EDITOR_BORDER_FALLBACK,
309
+ text,
310
+ );
311
+ }
312
+
319
313
  function clampRenderedLines(lines: string[], width: number): string[] {
320
314
  const maxWidth = Math.max(0, width);
321
315
  return lines.map((line) => truncateToWidth(line, maxWidth, ""));
@@ -342,24 +336,6 @@ function getEditorChromeWidths(
342
336
  return { rail, railWidth: visibleWidth(rail) };
343
337
  }
344
338
 
345
- function composeMetadataLine(
346
- left: string,
347
- right: string | undefined,
348
- width: number,
349
- ): string {
350
- if (!right) return left;
351
- const maxWidth = Math.max(0, width);
352
- const rightWidth = visibleWidth(right);
353
- if (rightWidth >= maxWidth) return truncateToWidth(right, maxWidth, "");
354
-
355
- const leftWidth = Math.max(0, maxWidth - rightWidth - 1);
356
- const leftText = truncateToWidth(left, leftWidth, "");
357
- const gap = " ".repeat(
358
- Math.max(1, maxWidth - visibleWidth(leftText) - rightWidth),
359
- );
360
- return `${leftText}${gap}${right}`;
361
- }
362
-
363
339
  function ansiStrippedText(line: string): string {
364
340
  return line
365
341
  .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
@@ -382,17 +358,6 @@ function parseEditorBorder(
382
358
  return match?.[1] ? { count: match[1] } : undefined;
383
359
  }
384
360
 
385
- function renderEditorBorder(
386
- width: number,
387
- direction: keyof ViewportCounts,
388
- count: string | undefined,
389
- ): string {
390
- if (!count) return "─".repeat(width);
391
- const arrow = direction === "above" ? "↑" : "↓";
392
- const indicator = `─── ${arrow} ${count} more `;
393
- return `${indicator}${"─".repeat(Math.max(0, width - visibleWidth(indicator)))}`;
394
- }
395
-
396
361
  function unwrapPolishedFrameOnly(
397
362
  lines: string[],
398
363
  config: ZentuiConfig,
@@ -458,35 +423,6 @@ function inspectPolishedFrameProvenance(
458
423
  return { safe: !unsafe, ownedFrame };
459
424
  }
460
425
 
461
- function vimModeColor(mode: string): string {
462
- switch (mode.toLowerCase()) {
463
- case "insert":
464
- return "success";
465
- case "normal":
466
- return "accent";
467
- case "ex":
468
- return "warning";
469
- case "replace":
470
- return "error";
471
- case "visual":
472
- return "syntaxKeyword";
473
- default:
474
- return "muted";
475
- }
476
- }
477
-
478
- function readVimStatus(
479
- editor: WrappedEditor,
480
- uiTheme: Theme,
481
- ): string | undefined {
482
- const mode = editor.getMode?.();
483
- if (typeof mode !== "string") return undefined;
484
- const normalized = mode.trim();
485
- if (!normalized) return undefined;
486
- const label = `${normalized.toUpperCase()} `;
487
- return safeThemeFg(uiTheme, vimModeColor(normalized), label);
488
- }
489
-
490
426
  function renderMinimalistFrameFromBase({
491
427
  width,
492
428
  baseRendered,
@@ -551,172 +487,6 @@ function renderMinimalistFrameFromBase({
551
487
  };
552
488
  }
553
489
 
554
- function renderPolishedFrame({
555
- width,
556
- baseRendered,
557
- autocompleteSource,
558
- autocompleteCapture,
559
- uiTheme,
560
- config,
561
- modelMeta,
562
- thinkingLevel,
563
- rightStatus,
564
- ownedFrame,
565
- trustedBaseFrame = false,
566
- borderColor,
567
- }: PolishedFrameOptions): PolishedFrameResult {
568
- if (width <= 2)
569
- return { lines: clampRenderedLines(baseRendered, width), decorated: false };
570
-
571
- if (baseRendered.length < 2) {
572
- return { lines: clampRenderedLines(baseRendered, width), decorated: false };
573
- }
574
- if (ownedFrame && !isPolishedFrameSplit(ownedFrame, baseRendered.length)) {
575
- return { lines: clampRenderedLines(baseRendered, width), decorated: false };
576
- }
577
-
578
- const autocomplete = ownedFrame
579
- ? { known: true, count: ownedFrame.trailingLines.length }
580
- : autocompleteCount(autocompleteSource, autocompleteCapture, baseRendered);
581
- if (!autocomplete.known) {
582
- return { lines: clampRenderedLines(baseRendered, width), decorated: false };
583
- }
584
- const editorFrame =
585
- !ownedFrame && autocomplete.count > 0
586
- ? baseRendered.slice(0, -autocomplete.count)
587
- : baseRendered;
588
- const autocompleteLines = ownedFrame
589
- ? ownedFrame.trailingLines
590
- : autocomplete.count > 0
591
- ? baseRendered.slice(-autocomplete.count)
592
- : [];
593
- if (editorFrame.length < 2) {
594
- return { lines: clampRenderedLines(baseRendered, width), decorated: false };
595
- }
596
-
597
- const parsedTop = parseEditorBorder(editorFrame[0] ?? "", "above");
598
- const parsedBottom = parseEditorBorder(editorFrame.at(-1) ?? "", "below");
599
- if (!ownedFrame && !trustedBaseFrame && (!parsedTop || !parsedBottom)) {
600
- return { lines: clampRenderedLines(baseRendered, width), decorated: false };
601
- }
602
- const editorLines = ownedFrame?.editorLines ?? editorFrame.slice(1, -1);
603
- const viewport = ownedFrame?.viewport ?? {
604
- above: parsedTop?.count,
605
- below: parsedBottom?.count,
606
- };
607
- const lines = renderPolishedEditorFrame({
608
- width,
609
- editorLines,
610
- autocompleteLines,
611
- viewport,
612
- uiTheme,
613
- config,
614
- modelMeta,
615
- thinkingLevel,
616
- rightStatus,
617
- borderColor,
618
- });
619
- POLISHED_FRAME_SPLITS.set(lines, {
620
- rows: Object.freeze([...lines]),
621
- split: {
622
- editorLines,
623
- trailingLines: autocompleteLines,
624
- viewport,
625
- },
626
- });
627
- return { lines, decorated: true };
628
- }
629
-
630
- /** Pure Opencode frame composition shared by the live editor and settings preview. */
631
- export function renderPolishedEditorFrame({
632
- width,
633
- editorLines,
634
- autocompleteLines = [],
635
- viewport = {},
636
- uiTheme,
637
- config,
638
- modelMeta,
639
- thinkingLevel,
640
- rightStatus,
641
- borderColor,
642
- }: PolishedEditorFrameOptions): string[] {
643
- if (width <= 2) return clampRenderedLines(editorLines, width);
644
- const reset = "\x1b[0m";
645
- const colorSource = config.components.editor.colorSource;
646
- const { rail, railWidth } = getEditorChromeWidths(config, uiTheme, reset);
647
- const innerWidth = Math.max(0, width - railWidth);
648
- const meta = renderEditorMetadataFormat(
649
- config.components.editor.styles.opencode.metadataFormat,
650
- {
651
- model: modelMeta.modelLabel,
652
- modelId: modelMeta.modelId ?? "",
653
- modelName: modelMeta.modelName ?? "",
654
- provider: modelMeta.providerLabel,
655
- thinking: thinkingLevel ?? "",
656
- sessionName: modelMeta.sessionName ?? "",
657
- },
658
- uiTheme,
659
- config,
660
- );
661
- const railedMeta = composeMetadataLine(meta, rightStatus, innerWidth);
662
-
663
- const renderStaticBorder = (text: string) =>
664
- renderStyleForSourceOrFallback(
665
- uiTheme,
666
- colorSource,
667
- config.colors.editorBorder,
668
- EDITOR_BORDER_FALLBACK,
669
- text,
670
- );
671
- const renderBorder = (text: string) => {
672
- if (
673
- config.components.editor.borderColorMode !== "adaptive" ||
674
- typeof borderColor !== "function"
675
- ) {
676
- return renderStaticBorder(text);
677
- }
678
- try {
679
- const rendered = borderColor(text);
680
- return typeof rendered === "string" ? rendered : renderStaticBorder(text);
681
- } catch {
682
- return renderStaticBorder(text);
683
- }
684
- };
685
- const top = renderBorder(
686
- renderEditorBorder(
687
- width,
688
- "above",
689
- config.components.editor.viewportIndicators ? viewport.above : undefined,
690
- ),
691
- );
692
- const bottom = renderBorder(
693
- renderEditorBorder(
694
- width,
695
- "below",
696
- config.components.editor.viewportIndicators ? viewport.below : undefined,
697
- ),
698
- );
699
- const completionLines =
700
- config.components.editor.styles.opencode.completionMenu === "palette"
701
- ? renderCompletionPalette({
702
- lines: autocompleteLines,
703
- width,
704
- theme: uiTheme,
705
- renderSeparator: renderBorder,
706
- ownedBackground: false,
707
- })
708
- : autocompleteLines;
709
- const lines = ["", ...editorLines, "", railedMeta];
710
- const renderedLines = [
711
- top,
712
- ...lines.map((line) => `${rail}${fillLine(line, innerWidth)}`),
713
- bottom,
714
- ...completionLines,
715
- ];
716
-
717
- return clampRenderedLines(renderedLines, width);
718
- }
719
-
720
490
  export class PolishedEditor extends CustomEditor {
721
491
  private readonly getModelMeta: () => EditorMeta;
722
492
  private readonly getThinkingLevel: () => string | undefined;
@@ -752,83 +522,45 @@ export class PolishedEditor extends CustomEditor {
752
522
 
753
523
  render(width: number): string[] {
754
524
  const config = this.getConfig();
755
- if (!config.components.editor.enabled) {
525
+ if (config.components.editor.style === "off") {
526
+ const offBorder = offEditorBorderColor(config, this.uiTheme);
527
+ if (offBorder) this.borderColor = offBorder;
756
528
  this.reportMinimalistDecoration(false);
757
529
  return clampRenderedLines(super.render(width), width);
758
530
  }
759
- if (config.components.editor.style === "minimalist") {
760
- if (width <= 4) {
761
- this.reportMinimalistDecoration(false);
762
- return clampRenderedLines(super.render(width), width);
763
- }
764
- let captured: { value: string[]; capture?: AutocompleteCapture };
765
- try {
766
- captured = renderWithAutocompleteCapture(
767
- this as unknown as AutocompleteEditorInternals,
768
- () => super.render(Math.max(0, width - 4)),
769
- );
770
- } catch {
771
- this.reportMinimalistDecoration(false);
772
- return clampRenderedLines(super.render(width), width);
773
- }
774
- try {
775
- const result = renderMinimalistFrameFromBase({
776
- width,
777
- baseRendered: captured.value,
778
- autocompleteSource: this as unknown as AutocompleteEditorInternals,
779
- autocompleteCapture: captured.capture,
780
- uiTheme: this.uiTheme,
781
- config,
782
- inputText: this.getText(),
783
- metadata: this.getMinimalistMetadata(),
784
- trustedBaseFrame: true,
785
- borderColor: this.borderColor,
786
- });
787
- this.reportMinimalistDecoration(result.decorated);
788
- return result.lines;
789
- } catch {
790
- this.reportMinimalistDecoration(false);
791
- return clampRenderedLines(captured.value, width);
792
- }
793
- }
794
- this.reportMinimalistDecoration(false);
795
- if (width <= 2) {
531
+ if (width <= 4) {
532
+ this.reportMinimalistDecoration(false);
796
533
  return clampRenderedLines(super.render(width), width);
797
534
  }
798
-
799
- const { railWidth } = getEditorChromeWidths(
800
- config,
801
- this.uiTheme,
802
- "\x1b[0m",
803
- );
804
- const innerWidth = Math.max(0, width - railWidth);
805
535
  let captured: { value: string[]; capture?: AutocompleteCapture };
806
536
  try {
807
537
  captured = renderWithAutocompleteCapture(
808
538
  this as unknown as AutocompleteEditorInternals,
809
- () => super.render(innerWidth),
539
+ () => super.render(Math.max(0, width - 4)),
810
540
  );
811
541
  } catch {
542
+ this.reportMinimalistDecoration(false);
812
543
  return clampRenderedLines(super.render(width), width);
813
544
  }
814
545
  try {
815
- const result = renderPolishedFrame({
546
+ const result = renderMinimalistFrameFromBase({
816
547
  width,
817
548
  baseRendered: captured.value,
818
549
  autocompleteSource: this as unknown as AutocompleteEditorInternals,
819
550
  autocompleteCapture: captured.capture,
820
551
  uiTheme: this.uiTheme,
821
552
  config,
822
- modelMeta: this.getModelMeta(),
823
- thinkingLevel: this.getThinkingLevel(),
553
+ inputText: this.getText(),
554
+ metadata: this.getMinimalistMetadata(),
824
555
  trustedBaseFrame: true,
825
556
  borderColor: this.borderColor,
826
557
  });
827
- if (result.decorated) return result.lines;
558
+ this.reportMinimalistDecoration(result.decorated);
559
+ return result.lines;
828
560
  } catch {
829
- // Decoration is optional; preserve the completed same-render rows below.
561
+ this.reportMinimalistDecoration(false);
562
+ return clampRenderedLines(captured.value, width);
830
563
  }
831
- return clampRenderedLines(captured.value, width);
832
564
  }
833
565
  }
834
566
 
@@ -956,73 +688,25 @@ export class WrappedPolishedEditor implements EditorComponent {
956
688
 
957
689
  render(width: number): string[] {
958
690
  const config = this.getConfig();
959
- if (!config.components.editor.enabled) {
691
+ if (config.components.editor.style === "off") {
692
+ const offBorder = offEditorBorderColor(config, this.uiTheme);
693
+ if (offBorder) this.borderColor = offBorder;
960
694
  this.reportMinimalistDecoration(false);
961
695
  return clampRenderedLines(this.base.render(width), width);
962
696
  }
963
- if (config.components.editor.style === "minimalist") {
964
- if (width <= 4) {
965
- this.reportMinimalistDecoration(false);
966
- return clampRenderedLines(this.base.render(width), width);
967
- }
968
- let captured: { value: string[]; capture?: AutocompleteCapture };
969
- try {
970
- captured = renderWithAutocompleteCapture(this.base, () =>
971
- this.base.render(Math.max(0, width - 4)),
972
- );
973
- } catch {
974
- this.reportMinimalistDecoration(false);
975
- return clampRenderedLines(this.base.render(width), width);
976
- }
977
- try {
978
- const provenance = inspectPolishedFrameProvenance(
979
- this.base,
980
- captured.value,
981
- config,
982
- this.uiTheme,
983
- );
984
- if (provenance.safe) {
985
- const result = renderMinimalistFrameFromBase({
986
- width,
987
- baseRendered: captured.value,
988
- autocompleteSource: this.base,
989
- autocompleteCapture: captured.capture,
990
- uiTheme: this.uiTheme,
991
- config,
992
- inputText: this.base.getText(),
993
- metadata: this.getMinimalistMetadata(),
994
- ownedFrame: provenance.ownedFrame,
995
- borderColor: this.borderColor,
996
- });
997
- if (result.decorated) {
998
- this.reportMinimalistDecoration(true);
999
- return result.lines;
1000
- }
1001
- }
1002
- } catch {
1003
- // Decoration is optional; preserve the completed same-render rows below.
1004
- }
697
+ if (width <= 4) {
1005
698
  this.reportMinimalistDecoration(false);
1006
- return clampRenderedLines(captured.value, width);
699
+ return clampRenderedLines(this.base.render(width), width);
1007
700
  }
1008
- this.reportMinimalistDecoration(false);
1009
- if (width <= 2) return clampRenderedLines(this.base.render(width), width);
1010
-
1011
- const { railWidth } = getEditorChromeWidths(
1012
- config,
1013
- this.uiTheme,
1014
- "\x1b[0m",
1015
- );
1016
- const innerWidth = Math.max(0, width - railWidth);
1017
701
  let captured: { value: string[]; capture?: AutocompleteCapture };
1018
702
  try {
1019
703
  captured = renderWithAutocompleteCapture(this.base, () =>
1020
- this.base.render(innerWidth),
704
+ this.base.render(Math.max(0, width - 4)),
1021
705
  );
1022
706
  } catch {
707
+ this.reportMinimalistDecoration(false);
1023
708
  return clampRenderedLines(this.base.render(width), width);
1024
709
  }
1025
- let result: PolishedFrameResult | undefined;
1026
710
  try {
1027
711
  const provenance = inspectPolishedFrameProvenance(
1028
712
  this.base,
@@ -1031,24 +715,27 @@ export class WrappedPolishedEditor implements EditorComponent {
1031
715
  this.uiTheme,
1032
716
  );
1033
717
  if (provenance.safe) {
1034
- result = renderPolishedFrame({
718
+ const result = renderMinimalistFrameFromBase({
1035
719
  width,
1036
720
  baseRendered: captured.value,
1037
721
  autocompleteSource: this.base,
1038
722
  autocompleteCapture: captured.capture,
1039
723
  uiTheme: this.uiTheme,
1040
724
  config,
1041
- modelMeta: this.getModelMeta(),
1042
- thinkingLevel: this.getThinkingLevel(),
1043
- rightStatus: readVimStatus(this.base, this.uiTheme),
725
+ inputText: this.base.getText(),
726
+ metadata: this.getMinimalistMetadata(),
1044
727
  ownedFrame: provenance.ownedFrame,
1045
728
  borderColor: this.borderColor,
1046
729
  });
730
+ if (result.decorated) {
731
+ this.reportMinimalistDecoration(true);
732
+ return result.lines;
733
+ }
1047
734
  }
1048
735
  } catch {
1049
736
  // Decoration is optional; preserve the completed same-render rows below.
1050
737
  }
1051
- if (result?.decorated) return result.lines;
738
+ this.reportMinimalistDecoration(false);
1052
739
  return clampRenderedLines(captured.value, width);
1053
740
  }
1054
741
 
@@ -22,7 +22,7 @@ import {
22
22
  formatUsernameHostLabel,
23
23
  } from "../../shared/format.ts";
24
24
  import { resolveRuntimeSymbol } from "../../shared/icons.ts";
25
- import { renderStyleForSource } from "../../shared/style.ts";
25
+ import { renderSourceColor, renderStyleForSource } from "../../shared/style.ts";
26
26
  import { sanitizeEditorMetadataText } from "../editor/editor-metadata-format.ts";
27
27
  import {
28
28
  collectExtensionStatusSegments,
@@ -322,10 +322,12 @@ export function installFooter(
322
322
  separatorText[config.components.footer.styles.starship.separator],
323
323
  );
324
324
  const innerWidth = Math.max(1, width - 2);
325
- const cwdLabel = renderStyleForSource(
325
+ const cwdLabel = renderSourceColor(
326
326
  theme,
327
327
  colorSource,
328
328
  config.colors.cwd,
329
+ "cwd",
330
+ "bold cyan",
329
331
  formattedCwd,
330
332
  );
331
333
  const needsSessionName =
@@ -932,10 +934,12 @@ export function installFooter(
932
934
 
933
935
  const chunkBudget = compactChunkBudget(innerWidth);
934
936
  const compactCwdLabel = truncateToWidth(
935
- renderStyleForSource(
937
+ renderSourceColor(
936
938
  theme,
937
939
  colorSource,
938
940
  config.colors.cwd,
941
+ "cwd",
942
+ "bold cyan",
939
943
  sanitizeEditorMetadataText(
940
944
  formatCwdLabel(ctx.cwd, config.icons.cwd, {
941
945
  mode: "basename",
@@ -427,6 +427,54 @@ export function renderStyleForSourceOrFallback(
427
427
  return renderStyleForSource(theme, source, style ?? fallbackStyle, text);
428
428
  }
429
429
 
430
+ /**
431
+ * 主题画板存在性探测:pi Theme 暴露 fgColors Map;测试或其他实现可
432
+ * 提供 hasThemeToken 回调,缺失时保守按“无该 token”处理。
433
+ */
434
+ function themeHasToken(theme: ThemeLike, token: string): boolean {
435
+ if (
436
+ typeof (theme as { hasThemeToken?: (t: string) => boolean })
437
+ .hasThemeToken === "function"
438
+ ) {
439
+ return (
440
+ theme as unknown as { hasThemeToken(t: string): boolean }
441
+ ).hasThemeToken(token);
442
+ }
443
+ const fgColors = (theme as unknown as { fgColors?: Map<string, string> })
444
+ .fgColors;
445
+ return fgColors instanceof Map && fgColors.has(token);
446
+ }
447
+
448
+ /**
449
+ * 颜色统一渲染入口(配置优先,其次主题 token,最后原生默认):
450
+ * - 配置了 spec:按 colorSource 现有语义解释(theme=token/hex,terminal=终端色);
451
+ * - 未配置且 theme 源:主题定义了 themeToken 时用主题色(可指向 vars 变量),
452
+ * 否则回落原生默认 themeDefault;
453
+ * - 未配置且 terminal 源:使用固定终端默认 terminalDefault。
454
+ */
455
+ export function renderSourceColor(
456
+ theme: ThemeLike,
457
+ source: ColorSource,
458
+ spec: ColorSpec | undefined,
459
+ themeToken: string,
460
+ themeDefault: ColorSpec,
461
+ text: string,
462
+ ): string {
463
+ if (typeof spec === "string" && spec.trim() !== "") {
464
+ return renderStyleForSource(theme, source, spec, text);
465
+ }
466
+ // 未配置时:theme 源优先主题专有 token,terminal 源与主题缺 token 时
467
+ // 都回落 Pi 原生默认(主题语义色),保证两种 colorSource 默认观感一致。
468
+ if (source !== "terminal" && themeHasToken(theme, themeToken)) {
469
+ try {
470
+ return theme.fg(themeToken, text);
471
+ } catch {
472
+ // 主题色解析失败时继续回退原生默认。
473
+ }
474
+ }
475
+ return renderThemeStyle(theme, themeDefault, text);
476
+ }
477
+
430
478
  export function renderStyleForSourceOrFallbackStrict(
431
479
  theme: ThemeLike,
432
480
  source: ColorSource,
@@ -52,6 +52,11 @@ export const TOOL_EXPANDED_BACKGROUND_PATCH = Symbol.for(
52
52
  "pi.ccstyle.tool-expanded-background-patch",
53
53
  );
54
54
 
55
+ // ── 工具 updateDisplay 跨 toggle 重建缓存 ──
56
+ export const TOGGLE_RENDER_CACHE_PATCH = Symbol.for(
57
+ "pi.ccstyle.toggle-render-cache-patch",
58
+ );
59
+
55
60
  // ── 消息组件补丁 ──
56
61
  export const MESSAGE_DISPLAY_PATCH = Symbol.for(
57
62
  "pi.ccstyle.message-display-patch",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-one-ui",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A single, customizable Pi UI package with layout-oriented Context and Shell rendering for Pi.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -81,7 +81,10 @@
81
81
  "thinkingHigh": "accentBright",
82
82
  "thinkingXhigh": "#4db9cd",
83
83
  "thinkingMax": "cyan",
84
- "bashMode": "green"
84
+ "bashMode": "green",
85
+ "cwd": "blue",
86
+ "editorModel": "blue",
87
+ "editorBorder": "blue"
85
88
  },
86
89
  "export": {
87
90
  "pageBg": "bg",
@@ -81,7 +81,10 @@
81
81
  "thinkingHigh": "accentBright",
82
82
  "thinkingXhigh": "#4a8fa3",
83
83
  "thinkingMax": "cyan",
84
- "bashMode": "green"
84
+ "bashMode": "green",
85
+ "cwd": "blue",
86
+ "editorModel": "blue",
87
+ "editorBorder": "blue"
85
88
  },
86
89
  "export": {
87
90
  "pageBg": "bg",