mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-04 09:21:18 +00:00
ab34d9fb65
Red commit: `bcfc74de` (CI: https://github.com/Kpa-clawbot/CoreScope/actions?query=branch%3Afix%2Fissue-1206) Fixes #1206. ## Problem On Live Map the VCR (timeline/playback) bar overlays the bottom of the viewport. Bottom-pinned overlays — the live packet feed, the legend, any corner panel — used hard-coded `bottom: 58–88px` offsets that are smaller than the real bar height (two-row mobile layout + `env(safe-area-inset-bottom)` push it to ~80px and beyond). The last N packet-feed rows slid under the bar and became unreadable / unclickable. ## Fix Publish the bar's measured height as a CSS variable on the live page and bind every bottom-anchored overlay to it. - `public/live.js` — new `initVCRHeightTracker()` runs after init; uses `ResizeObserver` + `resize` / `visualViewport.resize` to keep `--vcr-bar-height` on `.live-page` in sync with `#vcrBar`. - `public/live.css` — `.live-feed`, `.feed-show-btn`, and the `.live-overlay[data-position="bl"|"br"]` corner slots now use `bottom: calc(var(--vcr-bar-height, 58px) + 10px)`. The feed's `max-height` is also capped against `100dvh - top - vcr - margin` so its scroll container can never extend past the bar. - Stale per-breakpoint overrides (the `@supports(env(safe-area-inset))` hard-coded `78px + safe-area` for feed/legend) are removed in favor of the single tracked variable. ## TDD - Red commit `bcfc74de` adds `test-issue-1206-vcr-overlap-e2e.js`: asserts `#liveFeed.getBoundingClientRect().bottom <= #vcrBar.top` (and same for the last row) at desktop 1280x800 and mid 720x800. Verified locally that reverting the green commit makes the feed-bottom assertions fail (feed bottom 742px > VCR top 721px) — see PR body for exact numbers from the local run. - Green commit `1ad17e7f` makes all 5 assertions pass. ## Browser verified Local Go server with `test-fixtures/e2e-fixture.db`, headless Chromium via the new E2E test — all 5 assertions green. ## E2E assertion added `test-issue-1206-vcr-overlap-e2e.js:84` (bottom-row vs VCR-top) plus container check at `:74`. --------- Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: clawbot <bot@corescope.local>
163 lines
6.5 KiB
JavaScript
163 lines
6.5 KiB
JavaScript
/**
|
|
* E2E (#1206): VCR controls panel must not occlude the bottom of the
|
|
* Live Map packet list (live feed).
|
|
*
|
|
* Acceptance: the bottom of the last visible packet-list row must be at or
|
|
* above the top of the VCR bar — i.e. lastRow.bottom ≤ vcr.top — when both
|
|
* the feed and VCR are visible.
|
|
*
|
|
* Run: BASE_URL=http://localhost:13581 node test-issue-1206-vcr-overlap-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(' \u2713 ' + name); }
|
|
catch (e) { failed++; console.error(' \u2717 ' + name + ': ' + e.message); }
|
|
}
|
|
function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); }
|
|
|
|
async function gotoLive(page) {
|
|
await page.goto(BASE + '/#/live', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForSelector('#liveMap');
|
|
await page.waitForSelector('#vcrBar');
|
|
await page.waitForSelector('#liveFeed');
|
|
// Let the feed populate from the fixture
|
|
await page.waitForFunction(() => {
|
|
var f = document.querySelector('#liveFeed .panel-content');
|
|
return f && f.children.length > 0;
|
|
}, { timeout: 8000 }).catch(() => {});
|
|
await page.waitForTimeout(300);
|
|
}
|
|
|
|
async function measure(page) {
|
|
return page.evaluate(() => {
|
|
var feed = document.getElementById('liveFeed');
|
|
var vcr = document.getElementById('vcrBar');
|
|
var legend = document.getElementById('liveLegend');
|
|
var content = feed ? feed.querySelector('.panel-content') : null;
|
|
if (!feed || !vcr || !content) return null;
|
|
var rows = content.children;
|
|
var lastRow = rows.length ? rows[rows.length - 1] : null;
|
|
var feedRect = feed.getBoundingClientRect();
|
|
var vcrRect = vcr.getBoundingClientRect();
|
|
var legendRect = legend ? legend.getBoundingClientRect() : null;
|
|
return {
|
|
feedBottom: feedRect.bottom,
|
|
vcrTop: vcrRect.top,
|
|
vcrHeight: vcrRect.height,
|
|
lastRowBottom: lastRow ? lastRow.getBoundingClientRect().bottom : null,
|
|
rowCount: rows.length,
|
|
feedVisible: feedRect.width > 0 && feedRect.height > 0,
|
|
vcrVisible: vcrRect.width > 0 && vcrRect.height > 0,
|
|
legendBottom: legendRect ? legendRect.bottom : null,
|
|
legendVisible: legendRect ? (legendRect.width > 0 && legendRect.height > 0) : false,
|
|
};
|
|
});
|
|
}
|
|
|
|
// Force the VCR bar to a tall height to simulate real-world conditions
|
|
// (mobile two-row layout, safe-area-inset). Bottom-pinned overlays MUST
|
|
// track --vcr-bar-height (set by ResizeObserver on .vcr-bar) — anything
|
|
// using a hard-coded offset will overlap once the bar grows.
|
|
async function inflateVCR(page, heightPx) {
|
|
await page.evaluate((h) => {
|
|
var bar = document.getElementById('vcrBar');
|
|
if (bar) {
|
|
bar.style.minHeight = h + 'px';
|
|
bar.style.height = h + 'px';
|
|
}
|
|
}, heightPx);
|
|
// Allow ResizeObserver + frame
|
|
await page.waitForTimeout(120);
|
|
}
|
|
|
|
(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=== #1206 VCR overlap E2E against ' + BASE + ' ===');
|
|
|
|
// Desktop viewport — feed is bl-pinned by default, VCR is full-width bottom.
|
|
{
|
|
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
const page = await ctx.newPage();
|
|
page.setDefaultTimeout(8000);
|
|
page.on('pageerror', (e) => console.error('[pageerror]', e.message));
|
|
await step('[1280x800] navigate to /live, feed + VCR present', async () => {
|
|
await gotoLive(page);
|
|
const m = await measure(page);
|
|
assert(m, 'feed/vcr/content not found');
|
|
assert(m.feedVisible, 'live feed must be visible');
|
|
assert(m.vcrVisible, 'vcr bar must be visible');
|
|
assert(m.vcrHeight >= 20, 'vcr should have a real height, got ' + m.vcrHeight);
|
|
});
|
|
|
|
await step('[1280x800] live feed container bottom <= VCR top (no overlap)', async () => {
|
|
const m = await measure(page);
|
|
assert(m.feedBottom <= m.vcrTop + 0.5,
|
|
'feed bottom (' + m.feedBottom + ') must be <= vcr top (' + m.vcrTop + ')');
|
|
});
|
|
|
|
await step('[1280x800] last packet row bottom <= VCR top (no row occluded)', async () => {
|
|
const m = await measure(page);
|
|
if (m.lastRowBottom == null) {
|
|
// No rows in fixture — fall back to container check, already covered.
|
|
return;
|
|
}
|
|
assert(m.lastRowBottom <= m.vcrTop + 0.5,
|
|
'last packet row bottom (' + m.lastRowBottom + ') must be <= vcr top (' + m.vcrTop + ')');
|
|
});
|
|
|
|
await step('[1280x800] legend bottom <= VCR top (no overlap, default height)', async () => {
|
|
const m = await measure(page);
|
|
if (!m.legendVisible) return; // legend hidden = no overlap to test
|
|
assert(m.legendBottom <= m.vcrTop + 0.5,
|
|
'legend bottom (' + m.legendBottom + ') must be <= vcr top (' + m.vcrTop + ')');
|
|
});
|
|
|
|
await step('[1280x800] inflate VCR to 120px — feed AND legend still clear bar', async () => {
|
|
await inflateVCR(page, 120);
|
|
const m = await measure(page);
|
|
assert(m.vcrHeight >= 100, 'inflate failed, vcr height = ' + m.vcrHeight);
|
|
assert(m.feedBottom <= m.vcrTop + 0.5,
|
|
'feed bottom (' + m.feedBottom + ') must be <= vcr top (' + m.vcrTop + ') after inflate');
|
|
if (m.legendVisible) {
|
|
assert(m.legendBottom <= m.vcrTop + 0.5,
|
|
'legend bottom (' + m.legendBottom + ') must be <= vcr top (' + m.vcrTop + ') after inflate — ' +
|
|
'legend is not tracking --vcr-bar-height');
|
|
}
|
|
});
|
|
|
|
await ctx.close();
|
|
}
|
|
|
|
// Mobile-medium viewport (768) — feed still rendered; same invariant must hold.
|
|
{
|
|
const ctx = await browser.newContext({ viewport: { width: 720, height: 800 } });
|
|
const page = await ctx.newPage();
|
|
page.setDefaultTimeout(8000);
|
|
page.on('pageerror', (e) => console.error('[pageerror]', e.message));
|
|
await step('[720x800] navigate to /live', async () => { await gotoLive(page); });
|
|
|
|
await step('[720x800] feed bottom <= VCR top', async () => {
|
|
const m = await measure(page);
|
|
if (!m || !m.feedVisible) return; // feed hidden at this width = no overlap to test
|
|
assert(m.feedBottom <= m.vcrTop + 0.5,
|
|
'feed bottom (' + m.feedBottom + ') must be <= vcr top (' + m.vcrTop + ')');
|
|
});
|
|
|
|
await ctx.close();
|
|
}
|
|
|
|
await browser.close();
|
|
console.log('\n#1206 VCR overlap: ' + passed + ' passed, ' + failed + ' failed');
|
|
process.exit(failed ? 1 : 0);
|
|
})().catch((e) => { console.error(e); process.exit(1); });
|