@pi-archimedes/ui 2.9.3 → 2.10.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/README.md CHANGED
@@ -48,6 +48,7 @@ TUI enhancements for the Pi Archimedes monorepo.
48
48
  | `labelText` | string | `"Thinking..."` | Text shown before thinking blocks |
49
49
  | `labelColor` | string | `"255,215,0"` | RGB color for the thinking label (e.g. `255,215,0`) |
50
50
  | `animationStyle` | "diagonal" \| "top-right" \| "bottom-left" \| "bottom-right" \| "center-out" \| "wave" \| "horizontal" \| "vertical" \| "vertical-up" | `"vertical-up"` | Startup logo animation |
51
+ | `colorfulLogo` | boolean | `false` | Use pi's brand colors for the splash logo |
51
52
  | `editorSpinBorder` | boolean | `true` | Animated spinner border on editor |
52
53
  | `editorSpinSpeed` | "slow" \| "normal" \| "fast" | `"normal"` | Editor border spinner speed |
53
54
  | `editorSpinStyle` | "typing" \| "pulse" \| "rain" \| "cascade" \| "columns" \| "wave-rows" \| "diagonal-swipe" \| "sparkle" \| "pendulum" \| "marquee" | `"pendulum"` | Editor spinner style |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-archimedes/ui",
3
- "version": "2.9.3",
3
+ "version": "2.10.0",
4
4
  "description": "Terminal UI enhancements, styled bash tool rendering, and visual presentation components for Pi",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -28,7 +28,7 @@
28
28
  "./config": "./src/config.ts"
29
29
  },
