diff --git a/public/nodes.js b/public/nodes.js
index 439c3677..37b4d89f 100644
--- a/public/nodes.js
+++ b/public/nodes.js
@@ -315,29 +315,34 @@
let regionChangeHandler = null;
+ // Show full-screen node detail view (works on any screen size)
+ function showFullScreenNode(pubkey) {
+ var app = document.getElementById('app');
+ app.innerHTML = '
';
+ document.getElementById('nodeBackBtn').addEventListener('click', function() { location.hash = '#/nodes'; });
+ loadFullNode(pubkey);
+ document.addEventListener('keydown', function nodesEsc(e) {
+ if (e.key === 'Escape') {
+ document.removeEventListener('keydown', nodesEsc);
+ location.hash = '#/nodes';
+ }
+ });
+ }
+
function init(app, routeParam) {
directNode = routeParam || null;
if (directNode && window.innerWidth <= 640) {
- // Full-screen single node view (mobile only)
- app.innerHTML = ``;
- document.getElementById('nodeBackBtn').addEventListener('click', () => { location.hash = '#/nodes'; });
- loadFullNode(directNode);
- // Escape to go back to nodes list
- document.addEventListener('keydown', function nodesEsc(e) {
- if (e.key === 'Escape') {
- document.removeEventListener('keydown', nodesEsc);
- location.hash = '#/nodes';
- }
- });
+ // Full-screen single node view (mobile)
+ showFullScreenNode(directNode);
return;
}
@@ -1045,10 +1050,17 @@
var link = e.target.closest('a.btn-primary[href^="#/nodes/"]');
if (link) {
e.preventDefault();
- var target = link.getAttribute('href');
- // Always clear and reassign — hashchange won't fire if hash already matches
- history.replaceState(null, '', '#/');
- location.hash = target.substring(1);
+ var href = link.getAttribute('href');
+ var pubkey = decodeURIComponent(href.replace('#/nodes/', '').replace('/analytics', ''));
+ if (href.includes('/analytics')) {
+ // Navigate to analytics page
+ history.replaceState(null, '', '#/');
+ location.hash = '/nodes/' + encodeURIComponent(pubkey) + '/analytics';
+ } else {
+ // Show full-screen node detail view
+ showFullScreenNode(pubkey);
+ history.replaceState(null, '', '#/nodes/' + encodeURIComponent(pubkey));
+ }
return;
}
if (e.target.closest('.panel-close-btn')) {
diff --git a/test-e2e-playwright.js b/test-e2e-playwright.js
index 4e6f9a6c..e397c604 100644
--- a/test-e2e-playwright.js
+++ b/test-e2e-playwright.js
@@ -231,8 +231,8 @@ async function run() {
assert(hasStatus, 'No status indicator found in node detail');
});
- // Test: Node side panel Details link navigates to full detail page (#778)
- await test('Node side panel Details link navigates', async () => {
+ // Test: Node side panel Details link opens full-screen detail view (#778)
+ await test('Node side panel Details link opens full detail', async () => {
await page.goto(`${BASE}/#/nodes`, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('table tbody tr');
// Click first row to open side panel
@@ -243,16 +243,12 @@ async function run() {
// Find the Details link in the side panel
const detailsLink = await page.$('#nodesRight a.btn-primary[href^="#/nodes/"]');
assert(detailsLink, 'Details link not found in side panel');
- const href = await detailsLink.getAttribute('href');
- // Click the Details link — this should navigate to the full detail page
+ // Click the Details link — should open full-screen node detail view
await detailsLink.click();
- // Wait for navigation — the full detail page has sections like neighbors/packets
- await page.waitForFunction((expectedHash) => {
- return location.hash === expectedHash;
- }, href, { timeout: 5000 });
- // Verify we're on the full detail page (should have section tabs or detail content)
- const hash = await page.evaluate(() => location.hash);
- assert(hash === href, `Expected hash "${href}" but got "${hash}"`);
+ // Wait for the full-screen node detail view to render
+ await page.waitForSelector('.node-fullscreen', { timeout: 5000 });
+ const hasFullBody = await page.$('.node-full-body');
+ assert(hasFullBody, 'Full-screen node detail body not found');
});
// Test: Nodes page has WebSocket auto-update listener (#131)