From abd9c46aa7b5a73d04aa3d4f6a678741c83b8524 Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Tue, 21 Apr 2026 22:37:15 -0700 Subject: [PATCH] fix: side-panel Details button opens full-screen on desktop (#892) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Symptom 🔍 Details button in the nodes side panel does nothing on click. ## Root cause (4th regression of the same shape) - Row click → `selectNode()` → `history.replaceState(null, '', '#/nodes/' + pk)` - Details button click → `location.hash = '#/nodes/' + pk` - Hash is already that value → assignment is a no-op → no `hashchange` event → no router → panel stays open. ## Fix Mirror the analytics-link branch already inside the panel click handler: `destroy()` then `init(appEl, pubkey)` directly (which hits the `directNode` full-screen branch unconditionally). Also `replaceState` to keep the URL in sync. ## Test New Playwright E2E: open side panel via row click, click Details, assert `.node-fullscreen` appears. ## Why this keeps regressing Every time we tighten the row-click handler to use `replaceState` (correct — avoids hashchange flicker), the button-click handler that uses `location.hash` becomes a no-op for the same pubkey. Need to remember they're coupled. Worth a follow-up to extract a `navigateToNode(pk)` helper that always works regardless of current hash state — filing as #890-followup if not already there. Co-authored-by: you --- public/nodes.js | 18 +++++++++++++++--- test-e2e-playwright.js | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) 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