mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-05 06:22:10 +00:00
b5a1642024
## Summary
Custom navbar logos via `branding.logoUrl` were rendered squished. The
CSS rule `.brand-logo { width: 125px }` was pinned to the default
inline-SVG wordmark's viewBox aspect (~3.08:1), and when customize-v2
swapped the inline `<svg>` for an `<img>`, that `<img>` inherited the
same fixed 125px width — stretching every non-3.08:1 image into a pill.
## Root cause
- `public/style.css:520` — `.brand-logo { width: 125px }` applied
regardless of element type.
- `public/customize-v2.js:75-77` — `_setBrandLogoUrl` additionally
hardcoded `width="125" height="36"` attributes on the created `<img>`,
overriding any CSS aspect rescue.
- Mobile media query (`style.css:1729`) had the same issue with `width:
112px`.
## Fix
Split the CSS rule by element type:
- `svg.brand-logo` — keeps 125×36 pin for the default wordmark (no
regression).
- `img.brand-logo` — `width: auto`, `max-width: 200px`, `object-fit:
contain` so the operator image's natural aspect is preserved with a sane
cap so very-wide logos can't blow nav layout.
- Mobile `@media` mirrors the split (svg 112×32 pinned, img auto width
with 180px cap).
- Drop the hardcoded `width=125`/`height=36` attrs from the `<img>`
created in `customize-v2 _setBrandLogoUrl`.
## TDD
Red commit `a20b7d7`: 4 assertions, all fail on master.
Green commit `533f464`: same 4 assertions, all pass.
```
✓ img.brand-logo CSS rule exists and uses width:auto (not pinned)
✓ svg.brand-logo CSS rule still pins width:125px (no default regression)
✓ mobile media-query splits the .brand-logo rule into svg/img variants
✓ customize-v2 _setBrandLogoUrl does NOT hardcode width/height attrs on the IMG
```
## Verification plan post-merge
Hot-deploy to staging and CDP-verify:
1. Default SVG wordmark still renders at 125×36 (no default regression).
2. Square 100×100 data-URI logo renders as ~36×36 (was 125×36 pill).
3. Tall 100×300 data-URI logo renders as ~12×36 (was 125×36 pill).
Closes #1450
---------
Co-authored-by: openclaw-bot <bot@openclaw.local>
123 lines
5.3 KiB
JavaScript
123 lines
5.3 KiB
JavaScript
/**
|
|
* #1450 — Custom navbar logo aspect ratio preservation.
|
|
*
|
|
* The default inline-SVG wordmark has a fixed 125x36 box matching its
|
|
* viewBox; pinning it via `.brand-logo { width: 125px }` was correct for
|
|
* the SVG. When the operator sets `branding.logoUrl`, customize-v2 swaps
|
|
* the inline <svg> for an <img class="brand-logo"> — that <img> then
|
|
* inherits the same `width: 125px` (plus hardcoded width/height attrs),
|
|
* stretching every non-3.08:1 image into a pill shape.
|
|
*
|
|
* Fix: split the CSS rule so `svg.brand-logo` keeps the 125px pin, but
|
|
* `img.brand-logo` uses `width: auto` (with a `max-width` cap) so the
|
|
* natural aspect ratio of operator-provided images is preserved. Drop
|
|
* the hardcoded width/height attrs from the IMG element created in
|
|
* customize-v2 _setBrandLogoUrl.
|
|
*
|
|
* Pure-Node, no browser. Parses style.css + greps customize-v2.js.
|
|
*/
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = __dirname;
|
|
const CSS = fs.readFileSync(path.join(ROOT, 'public/style.css'), 'utf8');
|
|
const CUSTOMIZE = fs.readFileSync(path.join(ROOT, 'public/customize-v2.js'), 'utf8');
|
|
|
|
let passed = 0, failed = 0;
|
|
function test(name, fn) {
|
|
try { 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'); }
|
|
|
|
// Extract bodies of ALL CSS rules whose selector matches `sel` (literal)
|
|
// at brace-depth 0 (i.e. NOT inside an @media or other at-rule). Returns
|
|
// an array of declaration-body strings.
|
|
function findTopLevelRules(css, sel) {
|
|
const out = [];
|
|
let depth = 0;
|
|
let i = 0;
|
|
while (i < css.length) {
|
|
const ch = css[i];
|
|
if (ch === '{') { depth++; i++; continue; }
|
|
if (ch === '}') { depth--; i++; continue; }
|
|
if (depth === 0 && ch === sel[0]) {
|
|
// Try to match selector at this position
|
|
if (css.startsWith(sel, i)) {
|
|
// Make sure prev char is whitespace/newline/, /} to avoid mid-token matches
|
|
const prev = i > 0 ? css[i - 1] : '\n';
|
|
if (/[\s},]/.test(prev)) {
|
|
// Find next `{`
|
|
const open = css.indexOf('{', i + sel.length);
|
|
if (open > 0) {
|
|
const close = css.indexOf('}', open);
|
|
if (close > open) {
|
|
out.push(css.slice(open + 1, close));
|
|
i = close + 1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function findTopLevelRule(css, sel) {
|
|
const all = findTopLevelRules(css, sel);
|
|
return all.length ? all[0] : null;
|
|
}
|
|
|
|
console.log('── #1450 logo aspect ratio ──');
|
|
|
|
test('img.brand-logo CSS rule exists and uses width:auto (not pinned)', () => {
|
|
const body = findTopLevelRule(CSS, 'img.brand-logo');
|
|
assert(body, 'expected a top-level `img.brand-logo` CSS rule');
|
|
assert(/width\s*:\s*auto/i.test(body),
|
|
'expected `width: auto` in img.brand-logo (got: ' + body.trim().replace(/\s+/g, ' ') + ')');
|
|
assert(/max-width\s*:\s*\d+px/i.test(body),
|
|
'expected a `max-width: <N>px` cap on img.brand-logo to prevent very-wide logos blowing nav layout');
|
|
assert(/height\s*:\s*36px/i.test(body),
|
|
'expected `height: 36px` on img.brand-logo (matches default SVG height)');
|
|
});
|
|
|
|
test('svg.brand-logo CSS rule still pins width:125px (no default regression)', () => {
|
|
const body = findTopLevelRule(CSS, 'svg.brand-logo');
|
|
assert(body, 'expected a top-level `svg.brand-logo` CSS rule keeping the default-wordmark pin');
|
|
assert(/width\s*:\s*125px/i.test(body),
|
|
'svg.brand-logo MUST keep width: 125px to preserve the default wordmark layout');
|
|
assert(/height\s*:\s*36px/i.test(body),
|
|
'svg.brand-logo MUST keep height: 36px');
|
|
});
|
|
|
|
test('mobile media-query splits the .brand-logo rule into svg/img variants', () => {
|
|
// Find the @media block(s) and look for split rules.
|
|
// We just require BOTH `svg.brand-logo` and `img.brand-logo` to appear
|
|
// somewhere inside an @media block, AND the bare `.brand-logo { height
|
|
// ... width: <px> }` form to NOT exist in tablet/mobile breakpoints
|
|
// (since that pinned width is what we're getting away from for IMG).
|
|
const mediaIdx = CSS.indexOf('@media');
|
|
assert(mediaIdx > 0, 'expected @media blocks in style.css');
|
|
const tail = CSS.slice(mediaIdx);
|
|
assert(/svg\.brand-logo\s*\{[^}]*width\s*:\s*\d+px/i.test(tail),
|
|
'expected `svg.brand-logo { ... width:<N>px }` inside a media query (tablet pin)');
|
|
assert(/img\.brand-logo\s*\{[^}]*width\s*:\s*auto/i.test(tail),
|
|
'expected `img.brand-logo { ... width:auto }` inside a media query (tablet IMG variant)');
|
|
});
|
|
|
|
test('customize-v2 _setBrandLogoUrl does NOT hardcode width/height attrs on the IMG', () => {
|
|
// The fix removes these two setAttribute calls so CSS img.brand-logo
|
|
// governs sizing without overriding aspect.
|
|
assert(!/img\.setAttribute\(\s*['"]width['"]\s*,\s*['"]125['"]\s*\)/.test(CUSTOMIZE),
|
|
'customize-v2.js still sets img width="125" — that overrides the CSS width:auto and squishes custom logos');
|
|
assert(!/img\.setAttribute\(\s*['"]height['"]\s*,\s*['"]36['"]\s*\)/.test(CUSTOMIZE),
|
|
'customize-v2.js still sets img height="36" — CSS height:36px is sufficient and the attr blocks aspect math when only one dim is constrained');
|
|
});
|
|
|
|
console.log('\n' + passed + ' passed, ' + failed + ' failed');
|
|
process.exit(failed === 0 ? 0 : 1);
|