From 975c804cc72fd17129c8cb24d9a3f7d788eb7482 Mon Sep 17 00:00:00 2001 From: ZDDC Date: Tue, 9 Jun 2026 16:08:57 -0500 Subject: [PATCH] feat(classifier): click-to-preview in Classify & Copy mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- classifier/js/preview.js | 23 ++++++++++++++++++++++- classifier/js/tree.js | 9 +++++++-- tests/classify.spec.js | 16 ++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/classifier/js/preview.js b/classifier/js/preview.js index 8e1d8f4..15d5ea3 100644 --- a/classifier/js/preview.js +++ b/classifier/js/preview.js @@ -521,7 +521,28 @@ } // Export module + // Preview a file on demand (Classify & Copy mode). Snapshot-loaded files + // have no handle yet — resolve it from the workspace root (one-click read + // permission re-grant) before opening the preview window. + async function previewFile(file) { + try { + if (!file.handle && !file.isVirtual && window.app.rootHandle) { + if (window.app.modules.persist && window.app.modules.persist.verifyPermission) { + const ok = await window.app.modules.persist.verifyPermission(window.app.rootHandle, false); + if (!ok) { if (window.zddc) window.zddc.toast('Permission to read the source directory was denied.', 'error'); return; } + } + await window.app.modules.scanner.resolveFileHandle(window.app.rootHandle, file); + } + await openPreviewWindow(file); + } catch (e) { + if (window.zddc) { + window.zddc.toast('Couldn’t preview ' + (file.originalFilename || 'file') + ' — ' + (e.message || e), 'error'); + } + } + } + window.app.modules.preview = { - init + init, + previewFile }; })(); diff --git a/classifier/js/tree.js b/classifier/js/tree.js index c8497d6..1f49466 100644 --- a/classifier/js/tree.js +++ b/classifier/js/tree.js @@ -270,6 +270,7 @@ item.className = 'file-item'; item.style.paddingLeft = `${level * 1.5}rem`; item.draggable = true; + item.title = 'Click to preview · drag onto a tracking folder or transmittal to assign'; const key = c.srcKeyForFile(file); item.dataset.key = key; const st = c.fileState(file); @@ -688,11 +689,15 @@ var ft = window.app.dom.folderTree; if (!ft) { classifyWired = false; return; } ft.addEventListener('contextmenu', onContextMenu); + // Single-click a source file → preview it (the "look at it, then assign" + // half of the workflow). Drag still assigns; right-click excludes. ft.addEventListener('click', function (e) { if (!classifyOn()) return; var fe = e.target.closest('.file-item'); - if (fe && fe.dataset.key && window.app.modules.targetTree) { - window.app.modules.targetTree.reveal(fe.dataset.key); + if (!fe || !fe.dataset.key) return; + var file = findFileByKey(fe.dataset.key); + if (file && window.app.modules.preview && window.app.modules.preview.previewFile) { + window.app.modules.preview.previewFile(file); } }); } diff --git a/tests/classify.spec.js b/tests/classify.spec.js index 0398b3a..864a663 100644 --- a/tests/classify.spec.js +++ b/tests/classify.spec.js @@ -244,6 +244,22 @@ test('source file rows render with a state dot in classify mode', async ({ page await expect(page.locator('#folderTree .file-item .cl-dot--none')).toBeVisible(); }); +test('classify: single-click a source file triggers preview', async ({ page }) => { + await page.click('#modeClassifyBtn'); + const previewed = await page.evaluate(() => { + let got = null; + window.app.modules.preview.previewFile = (f) => { got = f.originalFilename; }; // capture, skip popup + window.app.folderTree = [{ + name: 'Root', path: 'Root', expanded: true, scanState: 'done', + files: [{ originalFilename: 'Foundation Plan', extension: 'pdf', folderPath: 'Root' }], children: [], + }]; + window.app.modules.tree.render(); + document.querySelector('#folderTree .file-item').click(); + return got; + }); + expect(previewed).toBe('Foundation Plan'); +}); + test('classify: a folder with files but no subfolders is expandable (drag source)', async ({ page }) => { await page.click('#modeClassifyBtn'); await page.evaluate(() => {