fix(#1833): pin legend toggle button above VCR bar on Live view (#1834)

## Summary

Fixes #1833 — the `.legend-toggle-btn` (palette icon) on the Live view
was hardcoded to `bottom: 1rem`, so on typical desktop viewports it sat
underneath the VCR playback bar and was unreachable. The reporter had to
hide the legend via `localStorage` to work around it.

## Fix

`public/live.css:1313` — one-character-class change:

```diff
 .legend-toggle-btn {
   position: fixed;
-  bottom: 1rem;
+  bottom: calc(var(--vcr-bar-height, 58px) + 10px);
   right: 1rem;
```

Mirrors the existing pattern already used by every other bottom-pinned
Live overlay:

- `.live-feed` (live.css:1204)
- `.live-overlay[data-position="br"]` (live.css:1372)
- `.feed-show-btn` (live.css:903)

`--vcr-bar-height` is maintained by the ResizeObserver on `.vcr-bar`, so
the button now tracks bar growth (mobile two-row layout,
safe-area-inset) instead of overlapping.

Same class of regression as #685 / #1206 / #1107 — an overlay that was
missed in the prior sweep.

## TDD

- Red commit `f6a938b7`: `test-issue-1833-legend-toggle-vcr-offset.js`
asserts `.legend-toggle-btn`'s `bottom` declaration references
`var(--vcr-bar-height`. Fails on assertion (not build error) against the
hardcoded `1rem`.
- Green commit `e399092b`: the CSS one-liner above. All 3 assertions
pass.

Grep-based CSS assertion per AGENTS.md § "E2E-DOM-grep exemption" —
there is no existing Playwright test in this area that measures the
toggle-button rect against `--vcr-bar-height`, and standing one up would
be disproportionate for a single-property fix that mirrors three
existing, tested overlay patterns.

## Preflight

`bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master`
— all hard gates pass, no warnings.

## Browser verified

Not required per fix-issue skill (CSS-only, mirrors three existing
tested patterns). Staging will pick up the change on merge; visual
regression will be caught if the mirrored pattern breaks (grep test in
this PR + existing E2E tests on the sister overlays).

---------

Co-authored-by: corescope-bot <bot@corescope.local>
Co-authored-by: openclaw-bot <bot@openclaw.local>
This commit is contained in:
Kpa-clawbot
2026-07-08 18:14:01 -07:00
committed by GitHub
co-authored by corescope-bot openclaw-bot
parent 56fe844871
commit 4f7bb245d4
3 changed files with 81 additions and 3 deletions
+1 -1
View File
@@ -1310,7 +1310,7 @@ input.live-node-filter-input:focus {
* inside the map container. */
.legend-toggle-btn {
position: fixed;
bottom: 1rem;
bottom: calc(var(--vcr-bar-height, 58px) + 10px);
right: 1rem;
z-index: 500;
background: color-mix(in srgb, var(--surface-1) 92%, transparent);
+2 -2
View File
@@ -90,8 +90,8 @@ if (legendBtn) {
'.legend-toggle-btn uses position: fixed (pinned to viewport)'
);
assert(
/bottom\s*:\s*1rem/.test(legendBtn),
'.legend-toggle-btn pinned at bottom: 1rem'
/bottom\s*:\s*(1rem|max\(1rem|calc\(\s*var\(--vcr-bar-height)/.test(legendBtn),
'.legend-toggle-btn pinned at bottom: 1rem / max(1rem,...) / calc(var(--vcr-bar-height,...)+...) (VCR-bar-aware, #1833)'
);
assert(
/right\s*:\s*1rem/.test(legendBtn),
@@ -0,0 +1,78 @@
/**
* Regression (#1833): the .legend-toggle-btn on the Live view was hardcoded
* to `bottom: 1rem`, so it sat underneath the VCR playback bar and was
* unreachable. Sister overlays (.live-feed, .live-overlay[data-position="br"],
* .feed-show-btn) all offset their bottom by `var(--vcr-bar-height, ...)`.
*
* Same class of regression as #685 / #1206 / #1107. Test asserts the CSS rule
* uses --vcr-bar-height so bottom-pinned Live overlays stay pinned above the
* VCR bar. Grep-based per AGENTS.md E2E-DOM-grep exemption.
*
* Run: node test-issue-1833-legend-toggle-vcr-offset.js
*/
'use strict';
const fs = require('fs');
const path = require('path');
const cssPath = path.join(__dirname, 'public', 'live.css');
const css = fs.readFileSync(cssPath, 'utf8');
let passed = 0, failed = 0;
function step(name, fn) {
try { 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'); }
// Extract a rule body whose selector list is EXACTLY the given selector
// (avoids matching compound rules like `.live-feed .panel-content`).
function extractRule(source, selector) {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp('(?:^|})\\s*' + escaped + '\\s*\\{', 'm');
const m = re.exec(source);
assert(m, 'selector not found (exact match): ' + selector);
const open = source.indexOf('{', m.index);
const close = source.indexOf('}', open);
assert(open !== -1 && close !== -1, 'malformed rule for: ' + selector);
return source.slice(open + 1, close);
}
console.log('#1833: .legend-toggle-btn must offset for --vcr-bar-height');
step('.legend-toggle-btn rule exists', () => {
assert(/\.legend-toggle-btn\s*\{/.test(css), '.legend-toggle-btn rule missing from live.css');
});
step('.legend-toggle-btn bottom uses var(--vcr-bar-height)', () => {
// There may be several `.legend-toggle-btn { ... }` blocks (media queries,
// fullscreen). Only the one that declares `bottom` matters here.
const re = /\.legend-toggle-btn\s*\{([^}]*)\}/g;
let m, bottomValue = null;
while ((m = re.exec(css))) {
const bodyMatch = m[1].match(/(?:^|;|\s)bottom\s*:\s*([^;]+);/);
if (bodyMatch) { bottomValue = bodyMatch[1].trim(); break; }
}
assert(bottomValue !== null, 'no .legend-toggle-btn rule declares `bottom`');
assert(
/var\(\s*--vcr-bar-height/.test(bottomValue),
'.legend-toggle-btn bottom does not reference --vcr-bar-height (got: ' + bottomValue + ')'
);
});
step('sister overlays still track --vcr-bar-height (guard against regression sweep)', () => {
// Sanity check: the pattern we mirror is the same one already used by
// .live-feed, .live-overlay[data-position="br"], and .feed-show-btn.
// These selectors each have at least one rule whose body sets
// `bottom: calc(var(--vcr-bar-height, ...) ...)`.
const patterns = [
/\.live-feed\s*\{[^}]*var\(\s*--vcr-bar-height/,
/\.live-overlay\[data-position="br"\][^{]*\{[^}]*var\(\s*--vcr-bar-height/,
/\.feed-show-btn\s*\{[^}]*var\(\s*--vcr-bar-height/,
];
for (const re of patterns) {
assert(re.test(css), 'sister overlay pattern missing: ' + re.source);
}
});
console.log('\nResults: ' + passed + ' passed, ' + failed + ' failed');
if (failed > 0) process.exit(1);