@narumitw/pi-webui 0.28.0 โ†’ 0.29.2

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@narumitw/pi-webui)](https://www.npmjs.com/package/@narumitw/pi-webui) [![Pi extension](https://img.shields.io/badge/Pi-extension-blue)](https://pi.dev) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
4
4
 
5
- `@narumitw/pi-webui` adds a private, lightweight browser companion to the current terminal-owned [Pi Coding Agent](https://pi.dev) session. It displays Pi's semantic conversation and tool activity as they happen and can send text or sanitized images back into that same session.
5
+ `@narumitw/pi-webui` adds a private, focused browser companion to the current terminal-owned [Pi Coding Agent](https://pi.dev) session. It displays Pi's semantic conversation and tool activity as they happen and can send text or sanitized images back into that same session.
6
6
 
7
7
  This package is intentionally different from the broader, separately maintained `@narumitw/pi-web` application. WebUI has one current-session chat page and no session manager, shell, file browser, git UI, control room, or task board.
8
8
 
@@ -16,7 +16,7 @@ This package is intentionally different from the broader, separately maintained
16
16
  - Accepts pasted, dropped, or selected PNG, JPEG, WebP, GIF, BMP, TIFF, HEIC/HEIF, and AVIF images, strips metadata server-side, applies Pi-compatible size limits, and provides ordered thumbnails plus an enlarged preview.
17
17
  - Reconnects from an ordered event cursor and replaces state from an authoritative snapshot after a gap.
18
18
  - Keeps a failed browser draft and prevents rapid duplicate submission with request IDs.
19
- - Uses no frontend framework, build step, browser storage, remote service, or automatically launched browser.
19
+ - Uses React with Radix Primitives, Themes, Colors, and Icons for accessible controls, disclosures, overlays, adaptive color, and consistent iconography. Published browser assets are bundled locally; runtime use needs no CDN, remote service, browser storage, or automatically launched browser.
20
20
 
21
21
  ## ๐Ÿ“ฆ Install
22
22
 
@@ -168,7 +168,10 @@ src/server.ts authenticated loopback HTTP/SSE server and raw attachment p
168
168
  src/image-limits.ts shared configurable defaults, ceilings, and provider constraints
169
169
  src/images.ts bounded provider-ready image processing
170
170
  src/pi-settings.ts effective Pi image settings reader
171
- src/web/ framework-free browser page
171
+ src/web/ui/ React source using Radix UI and the browser protocol client
172
+ src/web/app.js generated, bundled browser application
173
+ src/web/app.css generated Radix Themes, Colors, and local presentation styles
174
+ src/web/index.html minimal authenticated browser shell
172
175
  ```
173
176
 
174
177
  ## ๐Ÿงช Development
@@ -176,13 +179,14 @@ src/web/ framework-free browser page
176
179
  From the repository root:
177
180
 
178
181
  ```bash
182
+ npm --workspace @narumitw/pi-webui run build:web
179
183
  npm --workspace @narumitw/pi-webui run check
180
184
  npm test
181
185
  just try webui
182
186
  just pack webui
183
187
  ```
184
188
 
185
- The package preview must contain its manifest, license, README, TypeScript source, and static web assets, but no tests, fixtures, cache, or `node_modules`.
189
+ Edit browser code under `src/web/ui/`, then run `build:web`; `check:web` (included in the package typecheck) rejects stale generated assets. The package preview must contain its manifest, license, README, TypeScript and browser source, and bundled static assets, but no tests, fixtures, cache, build scripts, or `node_modules`.
186
190
 
187
191
  ## ๐Ÿ”Ž Keywords
188
192
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@narumitw/pi-webui",
3
- "version": "0.28.0",
4
- "description": "Lightweight local web companion for the current Pi terminal session.",
3
+ "version": "0.29.2",
4
+ "description": "Local Radix UI web companion for the current Pi terminal session.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "private": false,
@@ -24,13 +24,21 @@
24
24
  ]
25
25
  },
26
26
  "scripts": {
27
- "check": "biome check --vcs-use-ignore-file=false src test package.json tsconfig.json && npm run typecheck",
28
- "format": "biome check --write --vcs-use-ignore-file=false src test package.json tsconfig.json",
29
- "typecheck": "tsc --noEmit"
27
+ "build:web": "node scripts/build-web.mjs",
28
+ "check": "biome check --vcs-use-ignore-file=false src test scripts package.json tsconfig.json && npm run typecheck",
29
+ "check:web": "node scripts/build-web.mjs --check",
30
+ "format": "biome check --write --vcs-use-ignore-file=false src test scripts package.json tsconfig.json && npm run build:web",
31
+ "typecheck": "npm run check:web && tsc --noEmit"
30
32
  },
31
33
  "dependencies": {
34
+ "@radix-ui/colors": "^3.0.0",
35
+ "@radix-ui/react-icons": "^1.3.2",
36
+ "@radix-ui/themes": "^3.3.0",
32
37
  "bmp-js": "^0.1.0",
33
38
  "heic-decode": "^2.1.0",
39
+ "radix-ui": "1.6.6",
40
+ "react": "^19.2.8",
41
+ "react-dom": "^19.2.8",
34
42
  "sharp": "^0.35.3"
35
43
  },
36
44
  "peerDependencies": {
@@ -43,6 +51,7 @@
43
51
  "@earendil-works/pi-ai": "0.80.10",
44
52
  "@earendil-works/pi-coding-agent": "0.80.10",
45
53
  "@earendil-works/pi-tui": "0.80.10",
54
+ "esbuild": "^0.25.12",
46
55
  "typescript": "7.0.2"
47
56
  },
48
57
  "repository": {
package/src/server.ts CHANGED
@@ -23,6 +23,7 @@ const REQUEST_ID = /^[A-Za-z0-9_-]{1,120}$/;
23
23
  const MAX_REQUESTS = 128;
24
24
  const DEFAULT_MAX_DRAFT_TEXT_BYTES = 1024 * 1024;
25
25
  const SSE_FLUSH_TIMEOUT_MS = 250;
26
+ const CSP_NONCE_PLACEHOLDER = "__PI_CSP_NONCE__";
26
27
 
27
28
  export interface WebSendRequest {
28
29
  requestId: string;
@@ -80,6 +81,7 @@ export class WebUIServer {
80
81
  readonly origin: string;
81
82
  private bootstrapToken?: string;
82
83
  private readonly sessionSecret = token();
84
+ private readonly cspNonce = token();
83
85
  private readonly cookieName = `pi_webui_${randomBytes(8).toString("hex")}`;
84
86
  private readonly sockets = new Set<Socket>();
85
87
  private readonly sseClients = new Set<SseClient>();
@@ -231,17 +233,12 @@ export class WebUIServer {
231
233
  await this.asset(response, "index.html", "text/html; charset=utf-8");
232
234
  return;
233
235
  }
234
- if (
235
- request.method === "GET" &&
236
- ["/app.js", "/state.js", "/markdown.js", "/transcript.js", "/image-drag.js"].includes(
237
- url.pathname,
238
- )
239
- ) {
240
- await this.asset(response, url.pathname.slice(1), "text/javascript; charset=utf-8");
236
+ if (request.method === "GET" && url.pathname === "/app.js") {
237
+ await this.asset(response, "app.js", "text/javascript; charset=utf-8");
241
238
  return;
242
239
  }
243
- if (request.method === "GET" && url.pathname === "/styles.css") {
244
- await this.asset(response, "styles.css", "text/css; charset=utf-8");
240
+ if (request.method === "GET" && url.pathname === "/app.css") {
241
+ await this.asset(response, "app.css", "text/css; charset=utf-8");
245
242
  return;
246
243
  }
247
244
  if (request.method === "GET" && url.pathname === "/api/state") {
@@ -745,7 +742,11 @@ export class WebUIServer {
745
742
  }
746
743
 
747
744
  private async asset(response: ServerResponse, name: string, contentType: string): Promise<void> {
748
- const content = await readAsset(name);
745
+ const asset = await readAsset(name);
746
+ const content =
747
+ name === "index.html"
748
+ ? Buffer.from(asset.toString("utf8").replaceAll(CSP_NONCE_PLACEHOLDER, this.cspNonce))
749
+ : asset;
749
750
  response.writeHead(200, { "Content-Type": contentType, "Content-Length": content.byteLength });
750
751
  response.end(content);
751
752
  }
@@ -754,7 +755,7 @@ export class WebUIServer {
754
755
  response.setHeader("Cache-Control", "no-store");
755
756
  response.setHeader(
756
757
  "Content-Security-Policy",
757
- "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'",
758
+ `default-src 'self'; script-src 'self'; style-src 'self' 'nonce-${this.cspNonce}'; img-src 'self' data: blob:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'`,
758
759
  );
759
760
  response.setHeader("Referrer-Policy", "no-referrer");
760
761
  response.setHeader("X-Content-Type-Options", "nosniff");