From 59506db7a9cb79a80e213a95659aea0a82244d09 Mon Sep 17 00:00:00 2001 From: Protocolbot Date: Wed, 8 Jul 2026 13:59:55 -0600 Subject: [PATCH] =?UTF-8?q?fix:=20back=20button=20navigation=20=E2=80=94?= =?UTF-8?q?=20guard=20hash=20setting=20during=20route()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the back button triggers hashchange -> route(), the isRouting flag prevents navigate() and showDetail() from re-setting location.hash, which was creating duplicate history entries and trapping the user. Now the back button properly traverses history. --- frontend/app.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index a9e58c9..f6fdf5e 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -63,6 +63,7 @@ async function init() { } function route() { + isRouting = true; const hash = location.hash.slice(1) || 'library'; if (hash.startsWith('protocols/')) { showDetail(hash.split('/')[1]); @@ -81,14 +82,15 @@ function route() { } else { navigate('library'); } + isRouting = false; } let searchTimer = null; let currentRoute = ''; +let isRouting = false; function navigate(view) { - // Prevent double navigation from hashchange - if (currentRoute === view) return; + if (currentRoute === view && view !== 'library') return; currentRoute = view; document.querySelectorAll('.view').forEach(v => v.style.display = 'none'); document.getElementById('view-' + view).style.display = 'block'; @@ -96,11 +98,17 @@ function navigate(view) { document.querySelectorAll('.mobile-panel a').forEach(a => a.classList.toggle('active', a.dataset.view === view)); closeMenu(); updateBreadcrumb(view); - if (view === 'library') { location.hash = 'library'; loadProtocols(); } - if (view === 'collections') { location.hash = 'collections'; loadCollections(); } + if (!isRouting) { + if (view === 'library') { location.hash = 'protocols'; } + if (view === 'collections') { location.hash = 'collections'; } + if (view === 'about') { location.hash = 'about'; } + if (view === 'terms') { location.hash = 'terms'; } + } + if (view === 'library') loadProtocols(); + if (view === 'collections') loadCollections(); if (view === 'create') { editingSlug = null; resetForm(); } if (view === 'create-collection') { editingCollectionSlug = null; resetCollectionForm(); } - if (view === 'about') { location.hash = 'about'; } + if (view === 'about') updateAboutPage(); window.scrollTo(0, 0); } @@ -313,7 +321,7 @@ function renderProtocolGrid(protocols) { // --- Detail --- async function showDetail(slug) { - if (location.hash !== '#protocols/' + slug) location.hash = 'protocols/' + slug; + location.hash = 'protocols/' + slug; document.querySelectorAll('.view').forEach(v => v.style.display = 'none'); document.getElementById('view-detail').style.display = 'block'; document.querySelectorAll('header nav a').forEach(a => a.classList.remove('active')); @@ -713,7 +721,7 @@ async function saveCollection(e) { } async function showCollectionDetail(slug) { - if (location.hash !== '#collections/' + slug) location.hash = 'collections/' + slug; + location.hash = 'collections/' + slug; document.querySelectorAll('.view').forEach(v => v.style.display = 'none'); document.getElementById('view-collection-detail').style.display = 'block'; document.querySelectorAll('header nav a').forEach(a => a.classList.remove('active')); @@ -786,7 +794,6 @@ async function deleteCollection(slug) { // --- User Profile --- async function showUser(username) { - if (location.hash !== '#users/' + username && location.hash !== '#user/' + username) location.hash = 'users/' + username; document.querySelectorAll('.view').forEach(v => v.style.display = 'none'); document.getElementById('view-detail').style.display = 'block'; document.querySelectorAll('header nav a').forEach(a => a.classList.remove('active'));