ZDDC/classifier/template.html
ZDDC 9ca24eb3f1 feat(classifier): By-transmittal is a per-file grid with a single folder-path input
Standardize the two classify tabs: By-transmittal drops the party→slot→bin
tree (and its multi-field "+ Transmittal" form) for a flat per-file grid that
mirrors By-tracking. Each file gets ONE editable text input — its full
transmittal folder path "<party>/<received|issued>/<YYYY-MM-DD_TN (STATUS) -
Title>". Committing it find-or-creates the party/slot/bin; structure stays
derived, never stored.

Drag-and-drop (from the source tree onto the grid):
- plain drop on a routed row → the dropped files JOIN that row's folder;
- ⌘/Ctrl-drop on a routed row → a prompt prefilled with that folder's path
  lets you edit it into a NEW transmittal the files go to (the original is
  untouched; an unedited path dedups via find-or-create);
- drop on empty space / an unrouted row → files are added as blank rows to fill.

Model (classify.js): adds a `transmittalWorkset` (parallel to trackingWorkset)
plus addToTransmittalGrid / removeFromTransmittalGrid / transmittalGridKeys and
setTransmittalPath(keys, path) — the single parser for "<party>/<slot>/<folder>"
that also prunes any bin a re-route empties. app.js importPaths now reuses
setTransmittalPath for its route axis (one parser, less duplication).

Removes the now-dead tree rendering/CRUD (party/bin nodes, binForm, the bin
filename editor, the bin drop zone). Tests updated to the grid model: tab
render shows the folder-path input; drop join/branch/empty; edit re-routes and
prunes the emptied folder; ✕ removes. 71/71 classify specs pass.

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

