mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-04 05:21:18 +00:00
b2d654bf61
## Closes #1415 — packets cross-viewport jank ## Closes #1458 — Tufte mobile-packets P0 findings (folded into same branch) Single PR covers both issues — they touch the same files (`public/packets.js`, `public/style.css`) and a split would invite merge thrash. ### #1415 — column priority + chrome compaction Locked column-priority tiers (operator spec): | Tier | Viewport | Columns | |---|---|---| | 1 | always (mobile through desktop) | expand · time · type · details | | 2 | tablet+ (>768px) | path | | 3 | desktop only (>1024px) | hash · observer · rpt | Enforced via existing `data-priority` system in `TableResponsive.apply` (priorities 3 → hide ≤1024, 5 → hide ≤768). CSS: - `.col-expand` pinned to `width/min-width/max-width: 32px` at every viewport — kills the 50–180px dead column that pushed every data column right. - `.col-details` capped at `max-width: 480px` so wide viewports stop wasting hundreds of px on the last column. - `@media (max-width: 480px)` hides page-header BYOP, shrinks the h2, and tightens row padding → pre-table chrome drops from ~280px to ~140px. ### #1458 — Tufte mobile P0 findings **P0-A: semantic-first detail panel.** Was: `"Packet Byte Breakdown (134 bytes)"` title + giant neon hex grid above the meaningful fields. Now: type badge + decoded summary + hop count + `src → dst` lead the panel, followed by the existing `.detail-meta` dl (reordered: Payload Type → Path → Timestamp → Observer). **P0-B: raw-bytes disclosure.** Hex legend / hex dump / field table wrapped in `<details class="detail-technical">`. Disclosure copy reads "Show raw bytes". Collapsed by default on phones (`window.innerWidth ≤ 480`), expanded on tablet+. **P0-C: mobile filter-zone collapse.** The always-on filter-expression input above `.filter-bar` is now wrapped with `.pkt-filter-expr` and hidden under the `@media (max-width: 480px)` block. Reveals when the existing "Filters ▾" toggle adds `.filters-expanded` to the sibling `.filter-bar` (CSS `:has()` selector — one tap reveals both chrome rows together). ### TDD `test-issue-1415-packets-layout.js` — pure source-grep, no browser: - col-expand class on first `<th>` + `<td>` + CSS 32px pin - locked column-priority tier values per column - `.col-details` max-width ≤ 480px - mobile @media block: hides BYOP, hides `.pkt-filter-expr` (revealed by `.filters-expanded`) - detail-meta order: Payload Type before Observer - `<details class="detail-technical">` wrapper exists with "Show raw bytes" summary - detail-title leads with a type badge; `.detail-srcdst` emitted - old "Packet Byte Breakdown (N bytes)" title literal removed Red commit `d4372d82` (8 assertion failures, no compile errors), green commit `4fab9dbd` (#1415 work), follow-up commit `a5218035` (#1458 work) keeps everything green. 26 assertions, 0 failed. --------- Co-authored-by: openclaw-bot <bot@openclaw>
170 lines
8.3 KiB
JavaScript
170 lines
8.3 KiB
JavaScript
/**
|
|
* #1415 — Packets cross-viewport jank source-grep test.
|
|
*
|
|
* Asserts the four code-level invariants required by the layout fix:
|
|
*
|
|
* 1. Expand-chevron column is pinned narrow at every viewport via an
|
|
* explicit `.col-expand` class on the first <th>/<td> AND a CSS rule
|
|
* pinning its width to ~32px (max-width ≤ 36px).
|
|
* 2. DETAILS column is capped — `.col-details` has a `max-width` ≤ 480px
|
|
* so wide viewports stop wasting hundreds of px on the last column.
|
|
* 3. Mobile chrome compaction — the `@media (max-width: 480px)` block
|
|
* hides `.col-details` (so the table doesn't carry the dead column to
|
|
* mobile) AND hides the BYOP button in `.page-header` (operator
|
|
* request: reclaim 60+ px of pre-table chrome).
|
|
* 4. Mobile-priority detail order — `renderDetail()` renders the Payload
|
|
* Type as the FIRST `<dt>` of `.detail-meta` (operator's "lead with
|
|
* packet type"), and wraps the byte-breakdown / hex-dump / field-table
|
|
* into a `<details class="detail-technical">` element so the
|
|
* technical fields collapse on mobile (collapsed by default, open on
|
|
* desktop via the `open` attribute being conditionally set).
|
|
*
|
|
* Strategy: pure source-grep — no browser, no playwright. The grep is the
|
|
* gate. If someone reverts any of the four fixes, the corresponding assert
|
|
* fails. Cheap to run, deterministic, runs in CI without browser deps.
|
|
*/
|
|
'use strict';
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
let passed = 0, failed = 0;
|
|
function assert(cond, msg) {
|
|
if (cond) { passed++; console.log(' \u2705 ' + msg); }
|
|
else { failed++; console.error(' \u274c ' + msg); }
|
|
}
|
|
|
|
const pktJs = fs.readFileSync(path.join(__dirname, 'public/packets.js'), 'utf8');
|
|
const css = fs.readFileSync(path.join(__dirname, 'public/style.css'), 'utf8');
|
|
|
|
// ── 1. col-expand class + CSS pin ────────────────────────────────────────
|
|
assert(
|
|
/<th[^>]*class="col-expand"/.test(pktJs),
|
|
'packets.js header has <th class="col-expand"> on the first column'
|
|
);
|
|
assert(
|
|
/<td class="col-expand"/.test(pktJs),
|
|
'packets.js row builders emit <td class="col-expand"> for the chevron cell'
|
|
);
|
|
|
|
// CSS must pin width somewhere in the .col-expand selector.
|
|
var colExpandBlocks = css.match(/\.col-expand\b[^{}]*\{[^}]*\}/g) || [];
|
|
var pinned = colExpandBlocks.some(function (b) {
|
|
return /max-width:\s*3[26]px/.test(b) && /min-width:\s*3[26]px/.test(b);
|
|
});
|
|
assert(pinned, 'style.css .col-expand pins min-width AND max-width to ~32px');
|
|
|
|
// ── 1b. Locked column-priority tiers (operator spec) ─────────────────────
|
|
// Tier 1 (always — even on smallest mobile): expand, time, type, details
|
|
// Tier 2 (tablet+): path
|
|
// Tier 3 (desktop only): hash, observer, rpt
|
|
// Region/Size/HB stay at the existing low-priority tiers (already 3-5).
|
|
//
|
|
// Mapping to priority values (see TableResponsive doc at top of packets.js):
|
|
// priority 1 → always visible
|
|
// priority 3 → hidden ≤ 1024 (desktop-only)
|
|
// priority 5 → hidden ≤ 768 (tablet+ only)
|
|
function colPriority(klass) {
|
|
var re = new RegExp('<th[^>]*class="' + klass + '"[^>]*data-priority="(\\d+)"');
|
|
var m = pktJs.match(re);
|
|
return m ? parseInt(m[1], 10) : null;
|
|
}
|
|
assert(colPriority('col-expand') === 1, 'col-expand is tier-1 priority (always visible)');
|
|
assert(colPriority('col-time') === 1, 'col-time is tier-1 priority (always visible)');
|
|
assert(colPriority('col-type') === 1, 'col-type is tier-1 priority (always visible)');
|
|
assert(colPriority('col-details') === 1, 'col-details is tier-1 priority (always visible)');
|
|
assert(colPriority('col-path') === 5, 'col-path is tier-2 (hidden ≤768, tablet+ only)');
|
|
assert(colPriority('col-hash') === 3, 'col-hash is tier-3 (desktop only, hidden ≤1024)');
|
|
assert(colPriority('col-observer') === 3, 'col-observer is tier-3 (desktop only, hidden ≤1024)');
|
|
assert(colPriority('col-rpt') === 3, 'col-rpt is tier-3 (desktop only, hidden ≤1024)');
|
|
|
|
// ── 2. DETAILS column capped ─────────────────────────────────────────────
|
|
var colDetailsBlocks = css.match(/\.col-details\b[^{}]*\{[^}]*\}/g) || [];
|
|
var capped = colDetailsBlocks.some(function (b) {
|
|
var m = b.match(/max-width:\s*(\d+)px/);
|
|
return m && parseInt(m[1], 10) <= 480 && parseInt(m[1], 10) >= 200;
|
|
});
|
|
assert(capped, 'style.css caps .col-details with max-width ≤ 480px');
|
|
|
|
// ── 3. Mobile compaction — DETAILS hidden + BYOP hidden under 480 ────────
|
|
var mobileBlock = (function () {
|
|
var idx = css.indexOf('@media (max-width: 480px)');
|
|
if (idx < 0) return '';
|
|
var depth = 0, start = -1, end = -1;
|
|
for (var i = idx; i < css.length; i++) {
|
|
var c = css[i];
|
|
if (c === '{') { if (depth === 0) start = i; depth++; }
|
|
else if (c === '}') { depth--; if (depth === 0) { end = i; break; } }
|
|
}
|
|
return start > 0 && end > 0 ? css.slice(start, end + 1) : '';
|
|
})();
|
|
assert(mobileBlock.length > 0, 'style.css has a @media (max-width: 480px) block');
|
|
assert(
|
|
/pkt-byop[^{}]*\{[^}]*display:\s*none/.test(mobileBlock),
|
|
'mobile @media block hides the BYOP button (chrome compaction)'
|
|
);
|
|
// Note: per LOCKED spec, col-details is tier-1 and stays visible at mobile.
|
|
// It is the col-path / col-hash / col-observer / col-rpt that drop on mobile,
|
|
// already enforced via data-priority above (TableResponsive.apply).
|
|
|
|
// ── 4. renderDetail mobile-priority ordering ────────────────────────────
|
|
var dlMatch = pktJs.match(/<dl class="detail-meta">([\s\S]*?)<\/dl>/);
|
|
assert(!!dlMatch, 'renderDetail emits <dl class="detail-meta">');
|
|
if (dlMatch) {
|
|
var dlBody = dlMatch[1];
|
|
var idxType = dlBody.indexOf('Payload Type');
|
|
var idxObs = dlBody.indexOf('Observer');
|
|
assert(idxType >= 0, '.detail-meta still includes Payload Type row');
|
|
assert(idxObs >= 0, '.detail-meta still includes Observer row');
|
|
assert(
|
|
idxType >= 0 && idxObs >= 0 && idxType < idxObs,
|
|
'.detail-meta lists Payload Type BEFORE Observer (mobile-priority order)'
|
|
);
|
|
}
|
|
|
|
// Wrap hex / breakdown / observations in a collapsible technical section.
|
|
assert(
|
|
/<details[^>]*class="detail-technical"/.test(pktJs),
|
|
'renderDetail wraps technical fields in <details class="detail-technical">'
|
|
);
|
|
|
|
// ── 5. #1458 P0-A — semantic-first detail title ─────────────────────────
|
|
// Previously the title hard-coded "Packet Byte Breakdown (N bytes)" when
|
|
// raw_hex was present. Must be replaced by a type-badge + summary header.
|
|
assert(
|
|
!/Packet Byte Breakdown/.test(pktJs),
|
|
'renderDetail no longer leads with "Packet Byte Breakdown (N bytes)" title'
|
|
);
|
|
assert(
|
|
/<div class="detail-title">[\s\S]{0,200}badge badge-\$\{payloadTypeColor/.test(pktJs),
|
|
'detail-title leads with a type badge (semantic identity first)'
|
|
);
|
|
assert(
|
|
/<div class="detail-srcdst">/.test(pktJs),
|
|
'renderDetail emits a .detail-srcdst row (src → dst summary)'
|
|
);
|
|
|
|
// ── 6. #1458 P0-B — raw-bytes disclosure copy ───────────────────────────
|
|
assert(
|
|
/<summary>Show raw bytes<\/summary>/.test(pktJs),
|
|
'detail-technical disclosure summary reads "Show raw bytes" (per spec)'
|
|
);
|
|
|
|
// ── 7. #1458 P0-C — mobile filter-zone collapse ─────────────────────────
|
|
assert(
|
|
/pkt-filter-expr/.test(pktJs),
|
|
'always-on filter input wrapper carries class .pkt-filter-expr'
|
|
);
|
|
assert(
|
|
/\.pkt-filter-expr[^{}]*\{[^}]*display:\s*none/.test(mobileBlock),
|
|
'mobile @media (max-width: 480px) hides .pkt-filter-expr by default'
|
|
);
|
|
assert(
|
|
/\.filter-bar\.filters-expanded[^{}]*\.pkt-filter-expr[^{}]*\{[^}]*display:/.test(mobileBlock) ||
|
|
/:has\(\.filter-bar\.filters-expanded\)[^{}]*\.pkt-filter-expr[^{}]*\{[^}]*display:/.test(mobileBlock),
|
|
'expanded filters reveal .pkt-filter-expr on mobile (Filters ▾ toggle)'
|
|
);
|
|
|
|
// ── Summary ──────────────────────────────────────────────────────────────
|
|
console.log('\n' + passed + ' passed, ' + failed + ' failed');
|
|
process.exit(failed === 0 ? 0 : 1);
|