{"record":{"id":"24366833da3f7d50","repo":"nexu-io/open-design","slug":"zip-does-not-contain-an-html-file","errorCode":null,"errorMessage":"zip does not contain an HTML file","messagePattern":"zip does not contain an HTML file","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/daemon/src/design/claude-design-import.ts","lineNumber":58,"sourceCode":"    // streaming/data-descriptor zips (it can read 0 even when the payload\n    // carries real data). The inflate cap and the post-decode size checks below\n    // are authoritative.\n    const body = readEntryBody(zip, entry);\n    if (body.length > MAX_FILE_BYTES) {\n      throw new Error(`zip file too large: ${relPath}`);\n    }\n    if (entry.uncompressedSize > 0 && body.length !== entry.uncompressedSize) {\n      throw new Error(`zip entry size mismatch: ${relPath}`);\n    }\n    totalBytes += body.length;\n    if (totalBytes > MAX_TOTAL_BYTES) throw new Error('zip is too large');\n\n    files.push({ path: relPath, body: normalizeImportedClaudeDesignFile(relPath, body) });\n  }\n\n  if (files.length === 0) throw new Error('zip contains no files');\n  const entryFile = chooseEntryFile(files.map((f) => f.path));\n  if (!entryFile) throw new Error('zip does not contain an HTML file');\n\n  const dirCreates = new Map<string, Promise<string | undefined>>();\n  const ensureDir = (dir: string) => {\n    let pending = dirCreates.get(dir);\n    if (!pending) {\n      pending = mkdir(dir, { recursive: true });\n      dirCreates.set(dir, pending);\n    }\n    return pending;\n  };\n\n  await mkdir(projectDir, { recursive: true });\n  await Promise.all(files.map(async (f) => {\n    const target = safeJoin(projectDir, f.path);\n    await ensureDir(path.dirname(target));\n    await writeFile(target, f.body);\n  }));\n","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/design/claude-design-import.ts#L40-L76","documentation":"Thrown by `importClaudeDesignZip` after collecting files if `chooseEntryFile` returns null — meaning no entry path matched `/\\.html?$/i`. Claude Design exports are HTML-based and the importer requires at least one `.html` or `.htm` file to select as the entry point.","triggerScenarios":"An archive that contains files (passes the empty-archive check) but none of them have an `.html` or `.htm` extension — e.g. only `.css`, `.js`, `.png`, or extensionless files.","commonSituations":"Zipping the wrong directory (assets folder instead of the build output). An export pipeline that emits `index` without the `.html` extension. A non-Claude-Design archive uploaded through the Claude Design import path.","solutions":["Ensure the archive includes at least one `.html`/`.htm` file (typically `index.html`).","Re-export from Claude Design so the HTML entry is included.","Confirm you are uploading a Claude Design HTML export and not a different archive type."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const fileEntries = entries.filter((e) => !e.isDirectory).map((e) => e.name);\nconst hasHtml = fileEntries.some((p) => /\\.html?$/i.test(p));\nif (!hasHtml) {\n  throw new Error('archive contains no .html/.htm entry file');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await importClaudeDesignZip(zipPath, projectDir);\n} catch (err) {\n  if (err instanceof Error && err.message === 'zip does not contain an HTML file') {\n    // ensure the archive includes index.html and re-upload\n  }\n  throw err;\n}","preventionTips":["Ensure the archive includes at least one `.html`/`.htm` file (typically `index.html`).","Confirm you are uploading a Claude Design HTML export, not an unrelated archive.","Re-export from Claude Design if the HTML entry is missing."],"tags":["zip","claude-design","validation","html","import"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}