@narumitw/pi-image-drop 0.25.0 โ†’ 0.30.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
@@ -4,7 +4,7 @@
4
4
 
5
5
  `@narumitw/pi-image-drop` adds one `/image-drop` command to the latest [Pi Coding Agent](https://pi.dev). It serves a private loopback page where you can paste, drop, choose, preview, reorder, retry, and remove local images. The ordered batch is attached to your next non-empty interactive Pi message.
6
6
 
7
- The page never contains a prompt or Attach button: Pi remains the only place where messages are written and sent.
7
+ The page never contains a prompt or Attach button: Pi remains the only place where messages are written and sent. Its React and TypeScript frontend uses Radix Themes, Primitives, Colors, and Icons, and ships as local bundled assets with no CDN or runtime build step.
8
8
 
9
9
  ## โœจ Features
10
10
 
@@ -14,6 +14,7 @@ The page never contains a prompt or Attach button: Pi remains the only place whe
14
14
  - Supports PNG, JPEG, WebP, GIF, BMP, TIFF, HEIC/HEIF, and AVIF input.
15
15
  - Applies orientation, strips private metadata, and enforces Pi-compatible image limits.
16
16
  - Keeps bounded sent-image history for explicit re-attachment during the live session.
17
+ - Uses Radix UI for an adaptive light/dark design system, accessible dialogs and disclosures, semantic colors, and consistent action icons.
17
18
  - Reports batch state above Pi's editor and can start automatically with each session.
18
19
 
19
20
  ## ๐Ÿ“ฆ Install
@@ -139,14 +140,19 @@ Then open the unchanged `http://127.0.0.1:45678/...` link locally. Image Drop do
139
140
  ## ๐Ÿ—‚๏ธ Package layout
140
141
 
141
142
  ```text
142
- src/image-drop.ts Pi extension entrypoint
143
+ src/index.ts Pi package entrypoint
144
+ src/image-drop.ts extension registration and command orchestration
143
145
  src/runtime.ts Pi lifecycle and message orchestration
144
146
  src/batch.ts in-memory draft and sent-history state machine
145
147
  src/images.ts bounded image processing
146
148
  src/server.ts authenticated loopback HTTP/SSE server
147
149
  src/settings.ts extension settings
148
150
  src/pi-settings.ts effective Pi image settings adapter
149
- src/web/ framework-free browser page
151
+ src/web/ui/ authored React and TypeScript browser source
152
+ src/web/app.js generated bundled React application
153
+ src/web/state.js generated compatibility helper module
154
+ src/web/styles.css generated Radix Themes, Colors, and local styles
155
+ src/web/index.html minimal authenticated React shell
150
156
  ```
151
157
 
152
158
  ## ๐Ÿงช Development
@@ -154,13 +160,14 @@ src/web/ framework-free browser page
154
160
  From the repository root:
155
161
 
156
162
  ```bash
163
+ npm --workspace @narumitw/pi-image-drop run build:web
157
164
  npm --workspace @narumitw/pi-image-drop run check
158
165
  npm test
159
166
  just try image-drop
160
167
  just pack image-drop
161
168
  ```
162
169
 
163
- The dry-run package must contain the manifest, license, README, TypeScript sources, and static web assets, but no tests, fixtures, image bytes, or `node_modules`.
170
+ Edit browser code under `src/web/ui/`, then run `build:web`. The package typecheck runs `check:web`, which rebuilds in a temporary directory and rejects stale generated assets. The dry-run package must contain the manifest, license, README, TypeScript/TSX sources, and bundled static web assets, but no tests, fixtures, image bytes, build scripts, or `node_modules`.
164
171
 
165
172
  ## ๐Ÿš€ Publishing
166
173
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-image-drop",
3
- "version": "0.25.0",
3
+ "version": "0.30.0",
4
4
  "description": "Pi extension for staging browser images and attaching them to the next message.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,17 +20,25 @@
20
20
  ],
