diff --git a/data/zxdb/software_hashes.json b/data/zxdb/software_hashes.json index 4cc4ef7..2e2c13f 100644 --- a/data/zxdb/software_hashes.json +++ b/data/zxdb/software_hashes.json @@ -1,5 +1,5 @@ { - "exportedAt": "2026-02-17T16:18:44.812Z", + "exportedAt": "2026-02-17T16:24:42.403Z", "count": 32960, "rows": [ { diff --git a/docs/plans/tape-identifier.md b/docs/plans/tape-identifier.md new file mode 100644 index 0000000..d452ca4 --- /dev/null +++ b/docs/plans/tape-identifier.md @@ -0,0 +1,67 @@ +# Plan: Tape Identifier Dropzone on /zxdb + +## Context + +We have 32,960 rows in `software_hashes` with MD5, CRC32, size, and inner_path for tape-image contents. This feature exposes that data to users: drop a tape file, get it identified against the ZXDB database. + +Uses RSC (server actions) rather than an API endpoint to make bulk scripted identification harder. + +## Architecture + +**Client-side:** Compute MD5 + file size in the browser, then call a server action with just those two values (file never leaves the client). + +**Server-side:** A Next.js Server Action looks up `software_hashes` by MD5 (and optionally size_bytes for disambiguation), joins to `downloads` and `entries` to return the entry title, download details, and a link. + +**Client-side MD5:** Web Crypto doesn't support MD5. Include a small pure-JS MD5 utility (~80 lines, well-known algorithm). No new npm dependencies. + +## Files to Create/Modify + +### 1. `src/utils/md5.ts` — Pure-JS MD5 for browser use +- Exports `async function computeMd5(file: File): Promise` +- Reads file as ArrayBuffer, computes MD5, returns hex string +- Standard MD5 algorithm implementation, typed for TypeScript + +### 2. `src/app/zxdb/actions.ts` — Server Action +- `'use server'` directive +- `identifyTape(md5: string, sizeBytes: number)` +- Queries `software_hashes` JOIN `downloads` JOIN `entries` by MD5 +- If multiple matches and size_bytes narrows it, filter further +- Returns array of `{ downloadId, entryId, entryTitle, innerPath, md5, crc32, sizeBytes }` + +### 3. `src/app/zxdb/TapeIdentifier.tsx` — Client Component +- `'use client'` +- States: `idle` → `hashing` → `identifying` → `results` / `not-found` +- Dropzone UI: + - Dashed border card, large tape icon, "Drop a tape file to identify it" + - Lists supported formats: `.tap .tzx .pzx .csw .p .o` + - Also has a hidden `` with a "or choose file" link + - Drag-over highlight state +- On file drop/select: + - Validate extension against supported list + - Show spinner + "Computing hash..." + - Compute MD5 + size client-side + - Call server action `identifyTape(md5, size)` + - Show spinner + "Searching ZXDB..." +- Results view (replaces dropzone): + - Match found: entry title as link to `/zxdb/entries/{id}`, inner filename, MD5, file size + - Multiple matches: list all + - No match: "No matching tape found in ZXDB" + - "Identify another tape" button to reset + +### 4. `src/app/zxdb/page.tsx` — Add TapeIdentifier section +- Insert `` as a new section between the hero and "Start exploring" grid +- Wrap in a card with distinct styling to make it visually prominent + +### 5. `src/server/repo/zxdb.ts` — Add lookup function +- `lookupByMd5(md5: string)` — joins `software_hashes` → `downloads` → `entries` +- Returns download_id, entry_id, entry title, inner_path, hash details + +## Verification + +- Visit http://localhost:4000/zxdb +- Dropzone should be visible and prominent between hero and navigation grid +- Drop a known .tap/.tzx file → should show the identified entry with a link +- Drop an unknown file → should show "No matching tape found" +- Click "Identify another tape" → resets to dropzone +- Check file never leaves browser (Network tab: only the server action call with md5 + size) +- Verify non-supported extensions are rejected with helpful message