From 0d9d9ecd574fde1220ae12d8c6c75cbe2b6cf539 Mon Sep 17 00:00:00 2001 From: MarekWo Date: Tue, 21 Jul 2026 21:08:19 +0200 Subject: [PATCH] feat(pathanalyzer): map view polish - channel label, auto path, 45vh map - Show the channel name (dimmed, next to the sender) on map-view tiles - Clicking a message now auto-selects and draws its shortest routed echo (fewest hops, ties -> first); re-clicking keeps the user's echo choice - Mobile: map takes a fixed 45% of the viewport height and the path list gets the remaining space (was min 300px map / max 40% list) Co-Authored-By: Claude Fable 5 --- app/static/js/path-analyzer.js | 26 ++++++++++++++++++++++++-- app/templates/path-analyzer.html | 18 ++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/app/static/js/path-analyzer.js b/app/static/js/path-analyzer.js index c8f1212..33efe97 100644 --- a/app/static/js/path-analyzer.js +++ b/app/static/js/path-analyzer.js @@ -688,6 +688,18 @@ function paBuildLegend(echo) { return legend; } +// Default echo selection: the routed echo with the fewest hops +// (ties -> first in arrival order) +function paShortestEchoIdx(msg) { + let best = null; + msg.echoView.forEach((echo, idx) => { + if (echo.hops > 0 && (best === null || echo.hops < msg.echoView[best].hops)) { + best = idx; + } + }); + return best; +} + function paRenderMapView() { paInitMap(); paPlotBaseMarkers(); @@ -715,13 +727,21 @@ function paRenderMapView() { const head = document.createElement('div'); head.className = 'd-flex justify-content-between gap-2'; + const left = document.createElement('div'); + left.className = 'd-flex align-items-baseline gap-1'; + left.style.minWidth = '0'; const senderEl = document.createElement('strong'); senderEl.className = 'text-truncate'; senderEl.textContent = msg.sender || '—'; + const chanEl = document.createElement('span'); + chanEl.className = 'pa-map-msg-chan'; + chanEl.textContent = msg.channel_name || `#${msg.channel_idx}`; + left.appendChild(senderEl); + left.appendChild(chanEl); const timeEl = document.createElement('span'); timeEl.className = 'text-muted text-nowrap'; timeEl.textContent = paFormatTime(msg).slice(5, 16); - head.appendChild(senderEl); + head.appendChild(left); head.appendChild(timeEl); entry.appendChild(head); @@ -735,7 +755,9 @@ function paRenderMapView() { } entry.addEventListener('click', () => { - paMapSelection = { msgId: msg.id, echoIdx: null }; + // Re-clicking the selected message keeps the user's echo choice + if (paMapSelection.msgId === msg.id) return; + paMapSelection = { msgId: msg.id, echoIdx: paShortestEchoIdx(msg) }; paRenderMapView(); }); diff --git a/app/templates/path-analyzer.html b/app/templates/path-analyzer.html index 0af29d5..06fde7c 100644 --- a/app/templates/path-analyzer.html +++ b/app/templates/path-analyzer.html @@ -125,11 +125,19 @@ flex-direction: column; } + /* Message/path list gets the remaining space, the map keeps a + fixed ~45% of the viewport height */ #paMapSidebar { width: auto; min-width: 0; - max-height: 40%; - flex-shrink: 1; + max-height: none; + flex: 1 1 0; + min-height: 0; + } + + #paMap { + flex: 0 0 45vh; + min-height: 0; } } @@ -364,6 +372,12 @@ color: #6f42c1; } + .pa-map-msg-chan { + font-size: 0.72rem; + color: var(--text-secondary, #6c757d); + white-space: nowrap; + } + .pa-map-msg-preview { font-size: 0.75rem; color: var(--text-secondary, #6c757d);