From dd5984abe0f6874d3b5d8a8b14753552cde11b17 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Mon, 13 Jul 2026 15:19:40 +0000 Subject: [PATCH 1/3] Adopt @screenly-labs/signage-kit for the degraded-mode pipeline Replace world-clock's inlined degraded-mode plumbing with the shared package (github:Screenly-Labs/signage-kit#v0.1.1): - build.ts: use the kit's bundleJs({ format: 'esm' }) (world-clock loads the bundle as a module) + processCss({ includeDegraded: true }) + injectGate, dropping the local esbuild/Lightning-CSS/browserslist wiring. - main.ts: import the shared polyfills shim; delete the local polyfills.ts. - package.json: drop the local browserslist field; add the kit dependency. The app-specific container-query (cqw) clock fallback stays in the app CSS. Build output verified equivalent: 0 @layer, kill-switch present, 0 :lang(), gate injected, @supports-not(container-type) fallback intact, JS es2017. Co-Authored-By: Claude Opus 4.8 (1M context) --- assets/styles/main.css | 19 ++++-------------- build.ts | 44 +++++++++++------------------------------- bun.lock | 3 +++ index.html | 20 ++----------------- package.json | 9 ++------- src/main.ts | 4 ++-- src/polyfills.ts | 23 ---------------------- 7 files changed, 24 insertions(+), 98 deletions(-) delete mode 100644 src/polyfills.ts diff --git a/assets/styles/main.css b/assets/styles/main.css index a313302..3a2c09b 100644 --- a/assets/styles/main.css +++ b/assets/styles/main.css @@ -531,22 +531,11 @@ body { } /* ========================================================================= - Degraded mode — old/weak signage players (html.legacy set by the inline gate - in index.html). Assume the device can't afford animation; drop it entirely. - .anim has a base opacity:0 and only appears via the entrance animation, so the - legacy block reveals it (opacity:1), mirroring the reduced-motion resting - state; the ambient breathe is then cancelled. + Degraded mode — app-specific resting state (the generic html.legacy kill-switch + is prepended at build by @screenly-labs/signage-kit). .anim has a base + opacity:0 and only appears via the entrance animation, so reveal it here + (the kill-switch cancels the animation itself), mirroring reduced-motion. ========================================================================= */ html.legacy .anim { opacity: 1; } -html.legacy *, -html.legacy *::before, -html.legacy *::after { - /* biome-ignore lint/complexity/noImportantStyles: kill-switch must override the entrance/ambient animation rules */ - animation: none !important; - /* biome-ignore lint/complexity/noImportantStyles: kill-switch must override any transition */ - transition: none !important; - /* biome-ignore lint/complexity/noImportantStyles: release GPU layer hints on weak devices */ - will-change: auto !important; -} diff --git a/build.ts b/build.ts index 4dfe185..d148666 100644 --- a/build.ts +++ b/build.ts @@ -9,20 +9,11 @@ // project subpath like https://.github.io/world-clock/. import { cp, mkdir, rm } from 'node:fs/promises' -import browserslist from 'browserslist' -import { build as esbuild } from 'esbuild' -import { browserslistToTargets, transform as lightningcss } from 'lightningcss' +import { bundleJs, injectGate, processCss } from '@screenly-labs/signage-kit/build' import { run as syncFonts } from './sync-fonts.ts' const DIST = 'dist' -// The `browserslist` field in package.json is the CSS support floor: Lightning -// CSS down-levels the stylesheet to it. The JS is lowered separately by esbuild to -// a fixed ES2017 syntax floor (kept at/below the browserslist minimum); esbuild -// can't read browserslist, so keep the two in sync if you change the floor. See -// the degraded-mode notes in index.html / main.css. -const cssTargets = browserslistToTargets(browserslist()) - // Vendor the Bun-managed webfonts into ./assets/fonts before copying them on. await syncFonts() @@ -30,22 +21,11 @@ await syncFonts() await rm(DIST, { recursive: true, force: true }) await mkdir(`${DIST}/styles`, { recursive: true }) -// JS: esbuild bundles main.ts (inlining clocks.ts + the polyfills shim) into one -// minified ES module and lowers modern syntax (?., ??, spread) to the ES2017 -// floor so old signage players can parse it. Kept as an ES module (the page -// still loads it with + diff --git a/package.json b/package.json index 38dbca6..dd25922 100644 --- a/package.json +++ b/package.json @@ -17,15 +17,10 @@ "serve": "bun run build.ts && bunx serve dist" }, "license": "AGPL-3.0-only", - "browserslist": [ - "chrome >= 87", - "firefox >= 78", - "safari >= 14.1", - "edge >= 87" - ], "dependencies": { "@fontsource-variable/fraunces": "^5.2.9", - "@fontsource-variable/hanken-grotesk": "^5.2.8" + "@fontsource-variable/hanken-grotesk": "^5.2.8", + "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#v0.1.1" }, "devDependencies": { "@biomejs/biome": "^2.5.1", diff --git a/src/main.ts b/src/main.ts index 06f8532..4816ae3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,8 +5,8 @@ // index.html — no framework, no server. // Side-effect import: installs the replaceChildren shim for the older-browser -// degraded mode. Must stay first so the shim is in place before any render. -import './polyfills' +// degraded mode (shared across all apps). Must stay first. +import '@screenly-labs/signage-kit/polyfills' import { buildFormatters, type ClockConfig, diff --git a/src/polyfills.ts b/src/polyfills.ts deleted file mode 100644 index 4262c22..0000000 --- a/src/polyfills.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Minimal runtime shim for the older-browser degraded mode. esbuild lowers modern -// *syntax* (?., ??, spread) at build time, but it does not add missing runtime -// APIs. The one DOM gap the apps can hit at the support floor is -// Element.prototype.replaceChildren (Chrome 86 / Safari 14, 2020); it's included -// as a defensive shim even in apps that don't call it directly. Everything else -// the apps use (fetch, Intl, URLSearchParams) predates the floor, so no core-js. -// -// Imported only for its side effect (installing the shim) as the first line of -// main.ts; it exports nothing. - -if (typeof Element !== 'undefined' && !Element.prototype.replaceChildren) { - Element.prototype.replaceChildren = function replaceChildren( - this: Element, - ...nodes: (Node | string)[] - ): void { - // Build the new children in a fragment first so a bad node throws before we - // touch the element, keeping the swap effectively atomic like the native API. - const frag = document.createDocumentFragment() - frag.append(...nodes) - while (this.firstChild) this.removeChild(this.firstChild) - this.appendChild(frag) - } -} From 08cca39f9581064c9eb5f5da7b0640fa15c0ccba Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Mon, 13 Jul 2026 15:28:34 +0000 Subject: [PATCH 2/3] Address Copilot review: bump to signage-kit v0.1.2, guard gate injection - Point at signage-kit#v0.1.2, where injectGate throws instead of silently no-oping when the stylesheet anchor is missing. - build.ts: wrap the injectGate call in the same try/catch as the JS/CSS steps so a failed injection reports a consistent error and fails the build. Co-Authored-By: Claude Opus 4.8 (1M context) --- build.ts | 10 +++++++++- bun.lock | 4 ++-- package.json | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/build.ts b/build.ts index d148666..7cfe9f6 100644 --- a/build.ts +++ b/build.ts @@ -47,7 +47,15 @@ try { console.log(`✓ CSS: ${DIST}/styles/main.css`) // Copy the HTML shell (with the shared degraded-mode gate injected) and the fonts. -await Bun.write(`${DIST}/index.html`, injectGate(await Bun.file('index.html').text())) +// injectGate throws if it can't find the stylesheet anchor, so a template change +// that drops the gate fails the build loudly instead of shipping a gate-less page. +try { + await Bun.write(`${DIST}/index.html`, injectGate(await Bun.file('index.html').text())) +} catch (error) { + console.error('✗ Failed to inject the degraded-mode gate into index.html') + console.error(error) + process.exit(1) +} console.log(`✓ HTML: ${DIST}/index.html`) await cp('assets/fonts', `${DIST}/fonts`, { recursive: true }) diff --git a/bun.lock b/bun.lock index cc19eae..38153ce 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,7 @@ "dependencies": { "@fontsource-variable/fraunces": "^5.2.9", "@fontsource-variable/hanken-grotesk": "^5.2.8", - "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#v0.1.1", + "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#v0.1.2", }, "devDependencies": { "@biomejs/biome": "^2.5.1", @@ -94,7 +94,7 @@ "@fontsource-variable/hanken-grotesk": ["@fontsource-variable/hanken-grotesk@5.2.8", "", {}, "sha512-K7hu09ZKReBc7EifRLeM4K94NZBrxFEg0nGcISmVwlFQOJo4EmYkLXTWEwr5JcNW4z2hr5zy8vLS2sxC8JDmrQ=="], - "@screenly-labs/signage-kit": ["@screenly-labs/signage-kit@github:Screenly-Labs/signage-kit#231c96d", { "peerDependencies": { "@csstools/postcss-cascade-layers": "^6", "browserslist": "^4", "esbuild": "^0.28", "lightningcss": "^1.32", "postcss": "^8" }, "optionalPeers": ["@csstools/postcss-cascade-layers", "postcss"] }, "Screenly-Labs-signage-kit-231c96d", "sha512-XdQcwnU/YsWoQllbwv3HqQ6Jj87wRYuC7Wp7aQu+bqjH9+iu9jbphqVOKgmMBs4S1T7KM1VMafk408ij+GXrtQ=="], + "@screenly-labs/signage-kit": ["@screenly-labs/signage-kit@github:Screenly-Labs/signage-kit#ca80193", { "peerDependencies": { "@csstools/postcss-cascade-layers": "^6", "browserslist": "^4", "esbuild": "^0.28", "lightningcss": "^1.32", "postcss": "^8" }, "optionalPeers": ["@csstools/postcss-cascade-layers", "postcss"] }, "Screenly-Labs-signage-kit-ca80193", "sha512-KKT60B/N6UD1ArTnFa//JsTZQEYPtjdnf+a48HCvkyy1Ir32HZgxwT51mK1Sr84P+B3U+4WE/N4GR4VnAKc1nw=="], "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], diff --git a/package.json b/package.json index dd25922..d477bba 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "dependencies": { "@fontsource-variable/fraunces": "^5.2.9", "@fontsource-variable/hanken-grotesk": "^5.2.8", - "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#v0.1.1" + "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#v0.1.2" }, "devDependencies": { "@biomejs/biome": "^2.5.1", From 54f8c898e2d7a6b4c60ff8794329a2d73ef06fe8 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Mon, 13 Jul 2026 15:31:32 +0000 Subject: [PATCH 3/3] Pin signage-kit at CalVer 2026.7.0 The kit switched from 0.1.x SemVer to CalVer; repoint the dependency. Co-Authored-By: Claude Opus 4.8 (1M context) --- bun.lock | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 38153ce..78e3041 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,7 @@ "dependencies": { "@fontsource-variable/fraunces": "^5.2.9", "@fontsource-variable/hanken-grotesk": "^5.2.8", - "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#v0.1.2", + "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#2026.7.0", }, "devDependencies": { "@biomejs/biome": "^2.5.1", @@ -94,7 +94,7 @@ "@fontsource-variable/hanken-grotesk": ["@fontsource-variable/hanken-grotesk@5.2.8", "", {}, "sha512-K7hu09ZKReBc7EifRLeM4K94NZBrxFEg0nGcISmVwlFQOJo4EmYkLXTWEwr5JcNW4z2hr5zy8vLS2sxC8JDmrQ=="], - "@screenly-labs/signage-kit": ["@screenly-labs/signage-kit@github:Screenly-Labs/signage-kit#ca80193", { "peerDependencies": { "@csstools/postcss-cascade-layers": "^6", "browserslist": "^4", "esbuild": "^0.28", "lightningcss": "^1.32", "postcss": "^8" }, "optionalPeers": ["@csstools/postcss-cascade-layers", "postcss"] }, "Screenly-Labs-signage-kit-ca80193", "sha512-KKT60B/N6UD1ArTnFa//JsTZQEYPtjdnf+a48HCvkyy1Ir32HZgxwT51mK1Sr84P+B3U+4WE/N4GR4VnAKc1nw=="], + "@screenly-labs/signage-kit": ["@screenly-labs/signage-kit@github:Screenly-Labs/signage-kit#eb470fb", { "peerDependencies": { "@csstools/postcss-cascade-layers": "^6", "browserslist": "^4", "esbuild": "^0.28", "lightningcss": "^1.32", "postcss": "^8" }, "optionalPeers": ["@csstools/postcss-cascade-layers", "postcss"] }, "Screenly-Labs-signage-kit-eb470fb", "sha512-acuAb2R/77crUjgnT9GFVlNdpgUTzvK5OLRRsZsZ30dZcJ7ThH/f8al2janEIJWC1JoyqTiarJ2x1uAH6In86A=="], "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], diff --git a/package.json b/package.json index d477bba..c12dcad 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "dependencies": { "@fontsource-variable/fraunces": "^5.2.9", "@fontsource-variable/hanken-grotesk": "^5.2.8", - "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#v0.1.2" + "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#2026.7.0" }, "devDependencies": { "@biomejs/biome": "^2.5.1",