mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 13:53:12 -06:00
Two field-reported gaps, unrelated except that both are about being able to read something off a screen. A PANEL'S IPv6 WAS NEVER COLLECTED, LET ALONE SHOWN. DeviceInfo.getLocalIp() filters to Inet4Address, so a v6-only panel reported no address at all and the dashboard rendered a dash for a screen that was perfectly reachable. It now reports both stacks in their own fields: a dual-stack panel genuinely has two addresses and either may be the one you need, so collapsing them into one column would make it mean "whichever interface enumerated first". Link-local (fe80::/10) is deliberately excluded. Every interface has one, they tend to enumerate first, and none can be dialled without also knowing the zone index — so admitting them would fill the field with a string nobody can paste anywhere and hide the address that works. Any %iface suffix is trimmed for the same reason. The 45-char cap the writer already applied is exactly the longest legitimate IPv6 text form, so it needed no change. The dashboard card renders only when a panel actually has a v6 address, rather than showing an empty row to the overwhelmingly v4 fleet. THE PAIRING CODE DID NOT SCALE, WHICH IS WORST WHERE IT MATTERS MOST. Every size on the pre-playback screens was a hard-coded pixel value. A CSS pixel covers a quarter of the screen area on a 4K panel that it does on 1080p, and a sixteenth on 8K — so the 72px code that fills a 1080p screen is a smudge on the 4K wall it was installed on, which is where signage actually goes. What has to stay constant is ANGULAR size, so the root font size is now viewport-proportional and everything on those screens is a rem against it. The code holds 6.67% of screen height at every resolution: 72px at 1080p — bit for bit what it renders today, so nothing changes for the existing fleet — 144px at 4K, 288px at 8K. Verified in a browser rather than by arithmetic: at a 1409px viewport the root computes to 13.0473px, which is 0.926vmin to four decimals. vmin, not vw, because portrait-mounted panels are common here and vw would render a 1080x1920 screen at half size. Clamped at both ends so the dashboard's preview iframe stays legible instead of microscopic and an ultrawide does not get silly. Applied to the web player (which BrightSign also runs) and to Tizen, where a 1920x1080 logical viewport makes it arithmetically identical to the values it replaces — the point being the panels where it is not. A test asserts the scaling cannot reach playback content: the whole safety argument is that only the chrome uses rem, and a stage or zone rule adopting it would start resizing CONTENT, which is a worse bug than the one being fixed. Android is untouched — its pairing code already autosizes within a dp-scaled layout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014skWYXJUWhF73EvNPgB2AS
93 lines
4.8 KiB
JavaScript
93 lines
4.8 KiB
JavaScript
'use strict';
|
|
|
|
/*
|
|
* The pairing code has to be readable on the panel it is displayed on.
|
|
*
|
|
* A CSS pixel covers a quarter of the screen area on a 4K panel that it does on 1080p, and a
|
|
* sixteenth on 8K. Every size on the player's pre-playback screens was a hard-coded pixel value,
|
|
* so the 72px pairing code that fills a 1080p screen became a smudge on a 4K wall — reported from
|
|
* the field, on exactly the screens signage gets installed on.
|
|
*
|
|
* The fix is a viewport-proportional root font size with everything on those screens expressed in
|
|
* rem, so the code holds the same ANGULAR size at any panel resolution. These tests pin the two
|
|
* properties that actually matter: nothing on those screens is left in px, and the scaling cannot
|
|
* leak into playback content.
|
|
*/
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = path.join(__dirname, '..', '..');
|
|
const read = (p) => fs.readFileSync(path.join(ROOT, p), 'utf8');
|
|
|
|
const SURFACES = [
|
|
{ name: 'web player', file: 'server/player/index.html' },
|
|
{ name: 'tizen player', file: 'tizen/css/style.css' },
|
|
];
|
|
|
|
for (const { name, file } of SURFACES) {
|
|
test(`${name}: the root font size is viewport-proportional and clamped at both ends`, () => {
|
|
const src = read(file);
|
|
const m = src.match(/html\s*\{\s*font-size:\s*clamp\(\s*([\d.]+)px\s*,\s*([\d.]+)vmin\s*,\s*([\d.]+)px\s*\)/);
|
|
assert.ok(m, `${file} must set a clamped, vmin-based root font size`);
|
|
const [, min, vmin, max] = m.map(Number);
|
|
|
|
// vmin, not vw: a portrait-mounted panel is as common as a landscape one, and vw would render
|
|
// a 1080x1920 screen at half size.
|
|
assert.ok(vmin > 0, 'the middle term must actually scale with the viewport');
|
|
// 1rem should land on 10px at a 1080-tall viewport, so the rem values read as "px at 1080p"
|
|
// and a reviewer can check them against the design at a glance.
|
|
assert.ok(Math.abs(vmin * 1080 / 100 - 10) < 0.1, `1rem must be ~10px at 1080p, got ${vmin * 1080 / 100}px`);
|
|
// The floor keeps a dashboard preview iframe and a laptop window legible rather than
|
|
// microscopic; the ceiling stops an ultrawide from getting silly.
|
|
assert.ok(min >= 6 && min <= 10, `floor should keep small viewports readable, got ${min}px`);
|
|
assert.ok(max >= 32, `ceiling should not cap a genuine 8K panel too early, got ${max}px`);
|
|
});
|
|
}
|
|
|
|
test('web player: the pairing code scales, and is still the biggest thing on the screen', () => {
|
|
const src = read('server/player/index.html');
|
|
const rule = src.slice(src.indexOf('.pairing-code {'), src.indexOf('.pairing-hint'));
|
|
const size = rule.match(/font-size:\s*([\d.]+)rem/);
|
|
assert.ok(size, 'the pairing code must be sized in rem, not px');
|
|
assert.ok(Number(size[1]) >= 7, 'the code is what someone squints at from across a room');
|
|
assert.match(rule, /letter-spacing:\s*[\d.]+rem/, 'letter-spacing must scale with it or the digits collide');
|
|
|
|
const h1 = src.match(/#setupScreen h1 \{ font-size: ([\d.]+)rem/);
|
|
assert.ok(h1 && Number(size[1]) > Number(h1[1]), 'the code must outrank the product name');
|
|
});
|
|
|
|
test('tizen player: the pairing code scales too', () => {
|
|
const src = read('tizen/css/style.css');
|
|
const rule = src.slice(src.indexOf('.code {'), src.indexOf('.hint {'));
|
|
assert.match(rule, /font-size:\s*[\d.]+rem/, 'the Tizen pairing code must be sized in rem');
|
|
assert.match(rule, /letter-spacing:\s*[\d.]+rem/);
|
|
});
|
|
|
|
test('no pre-playback screen is left in hard-coded px', () => {
|
|
const src = read('server/player/index.html');
|
|
// Everything from the setup screen through the status overlay. A px value surviving in here is
|
|
// one element that stays put while the rest of the screen grows around it.
|
|
for (const selector of ['#setupScreen h1', '#setupScreen .subtitle', '.pairing-hint', '.status-msg', '#statusOverlay h2', '#statusOverlay p']) {
|
|
const at = src.indexOf(selector + ' {');
|
|
assert.ok(at > 0, `${selector} not found`);
|
|
const rule = src.slice(at, src.indexOf('}', at));
|
|
const px = rule.match(/font-size:\s*[\d.]+px/);
|
|
assert.equal(px, null, `${selector} still has a hard-coded font-size — it will not scale`);
|
|
}
|
|
});
|
|
|
|
test('the scaling cannot reach playback content', () => {
|
|
const src = read('server/player/index.html');
|
|
// The whole safety argument for moving the root font size is that only the pre-playback chrome
|
|
// uses rem. If a stage/zone/PiP rule starts using rem, resizing a panel would start resizing
|
|
// CONTENT, which is a different and much worse bug than the one being fixed here.
|
|
for (const selector of ['#stage', '.zone', '#pip']) {
|
|
const at = src.indexOf(selector + ' {');
|
|
if (at < 0) continue;
|
|
const rule = src.slice(at, src.indexOf('}', at));
|
|
assert.ok(!/[\d.]rem/.test(rule), `${selector} must not use rem — content sizing must stay independent`);
|
|
}
|
|
});
|