Commit graph

10 commits

Author SHA1 Message Date
ce6efb0201 feat(classifier): CSV path round-trip — export filtered paths, import old→new mapping
Add an AI-friendly classification round-trip alongside the By-tracking grid:

- "⬇ Export paths" (filetree header): downloads the filtered file list as a
  1-column CSV of full (root-relative) paths — the same keys the importer
  matches on. Hand it to an LLM to classify into
  <party>/<direction>/<transmittal>/<file>.ext.
- "Import paths…" (above the target list): loads a 2-column CSV (old path,
  new path). Each new path drives both axes — the trailing filename sets the
  tracking number (rename, via parseFilename → tracking tree) and the leading
  <party>/<direction>/<transmittal> segments route a transmittal (via
  parseFolder → transmittal tree). MERGE semantics: only files named in the
  CSV are touched; others keep their classification.
- Per-row problems (unknown old path, unparseable filename/transmittal, bad
  direction) are collected and offered as a downloadable errors CSV, with a
  summary toast — scales to thousands of rows. Either axis can apply
  independently, so a filename-only new path is a rename with no error.

This replaces the JSON "Export for editing" / "Import edits" pair (the CSV
path form is fully expressive for this model and simpler to round-trip); the
TSV "Export list" clipboard→Excel button is kept. Buttons can grow into a
modal later if more options are needed.

Includes a Playwright test driving the real file-input import (rename+route,
filename-only, merge-preserves-unlisted, CSV-quoted comma in title, error row).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:38:15 -05:00
054cf2d79b feat(classifier): multi-select source files + drag to fill a contiguous block of rows
A contiguous run of drawings on the left commonly maps to a contiguous set of
rows on the right, so make that a single gesture.

- Left tree: ctrl/cmd-click toggles a source file into a multi-selection,
  shift-click ranges from the anchor, ctrl-shift-click adds a range (over the
  visible file order). Selected files get a highlight and drag together, in
  top-to-bottom order. A plain click still previews.
- By-tracking grid: dropping N dragged files onto a placeholder row fills the N
  consecutive placeholder rows from there (file[i] → row[i], Excel-style column
  fill). Drops are now handled at the grid-container level so the dragover shows
  a live indicator outlining EXACTLY the rows that will be updated
  (.tg-fill-target). Dropping over empty space / a file row still just adds the
  files as new rows. Fill walks the rows in DOM (display) order and looks each
  worklist row up by id from the model, so a re-render between binds can't
  disturb the loop.

Test: a 3-file drop on the first of three placeholders fills m1/m2/m3 in order
and consumes all three placeholders. 70 classify green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 09:57:57 -05:00
f82d6919b4 feat(classifier): one classify surface — By-tracking grid with Rename, transmittal with Copy
Collapse the two-mode tool (Classify & copy / Rename in place) into a single
surface. The top mode toggle and the standalone Rename spreadsheet are gone; the
By-tracking grid is now the one editable table, and the two operations are framed
as the two physical things you can do with a classified file:

- By tracking number → "Rename…": a tracking number + rev + title with no
  transmittal IS a rename, so this renames the name-complete grid files IN PLACE
  on disk (rename.js, lifted from the spreadsheet — HTTP move / FS copy+remove,
  resumable). Blocking red no-backup warning; a renamed file is now correctly
  named so it leaves the grid (classify.forgetFile).
- By transmittal → "Copy…": the existing resumable/verified archive copy, moved
  onto this tab; enabled once files are fully classified (tracking + transmittal).

"From a list" is folded into the By-tracking grid, not a separate tab. The grid
now holds two boring row kinds (one row ↔ 0-or-1 file):
- file rows (workset / placed), edited via setFileIdentity;
- placeholder rows = list rows with no file yet (Load…/Paste rows…/Match names),
  edited via the worklist setters; drop or match a file and it becomes a file row.
Dropping N files on a placeholder fans out over consecutive placeholders
(fillFromRow) — the start of the Excel-style block fill. New "⊕ Add filtered
files" pulls every file the left-tree filter shows into the grid. Source / Latest
rev fold in as optional (default-hidden) columns.

Chrome: removed the mode toggle + spreadsheet pane from the template; severed the
spreadsheet/sort/filter/resize/selection inits in app.js (modules stay bundled,
store still drives folder selection + reset); setMode() is now a no-arg enabler;
welcome tutorial rewritten to the single flow.

Tests: bootstrap via app.setMode() (no mode button); mode-switch test asserts the
single surface; worklist test drives placeholder rows in #trackingTree. 69
classify + 56 classifier/tables/cap green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 09:53:04 -05:00
1bb5d1ad97 feat(classifier): export filtered file list to TSV + direct path bind on paste-back
A round-trip workflow: export the file list, edit it in Excel, paste it back —
binding files by exact path or fuzzy name.

