diff --git a/classifier/css/layout.css b/classifier/css/layout.css index 77565a5..bee877a 100644 --- a/classifier/css/layout.css +++ b/classifier/css/layout.css @@ -613,12 +613,21 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o } .seltable__count { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; } .seltable__scroll { flex: 1; min-height: 0; overflow: auto; } -.seltable__table { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 0.82rem; } +/* width:auto + nowrap cells → each column shrinks to fit its header/longest cell. */ +.seltable__table { border-collapse: separate; border-spacing: 0; width: auto; font-size: 0.82rem; } .seltable__table th, .seltable__table td { border-bottom: 1px solid var(--border); padding: 0.25rem 0.5rem; text-align: left; white-space: nowrap; } .seltable__table thead th { position: sticky; top: 0; z-index: 2; background: var(--bg-secondary, var(--bg)); color: var(--text-muted); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; } +/* Per-column filter inputs: fill the column (min-width:0-ish) so they never + force a column wider than its header/cells. */ +.seltable__table thead tr.seltable__filters th { padding: 0.08rem 0.3rem; } +.seltable__colfilter { + width: 100%; min-width: 2rem; box-sizing: border-box; + padding: 0.1rem 0.3rem; border: 1px solid var(--border); border-radius: var(--radius); + background: var(--bg); color: var(--text); font-size: 0.72rem; font-weight: 400; letter-spacing: 0; text-transform: none; +} .seltable__row { cursor: pointer; user-select: none; } .seltable__row:hover { background: var(--bg-hover); } .seltable__row.is-selected { background: var(--primary-light, rgba(37,99,235,0.12)); } diff --git a/classifier/js/tree.js b/classifier/js/tree.js index 62b2832..c87d760 100644 --- a/classifier/js/tree.js +++ b/classifier/js/tree.js @@ -5,6 +5,17 @@ (function() { 'use strict'; + // ── Sorting ──────────────────────────────────────────────────────────── + // Render the tree in a stable, human order: case-insensitive, natural + // (so "Rev 2" sorts before "Rev 10"). Non-mutating — sort copies at render. + function cmpName(a, b) { return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' }); } + function sortedFolders(list) { return (list || []).slice().sort(function (a, b) { return cmpName(a.name, b.name); }); } + function sortedFiles(list) { + return (list || []).slice().sort(function (a, b) { + return cmpName(window.zddc.joinExtension(a.originalFilename, a.extension), window.zddc.joinExtension(b.originalFilename, b.extension)); + }); + } + // ── Classify & Copy helpers ──────────────────────────────────────────── function classifyOn() { var c = window.app.modules.classify; @@ -175,7 +186,7 @@ return; } - window.app.folderTree.forEach(folder => { + sortedFolders(window.app.folderTree).forEach(folder => { if (!folderShown(folder)) return; const element = createFolderElement(folder); container.appendChild(element); @@ -360,7 +371,7 @@ if ((folder.expanded || autoOpen(folder)) && folder.children && folder.children.length > 0) { const childrenDiv = document.createElement('div'); childrenDiv.className = 'folder-children'; - folder.children.forEach(child => { + sortedFolders(folder.children).forEach(child => { if (!folderShown(child)) return; const childElement = createFolderElement(child, level + 1); childrenDiv.appendChild(childElement); @@ -373,7 +384,7 @@ if (classifyOn() && (folder.expanded || autoOpen(folder)) && folder.files && folder.files.length > 0) { const filesDiv = document.createElement('div'); filesDiv.className = 'folder-children folder-files'; - folder.files.forEach(function (file) { + sortedFiles(folder.files).forEach(function (file) { if (!fileShown(file)) return; filesDiv.appendChild(createFileElement(file, level + 1)); }); diff --git a/shared/seltable.css b/shared/seltable.css index 93d3b7c..931ee58 100644 --- a/shared/seltable.css +++ b/shared/seltable.css @@ -5,7 +5,8 @@ .seltable__bar { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.5rem; border-bottom: 1px solid var(--border); flex: 0 0 auto; } .seltable__count { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; } .seltable__scroll { flex: 1; min-height: 0; overflow: auto; } -.seltable__table { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 0.82rem; } +/* width:auto + nowrap cells → each column shrinks to fit its header/longest cell. */ +.seltable__table { border-collapse: separate; border-spacing: 0; width: auto; font-size: 0.82rem; } .seltable__table th, .seltable__table td { border-bottom: 1px solid var(--border); padding: 0.25rem 0.5rem; text-align: left; white-space: nowrap; } .seltable__table thead th { position: sticky; top: 0; z-index: 2; background: var(--bg-secondary, var(--bg)); @@ -13,7 +14,7 @@ } .seltable__table thead tr.seltable__filters th { top: 1.55rem; padding: 0.15rem 0.35rem; } .seltable__colfilter { - width: 100%; min-width: 5rem; padding: 0.15rem 0.35rem; + width: 100%; min-width: 2rem; box-sizing: border-box; padding: 0.15rem 0.35rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--text); font-size: 0.74rem; font-weight: 400; text-transform: none; letter-spacing: 0; } diff --git a/tests/classify.spec.js b/tests/classify.spec.js index 4ff80ad..2098bf6 100644 --- a/tests/classify.spec.js +++ b/tests/classify.spec.js @@ -247,6 +247,33 @@ 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('Folder Tree renders folders and files in natural, case-insensitive order', async ({ page }) => { + await page.click('#modeClassifyBtn'); + const order = await page.evaluate(() => { + window.app.folderTree = [{ + name: 'Root', path: 'Root', expanded: true, scanState: 'done', + children: [ + { name: 'Beta', path: 'Root/Beta', scanState: 'done', children: [], files: [] }, + { name: 'alpha', path: 'Root/alpha', scanState: 'done', children: [], files: [] }, + { name: 'Rev 10', path: 'Root/Rev 10', scanState: 'done', children: [], files: [] }, + { name: 'Rev 2', path: 'Root/Rev 2', scanState: 'done', children: [], files: [] }, + ], + files: [ + { originalFilename: 'zeta', extension: 'pdf', folderPath: 'Root' }, + { originalFilename: 'Apple', extension: 'pdf', folderPath: 'Root' }, + { originalFilename: 'banana', extension: 'pdf', folderPath: 'Root' }, + ], + }]; + window.app.modules.tree.render(); + return { + folders: Array.from(document.querySelectorAll('#folderTree .folder-children .folder-name')).map(e => e.textContent.trim()), + files: Array.from(document.querySelectorAll('#folderTree .folder-files .file-name')).map(e => e.textContent.trim()), + }; + }); + expect(order.folders).toEqual(['alpha', 'Beta', 'Rev 2', 'Rev 10']); // case-insensitive + natural (2 < 10) + expect(order.files).toEqual(['Apple.pdf', 'banana.pdf', 'zeta.pdf']); // case-insensitive +}); + test('classify: single-click a source file triggers preview', async ({ page }) => { await page.click('#modeClassifyBtn'); const previewed = await page.evaluate(() => {