21
21
  "pi": {
22
22
  "extensions": [
23
- "./src/image-drop.ts"
23
+ "./src/index.ts"
24
24
  ]
25
25
  },
26
26
  "scripts": {
27
- "check": "biome check . && npm run typecheck",
28
- "format": "biome check --write .",
29
- "typecheck": "tsc --noEmit"
27
+ "build:web": "node scripts/build-web.mjs",
28
+ "check": "biome check --vcs-use-ignore-file=false src/*.ts src/web/index.html src/web/ui 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/*.ts src/web/index.html src/web/ui 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": {
@@ -41,6 +49,9 @@
41
49
  "@biomejs/biome": "2.5.3",
42
50
  "@earendil-works/pi-ai": "0.80.3",
43
51
  "@earendil-works/pi-coding-agent": "0.80.3",
52
+ "@types/react": "19.2.17",
53
+ "@types/react-dom": "19.2.3",
54
+ "esbuild": "0.25.12",
44
55
  "typescript": "6.0.3"
45
56
  },
46
57
  "repository": {
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from "./image-drop.js";
package/src/runtime.ts CHANGED
@@ -248,10 +248,7 @@ export class ImageDropRuntime {
248
248
  if (generation !== this.generation) return false;
249
249
  if (batch.publicState().phase !== "ready") {
250
250
  this.restoreEditor(ctx, text);
251
- ctx.ui.notify(
252
- "Images could not be updated for the current auto-resize setting.",
253
- "warning",
254
- );
251
+ ctx.ui.notify("Images could not be updated for the current auto-resize setting.", "warning");
255
252
  this.updateWidget(ctx);
256
253
  return false;
257
254
  }
package/src/server.ts CHANGED
@@ -11,6 +11,7 @@ import type { ImageDropSettings } from "./settings.js";
11
11
 
12
12
  const JSON_LIMIT = 64 * 1024;
13
13
  const CLIENT_ID = /^[A-Za-z0-9_-]{1,80}$/;
14
+ const CSP_NONCE_PLACEHOLDER = "__PI_CSP_NONCE__";
14
15
 
15
16
  export interface ImageDropServerOptions {
16
17
  batch: BatchStore;
@@ -47,6 +48,7 @@ export class ImageDropServer {
47
48
  readonly origin: string;
48
49
  private bootstrapToken?: string;
49
50
  private readonly sessionSecret = token();
51
+ private readonly cspNonce = token();
50
52
  private readonly cookieName = `pi_image_drop_${randomBytes(8).toString("hex")}`;
51
53
  private readonly abortController = new AbortController();
52
54
  private readonly sockets = new Set<Socket>();
@@ -463,7 +465,11 @@ export class ImageDropServer {
463
465
  }
464
466
 
465
467
  private async asset(response: ServerResponse, name: string, contentType: string): Promise<void> {
466
- const data = await readAsset(name);
468
+ const asset = await readAsset(name);
469
+ const data =
470
+ name === "index.html"
471
+ ? Buffer.from(asset.toString("utf8").replaceAll(CSP_NONCE_PLACEHOLDER, this.cspNonce))
472
+ : asset;
467
473
  response.setHeader("Content-Type", contentType);
468
474
  response.setHeader("Content-Length", data.byteLength);
469
475
  response.writeHead(200).end(data);
@@ -473,7 +479,7 @@ export class ImageDropServer {
473
479
  response.setHeader("Cache-Control", "no-store");
474
480
  response.setHeader(
475
481
  "Content-Security-Policy",
476
- "default-src 'self'; img-src 'self' blob:; script-src 'self'; style-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'",
482
+ `default-src 'self'; img-src 'self' blob:; script-src 'self'; style-src 'self' 'nonce-${this.cspNonce}'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'`,
477
483
  );
478
484
  response.setHeader("Referrer-Policy", "no-referrer");
479
485
  response.setHeader("X-Content-Type-Options", "nosniff");