Export (left / folder tree, Classify mode):
- A "⬆ Export list" button in the Show toolbar copies the CURRENT filtered file
  set (name search + the Show toggles) to TSV — header `path<TAB>file`, files
  only, no folder rows. It honours every active filter but ignores expand/collapse
  (display-only), so files inside collapsed folders are included. `path` is the
  file's root-relative key; `file` is the bare filename. Clipboard copy, with a
  .tsv download fallback when the clipboard API is blocked (e.g. file://).
- Reuses the tree's computeVisible() so the export matches exactly what the
  filters show.

Paste-back (right / "Paste rows"): the Current name column now accepts either form
- Bare filename → today's behaviour: fuzzy name match / drop later.
- Full path (the exported `path`) → binds that EXACT file directly on paste:
  proposeMatches detects currentName === a file's key as a confidence-1 `via:'path'`
  match (the strongest signal), folded into the existing auto-assign pass. A path
  that matches nothing falls back to its basename for fuzzy matching.
- A pasted full path on a row with NO tracking number yet is still CLAIMED
  (recorded in row.bound); when a tracking/rev later lands, restampRow places the
  claimed file onto the new leaf. (User choice A.)

Tests: export TSV honours the filter + includes collapsed folders; a full-path
Current name binds the exact file (and leaves others untouched); a path with no
tracking is claimed then placed once tracking is filled. 69 classify green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:50:49 -05:00
2989e6e847 ux(classifier): stack the folder count below the folder name
The "direct+total folders/files" badge was right-aligned on the folder row;
move it under the name. The name + count now live in a vertical .folder-namebox
(flex column, flex:1), and the row aligns items to the top so the toggle/icon
sit on the name line with the count beneath. Dropped the count's right margin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:09:46 -05:00
8473ed3393 feat(classifier): Folder Tree count badge reflects the active filters
The per-folder "direct+total folders / files" badge always showed the raw
scanned totals, even while a filter narrowed the tree — so "9+1799 folders,
0+6203 files" stayed put no matter what you filtered to.

computeVisible already does a single filtered pass (applying the Show checkboxes
via classifyAllows and the autofilter via nameHit); accumulate per-folder visible
direct/total counts there and have populateCount use them whenever a filter is
active (raw totals otherwise). So the badge now shows the post-filter totals for
both the autofilter and the Show checkboxes — a collapsed folder's badge tells
you how many matching items are inside.

Test: a filtered tree's root badge drops from "2 folders, 0+3 files" to
"1 folder, 0+1 file". Classifier suites 69 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:47:11 -05:00
0d8125a331 fix(classifier): the Folder Tree autofilter no longer auto-expands
The name filter (and the search-reveal path logic) used to force-expand every
folder on the way to a match, reshaping the tree the moment you typed. If you'd
collapsed everything except the one subtree you cared about, filtering blew the
rest open.

Now the filter only shows/hides rows IN PLACE: expansion is driven solely by the
user's folder.expanded. A collapsed folder that contains matches is still shown
(so you can open it) but stays collapsed — the filter never changes expand state.
Removed the autoOpen() force-expand and the `open` path-map it read.

Tests updated to the new contract: filter hides non-matches in place within
expanded folders, shows match-containing folders collapsed without revealing
their files, and leaves expand state untouched. Classifier suites 68 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:29:45 -05:00
f66b9c5d55 feat(classifier): "From a list" — a scratch worklist that materializes tracking placements
Rework the "By existing" catalog into a "From a list" scratch worklist that can
be populated three ways and cleared without losing work.

Core model — retire the separate `mdl` assignment axis. Dropping a file on a row
now MATERIALIZES a real "By tracking number" placement (assignFromRow →
addTrackingPath + place(…,'tracking') + title override), reusing the typed-
filename path. Consequences:
- Clearing the list can't lose classifications (no assignment references a row);
  dropped files show under By tracking number.
- The two name axes collapse into one (place/deriveTarget/fileCategory simplified).
- Workspace migration: load() materializes any legacy `mdlNodeId` into a tracking
  placement BEFORE anything can prune, so saved workspaces keep their work.
- A tracking number's last segment is an ancestor and the revision is always the
  leaf, so a blank-revision drop lands on a `pending` placeholder leaf; editing
  the row's tracking number or revision re-stamps the row's files onto the new
  leaf and prunes the emptied one.

UI:
- One editable Tracking number column (no per-field split) + editable Title — so
  you can drop on an entry and bump e.g. the sequence for a new drawing.
- Source column (MDL / arch / pasted) with an amber "new"/"unverified" badge when
  a pasted number matches nothing scanned — the typo catcher.
- Toolbar: Load… (now appends) · Paste rows… ·  Match names · Clear list ·
  Hide assigned. Tab renamed "From a list"; goal line + hint teach the scratch-pad.
- Paste dialog (Ctrl-V on the panel too) with a live parse preview; parsePastedRows
  handles 3-col, a split status column, a pasted full filename, header + bad rows.
- Match names reviews proposeMatches (filename ⊇ a known tracking number) and
  assigns the checked pairs.

Tests: materialize+clear, tracking-edit re-stamp + prune, legacy migration,
parsePastedRows, proposeMatches; 66 classify+classifier green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:48:26 -05:00
cfdf0f6db9 fix(classifier): fit-to-content catalog columns + sorted Folder Tree
Two UI fixes:

- "By existing" catalog columns were far too wide. The seltable forced the
  table to width:100% (auto-layout then stretches columns) and — in the
  classifier's copy — the per-column filter <input>s had no styling, so each
  fell back to its ~170px intrinsic width and dictated the column width. Set
  the table to width:auto (cells are already nowrap → fit header/longest cell)
  and style .seltable__colfilter to fill its column (min-width:2rem,
  box-sizing:border-box) so the inputs never widen a column. Applied to both
  the classifier copy and shared/seltable.css (same fix for the tables tool's
  "Add from archive" table).
- The left Folder Tree rendered folders and files in raw scan order. Sort both
  at render — case-insensitive, natural (so "Rev 2" precedes "Rev 10") — via a
  non-mutating slice().sort() at each render point in tree.js.

Tests: a new spec asserts the natural/case-insensitive tree order; 62 classify
+ classifier green (108 across classify/classifier/tables/tables-mdl).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:00:23 -05:00
Me Here
2bc582fd9e ZDDC: document-control tools + zddc-server 2026-06-11 13:32:31 -05:00