Commit graph

6 commits

Author SHA1 Message Date
61c1b4f90d feat(classifier): server copy picks any accessible project's archive
Replaces the raw archive-URL prompt with a project picker: copyToServer fetches
the caller's projects from /.profile/access and lets them choose one; files are
PUT under <project>/archive/<party>/<received|issued>/<transmittal>/. Write
permission is enforced server-side, so a 403 surfaces per file (and resume
retries). Same resumable engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 08:53:37 -05:00
144baeec61 feat(classifier): Copy → server archive (HTTP) or local folder, resumable
Replaces the zip option. Copy now offers two real destinations, both filing
under <party>/<received|issued>/<transmittal>/<name>:
- Server archive — PUTs each file into a zddc-server over HTTP via the
  zddc-source HttpDirectoryHandle (mkdir as needed). Offered only over http(s);
  prompts for the archive URL (guessed from the current path's archive/ segment).
- Local folder — the File System Access picker (choose archive/ to file directly).

Both reuse the one copy engine and are efficiently resumable: copyOne probes the
target with a cheap stat/HEAD and SKIPS anything already present — no source
read, no hashing — so a re-run after an interruption only does what's left.
Canonical ZDDC names + the WORM archive mean an existing name is the same
document, so we never overwrite (the old content-diff path is dropped).

Tests: re-run skips an existing local target; PUT into a server-style handle then
resume-skips it (52 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 08:43:53 -05:00
325dce9f3e feat(classifier): Copy prompts download .zip or copy to a directory
Copy now opens a small dialog: copy the canonical files into a folder (pick your
archive/ to file them straight into <party>/<received|issued>/<transmittal>/),
or download a single .zip with the same layout (unzips into place). The zip path
reuses readSource + the derived outRel, so the bytes and folder structure match
the directory copy exactly; conflicts are still skipped beforehand.

Test: the zip is built with one entry at ClientCorp/received/<transmittal>/
ACME-MECH-0001_A (IFR) - foundation.pdf (52 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 08:31:35 -05:00
203674ee4c feat(classifier): persist & copy files inside .zip archives
Zip members were live-only: expandable while the source was connected, but the
workspace snapshot dropped the archive (.zip became a plain file), so a
classification made inside one vanished on reopen — and copy couldn't extract it
anyway (it tried to walk the archive path as a real directory).

Now zips are first-class:
- snapshotTree/loadSnapshot persist the scanned archive subtree — zip-root +
  virtual folders + members carry isVirtual/zipPath/zipEntryPath, so the tree
  rebuilds on reopen and assignments inside an archive survive. An archive that
  was never opened persists as a lazy 'zip' node that reopens on demand.
- scanner.ensureZipLoaded(rootHandle, zipPath) reloads an archive from the
  workspace root when the in-memory cache is cold (post-restore); scanZipNode
  falls back to it when a restored zip node has no live file object.
- copy.js reads a member via scanner.extractZipMember (Blob from the archive)
  instead of a non-existent file handle; preview.js reloads the archive for a
  restored member before opening it.

This also reconciles export/import with the snapshot: both now keep zip members,
so a round-trip no longer leaves dangling in-archive assignments.

Tests: zip subtree snapshot round-trip; copy extracts a member to the output (45).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:27:00 -05:00
1d09abdc8b feat(classifier): workspaces — scan-once, resume from snapshot (phase 6)
The classifier re-scanned the source on every session; on cloud-backed mounts
(OneDrive/Samba) that's minutes of per-op latency. Workspaces fix it: scan a
folder ONCE, snapshot the completed tree, and resume instantly — all
classification runs on the data model; the filesystem is only touched at copy.

- persist.js v2: multi-workspace IndexedDB (tiny 'index' store for the welcome
  list + 'data' store holding the source handle, tree snapshot, and map). DB v2.
- scanner.js: snapshotTree()/loadSnapshot() (compact, handle-less, marked done,
  totals recomputed) + lazy resolveFileHandle/resolveDirHandle from the root.
- workspace.js: welcome manager (new/open/rename/delete), debounced autosave of
  the active workspace, 'Refresh from disk' (re-scan → re-snapshot, path-keyed
  map carries over). New workspace = the one slow full scan; reopen = instant.
- copy.js: resolves snapshot files' handles from the workspace root with a
  one-click read permission re-grant; missing-on-disk files surface as errors.
- app.js: enterAppShell() shared by rename/workspace flows; exposes setMode;
  classify.js decoupled from persistence.
- template/css: welcome workspace list + header 'Workspaces' button.
- tests: snapshot round-trip, persist CRUD + classify-only-preserves-tree,
  copy-from-snapshot via mock root handle (28 classify/classifier tests green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:07:40 -05:00
420f735e89 feat(classifier): copy-out with duplicate detection + map restore (phase 5)
The Copy button (enabled once >=1 file is fully classified) copies the mapped
files into a user-chosen output directory under their canonical names/layout
<party>/{received,issued}/<transmittal>/<filename> — reading the source, never
writing it.

- copy.js: plan() (complete, non-excluded files) → conflict scan (two sources
  → same output path are reported + skipped) → copyTo() engine on the generic
  FS-Access shape (ensureDir + getFileHandle + createWritable). Per-file dedup:
  identical target (sha256) is skipped; existing-but-different is left
  untouched and reported; live footer progress; completion toast.
- app.js: restores the saved map on launch (keyed by source-relative path, so
  it re-attaches when the same directory is re-opened) and persists the source
  handle on open; Copy button wired.
- target-tree.js: enables/labels the Copy button from the done count.
- 2 copy-engine tests with mock FS handles (copy/skip/differ + conflict);
  24 classify+classifier tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:37:44 -05:00