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>
- Each source file row shows a classification state dot (unassigned →
has-tracking/transmittal → done), and each folder shows an aggregate dot
over its subtree.
- Right-click a file or folder to Exclude/Include from the copy (folder applies
to its whole subtree) or clear an axis; excluded files are struck through and
never copied.
- Cross-tree find is bidirectional: click a placed file in the target pane to
reveal+flash it in the source tree (expanding its folders); click a source
file to switch the target pane to its placed axis and flash the node.
- Target pane now reverse-looks-up over ALL scanned files (the left tree), not
the selection-scoped grid, with placements grouped in one pass per render.
- classify.getAssignment() read-only accessor; 5 new tests (18 total green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In Classify & Copy mode the left tree now lists each folder's files as
draggable rows (with a classification state dot), and folder rows are
draggable for a group-drag of the whole subtree. Target-tree nodes are drop
zones: a tracking folder (any node) or a transmittal bin; dropping assigns the
dragged source key(s) along that axis via classify.place().
- dnd.js: drag-payload bus (keys held in a module var since dataTransfer can't
be read during dragover; carries a marker for the copy cursor).
- tree.js: createFileElement + group-drag dragstart; classify-mode file rows.
- target-tree.js: setupDropZone with dragover highlight + drop assignment
(tracking = any node, transmittal = bins only).
- app.js: source tree re-renders on classify state change.
- 2 DnD drop-handler tests (14 total green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Header gets a Rename / Classify & Copy switch. In Classify & Copy mode the
spreadsheet pane is replaced by a tabbed target pane (By tracking number /
By transmittal), while the source tree stays on the left.
- target-tree.js: renders both trees from classify state; tracking-folder
create/rename/delete (leaf folders styled as the revision); party CRUD +
per-slot inline transmittal-bin form (date + TRN/SUB + seq + optional
status/title); shows the derived filename + a validation badge for each
placed file; live header stats (done / in progress / unassigned / excluded).
- app.js setMode(): swaps panes, toggles classify mode, re-renders both trees.
- 3 UI smoke tests added to classify.spec.js (12 total green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>