colorMode.respectExisting landed in 0.2.401 (thanks, this is #2050 item 1) and works at the generator level, but it does not reach the boot script that buddy serve emits. The option is accepted silently and has no effect, so the forced-theme case #2050 was opened about still regresses in production.
What I see
config/ui.ts:
app: {
colorMode: {
storageKey: 'uptimestatus-theme',
attribute: 'data-theme',
darkClass: null,
initialMode: 'auto',
respectExisting: true,
},
},curl a served page, installed @stacksjs/stx and bun-plugin-stx both at 0.2.401:
<script data-stx-color-mode-boot>(function(){"use strict";var c={"storageKey":"uptimestatus-theme","initialMode":"auto","darkClass":null,"attribute":"data-theme","autoValue":null}; ...No respectExisting key, and the emitted body contains neither forced nor any getAttribute call, so it has no way to consult what the server stamped.
Calling the generator directly on the same installed build gives the opposite:
import { generateColorModeBootScript } from '@stacksjs/stx/dist/color-mode-boot.js'
generateColorModeBootScript({
storageKey: 'uptimestatus-theme', initialMode: 'auto', darkClass: null,
attribute: 'data-theme', respectExisting: true,
})
// embedded config: {"storageKey":"...","initialMode":"auto","darkClass":null,
// "attribute":"data-theme","autoValue":null,"respectExisting":true}
// includes `forced`: true includes getAttribute: trueNote the generator embeds the key even when the option is false, so a served config with no respectExisting key at all cannot have come from this generator.
Behaviour
Running the actual served bytes against a forced page:
owner forced light, visitor stored dark -> data-theme = dark REGRESSION
owner forced dark, visitor stored light -> data-theme = light REGRESSIONRunning the generator's output with respectExisting: true against the same cases, all nine combinations of stamped value / stored value / OS preference are correct:
forced light, stored dark -> theme=light forced=light pref=dark
forced dark, stored light -> theme=dark forced=dark pref=light
"system", stored dark -> theme=dark forced=- pref=dark
"system", nothing, OS dark -> theme=dark forced=- pref=auto
no attribute, nothing, OS light -> theme=light forced=- pref=autoSo the fix itself is right. Only the plumbing to the serve path is missing.
What I ruled out
- Not a stale process. Killed every
buddy serve, restarted after the config edit, same output. - Not a stale
dist/.dist/index.htmlcontains no boot script at all, so the served page is not that artifact. - Not a second copy of the generator. Every file under
@stacksjs/stx/distthat mentionsautoValuealso mentionsrespectExisting,bun-plugin-stx/distcontains no copy, and nothing else in the app does either. - Not key-picking in the obvious places.
process.jsdoesconst colorModeConfig = options.app?.colorModeandproduction-builder.jsdoes the same, both wholesale, so neither drops the key on its own. Whatever assemblesoptions.appfor the serve render is where I would start looking: the other four keys arrive intact, soapp.colorModeis being forwarded, just not that field.
Worth checking whether the SSG path differs from serve here, since production-builder.js and process.js take the config by different routes and only the serve path is production for us.
Why it matters
This is silent. The option type-checks, the config loads, no warning is emitted, and the page looks correct in every case except the one the option exists for. Someone adopting respectExisting on the strength of #2050 would ship the forced-theme regression and only find out from a customer whose status page shows the wrong theme.
A cheap guard would be a one-grep check: if a served boot script mentioning forced is the signature of the fix being live, then a test asserting the injected script contains forced whenever respectExisting is set would have caught this, in the same spirit as the #2050 note about driving tests through the public entry points rather than the generators.
For now I have reverted the config block and kept the three hand-written per-page resolvers, with the finding recorded in config/ui.ts so nobody on my side retries it until a release emits forced.