From cc515b0f5610df276e9de45add786985c8626604 Mon Sep 17 00:00:00 2001 From: ZDDC Date: Sat, 9 May 2026 19:46:14 -0500 Subject: [PATCH] feat(landing): single-project click navigates to /archive.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously every project click — single or group — built archive.html?projects= and let the archive tool's URL-state detection fan out from there. For a single project that's a single-page-app trick that obscures the canonical URL. Now single-project clicks navigate to /archive.html instead. The benefit is direct URL manipulation: the user can swap archive.html for working/, staging/, reviewing/, archive//mdl/table.html etc. in the address bar without going back through landing. zddc-server's availability.go already auto-serves the right tool at each canonical folder, so the destinations resolve without any server change. Multi-project clicks (groups) keep the ?projects=A,B form because there's no single subtree root. ACL-trimmed groups that collapse to one project also take the new single-project path, since the result is effectively a single-project view either way. The ?v= channel selector continues to carry across both paths. Two existing landing.spec.js assertions updated to match the new single-project URL shape; multi-project assertion unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- landing/js/landing.js | 16 +++++++++++++++- tests/landing.spec.js | 10 ++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/landing/js/landing.js b/landing/js/landing.js index 32870df..a2e9951 100644 --- a/landing/js/landing.js +++ b/landing/js/landing.js @@ -429,8 +429,22 @@ function openArchiveWith(names) { if (!names || names.length === 0) return; var base = location.pathname.replace(/\/[^\/]*$/, '/'); - var params = ['projects=' + names.map(encodeURIComponent).join(',')]; var v = new URLSearchParams(location.search).get('v'); + + if (names.length === 1) { + // Single project → canonical project-subtree URL so the user + // can edit the address bar to swap archive.html for + // working/, staging/, reviewing/, etc. zddc-server's + // availability.go auto-serves the right tool at each. + // Multi-project (the `else` branch) keeps the ?projects= + // form because there's no single subtree root. + var url = base + encodeURIComponent(names[0]) + '/archive.html'; + if (v) url += '?v=' + encodeURIComponent(v); + navigate(url); + return; + } + + var params = ['projects=' + names.map(encodeURIComponent).join(',')]; if (v) params.push('v=' + encodeURIComponent(v)); navigate(base + 'archive.html?' + params.join('&')); } diff --git a/tests/landing.spec.js b/tests/landing.spec.js index a5bc044..ac0f5dc 100644 --- a/tests/landing.spec.js +++ b/tests/landing.spec.js @@ -66,7 +66,10 @@ test.describe('Landing page', () => { await page.locator('.project-table-row[data-name="197072"]').click(); const navTo = await page.evaluate(() => window.__navTo); - expect(navTo).toContain('archive.html?projects=197072'); + // Single-project click navigates to the project's canonical subtree + // (so the user can swap archive.html for working/, staging/, etc.). + expect(navTo).toContain('/197072/archive.html'); + expect(navTo).not.toContain('?projects='); }); test('column filters narrow the table; filters persist in URL', async ({ page }) => { @@ -217,8 +220,11 @@ test.describe('Landing page', () => { await page.locator('#openSelectedBtn').click(); const navTo = await page.evaluate(() => window.__navTo); - expect(navTo).toContain('archive.html?projects=176109'); + // After the visibility-filter trims to one project, this collapses + // to the single-project path (no ?projects= form). + expect(navTo).toContain('/176109/archive.html'); expect(navTo).not.toContain('197072'); + expect(navTo).not.toContain('?projects='); }); test('legacy presets are migrated to groups on first load', async ({ page }) => {