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>
This commit is contained in:
ZDDC 2026-06-09 16:08:57 -05:00
parent afcba81e61
commit 975c804cc7
3 changed files with 45 additions and 3 deletions

View file

@ -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('Couldnt preview ' + (file.originalFilename || 'file') + ' — ' + (e.message || e), 'error');
}
}
}
window.app.modules.preview = {
init
init,
previewFile
};
})();

View file

@ -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);
}
});
}

View file

@ -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(() => {