@narumitw/pi-webui 0.25.0 โ†’ 0.29.1

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
 
@@ -156,7 +156,8 @@ The page uses semantic headings, native disclosure/dialog controls, concise stat
156
156
  ## ๐Ÿ—‚๏ธ Package layout
157
157
 
158
158
  ```text
159
- src/webui.ts Pi extension entrypoint
159
+ src/index.ts Pi package entrypoint
160
+ src/webui.ts extension registration and command orchestration
160
161
  src/runtime.ts Pi lifecycle, commands, event projection, and browser message routing
161
162
  src/settings.ts global WebUI settings validation and atomic persistence
162
163
  src/conversation.ts bounded transcript snapshot and ordered event replay
@@ -167,7 +168,10 @@ src/server.ts authenticated loopback HTTP/SSE server and raw attachment p
167
168
  src/image-limits.ts shared configurable defaults, ceilings, and provider constraints
168
169
  src/images.ts bounded provider-ready image processing
169
170
  src/pi-settings.ts effective Pi image settings reader
170
- 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
171
175
  ```
172
176
 
173
177
  ## ๐Ÿงช Development
@@ -175,13 +179,14 @@ src/web/ framework-free browser page
175
179
  From the repository root:
176
180
 
177
181
  ```bash
182
+ npm --workspace @narumitw/pi-webui run build:web
178
183
  npm --workspace @narumitw/pi-webui run check
179
184
  npm test
180
185
  just try webui
181
186
  just pack webui
182
187
  ```
183
188
 
184
- 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`.
185
190
 
186
191
  ## ๐Ÿ”Ž Keywords
187
192
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@narumitw/pi-webui",
3
- "version": "0.25.0",
4
- "description": "Lightweight local web companion for the current Pi terminal session.",
3
+ "version": "0.29.1",
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,
@@ -20,17 +20,25 @@
20
20
  ],
21
21
  "pi": {
22
22
  "extensions": [
23
- "./src/webui.ts"
23
+ "./src/index.ts"
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/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from "./webui.js";
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");