diff --git a/public/nodes.js b/public/nodes.js index 71ad2e67..5d3355e8 100644 --- a/public/nodes.js +++ b/public/nodes.js @@ -1144,6 +1144,19 @@ makeColumnsResizable('#nodesTable', 'meshcore-nodes-col-widths'); } + /** + * Navigate to the full-screen node view for `pubkey` from anywhere within + * the nodes module. Single source of navigation truth — works regardless + * of current hash state (hash assignment alone is a no-op when the hash + * is already the target). + */ + function navigateToNode(pubkey) { + destroy(); + var appEl = document.getElementById('app'); + history.replaceState(null, '', '#/nodes/' + encodeURIComponent(pubkey)); + init(appEl, pubkey); + } + async function selectNode(pubkey) { // On mobile, navigate to full-screen node view if (window.innerWidth <= 640) { @@ -1307,12 +1320,11 @@ } catch {} } - // #856: Wire "Details" button to navigate to full-screen node view + // Wire "Details" button via the unified navigateToNode helper var detailBtn = panel.querySelector('.node-detail-btn'); if (detailBtn) { detailBtn.addEventListener('click', function() { - var pk = detailBtn.getAttribute('data-pubkey'); - location.hash = '#/nodes/' + pk; + navigateToNode(decodeURIComponent(detailBtn.getAttribute('data-pubkey'))); }); } diff --git a/test-e2e-playwright.js b/test-e2e-playwright.js index 1e9f0678..c1963964 100644 --- a/test-e2e-playwright.js +++ b/test-e2e-playwright.js @@ -2095,6 +2095,24 @@ async function run() { console.log(` ✓ obs A: ${snapA.pathBytes} path bytes / ${snapA.hopCount} hops; obs B: ${snapB.pathBytes} / ${snapB.hopCount}`); }); + // Test: clicking the 🔍 Details button in the nodes side panel navigates to + // the full-screen node detail view. Regression: hash already === target, + // so location.hash assignment was a no-op and the panel stayed open. + await test('Nodes side panel Details button opens full-screen view', async () => { + await page.goto(BASE + '#/nodes', { waitUntil: 'domcontentloaded' }); + await page.waitForSelector('table tbody tr[data-action]', { timeout: 15000 }); + await page.waitForTimeout(500); + // Open side panel + await page.click('table tbody tr[data-action]'); + await page.waitForSelector('#nodesRight .node-detail-btn', { timeout: 5000 }); + // Click Details + await page.click('#nodesRight .node-detail-btn'); + // Wait for full-screen view to appear + await page.waitForSelector('.node-fullscreen', { timeout: 5000 }); + const isFullScreen = await page.evaluate(() => !!document.querySelector('.node-fullscreen')); + assert(isFullScreen, 'Details button should open full-screen node view'); + }); + await browser.close(); // Summary