From c32871d9d09cebdc11417c517d4a7bcc8f881d8d Mon Sep 17 00:00:00 2001 From: root Date: Thu, 4 Jun 2026 23:23:00 +1000 Subject: [PATCH] fix(ui): no-cache static assets (stop stale CSS/JS after deploys); live nav-active sync; breadcrumb sized+themed to match back button Co-Authored-By: Claude Opus 4.8 --- public/components/sidebar.js | 18 +++++++++++++++--- public/style.css | 6 +++--- server.js | 6 +++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/public/components/sidebar.js b/public/components/sidebar.js index 34b4d67..28e8b6d 100644 --- a/public/components/sidebar.js +++ b/public/components/sidebar.js @@ -101,7 +101,15 @@ export function renderSidebar(root) { ) ); - renderSpaceTree(spacesContainer); + // Sync the active highlight across ALL nav items (global links + space tree) + // to the current hash. navItem only sets active at creation, so without this + // the highlight stays stuck on the previously-selected tab until a refresh. + function syncActive() { + root.querySelectorAll('a.sb-item').forEach(a => + a.classList.toggle('active', a.getAttribute('href') === location.hash)); + } + + renderSpaceTree(spacesContainer).then(syncActive); // Pending-count badge wiring on('pending-count', (n) => { @@ -110,6 +118,10 @@ export function renderSidebar(root) { if (n > 0) inboxItem.appendChild(el('span', { class: 'badge' }, String(n))); }); - // Refresh tree on hashchange (active highlight) and on space creation. - window.addEventListener('hashchange', () => renderSpaceTree(spacesContainer)); + // On navigation: re-render the tree (lazy state) then re-sync the highlight. + window.addEventListener('hashchange', async () => { + await renderSpaceTree(spacesContainer); + syncActive(); + }); + syncActive(); } diff --git a/public/style.css b/public/style.css index 7b6d9f8..809fa41 100644 --- a/public/style.css +++ b/public/style.css @@ -166,12 +166,12 @@ button.ghost:hover { color: var(--text); border-color: var(--accent-dim); } .doc-head .back-btn { margin-bottom: 0; } .doc-head .exp-menu { margin-left: auto; } -/* Breadcrumb: Space › parent › current */ -.crumbs { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 12px; } +/* Breadcrumb: Space › parent › current — sized + themed to sit inline with the back button */ +.crumbs { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 13px; font-family: var(--font-ui); line-height: 1; } .crumb { color: var(--muted); text-decoration: none; } .crumb:hover { color: var(--accent); } .crumb.current { color: var(--text); } -.crumb-sep { color: var(--accent-dim); } +.crumb-sep { color: var(--accent-dim); font-size: 14px; } /* Export dropdown */ .exp-menu { position: relative; } diff --git a/server.js b/server.js index c9e55f1..cdd39db 100644 --- a/server.js +++ b/server.js @@ -20,7 +20,11 @@ export function createApp() { limit: '10mb', verify: (req, _res, buf) => { req.rawBody = buf; } })); - app.use(express.static('public')); + // no-cache (not no-store): the browser may cache but must revalidate, so a + // deploy's new CSS/JS takes effect immediately instead of serving stale assets. + app.use(express.static('public', { + setHeaders: (res) => res.setHeader('Cache-Control', 'no-cache') + })); // /api/ingest/* bypasses agentOrOwner — webhooks authenticate via HMAC // and need access to req.rawBody captured above.