fix(client): serve /index.css from the frontend instead of the backend - #175
Conversation
Plugin iframes link /index.css for the theme variables — the plugin development guide documents it, and all 24 plugins in HomeGlowPlugins use it. In any container deployment that request has always failed. nginx proxied /index.css to the backend, and the backend answers it by reading client/src/index.css off disk, trying four candidate paths. server/Dockerfile builds with server/ as its context and does COPY . ., so the image contains server/ and nothing else; none of those paths can resolve. Every request logged three errors and returned a 500. It works when running natively from a full checkout, which is how it survived. The stylesheet is a client asset, so it is now served by the client. Vite emits an unhashed copy at the site root next to the hashed bundles, and nginx serves that file instead of proxying. The copy is emitted from src/index.css rather than duplicated into public/, so there is one source and no second file to drift. The backend handler is left in place on purpose. A plugin iframe is loaded from API_BASE_URL, so its root-relative /index.css resolves against the backend origin whenever that differs from the page's — which is exactly `npm run dev`. There the backend can read the file, and does. In containers API_BASE_URL is empty, the iframe is same-origin, and the request now lands on nginx. This finishes what jherforth#61 started: that fixed the routing so /index.css reached the backend rather than the SPA fallback, but nothing behind it could answer. Plugins still get the default palette rather than a customized theme — the app overrides about fifteen of these variables at runtime from user settings, and interfaceColors lives only in localStorage, so no server-side render could be correct either. Matching the live theme needs the host to inject values into the iframes and is a separate change.
…missing The /index.css handler answered a missing file with a hardcoded stylesheet. That substitute defined different variable names than client/src/index.css (--text-color, --text-color-rgb, --error-color, where the real sheet has --text, --text-secondary, --error) and inverted the theme model: light :root with a [data-theme="dark"] override, where the real sheet is dark :root with a [data-theme="light"] override. Because the backend image cannot contain client/src/index.css, that substitute was what every plugin actually received in every container deployment, for the whole life of the feature. Plugin authors wrote against its vocabulary — chore-metrics uses var(--text-color, #333), a name that does not exist in the real stylesheet. A stylesheet that silently supplies different values under different names is worse than none. Plugins already carry their own var(--x, default) fallbacks and those only work if this path stays quiet, so it now returns 404. Paired with serving /index.css from the frontend image: in containers the request no longer reaches this handler at all, and in native development the real file is present and served. This path is now only reachable on a genuinely broken install, where failing honestly is the point.
|
Reviewed critically and merging. The dependency is in place and I verified each structural claim rather than taking it on trust. The diagnosis is correct, and provable from the build config
The substitute is also the source of a confusion I ran into separately: it defined The ordering constraint is satisfiedHomeGlowPlugins#13 is merged, and What I verifiedThe emitted stylesheet is real, not a copy that can drift. Built the client on this branch:
Same-origin in containers holds. The dev path is not regressed — this was the one I most wanted to check, since breaking it would hit every developer. Backend on this branch: Server 225/225, client 281/281, translation parity, CI green on both jobs. One consequence worth stating plainlyFor a split-origin deployment — Also agreed on the known limit. Plugins getting the real palette but the default values is a genuine improvement over the wrong palette, and injecting the computed theme into iframes is rightly a separate change. Small thing: the body says "the real 5174-byte sheet" — it measures 5404 now. The number is just stale, the check behind it is the right one. |
Fixes #174.
Plugin iframes have never received the real theme stylesheet in a container
deployment. The backend serves
/index.cssby readingclient/src/index.cssoffdisk, trying four candidate paths;
server/Dockerfilebuilds withserver/asits Docker context, so the image contains
server/and nothing else and none ofthose paths can resolve. It works natively from a full checkout, which is how it
went unnoticed.
The request did not fail after the four ENOENTs the handler served a hardcoded
substitute, so plugins got a valid 200 with the wrong content, under different
variable names (
--text-color,--error-colorwhere the real sheet has--text,--error) and an inverted theme model.Changes
Serve it from the frontend. Vite emits an unhashed
index.cssat the siteroot from
src/index.css, and nginx serves that file instead of proxying. It isemitted rather than copied into
public/, so there is one source and no secondfile to drift.
Drop the substitute. A stylesheet that silently supplies different values
under different names is worse than none: plugins carry their own
var(--x, default)fallbacks and those only work if this path stays quiet. Itnow 404s.
The backend handler stays, deliberately. A plugin iframe loads from
API_BASE_URL, so its root-relative/index.cssresolves against the backendorigin whenever that differs from the page's which is exactly
npm run dev.There the file is present and served correctly. In containers
API_BASE_URLisempty, the iframe is same-origin, and the request reaches nginx.
Depends on jherforth/HomeGlowPlugins#13
This changes which variable names plugins receive, and the two stylesheets are
not subset and superset.
chore-metrics.htmlusesvar(--text-color, #333)— aname only the substitute defined so merging this alone leaves it rendering dark
grey text on the dark
--card-bg.Landing the plugin PR first is safe; landing this one first leaves that
plugin unreadable in the interval. All 24 plugins were checked and
chore-metricsis the only one using a retired name.Verified
On a container deployment, with the check that discriminates: front
GET /index.cssreturns the real 5174-byte sheet, and the backend originreturns 404. A 200 on the front door alone was true of the broken build too,
since the substitute answered it.
Known limit
Plugins now get the real palette but still the defaults, not a customized
theme. The app overrides roughly fifteen of these variables at runtime from user
settings, and
interfaceColorslives only in localStorage, so no server-siderender can be correct. Matching the live theme would need the host to inject
computed values into the iframes a separate, larger change.