282 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ZDDC Classifier</title>
<link rel="icon" type="image/svg+xml" href="{{FAVICON}}">
<style>
{{CSS_PLACEHOLDER}}
</style>
</head>
<body>
<div id="app">
<!-- Main Application -->
<div id="mainApp" class="main-app">
<!-- Header -->
<header class="app-header">
<div class="header-left">
<svg class="app-header__logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#1e3a5f"/>
<g fill="#fff">
<rect x="14" y="18" width="36" height="7"/>
<polygon points="43,25 50,25 21,43 14,43"/>
<rect x="14" y="43" width="36" height="7"/>
</g>
</svg>
<div class="header-title-group">
<span class="app-header__title">ZDDC Classifier</span>
<span class="build-timestamp">{{BUILD_LABEL}}</span>
</div>
<button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button>
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory" aria-label="Refresh" style="font-size:1.1rem;"></button>
<button id="workspacesBtn" class="btn btn-secondary btn-sm" title="Workspaces — open or create a classification project">≡ Workspaces</button>
<button id="connectDirBtn" class="btn btn-primary btn-sm" title="Connect this workspace's source directory to preview, copy, or finish scanning" hidden>⮷ Connect directory</button>
</div>
<div class="header-right">
<button id="theme-btn" class="btn btn-secondary" title="Theme: auto (follows OS)" aria-label="Theme: auto (follows OS)"></button>
<button id="help-btn" class="btn btn-secondary" title="Help">?</button>
</div>
</header>
<!-- Main Content -->
<div class="main-content">
<!-- Folder Tree -->
<aside class="folder-tree-pane" id="folderTreePane">
<div class="pane-header">
<div class="pane-header-title">
<button class="btn btn-sm collapse-tree-btn" id="collapseTreeBtn" title="Collapse folder tree"></button>
<h3>Folder Tree</h3>
</div>
<div class="pane-header-controls">
<label class="checkbox-label" title="Auto-scroll folder tree when hovering files">
<input type="checkbox" id="autoScrollCheckbox" checked>
Auto-scroll
</label>
<label class="checkbox-label" id="hideCompliantLabel">
<input type="checkbox" id="hideCompliantCheckbox">
Hide Compliant
</label>
</div>
</div>
<div id="classifyFilters" class="classify-filters tree-toolbar" hidden>
<span class="tree-toolbar__label">Show</span>
<label class="checkbox-label" title="Not assigned on either axis">
<input type="checkbox" id="showUnassignedCheckbox" checked>
Unassigned <span class="filter-count" id="showUnassignedCount"></span>
</label>
<label class="checkbox-label" title="Assigned in the OTHER tab but not this one — the to-do for this tab">
<input type="checkbox" id="showPartialCheckbox" checked>
Partial <span class="filter-count" id="showPartialCount"></span>
</label>
<label class="checkbox-label" title="Already assigned in the active tab">
<input type="checkbox" id="showAssignedCheckbox" checked>
Assigned <span class="filter-count" id="showAssignedCount"></span>
</label>
<label class="checkbox-label" title="Excluded files">
<input type="checkbox" id="showExcludedCheckbox" checked>
Excluded <span class="filter-count" id="showExcludedCount"></span>
</label>
<label class="checkbox-label" title="Folders that contain no files">
<input type="checkbox" id="showEmptyCheckbox" checked>
Empty
</label>
<button class="btn btn-sm export-list-btn" id="exportListBtn"
title="Copy the filtered file list (path + file columns, no folders) as TSV — paste into Excel, edit, then paste back via “Paste rows”. Paste a full path into the Current name column to bind that exact file.">⬆ Export list</button>
<button class="btn btn-sm export-list-btn" id="exportPathsBtn"
title="Download the filtered file list as a 1-column CSV of full paths. Feed it to an AI to classify into <party>/<direction>/<transmittal>/<file>.ext, then bring the 2-column result back via “Import paths” above the target list.">⬇ Export paths</button>
</div>
<input type="search" id="treeFilterInput" class="tree-filter" spellcheck="false"
placeholder="Filter files… (e.g. master deliverables list)" aria-label="Filter files">
<div id="folderTree" class="folder-tree">
<!-- Dynamically populated -->
</div>
<div class="resize-handle" id="treeResizeHandle"></div>
</aside>
<!-- The classify surface: one editable By-tracking grid + a
transmittal tree. Tracking number alone ⇒ a Rename (in place);
add a transmittal folder ⇒ a Copy into the archive. -->
<main class="target-pane" id="targetPane">
<div class="pane-header pane-header--target">
<p class="target-goal">Give each file a <strong>tracking number</strong> (revision + status + title) under <em>By tracking number</em> — that alone is a <strong>Rename</strong> in place. Route it into a <strong>transmittal folder</strong> under <em>By transmittal</em> to <strong>Copy</strong> it into the archive.</p>
<div class="target-tabs" role="tablist">
<button class="target-tab active" id="trackingTab" role="tab">By tracking number</button>
<button class="target-tab" id="transmittalTab" role="tab">By transmittal</button>
</div>
<div class="pane-header-right">
<span id="classifyStats" class="file-stats"></span>
<span class="header-divider">|</span>
<button id="importPathsBtn" class="btn btn-secondary btn-sm" title="Import a 2-column CSV (old path, new path). Each new path “<party>/<direction>/<transmittal>/<file>.ext” sets that files tracking number (rename) and routes it into a transmittal. Only files named in the CSV are touched — others keep their current classification. Export the source list first via “Export paths” on the left.">Import paths…</button>
<input type="file" id="importPathsInput" accept=".csv,text/csv,text/plain" hidden>
<button id="resetDatasetBtn" class="btn btn-sm btn-danger" title="Discard all classifications and start over from the raw scanned input (does not touch your files)">Reset</button>
</div>
</div>
<div class="target-body">
<section id="trackingPanel" class="target-panel">
<div class="target-panel__toolbar">
<button id="loadWorklistBtn" class="btn btn-sm btn-secondary" title="Add tracking numbers from the project archive/MDL (pick directories to scan) as rows to fill.">⊞ Load…</button>
<button id="pasteRowsBtn" class="btn btn-sm btn-secondary" title="Paste rows from Excel: Tracking · Rev (Status) · Title · Current name.">⎘ Paste rows…</button>
<button id="matchNamesBtn" class="btn btn-sm btn-secondary" title="Auto-match unassigned files to list rows by name.">⚡ Match names</button>
<button id="addFilteredBtn" class="btn btn-sm btn-secondary" title="Add every file the left tree currently shows (filters applied) to the grid.">⊕ Add filtered files</button>
<button id="clearListBtn" class="btn btn-sm btn-secondary" title="Remove the list (placeholder) rows. Every assignment is kept.">Clear list</button>
<button id="trackingColsBtn" class="btn btn-sm btn-secondary" title="Show or hide columns">Columns ▾</button>
<span class="target-hint">Drag files in (ctrl-shift-click the left tree to multi-select; drop on a row to fill a contiguous block), or “Add filtered files”. Type the tracking number, revision (“A (IFR)”) and title — already-ZDDC-named files fill in. “Load…” / “Paste rows…” add rows to drop files onto.</span>
<button id="renameBtn" class="btn btn-sm btn-danger" disabled title="Rename the name-complete files IN PLACE on disk. Destructive — no backup.">Rename…</button>
</div>
<input type="search" id="trackingFilterInput" class="tree-filter target-filter" spellcheck="false"
placeholder="Filter the grid…" aria-label="Filter the tracking grid">
<div id="trackingTree" class="target-tree"></div>
</section>
<section id="transmittalPanel" class="target-panel" hidden>
<div class="target-panel__toolbar">
<span class="target-hint">One row per file — type its transmittal folder: &lt;party&gt;/&lt;received|issued&gt;/&lt;YYYY-MM-DD_TN (STATUS) - Title&gt;. Drag files in: drop on a row to put the file in that same folder, ⌘/Ctrl-drop to branch a new transmittal from it.</span>
<button id="checkDuplicatesBtn" class="btn btn-secondary btn-sm" title="Check for files with the same tracking number + revision but different content (flagged ≠ in red)">Check</button>
<button id="copyOutputBtn" class="btn btn-primary btn-sm" disabled title="Copy fully-classified files (+ their transmittal folders) into the archive — server or a local folder. Source untouched, resumable, verified.">Copy…</button>
</div>
<input type="search" id="transmittalFilterInput" class="tree-filter target-filter" spellcheck="false"
placeholder="Filter the transmittal grid…" aria-label="Filter transmittal grid">
<div id="transmittalTree" class="target-tree"></div>
</section>
</div>
</main>
</div>
<!-- Page footer — scan status lives here -->
<footer class="app-footer">
<span id="scanStatus" class="scan-status" aria-live="polite"></span>
</footer>
<!-- Empty State — shown until a directory is selected -->
<div id="welcomeScreen" class="empty-state empty-state--overlay">
<div class="empty-state__inner empty-state__inner--centered welcome">
<h1 class="welcome__title">ZDDC Classifier</h1>
<p class="welcome__lede">Turn a messy folder of project files into clean, correctly-named ZDDC documents — organized by tracking number and transmittal — <strong>without ever changing your originals</strong>.</p>
<!-- Workspaces (Classify & Copy) -->
<section id="workspacesSection" class="workspaces">
<div class="ws-head">
<h2>Your workspaces</h2>
<div class="ws-head__actions">
<button id="importWorkspaceBtn" class="btn btn-secondary" title="Import a whole workspace (.zddc-workspace.json) exported from another browser — restores the scanned snapshot so you don't re-scan">⭱ Import workspace</button>
<button id="newWorkspaceBtn" class="btn btn-primary">+ New workspace</button>
</div>
<input type="file" id="importWorkspaceInput" accept="application/json,.json" hidden>
</div>
<div id="workspaceList" class="ws-list"><!-- rendered --></div>
</section>
<!-- One flow, two endings: rename in place or copy to the archive -->
<div class="welcome__methods">
<section class="method method--primary">
<h3 class="method__title">Classify, then Rename or Copy</h3>
<p class="method__what">Give each file a ZDDC name in the <strong>By tracking number</strong> grid. A tracking number alone <em>is</em> a rename; add a <strong>transmittal folder</strong> and it can be copied into the archive.</p>
<ol class="method__steps">
<li><strong>New workspace</strong> → pick a folder. It scans <em>once</em> and saves to this browser, so you can close the tab and pick up later.</li>
<li><strong>Add files to the grid</strong> — drag them from the left tree (ctrl-shift-click to multi-select and fill a block of rows), use <strong>⊕ Add filtered files</strong>, or <strong>⊞ Load…</strong> / <strong>⎘ Paste rows…</strong> a list of expected tracking numbers and drop the matching files on. Already-ZDDC-named files fill in automatically.</li>
<li>Type each file's <strong>tracking number</strong>, <strong>revision</strong> (e.g. <code>A (IFR)</code>) and <strong>title</strong>.</li>
<li><strong>Rename…</strong> (By tracking number) renames the named files <em>in place</em> on disk — <span class="method__tag method__tag--warn">destructive, no backup</span>. Or place them into a transmittal under <strong>By transmittal</strong> and <strong>Copy…</strong> them into the archive (source untouched, resumable, verified).</li>
</ol>
</section>
</div>
<!-- Browser Compatibility Warning -->
<div id="browserWarning" class="browser-warning hidden">
<h3>⚠️ Browser Not Supported</h3>
<p>This application requires the File System Access API, available only in Chromium-based browsers (Chrome, Edge, Brave, Opera).</p>
</div>
<p class="welcome__note">Everything runs in your browser — no files are uploaded. Tip: if your folder lives on OneDrive/SharePoint, set it to <em>“Always keep on this device”</em> first for a much faster scan.</p>
</div>
</div>
</div>
<!-- Help Panel -->
<aside id="help-panel" class="help-panel" hidden aria-labelledby="help-panel-title">
<div class="help-panel__header">
<h2 id="help-panel-title" class="help-panel__title">Help — ZDDC Classifier</h2>
<button type="button" class="help-panel__close" id="help-panel-close" aria-label="Close">&times;</button>
</div>
<div class="help-panel__body">
<h3>What is the Classifier?</h3>
<p>The Classifier is a spreadsheet-based tool for renaming files to ZDDC naming conventions. It reads a folder of files and presents them in an editable grid where you can set tracking number, revision, status, and title — then saves the renamed files back to disk.</p>
<h3>Getting Started</h3>
<ol>
<li>Click <strong>Use Local Directory</strong> to open a folder containing files to rename.</li>
<li>The folder tree on the left shows all sub-folders. Click a folder to load its files.</li>
<li>Edit cells in the spreadsheet to set the new filename components.</li>
<li>Click <strong>Save All</strong> (or save individual rows) to rename the files on disk.</li>
</ol>
<h3>Folder Tree</h3>
<dl>
<dt>Multi-select</dt>
<dd>Hold <kbd>Ctrl</kbd> and click to select multiple folders. Hold <kbd>Shift</kbd> to select a range. Files from all selected folders are shown together.</dd>
<dt>Hide Compliant</dt>
<dd>Hides folders where all files already have valid ZDDC names, letting you focus on work remaining.</dd>
<dt>Auto-scroll</dt>
<dd>When enabled, the folder tree scrolls to highlight the folder containing the row you are editing.</dd>
</dl>
<h3>Spreadsheet Editing</h3>
<dl>
<dt>Direct cell editing</dt>
<dd>Click any cell in the New Filename, Tracking, Rev, Status, or Title columns to edit it. Press <kbd>Enter</kbd> to confirm, <kbd>Escape</kbd> to cancel.</dd>
<dt>RC References</dt>
<dd>Type a formula like <code>=R[-1]C</code> to copy the value from the cell one row above in the same column — similar to Excel relative references.</dd>
<dt>Regex capture groups</dt>
<dd>Type a formula like <code>=RE(RC[-3], "(\w+)-(\d+)", "$1")</code> to extract a pattern from another cell using a regular expression.</dd>
<dt>Validation</dt>
<dd>Cells are validated automatically. Invalid values are highlighted in red. The New Filename column shows the composed result.</dd>
<dt>Column Filters</dt>
<dd>Each column header has a filter input. Supported syntax:</dd>
</dl>
<dl>
<dt><code>term</code></dt>
<dd>Contains "term" (case-insensitive)</dd>
<dt><code>!term</code></dt>
<dd>Does not contain "term"</dd>
<dt><code>^term</code></dt>
<dd>Starts with "term"</dd>
<dt><code>term$</code></dt>
<dd>Ends with "term"</dd>
<dt><code>a b</code></dt>
<dd>Matches both (AND)</dd>
<dt><code>a | b</code></dt>
<dd>Matches either (OR)</dd>
<dt><code>^IFA | ^IFB</code></dt>
<dd>Starts with IFA or IFB</dd>
<dt><code>!^~</code></dt>
<dd>Does not start with ~ (excludes drafts)</dd>
<dt><code>el.*spc</code></dt>
<dd>Regex: contains "el" followed by "spc" (use <code>.</code> for any char, <code>.*</code> for any sequence)</dd>
<dt><code>[ei]fa</code></dt>
<dd>Regex character class: matches "efa" or "ifa"</dd>
</dl>
<h3>Saving Files</h3>
<dl>
<dt>Save All</dt>
<dd>Renames all modified files in one operation. Confirms before proceeding.</dd>
<dt>Cancel All</dt>
<dd>Reverts all unsaved edits back to the original filenames.</dd>
<dt>SHA256</dt>
<dd>Enable to compute a cryptographic hash of each file. Use <strong>Export Hashes</strong> to save a <code>sha256sum</code>-compatible file.</dd>
</dl>
<h3>ZDDC Filename Format</h3>
<p>The required format is:</p>
<p><code>TRACKINGNUMBER_REVISION (STATUS) - Title.ext</code></p>
<p>Example: <code>123456-EL-SPC-2623_A (IFR) - Electrical Specification.pdf</code></p>
<p>Valid statuses: IFA, IFB, IFC, IFD, IFI, IFP, IFR, IFU, REC, RSA, RSB, RSC, RSD, RSI, ---</p>
</div>
</aside>
</div>
<script>
{{JS_PLACEHOLDER}}
</script>
</body>
</html>