30
30
  "dependencies": {
31
- "@pi-archimedes/core": "2.9.3"
31
+ "@pi-archimedes/core": "2.10.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@earendil-works/pi-ai": "^0.87.0",
@@ -44,6 +44,7 @@ describe("loadUIConfig", () => {
44
44
  labelText: "Thinking...",
45
45
  labelColor: "255,215,0",
46
46
  animationStyle: "vertical-up",
47
+ colorfulLogo: false,
47
48
  editorSpinBorder: true,
48
49
  editorSpinSpeed: "normal",
49
50
  editorSpinLabel: "Working",
@@ -89,6 +90,7 @@ describe("DEFAULT_UI_CONFIG", () => {
89
90
  labelText: "Thinking...",
90
91
  labelColor: "255,215,0",
91
92
  animationStyle: "vertical-up",
93
+ colorfulLogo: false,
92
94
  editorSpinBorder: true,
93
95
  editorSpinSpeed: "normal",
94
96
  editorSpinLabel: "Working",
package/src/config.ts CHANGED
@@ -87,6 +87,7 @@ export interface UIConfig {
87
87
  labelText: string;
88
88
  labelColor: string;
89
89
  animationStyle: AnimationStyle;
90
+ colorfulLogo: boolean;
90
91
  editorSpinBorder: boolean;
91
92
  editorSpinSpeed: "slow" | "normal" | "fast";
92
93
  editorSpinLabel: string;
@@ -106,6 +107,7 @@ export const DEFAULT_UI_CONFIG: UIConfig = {
106
107
  labelText: "Thinking...",
107
108
  labelColor: "255,215,0",
108
109
  animationStyle: "vertical-up",
110
+ colorfulLogo: false,
109
111
  editorSpinBorder: true,
110
112
  editorSpinSpeed: "normal",
111
113
  editorSpinLabel: "Working",
@@ -3,9 +3,9 @@ import { getUISettingsItems } from "./settings.js";
3
3
  import { DEFAULT_UI_CONFIG, ANIMATION_STYLES, type UIConfig } from "./config.js";
4
4
 
5
5
  describe("getUISettingsItems", () => {
6
- it("exposes all 14 UI setting items with correct defaults", () => {
6
+ it("exposes all 15 UI setting items with correct defaults", () => {
7
7
  const items = getUISettingsItems(DEFAULT_UI_CONFIG);
8
- expect(items).toHaveLength(14);
8
+ expect(items).toHaveLength(15);
9
9
 
10
10
  const ids = items.map((i) => i.id);
11
11
  expect(ids).toEqual([
@@ -19,6 +19,7 @@ describe("getUISettingsItems", () => {
19
19
  "labelText",
20
20
  "labelColor",
21
21
  "animationStyle",
22
+ "colorfulLogo",
22
23
  "editorSpinBorder",
23
24
  "editorSpinSpeed",
24
25
  "editorSpinStyle",
package/src/settings.ts CHANGED
@@ -76,6 +76,13 @@ export function getUISettingsItems(config: UIConfig): SettingItem[] {
76
76
  currentValue: config.animationStyle,
77
77
  values: [...ANIMATION_STYLES],
78
78
  },
79
+ {
80
+ id: "colorfulLogo",
81
+ label: "Colorful Logo",
82
+ description: "Use pi's brand colors for the splash logo",
83
+ currentValue: config.colorfulLogo ? "On" : "Off",
84
+ values: ["On", "Off"],
85
+ },
79
86
  {
80
87
  id: "editorSpinBorder",
81
88
  label: "Editor Spin Border",
@@ -42,7 +42,8 @@ export interface ListingRef {
42
42
  export function renderHeader(theme: Theme, ref: ListingRef, width: number, height: number): string[] {
43
43
  const dim = (t: string) => theme.fg("dim", t);
44
44
  const accent = (t: string) => theme.fg("accent", t);
45
- const logoLines = getShinedLogo(ref.frame, loadUIConfig().animationStyle);
45
+ const cfg = loadUIConfig();
46
+ const logoLines = getShinedLogo(ref.frame, cfg.animationStyle, cfg.colorfulLogo);
46
47
 
47
48
  // Use cached text lines if settled (no more animations)
48
49
  let listingLines: string[];
@@ -180,6 +180,26 @@ describe("getShinedLogo (truecolor)", () => {
180
180
  const result2 = mod.getShinedLogo(50, "wave");
181
181
  expect(result1).toEqual(result2);
182
182
  });
183
+
184
+ it("colorful logo paints every pixel with its pi brand color", () => {
185
+ const C = "228;138;122", B = "79;142;179", Y = "234;182;93", W = "255;255;255";
186
+ const pixels = [
187
+ [C, C, C, null],
188
+ [B, null, C, null],
189
+ [B, B, null, Y],
190
+ [B, null, null, Y],
191
+ ];
192
+ const cellColors = (row: string) =>
193
+ [...row.matchAll(/\x1b\[38;2;(\d+;\d+;\d+)m.\x1b\[0m| /g)].map((m) => m[1] ?? null);
194
+
195
+ const colorful = mod.getShinedLogo(500, "wave", true);
196
+ const plain = mod.getShinedLogo(500, "wave", false);
197
+ for (let y = 0; y < 8; y++) {
198
+ const expected = Array.from({ length: 16 }, (_, x) => pixels[Math.floor(y / 2)]![Math.floor(x / 4)]);
199
+ expect(cellColors(colorful[y]!)).toEqual(expected);
200
+ expect(cellColors(plain[y]!)).toEqual(expected.map((c) => (c ? W : null)));
201
+ }
202
+ });
183
203
  });
184
204
 
185
205
  // ── All animation styles ─────────────────────────────────────────────────────
@@ -23,6 +23,17 @@ export const LOGO = [
23
23
  "████ ████",
24
24
  ];
25
25
 
26
+ // Pi brand colors per logo pixel (4x4). Copied from pi-coding-agent's pi-logo.js, which is not exported.
27
+ const CORAL: [number, number, number] = [228, 138, 122];
28
+ const BLUE: [number, number, number] = [79, 142, 179];
29
+ const YELLOW: [number, number, number] = [234, 182, 93];
30
+ const PI_COLORS: ([number, number, number] | null)[][] = [
31
+ [CORAL, CORAL, CORAL, null],
32
+ [BLUE, null, CORAL, null],
33
+ [BLUE, BLUE, null, YELLOW],
34
+ [BLUE, null, null, YELLOW],
35
+ ];
36
+
26
37
  export const CHAR_FADE_FRAMES = 22;
27
38
  export const LOGO_SETTLE_FRAME = 90;
28
39
  export const LOGO_PAD = 0;
@@ -61,7 +72,7 @@ function computeRevealAt(x: number, y: number, style: AnimationStyle): number {
61
72
  }
62
73
  }
63
74
 
64
- export function getShinedLogo(frame: number, style: AnimationStyle = "wave"): string[] {
75
+ export function getShinedLogo(frame: number, style: AnimationStyle = "wave", colorful = false): string[] {
65
76
  if (!TRUECOLOR) return LOGO;
66
77
 
67
78
  return LOGO.map((line, y) => {
@@ -77,6 +88,12 @@ export function getShinedLogo(frame: number, style: AnimationStyle = "wave"): st
77
88
 
78
89
  const t = Math.min(1, age / CHAR_FADE_FRAMES);
79
90
  const eased = 1 - (1 - t) * (1 - t);
91
+ const pi = colorful ? PI_COLORS[Math.floor(y / 2)]![Math.floor(x / 4)] : null;
92
+ if (pi) {
93
+ result += rgb(lerp(50, pi[0], eased), lerp(50, pi[1], eased), lerp(50, pi[2], eased), char);
94
+ continue;
95
+ }
96
+
80
97
  const brightness = Math.floor(lerp(50, 255, eased));
81
98
  result += gray(brightness, char);
82
99
  }