From d5680e8e41b830fcf6b4ca271d732d3d80ecb431 Mon Sep 17 00:00:00 2001 From: Mitchell-Johnson Date: Sat, 10 Oct 2026 15:19:20 -0700 Subject: [PATCH] VGA console: stop importing spice-html5's demo stylesheet, which restyled every button spice.css sets body, *, button and canvas for the whole document. Bundled into the App chunk since 43ebacd (deployed as v0.1.0-188), it turned every button into a gradient pill, shifted 15% right and 8% up with a 150px minimum width, broke the rail and header layout, and bordered every graph canvas. The console is already styled by the .vga rules in styles.css. The file is removed from the vendored copy, and a new web test fails on any stylesheet import outside an allow-list. Co-Authored-By: Claude Opus 5.5 --- web/package.json | 2 +- web/scripts/test-css-imports.mjs | 20 +++ web/src/VgaConsole.tsx | 4 +- web/src/vendor/spice-html5/README-hamlab.md | 9 +- web/src/vendor/spice-html5/spice.css | 168 -------------------- 5 files changed, 32 insertions(+), 171 deletions(-) create mode 100644 web/scripts/test-css-imports.mjs delete mode 100644 web/src/vendor/spice-html5/spice.css diff --git a/web/package.json b/web/package.json index fc86e78..2d2d8d9 100644 --- a/web/package.json +++ b/web/package.json @@ -7,7 +7,7 @@ "dev": "vite", "build": "tsc --noEmit && vite build", "icons": "node scripts/icons.mjs", - "test": "node scripts/test-graphcols.mjs" + "test": "node scripts/test-graphcols.mjs && node scripts/test-css-imports.mjs" }, "dependencies": { "@xterm/addon-fit": "^0.10.0", diff --git a/web/scripts/test-css-imports.mjs b/web/scripts/test-css-imports.mjs new file mode 100644 index 0000000..0ae1ae4 --- /dev/null +++ b/web/scripts/test-css-imports.mjs @@ -0,0 +1,20 @@ +// node scripts/test-css-imports.mjs — every stylesheet the UI imports is one somebody has read. +// A third-party stylesheet usually carries its demo page's global rules (body, *, button); on +// 2026-10-09 one restyled every button in the app. Add a file here only once its rules are +// known to touch nothing but its own component. +import { readdirSync, readFileSync, statSync } from 'node:fs' +import { join, relative } from 'node:path' + +const allowed = new Set(['./styles.css', '@xterm/xterm/css/xterm.css']) +const src = new URL('../src', import.meta.url).pathname +const files = [] +const walk = (d) => { for (const n of readdirSync(d)) { const p = join(d, n); if (statSync(p).isDirectory()) { if (n !== 'vendor') walk(p) } else if (/\.(ts|tsx)$/.test(n)) files.push(p) } } +walk(src) +const fails = [] +for (const f of files) { + for (const m of readFileSync(f, 'utf8').matchAll(/import\s+(?:[^'"]*from\s+)?['"]([^'"]+\.css)['"]/g)) { + if (!allowed.has(m[1])) fails.push(`${relative(src, f)} imports ${m[1]}`) + } +} +if (fails.length) { console.error('css imports: not allowed:\n ' + fails.join('\n ')); process.exit(1) } +console.log(`css imports: ok (${files.length} files)`) diff --git a/web/src/VgaConsole.tsx b/web/src/VgaConsole.tsx index 5fea277..ec906e6 100644 --- a/web/src/VgaConsole.tsx +++ b/web/src/VgaConsole.tsx @@ -4,7 +4,9 @@ import { useEffect, useRef, useState } from 'react' import { wsURLPath } from './api' import { Btn, useEscape } from './ui' -import './vendor/spice-html5/spice.css' +// No spice.css: upstream's stylesheet is for its own demo page and sets body, *, button and +// canvas for the whole document. Imported here it restyled every button in Ham Lab. The +// console is styled by the .vga rules in styles.css. type Spice = typeof import('./vendor/spice-html5/src/main.js') diff --git a/web/src/vendor/spice-html5/README-hamlab.md b/web/src/vendor/spice-html5/README-hamlab.md index 5a0ca3a..977cbe0 100644 --- a/web/src/vendor/spice-html5/README-hamlab.md +++ b/web/src/vendor/spice-html5/README-hamlab.md @@ -1,9 +1,16 @@ # spice-html5, vendored Upstream: https://gitlab.freedesktop.org/spice/spice-html5 — commit b372aab (2026-09-21), -copied unchanged on 2026-10-09 (`src/`, `thirdparty/`, `spice.css`). LGPL-3.0-or-later; the +copied unchanged on 2026-10-09 (`src/`, `thirdparty/`). LGPL-3.0-or-later; the licence text is beside this file. It draws a VM's SPICE display into a canvas from a websocket carrying the raw SPICE stream — which is what Incus's VGA console websocket is — so Ham Lab's daemon bridges the two and this does the rest. Loaded lazily by `VgaConsole.tsx`, so the main bundle does not carry it. Not on npm in a current form (the `@spice-project/spice-html5` package stopped at 0.2.1 in 2019), hence the copy. + +Upstream's `spice.css` is deliberately **not** here. It is the stylesheet of spice-html5's +demo page, with rules for `body`, `*`, `button` and `canvas` that apply to the whole +document. It was copied and imported on 2026-10-09 and turned every button in Ham Lab into +a gradient pill shifted off its place (v0.1.0-188 to -191). The console is styled by the +`.vga` rules in `src/styles.css`; `scripts/test-css-imports.mjs` fails on any new +stylesheet import that has not been looked at. diff --git a/web/src/vendor/spice-html5/spice.css b/web/src/vendor/spice-html5/spice.css deleted file mode 100644 index 90aecd6..0000000 --- a/web/src/vendor/spice-html5/spice.css +++ /dev/null @@ -1,168 +0,0 @@ -body -{ - background-color: #999999; - color: #000000; - margin: 0; - padding: 0; - font-family: "Lucida Grande", "Lucida Sans Unicode", "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif; - font-size: 12pt; - line-height: 1.5em; -} - -* { margin: 0; } - -button -{ - cursor: pointer; - color: #000000; - background-color: #999999; - border: 1px solid #4a4a4a; - min-width: 150px; - border-radius: 10px; - background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#24414e)); - background: -moz-linear-gradient(top, #fff, #24414e); - -ms-transform: translate(15%, -8%); - transform: translate(15%, -8%); - font-size: .90em; - font-weight: bolder; - padding: 0px 0px 3px 0px; - margin-top: 5px; -} -button:focus -{ - outline: none; -} -button:hover -{ - background-color: #24414e -} -button:active -{ - background-color: #24414e; - transform: translate(14.85%, -7%); -} - -.SidenavClosed -{ - height: 100%; - width: 0; - position: fixed; - z-index: 1; - top: 0; - left: 0; - background-color: #bbbbbb; - box-shadow: 1px 0px #00000040; - overflow-x: hidden; - transition: 0.5s; - padding-top: 60px; -} -.SidenavOpen -{ - height: 100%; - width: 200px !important; - position: fixed; - z-index: 1; - top: 0; - left: 0; - background-color: #bbbbbb; - box-shadow: 1px 0px #00000040; - overflow-x: hidden; - transition: 0.5s; - padding-top: 60px; -} -#Sidenav label -{ - color: #000000; - margin-left: 3%; - text-shadow: 1px 1px 0px rgba(175, 210, 220, 0.8); - position: absolute; - font-size: .9em; -} -#Sidenav input -{ - padding: 3px; - background-color: #fAfAfA; - border: 1px inset #999999; - outline: none; - float: right; - margin-right: 3%; - -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; - box-sizing: border-box; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - position: relative; - max-width: 110px; -} -#Sidenav .closebtn -{ - position: absolute; - top: 10px; - right: 10px; - font-size: 25px; - margin-left: 50px; - cursor: pointer; - color: #000000; -} - -canvas -{ - border: 1px solid #000000; -} - -#login -{ - background: #bbbbbb; - z-index: 1; - padding-top: 2px; - border-bottom: 2px solid #00000040; -} -#login p -{ - margin-left: 27%; - display: inline; - font-size: large; -} - -#spice-area -{ - height: 100%; - width: 100%; - margin-left: auto; - margin-right: auto; -} -.spice-screen -{ - min-height: 600px; - height: 100%; - text-align: center; - padding: 0; - background-color: #999999; -} -.spice-message -{ - width: 90%; - height: 40%; - overflow: auto; - margin-left: auto; - margin-right: auto; - padding: 10px; - font-size: 8pt; - line-height: 1.1em; - font-family: 'Andale Mono', monospace; - background-color: #fAfAfA; - border: 1px inset #999999; - border-radius: 3px; - box-sizing: border-box; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; -} -.spice-message p { - margin-bottom: 0em; - margin-top: 0em; -} -.spice-message-warning { - color: orange; -} -.spice-message-error { - color: red; -} \ No newline at end of file