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>
The scan is I/O-bound on cloud-sync / network mounts (OneDrive, Samba) where
each directory read is a high-latency round-trip. More in-flight reads hide
that latency on the many-folders case. (A single large folder is still
enumerated one entry at a time by the File System Access API and can't be
parallelized.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scan concurrency: the scan is I/O-bound — each directory read is a network
round-trip to the share, so the lever is parallel in-flight reads, not CPU
threads. Replace the per-level BFS barrier (which idled workers waiting on
the slowest dir in each level) with a continuous shared-queue pool that
keeps up to SCAN_CONCURRENCY (16, up from 6) reads in flight at once,
pulling newly discovered child dirs as they land. Still roughly
breadth-first (FIFO), so top levels surface first. ensureScanned reuses it.
Error messages: translate File System Access DOMExceptions into accurate,
actionable text keyed on err.name (not the cryptic raw message, which reads
like a permission problem when it isn't). e.g. InvalidStateError now reads
'the folder changed on disk since it was first read … rescan' instead of
'an operation that depends on state cached in an interface object …'. The
raw name+message is appended in parens for copy-paste troubleshooting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The scan was slow because it OPENED every file (getFile() for size/lastModified
— which the grid doesn't even display) and read every ZIP inline. On a network
share that's a round-trip per file. Now:
- createFileObject builds rows from the directory entry name alone, no
getFile(); size/lastModified load on demand (preview/SHA/rename already call
getFile() themselves). The scan is now a pure directory listing.
- ZIPs are lazy: a .zip is an expandable node read only when opened
(scanZipNode), not during the walk.
- Footer shows live elapsed time (ticks every second), and a success toast
fires at completion with totals: "Scan complete — N folders, M files in Ts."
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Counts now read "direct+total" — e.g. "(2+10 folders, 15+300 files)". The
direct number (immediate children) shows as soon as a folder's own directory
is read; the total (whole-subtree) is accumulated progressively and flashes
grey until the subtree is fully scanned, then goes solid. The "+total" is
omitted once done and there's nothing deeper.
- Scan errors (permission denied, network hiccups on a share) now surface as a
toast (de-duped per path) instead of only console noise; a failed folder/zip
is marked done-empty so it doesn't wedge the walk.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the full depth-first "scan everything, then render once + expandAll +
selectAll" walk (which looked stalled and was a render bomb on a large network
drive) with a progressive, breadth-first scan:
- Walks level-by-level behind a bounded worker pool (6), rendering as it goes —
the top folder levels appear immediately, deeper levels fill in the
background. Workers await between directories so the UI stays responsive.
- Live status line under the tree header: "Scanning… N folders · M files —
<current path>", ending "Scanned … in Ts."
- Per-folder state machine (pending → scanning → children → done) with
immediate subfolder/file counts; the row is greyed (with a faint pulse) until
its whole subtree is scanned, then turns solid — the at-a-glance signal.
- Opening a folder jumps its subtree to the front of the scan (ensureScanned),
so an opened folder always shows complete contents; idempotent vs the
background walk.
- No more auto-expand/auto-select-all (that loaded the entire drive up front);
the root is selected so the grid shows its files immediately.
- ZIPs stay expandable, scanned inline into virtual nodes (already in memory
once read); whole zip subtree marked done at once.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ZDDC — Zero Day Document Control. A file-naming convention plus five
single-file HTML tools (archive, transmittal, classifier, mdedit,
landing) and an optional Go HTTP server (zddc-server) with ACL and a
virtual archive index. Self-contained, offline-capable, dependency-free.
See README.md for an overview, AGENTS.md and ARCHITECTURE.md for the
build/release/architecture detail, bootstrap/README.md for the
two-level deployment install pattern, and zddc/README.md for the
HTTP server.