pi-sdk-web 0.3.12 → 0.3.13

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/dist/server.js CHANGED
@@ -418,6 +418,16 @@ export class PiWebServer {
418
418
  // Used by the sidebar Tools refresh button
419
419
  this.broadcastState();
420
420
  break;
421
+ case "set_theme": {
422
+ // Browser theme switch (dark/light): swap the extension ANSI theme.
423
+ // CSS switches instantly on the client; the server theme only
424
+ // affects extension setStatus/setWidget generated after this point
425
+ // (same as TUI - status text is fixed at setStatus time).
426
+ const name = data.name === "light" ? "light" : "dark";
427
+ this.uiContext.setWebTheme(name);
428
+ this.broadcast({ type: "theme_set", data: { name } });
429
+ break;
430
+ }
421
431
  case "bash": {
422
432
  const command = typeof data.command === "string" ? data.command : "";
423
433
  if (!command)
@@ -246,6 +246,10 @@ class PiWebClient {
246
246
  return;
247
247
  }
248
248
  this.hasConnectedBefore = true;
249
+ // Sync the persisted theme to the server on first connection so
250
+ // extension ANSI colors match the CSS theme (server defaults to dark).
251
+ const stored = localStorage.getItem('piweb-theme') === 'bright' ? 'light' : (localStorage.getItem('piweb-theme') || 'light');
252
+ this.send({ type: 'set_theme', name: stored });
249
253
  };
250
254
  ws.onmessage = (ev) => this.handleMessage(ev.data);
251
255
  ws.onclose = () => {
@@ -270,20 +274,33 @@ class PiWebClient {
270
274
  // ------------------------------------------------------------------
271
275
 
272
276
  initThemeSwitch() {
273
- const stored = localStorage.getItem('piweb-theme') || 'bright';
274
- this.applyTheme(stored);
277
+ // Migrate the old 'bright' key to 'light' (renamed for TUI parity).
278
+ const stored = localStorage.getItem('piweb-theme') === 'bright' ? 'light' : (localStorage.getItem('piweb-theme') || 'light');
279
+ this.applyTheme(stored, true);
275
280
  document.querySelectorAll('.theme-option').forEach((el) => {
276
- el.addEventListener('click', () => this.applyTheme(el.dataset.theme));
281
+ el.addEventListener('click', () => this.applyTheme(el.dataset.theme, false));
277
282
  });
278
283
  }
279
284
 
280
- applyTheme(theme) {
281
- const bright = theme === 'bright';
282
- document.body.classList.toggle('theme-bright', bright);
283
- localStorage.setItem('piweb-theme', bright ? 'bright' : 'dark');
285
+ applyTheme(theme, initial) {
286
+ const light = theme === 'light';
287
+ const name = light ? 'light' : 'dark';
288
+ // CSS variables switch instantly (the "interface frame", like TUI's
289
+ // requestRender on theme change).
290
+ document.body.classList.toggle('theme-light', light);
291
+ localStorage.setItem('piweb-theme', name);
284
292
  document.querySelectorAll('.theme-option').forEach((el) => {
285
- el.classList.toggle('active', el.dataset.theme === (bright ? 'bright' : 'dark'));
293
+ el.classList.toggle('active', el.dataset.theme === name);
286
294
  });
295
+ if (initial) return;
296
+ // Tell the server to swap the extension ANSI theme: extensions that
297
+ // setStatus/setWidget AFTER this point generate colors with the new
298
+ // theme. Already-rendered status text keeps its old colors until the
299
+ // extension updates it (same as TUI - status text is fixed at setStatus
300
+ // time; theme change only repaints the frame).
301
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
302
+ this.send({ type: 'set_theme', name: name });
303
+ }
287
304
  }
288
305
 
289
306
  send(obj) {
@@ -310,6 +327,12 @@ class PiWebClient {
310
327
  case 'state':
311
328
  this.renderState(data.data);
312
329
  break;
330
+ case 'theme_set':
331
+ // Server acknowledged the theme switch. No reload needed: CSS
332
+ // variables already switched instantly and the server theme only
333
+ // affects extension setStatus/setWidget generated after this point
334
+ // (same as TUI).
335
+ break;
313
336
  case 'history':
314
337
  this.renderHistory(data.data);
315
338
  break;
@@ -14,7 +14,7 @@
14
14
  <span id="theme-switch">
15
15
  <span class="theme-option" data-theme="dark">Dark</span>
16
16
  <span class="theme-sep">|</span>
17
- <span class="theme-option" data-theme="bright">Bright</span>
17
+ <span class="theme-option" data-theme="light">Light</span>
18
18
  </span>
19
19
  <span id="msg-nav">
20
20
  <span class="nav-btn" id="nav-prev" title="Jump to previous user message">↑</span>
@@ -28,8 +28,8 @@
28
28
  --table-head-bg: rgba(128, 128, 128, 0.1);
29
29
  }
30
30
 
31
- /* Bright theme (colors aligned with Pi's light theme) */
32
- body.theme-bright {
31
+ /* Light theme (colors aligned with Pi's light theme) */
32
+ body.theme-light {
33
33
  --bg: #f5f5f5;
34
34
  --text: #1f2328;
35
35
  --dim: #767676;
@@ -3,23 +3,23 @@ import { readFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  /**
6
- * Build the official Pi theme (colors from the shipped dark.json) so
7
- * extensions calling ui.theme.fg("accent", text) / .bg(...) get REAL ANSI
8
- * escapes — identical to TUI (interactive-mode.ts returns the same Theme
9
- * instance from ctx.ui.theme). The browser renders the escapes as colored
10
- * spans (see app.js ansiToHtml), acting as the "terminal".
6
+ * Load a Pi theme (dark.json/light.json colors) so extensions calling
7
+ * ui.theme.fg("accent", text) / .bg(...) get REAL ANSI escapes — identical
8
+ * to TUI (interactive-mode.ts returns the same Theme instance from
9
+ * ctx.ui.theme). The browser renders the escapes as colored spans (see
10
+ * app.js ansiToHtml), acting as the "terminal".
11
11
  *
12
- * The Theme class and initTheme are exported by the Pi SDK; dark.json ships
13
- * in the package dist. Constructing the Theme directly keeps us independent
14
- * of initTheme's global side effects and gives the same result.
12
+ * The Theme class is exported by the Pi SDK and the theme JSONs ship in the
13
+ * package dist. Constructing the Theme directly keeps us independent of
14
+ * initTheme's global side effects and gives the same result.
15
15
  */
16
- function createWebTheme() {
16
+ function createWebTheme(name = "dark") {
17
17
  try {
18
18
  const mainEntry = fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"));
19
- const darkJson = JSON.parse(readFileSync(join(dirname(mainEntry), "modes", "interactive", "theme", "dark.json"), "utf8"));
19
+ const themeJson = JSON.parse(readFileSync(join(dirname(mainEntry), "modes", "interactive", "theme", `${name}.json`), "utf8"));
20
20
  const fgColors = {};
21
21
  const bgColors = {};
22
- // dark.json colors refer to vars by name (e.g. "accent" -> "#8abeb7")
22
+ // theme colors refer to vars by name (e.g. "accent" -> "#8abeb7")
23
23
  // or carry a literal hex. Split background keys from foreground keys by
24
24
  // the explicit ThemeBg set (mirrors Pi's createTheme in theme.ts) rather
25
25
  // than a "Bg" suffix: scrollbarThumb is a ThemeBg without that suffix.
@@ -33,8 +33,8 @@ function createWebTheme() {
33
33
  "toolSuccessBg",
34
34
  "toolErrorBg",
35
35
  ]);
36
- for (const [key, value] of Object.entries(darkJson.colors)) {
37
- const resolved = value.startsWith("#") ? value : darkJson.vars[value] ?? value;
36
+ for (const [key, value] of Object.entries(themeJson.colors)) {
37
+ const resolved = value.startsWith("#") ? value : themeJson.vars[value] ?? value;
38
38
  if (bgKeys.has(key)) {
39
39
  bgColors[key] = resolved;
40
40
  }
@@ -42,7 +42,7 @@ function createWebTheme() {
42
42
  fgColors[key] = resolved;
43
43
  }
44
44
  }
45
- return new PiTheme(fgColors, bgColors, "truecolor", { name: "dark" });
45
+ return new PiTheme(fgColors, bgColors, "truecolor", { name });
46
46
  }
47
47
  catch {
48
48
  // Last-resort identity: return the text argument unchanged (no ANSI).
@@ -202,6 +202,10 @@ export class WebUIContext {
202
202
  setTheme() {
203
203
  return { success: false, error: "Theme switching not supported in web mode" };
204
204
  }
205
+ /** Switch the theme used for extension ANSI colors (dark/light). */
206
+ setWebTheme(name) {
207
+ this.webTheme = createWebTheme(name);
208
+ }
205
209
  // ------------------------------------------------------------------
206
210
  // Tool output expansion (web always shows expandable blocks)
207
211
  // ------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sdk-web",
3
- "version": "0.3.12",
3
+ "version": "0.3.13",
4
4
  "description": "Browser Web access for Pi (AI coding agent) via the Pi SDK - standalone module, zero modification to Pi itself",
5
5
  "type": "module",
6
6
  "bin": {