Summary
POST /api/import/claude-design (the Import Claude Design ZIP button in the New Project panel) silently fails for valid .zip files in two distinct ways:
- Crash on zero-byte entries (Node 24).
inflateRawSync(compressed, { maxOutputLength: 0 }) throws RangeError [ERR_OUT_OF_RANGE] on Node 24 because maxOutputLength must be >= 1. Any entry whose central-directory uncompressedSize is 0 (empty files, common when the zip uses data descriptors — e.g. macOS Archive Utility, streaming zips) breaks the whole import.
MAX_FILES = 500 rejects medium-sized exports. Design-system / prototype exports commonly exceed 500 files (the test asset that surfaced this is 561 files / ~4.4 MB). The existing MAX_TOTAL_BYTES = 100 MB and MAX_FILE_BYTES = 25 MB guards already cover pathological inputs.
The web UI surfaces neither error: apps/web/src/state/projects.ts returns null on !resp.ok, and apps/web/src/App.tsx's handleImportClaudeDesign does if (!result) return;. After picking a file, the button appears to "do nothing".
Reproduction
- Build (or use an existing) zip with either:
- any entry whose central-directory
uncompressedSize is 0, or
- more than 500 entries.
- In the running app, click Import Claude Design ZIP in the New Project panel and pick the file.
- UI shows nothing.
- From a terminal, the underlying daemon error is visible:
curl -i -F "file=@./your.zip" http://127.0.0.1:<daemon-port>/api/import/claude-design
HTTP/1.1 400 Bad Request
{"error":"RangeError [ERR_OUT_OF_RANGE]: The value of \"options.maxOutputLength\" is out of range. It must be >= 1 and <= 9007199254740991. Received 0"}
or
{"error":"Error: zip contains too many files"}
Root cause
apps/daemon/src/claude-design-import.ts:
// L11
const MAX_FILES = 500;
// L104-117 readEntryBody (...):
// compressed = zip.slice(bodyStart, bodyEnd) // bodyEnd-bodyStart === entry.compressedSize
// ...
return inflateRawSync(compressed, { maxOutputLength: entry.uncompressedSize });
Node 24's zlib option validator now requires maxOutputLength >= 1, so the value 0 (legal in earlier versions) throws synchronously.
Suggested fix
Two-line patch in apps/daemon/src/claude-design-import.ts:
-const MAX_FILES = 500;
+const MAX_FILES = 5000;
@@
- if (entry.method === 0) return Buffer.from(compressed);
- return inflateRawSync(compressed, { maxOutputLength: entry.uncompressedSize });
+ if (entry.method === 0) return Buffer.from(compressed);
+ if (entry.uncompressedSize === 0) return Buffer.alloc(0);
+ return inflateRawSync(compressed, { maxOutputLength: entry.uncompressedSize });
Bytes-per-file and total-bytes guards already cap risk; MAX_FILES = 5000 is closer to what real design-system / prototype exports look like.
Related UX gap (separate but worth fixing together)
The web layer should surface the daemon's error field instead of swallowing it:
apps/web/src/state/projects.ts (importClaudeDesignZip): return the parsed JSON (including error) instead of null on !resp.ok, and have apps/web/src/App.tsx's handleImportClaudeDesign push the message into a toast. Without this, any future failure in the importer remains invisible to the user.
Environment
- Node
v24.15.0
pnpm 10.33.2
- macOS (darwin-arm64)
- Repo HEAD:
c69dee7 on main
Test asset
Reproduced consistently with a 4.4 MB / 561-file zip containing 74 HTML files at preview/**/*.html, plus one zero-byte docs/components/chip-removable.md. Both failure modes were confirmed: the zero-byte entry triggers (1), and even after patching (1), (2) blocks the import. Both fixes together let the import succeed end-to-end.
Summary
POST /api/import/claude-design(the Import Claude Design ZIP button in the New Project panel) silently fails for valid.zipfiles in two distinct ways:inflateRawSync(compressed, { maxOutputLength: 0 })throwsRangeError [ERR_OUT_OF_RANGE]on Node 24 becausemaxOutputLengthmust be>= 1. Any entry whose central-directoryuncompressedSizeis0(empty files, common when the zip uses data descriptors — e.g. macOS Archive Utility, streaming zips) breaks the whole import.MAX_FILES = 500rejects medium-sized exports. Design-system / prototype exports commonly exceed 500 files (the test asset that surfaced this is 561 files / ~4.4 MB). The existingMAX_TOTAL_BYTES = 100 MBandMAX_FILE_BYTES = 25 MBguards already cover pathological inputs.The web UI surfaces neither error:
apps/web/src/state/projects.tsreturnsnullon!resp.ok, andapps/web/src/App.tsx'shandleImportClaudeDesigndoesif (!result) return;. After picking a file, the button appears to "do nothing".Reproduction
uncompressedSizeis0, orRoot cause
apps/daemon/src/claude-design-import.ts:Node 24's
zliboption validator now requiresmaxOutputLength >= 1, so the value0(legal in earlier versions) throws synchronously.Suggested fix
Two-line patch in
apps/daemon/src/claude-design-import.ts:Bytes-per-file and total-bytes guards already cap risk;
MAX_FILES = 5000is closer to what real design-system / prototype exports look like.Related UX gap (separate but worth fixing together)
The web layer should surface the daemon's
errorfield instead of swallowing it:apps/web/src/state/projects.ts(importClaudeDesignZip): return the parsed JSON (includingerror) instead ofnullon!resp.ok, and haveapps/web/src/App.tsx'shandleImportClaudeDesignpush the message into a toast. Without this, any future failure in the importer remains invisible to the user.Environment
v24.15.0pnpm 10.33.2c69dee7onmainTest asset
Reproduced consistently with a 4.4 MB / 561-file zip containing 74 HTML files at
preview/**/*.html, plus one zero-bytedocs/components/chip-removable.md. Both failure modes were confirmed: the zero-byte entry triggers (1), and even after patching (1), (2) blocks the import. Both fixes together let the import succeed end-to-end.