Commit graph

19 commits

Author SHA1 Message Date
645b308ebc feat(classifier): "Partial" Show filter — assigned in the other tab only
Adds a fourth source-tree bucket relative to the active axis: a file assigned on
the OTHER axis but not this one. With its own "Show Partial" toggle (and count)
you can assign a batch in one tab, switch tabs, show only Partial, and finish
them off — working in chunks across the two axes.

fileCategory now returns excluded / assigned (this axis) / partial (other axis
only) / unassigned (neither); filters, counts and the toolbar checkbox follow.

Test: a tracking-only file reads as Partial on the transmittal tab and hides
when Partial is unchecked (51 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 08:27:39 -05:00
4b79386c4e ux(classifier): drop the hover "Extract All" button from folder rows
It wasn't needed and its margin-left:auto shoved the folder's direct/total stats
out of position. Removed the button and its now-unused helpers
(countZipDescendants / getZipDescendants / handleExtractAllZips) and CSS. The
per-archive "Extract" on an expanded zip-root stays.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:18:31 -05:00
1f8b4e4aaa feat(classifier): zips are single files by default, toggle to expand as a folder
Previously every .zip was auto-expanded into a folder of members (and was even
double-represented as both a file and a folder node). Now a .zip is one
classifiable file by default; right-click it → "Expand as folder" to pull its
members into the fileset, and right-click an expanded archive (or a member) →
"Collapse to single file" to go back. The toggle sits with Exclude in the
context menu.

- scanner: stop creating zip-root nodes during the scan; expandZipAsFolder /
  collapseZipToFile mutate the tree in place (re-reading members from the live
  handle or, for a restored workspace, lazily from the root) and recompute
  subtree totals. Mode is encoded by the tree shape, so it persists in the
  snapshot as-is.
- classify.dropAssignments clears the assignments that cease to exist when a zip
  flips mode (the single-file key on expand; the member keys on collapse).
- copy already handles both: a zip-as-file copies whole; members extract from
  the archive.

Also: a folder whose entire subtree is excluded now renders its name struck
through, mirroring the excluded-file style.

Tests: collapse restores the single .zip + drops member assignments; a
fully-excluded folder gets the struck-through class (48 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:34:25 -05:00
e1c479dba5 fix(classifier): search opens only the path to a hit, not the whole tree
Searching no longer force-expands every folder. computeVisible now returns an
"open" set holding just the connector folders on the path down to each match;
those open to expose the hit, while off-path branches and terminal nodes keep
their real collapse state (and honest ▶/▼ arrows). Reshaping the tree is the
user's call — the root's expand-all is one click away.

Test: a deep file hit opens its branch and leaves the sibling collapsed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:10:27 -05:00
15ce7098a0 fix(classifier): Show toggles preserve tree state; add Reset to raw input
- Toggling Show Unassigned/Assigned/Excluded/Empty no longer force-expands the
  whole tree. Auto-expand is now reserved for the NAME search (where revealing a
  match means expanding to it); the Show toggles only hide/show, leaving your
  expand/collapse state untouched. Also preserve scrollTop across re-render so a
  toggle doesn't jump the view to the top.
- Add a "Reset" button (danger-styled, beside Export/Import) that discards every
  classification — tracking + transmittal trees, assignments, excludes, title
  overrides — and returns to just the raw scanned input. Your files are never
  touched. Destructive + irreversible, so it confirms with an "Export first"
  warning and no-ops (info toast) when there's nothing to reset.

Tests: Show toggle preserves collapse vs. name-search auto-expand (classify 42).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:02:12 -05:00
aa34a4b3e7 feat(classifier): Show Empty toggle; tidy the folder-tree header
- Add a "Show Empty" checkbox (classify mode) — when off, folders whose whole
  subtree contains no files are hidden, decluttering messy scans.
- Move the Show Unassigned/Assigned/Excluded/Empty filters out of the cramped
  pane header into a dedicated "Show …" toolbar row beneath it (wraps cleanly).
- Drop the "X folders selected" text from the folder-tree header (selection
  still works; updateSelectedCount guards the now-absent element).

Test: Show Empty off hides file-less folders (classify.spec.js -> 41).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:52:03 -05:00
c61cac7c8f feat(classifier): live filter box above each file tree (reveals matches + path)
Adds an autofilter input above the source tree and above each target tree.
Typing substring-matches (ANDing space-separated terms) against the full file
path/name (and folder/node names) and reveals every match with the folder
hierarchy leading to it — non-matching branches collapse out, matching branches
auto-expand. So you can type "master deliverables list" and jump straight to it.

- Source tree (tree.js): one-pass visible-set over path+name; composes with the
  Show Unassigned/Assigned/Excluded toggles; auto-expands to reveal hits.
- Target trees (target-tree.js): tracking + transmittal nodes are filter-aware
  (match node names + each placed file's original/derived name); one shared
  query mirrored across both tab inputs.

Tests: source-tree path reveal + tracking-tree node filter (classify.spec.js -> 36).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:37:36 -05:00
139171481e feat(classifier): three-state filters, expand/collapse-all, drop-prompt, preview + editable filenames
Classify & Copy interaction pass (replaces the single "Hide Assigned" toggle):

- Source-tree filters: three "Show Unassigned / Show Assigned / Show Excluded"
  checkboxes (classify mode only) with live per-tab counts; "Hide Compliant" is
  now rename-mode only. Folders with nothing visible collapse out.
- Target tree: ctrl/cmd-click a toggle to expand/collapse the whole subtree.
- Tracking drop-to-any-level: dropping on a node that isn't already a complete
  leaf prompts for the remaining levels (e.g. "0001_0 (IFU)"), which are parsed
  and nested under the drop target. Dropping on a finished leaf assigns directly.
- Placed-file rows: click to preview; the derived filename is now an inline
  input — edit it (full "TRACKING_REV (STATUS) - Title.ext") and the item is
  re-filed onto the parsed tracking path (created if needed) + title override.

New classify helpers: trackingNodeComplete, trackingPathLabel. tree.setShowFilters
replaces setHideAssigned. Tests updated/added (classify.spec.js -> 33 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:08:30 -05:00
8f839fc0c9 feat(classifier): Hide Assigned filter, left-aligned node controls, brace-expand add
Classify & Copy polish — in either target tab the goal is to assign or exclude
every left-pane file until nothing remains:

- Hide Assigned checkbox (classify mode, in the folder-tree pane header):
  collapses the source tree to only what's left on the ACTIVE axis — hides
  files already assigned in the current tab (or excluded) and any folder whose
  scanned subtree is thereby empty. Re-renders on tab switch; target-tree
  exposes activeAxis().
- Node add/edit/delete controls moved to the LEFT of the level name and made
  always-visible (was right-aligned + hover-only), so building/pruning the
  tracking and transmittal trees is one click.
- Brace expansion in the add-folder box: "BMB-187023-{PM,EL,EM}-MOM-
  {0001-0002,0005}_A (IFR)" creates all 9 folders — {a,b} alternation +
  {N-M} zero-padded numeric ranges, cartesian product across groups; a
  multi-create is confirmed first. New classify.expandFolderPattern().

Tests: expandFolderPattern unit cases + a Hide-Assigned DOM test
(classify.spec.js → 29 passed; classifier.spec.js → 4 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 09:31:14 -05:00
975c804cc7 feat(classifier): click-to-preview in Classify & Copy mode
Identifying a file is half the workflow — you preview it to see what it is,
then assign its tracking number by drag. Preview was only wired into the old
Rename grid; in Classify & Copy a source file now previews on single-click
(drag still assigns, right-click excludes). preview.previewFile() resolves a
snapshot file's handle from the workspace root (one-click read re-grant) before
opening, so it works for resumed workspaces too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:08:57 -05:00
eb07e7622d fix(classifier): file-only folders are expandable in Classify mode
A folder with files but no subfolders got no expand toggle, so in Classify &
Copy mode its files (the drag source) could never be revealed — and leaf
folders full of files are exactly where the work is. Make a folder expandable
when it has files in classify mode; expanding lists the draggable file rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:23:26 -05:00
eb1e3ec948 feat(classifier): left-tree markers, exclude, cross-tree find (phase 4)
- 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>
2026-06-09 12:32:42 -05:00
47cf58b0e9 feat(classifier): drag-and-drop assignment (phase 3)
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>
2026-06-09 12:23:38 -05:00
389b2e94ac ux(classifier): blue completed counts; blue labels when row fully scanned
The black-completed vs grey-flashing distinction was too subtle. Completed
numbers (the direct count, always; the +total once final) now render in
var(--primary) — theme-aware blue in both light and dark. While a subtree
is still scanning its +total stays muted grey + pulses, so blue = done,
grey = in progress. Once both numbers are blue the row's folders/files
labels turn blue too (.folder-count.done .ct-label).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:11:51 -05:00
caff489206 perf(classifier): scan is a pure listing — no getFile() per file; lazy zips
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>
2026-06-09 10:55:29 -05:00
3d02084397 feat(classifier): direct+total counts in tree; toast scan errors
- 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>
2026-06-09 10:28:01 -05:00
ecb0a270cc feat(classifier): incremental scan — status, top-levels-first, per-folder state
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>
2026-06-09 10:03:46 -05:00
dc72df83e3 fix(classifier): rename inline tree-empty placeholder out of .empty-state
classifier/js/tree.js was inserting a <div class="empty-state">No
folders found</div> inside the folder-tree pane when the tree was
empty. That conflicted with the shared .empty-state rule promoted
in the previous commit — which expects an outer flex container with
a child .empty-state__inner card, used for the top-level welcome
overlay.

The two usages aren't the same thing semantically (one is the
welcome screen; one is a tiny inline "list is empty" placeholder
inside the folder tree). Rename the inline one to .tree-empty to
remove the collision. The spreadsheet.css rule that targeted the
old class is renamed to match; same padding/text-align/color.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:22:50 -05:00
ea385b5366 Initial commit
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.
2026-04-27 11:05:47 -05:00