fix(pathanalyzer): Messages and Repeaters views fit narrow screens

Rather than a card-per-row transform (too much scrolling for 400+
messages), narrow viewports (<=576px) keep the table but shed
secondary width:
- Hash, HB and Echoes columns hidden; packet hash + HB surface as a
  line at the top of the expanded detail row instead (still copyable)
- time renders as short date/time stacked on two lines (full timestamp
  on wide screens)
- sender/channel/message-preview columns capped with ellipsis, tighter
  cell padding, headers allowed to wrap
- stats view: Messages and As-last-hop columns hidden on phones
  (Repeater, Contact, Relayed, Avg SNR remain), contact names truncated
- expanded echo paths now wrap fully within the viewport, keeping every
  hop chip and the jump-to-map button reachable

Root cause of the first attempt failing: the media block sat mid-
stylesheet, so later equal-specificity base rules overrode it - it now
sits last, with a comment pinning it there.

Verified live via Playwright at 390px: zero horizontal overflow in
Messages (incl. an expanded 20-hop path, map button on-screen) and
Repeaters; desktop 1280px layout unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-07-19 21:53:57 +02:00
parent 0ba0503547
commit 0ad26a66f8
2 changed files with 128 additions and 10 deletions
+26 -5
View File
@@ -263,7 +263,10 @@ function paRenderTable() {
const tdTime = document.createElement('td');
tdTime.className = 'pa-time';
tdTime.textContent = paFormatTime(msg);
const full = paFormatTime(msg);
tdTime.innerHTML = `<span class="pa-time-full"></span><span class="pa-time-short"></span>`;
tdTime.querySelector('.pa-time-full').textContent = full;
tdTime.querySelector('.pa-time-short').textContent = full === '—' ? '—' : full.slice(5, 16);
tr.appendChild(tdTime);
const tdChannel = document.createElement('td');
@@ -283,6 +286,7 @@ function paRenderTable() {
tr.appendChild(tdContent);
const tdHash = document.createElement('td');
tdHash.className = 'pa-col-hash';
if (msg.packet_hash) {
const span = document.createElement('span');
span.className = 'pa-hash';
@@ -304,13 +308,13 @@ function paRenderTable() {
tr.appendChild(tdHops);
const tdHb = document.createElement('td');
tdHb.className = 'text-end';
tdHb.className = 'text-end pa-col-hb';
const hbSizes = [...new Set(msg.echoView.filter(e => e.hops > 0).map(e => e.hash_size || 1))].sort();
tdHb.textContent = hbSizes.length > 0 ? hbSizes.join(',') : '—';
tr.appendChild(tdHb);
const tdEchoes = document.createElement('td');
tdEchoes.className = 'text-end';
tdEchoes.className = 'text-end pa-col-echoes';
tdEchoes.textContent = msg.echoView.length;
tr.appendChild(tdEchoes);
@@ -322,6 +326,23 @@ function paRenderTable() {
const detailTd = document.createElement('td');
detailTd.className = 'pa-echo-cell';
detailTd.colSpan = 9;
if (msg.packet_hash) {
// Narrow screens hide the Hash/HB columns - surface both here
const hashLine = document.createElement('div');
hashLine.className = 'pa-detail-hash';
const hashSpan = document.createElement('span');
hashSpan.className = 'pa-hash';
hashSpan.textContent = msg.packet_hash;
hashSpan.title = 'Click to copy';
hashSpan.addEventListener('click', (e) => {
e.stopPropagation();
paCopyText(msg.packet_hash, 'Packet hash');
});
hashLine.append('Hash: ');
hashLine.appendChild(hashSpan);
if (hbSizes.length > 0) hashLine.append(` | HB: ${hbSizes.join(',')}`);
detailTd.appendChild(hashLine);
}
msg.echoView.forEach((echo, echoIdx) => {
detailTd.appendChild(paBuildEchoLine(msg, echo, echoIdx));
});
@@ -438,9 +459,9 @@ function paRenderStats() {
}
tr.appendChild(tdContact);
for (const val of [s.relayed, s.messages, s.lastHop]) {
for (const [val, cls] of [[s.relayed, ''], [s.messages, ' pa-col-msgs'], [s.lastHop, ' pa-col-lasthop']]) {
const td = document.createElement('td');
td.className = 'text-end';
td.className = 'text-end' + cls;
td.textContent = val;
tr.appendChild(td);
}
+102 -5
View File
@@ -166,6 +166,18 @@
color: #212529;
}
/* Packet hash inside the expanded detail row - narrow screens only,
where the Hash column is hidden */
.pa-detail-hash {
display: none;
font-size: 0.75rem;
padding-bottom: 0.2rem;
}
.pa-time-short {
display: none;
}
.pa-table-wrap {
overflow-x: auto;
}
@@ -324,6 +336,91 @@
display: block;
margin-bottom: 0.75rem;
}
/* Narrow screens: drop secondary columns so rows fit the viewport
and echo paths can wrap (keeps the jump-to-map button reachable).
Hash and HB move into the expanded detail row. Must stay LAST in
this stylesheet so it overrides the base rules above. */
@media (max-width: 576px) {
.pa-col-hash,
.pa-col-hb,
.pa-col-msgs,
.pa-col-lasthop {
display: none;
}
.pa-content-preview {
max-width: 9rem;
}
.pa-time-full {
display: none;
}
.pa-time-short {
display: inline;
}
.pa-detail-hash {
display: block;
}
.pa-echo-cell {
padding-left: 0.75rem;
}
/* Long contact names must not push the SNR column off-screen */
#paStatsBody td:nth-child(2) {
max-width: 8rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pa-col-echoes {
display: none;
}
.pa-table td,
.pa-table th {
padding: 0.3rem 0.2rem;
}
/* Truncate long channel names */
#paTableBody td:nth-child(3) {
max-width: 3.8rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Let long headers (Avg SNR...) wrap instead of widening columns */
.pa-table th {
white-space: normal;
}
/* Truncate long sender names */
#paTableBody td:nth-child(4) {
max-width: 5.5rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pa-content-preview {
max-width: 5.2rem;
}
/* Date and time stack into two lines */
.pa-time {
white-space: normal;
}
.pa-time-short {
display: inline-block;
width: 3.2rem;
}
}
</style>
</head>
<body>
@@ -399,10 +496,10 @@
<th>Channel</th>
<th>Sender</th>
<th>Message</th>
<th>Hash</th>
<th class="pa-col-hash">Hash</th>
<th class="text-end">Hops</th>
<th class="text-end" title="Path hash size in bytes per hop (from routed echoes)">HB</th>
<th class="text-end">Echoes</th>
<th class="text-end pa-col-hb" title="Path hash size in bytes per hop (from routed echoes)">HB</th>
<th class="text-end pa-col-echoes">Echoes</th>
</tr>
</thead>
<tbody id="paTableBody"></tbody>
@@ -416,9 +513,9 @@
<th>Contact</th>
<th class="text-end pa-sortable" data-sort="relayed"
title="Echoes whose path contains this hash (anywhere)">Relayed</th>
<th class="text-end pa-sortable" data-sort="messages"
<th class="text-end pa-sortable pa-col-msgs" data-sort="messages"
title="Distinct messages relayed through this hash">Messages</th>
<th class="text-end pa-sortable" data-sort="lastHop"
<th class="text-end pa-sortable pa-col-lasthop" data-sort="lastHop"
title="Echoes where this hash is the final hop (heard directly by us)">As last hop</th>
<th class="text-end pa-sortable" data-sort="avgSnr"
title="Average echo SNR, counted only when this hash is the final hop - SNR is measured at our receiver for the last hop">Avg SNR (last hop)</th>