mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-05 05:01:37 +00:00
16c48e73b3
Red commit: 61fcc8c19b96543f1b4bbd6fd2ce54e6265d5e38 (CI run: pending — see Checks tab on this PR) Fixes #1178 Fixes #1179 ## Summary Live page layout polish — both issues touch `public/live.css` + a small `public/live.js` slice, so they ship as one PR per AGENTS rule 34. ### #1178 — Header compactness + narrow-viewport collapse - `.live-header` total height ≤ 40px at desktop widths (smaller padding, gap, title font, and pill sizing; `max-height: 40px` as a belt-and-suspenders gate). - Body wrapped in `.live-header-body` so it can collapse cleanly. - New 32×32 toggle button `[data-live-header-toggle]`, hidden at wide viewports, visible at `≤768px`. ### #1179 — Controls pinned bottom-right + narrow-viewport collapse - New `.live-controls` cluster around the toggles list and audio controls, `position: fixed; right: 12px;` and `bottom: calc(78px + var(--bottom-nav-height, 56px) + env(safe-area-inset-bottom, 0px))`. - That bottom calc reserves space for the VCR bar **and** the bottom nav (#1061, currently in PR #1174). When the bottom-nav exposes `--bottom-nav-height` the cluster tracks it; otherwise the 56px fallback keeps it clear regardless of merge order. - `z-index: 1000` keeps it above map markers but below modals. - New 32×32 toggle button `[data-live-controls-toggle]`, hidden at wide viewports, visible at `≤768px`. ### Breakpoint + selectors - Narrow = `max-width: 768px` (matches #1061 bottom-nav activation). - Stable selectors for E2E: `[data-live-header-toggle]`, `[data-live-header-body]`, `[data-live-controls-toggle]`, `[data-live-controls-body]`. No DOM-order dependence. ### Bottom-nav coexistence The expanded narrow-viewport controls panel uses `max-height: 50vh; overflow-y: auto` on its toggles list, and the cluster's `bottom` reservation guarantees the panel's bottom edge sits above the (possibly absent) bottom-nav region. The E2E test asserts exactly this with `expandedRect.bottom + 8 < innerHeight − navH`, defaulting `navH` to 56 if `.bottom-nav` is not in the DOM yet. ### Theming All new colors via existing CSS tokens (`--surface-1`, `--text`, `--text-muted`, `--border`, `--accent`). check-css-vars passes. ### TDD - Red commit: `61fcc8c` — assertions only (no impl), wired into `.github/workflows/deploy.yml` Playwright matrix. - Green commit: `7d591be` — DOM split + CSS + collapse JS. - E2E assertion added: `test-live-layout-1178-1179-e2e.js:55` (desktop header height) through `:170` (narrow controls bottom-nav coexistence). ### Local verification ``` ./corescope-server -port 13581 -db test-fixtures/e2e-fixture.db & CHROMIUM_PATH=/usr/bin/chromium BASE_URL=http://localhost:13581 \ node test-live-layout-1178-1179-e2e.js # → 8/8 passed ``` --------- Co-authored-by: meshcore-bot <bot@meshcore.local> Co-authored-by: openclaw-bot <bot@openclaw.local>
79 lines
3.1 KiB
JavaScript
79 lines
3.1 KiB
JavaScript
/**
|
|
* E2E regression for #1180 review must-fix:
|
|
* MediaQueryList 'change' listener leak in wireLiveCollapseToggles().
|
|
*
|
|
* SPA navigates to /#/live, then bounces /#/explore ↔ /#/live N times.
|
|
* Each /#/live mount re-runs the wiring IIFE; without a guard, every
|
|
* mount calls narrowMql.addEventListener('change', applyForViewport)
|
|
* against a process-global MediaQueryList instance, so listeners
|
|
* accumulate without bound.
|
|
*
|
|
* live.js exposes a debug seam: window.__liveMQLBindCount is incremented
|
|
* exactly when the MQL listener is registered. After 5 round-trips it
|
|
* MUST be ≤ 1.
|
|
*
|
|
* Run: BASE_URL=http://localhost:13581 node test-live-mql-leak-1180-e2e.js
|
|
*/
|
|
'use strict';
|
|
const { chromium } = require('playwright');
|
|
|
|
const BASE = process.env.BASE_URL || 'http://localhost:13581';
|
|
|
|
let passed = 0, failed = 0;
|
|
async function step(name, fn) {
|
|
try { await fn(); passed++; console.log(' ✓ ' + name); }
|
|
catch (e) { failed++; console.error(' ✗ ' + name + ': ' + e.message); }
|
|
}
|
|
function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); }
|
|
|
|
async function gotoHash(page, hash) {
|
|
await page.evaluate((h) => { window.location.hash = h; }, hash);
|
|
// Allow router to run
|
|
await page.waitForTimeout(150);
|
|
}
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({
|
|
headless: true,
|
|
executablePath: process.env.CHROMIUM_PATH || undefined,
|
|
args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
|
|
});
|
|
|
|
console.log(`\n=== #1180 MQL listener leak E2E against ${BASE} ===`);
|
|
|
|
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
|
const page = await ctx.newPage();
|
|
page.setDefaultTimeout(8000);
|
|
page.on('pageerror', (e) => console.error('[pageerror]', e.message));
|
|
|
|
await step('initial /#/live load registers MQL listener at most once', async () => {
|
|
await page.goto(BASE + '/#/live', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForSelector('#liveHeader, .live-header', { timeout: 8000 });
|
|
await page.waitForTimeout(300);
|
|
const count = await page.evaluate(() => window.__liveMQLBindCount);
|
|
assert(typeof count === 'number',
|
|
'window.__liveMQLBindCount missing — debug seam not exposed by live.js');
|
|
assert(count <= 1, `expected MQL bind count ≤ 1 after first mount, got ${count}`);
|
|
});
|
|
|
|
await step('5 SPA round-trips do NOT accumulate MQL listeners', async () => {
|
|
for (let i = 0; i < 5; i++) {
|
|
await gotoHash(page, '#/packets');
|
|
await page.waitForTimeout(80);
|
|
await gotoHash(page, '#/live');
|
|
await page.waitForSelector('#liveHeader, .live-header', { timeout: 8000 });
|
|
await page.waitForTimeout(120);
|
|
}
|
|
const count = await page.evaluate(() => window.__liveMQLBindCount);
|
|
assert(typeof count === 'number',
|
|
'window.__liveMQLBindCount missing after navigations');
|
|
assert(count <= 1,
|
|
`MQL listener leak: bind count after 5 round-trips = ${count}, expected ≤ 1`);
|
|
});
|
|
|
|
await ctx.close();
|
|
await browser.close();
|
|
console.log(`\n=== Results: passed ${passed} failed ${failed} ===`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
})().catch(e => { console.error(e); process.exit(1); });
|