fix(theme): cache-bust slash-theme.css so theme changes reach existing users #27

Merged
mohlec merged 1 commit from fix/theme-css-cache-busting into slash/main 2026-08-05 02:32:00 +00:00
Owner

Fixes #26.

The bug

root.html referenced the theme stylesheet by a stable filename:

<link rel='stylesheet' href='/static/slash-theme.css'>

and Mattermost serves /static/ with:

Cache-Control: max-age=31556926, public

Every other asset is content-hashed by webpack (main.9035023454baa0d7cd87.js), so a new build invalidates it. slash-theme.css is copied verbatim by CopyPlugin and keeps its name forever, so an existing browser keeps its cached copy for up to a year and no theme change ever reaches an already-onboarded user.

How it surfaced

The AI-icon fix (#20 / PR #21) verified clean server-side — correct rule in the deployed CSS, /static/ai-agent-icon.svg returning 200 — while the reporting user still saw the old icon after deploy. A hard refresh fixed it instantly. The deploy had been correct the whole time.

The nastier part is that this breaks verification, not just delivery: a theme change can be deployed, checked, and confirmed correct while being invisible to every existing user. Fresh browser profiles always show the new file, so testing that way actively misleads.

The fix

<link rel='stylesheet' href='/static/slash-theme.css?bt=<%= typeof compilation !== "undefined" ? compilation.hash : Date.now() %>'>
  • ?bt= matches the convention already used in this codebase for remote_entry.js.
  • compilation is a default html-webpack-plugin template parameter, so no webpack config change is needed.
  • The typeof guard is deliberate. If that parameter ever goes away, this degrades to a timestamp instead of failing the whole webapp build — which matters while PRs are not compiled (#23), because a broken template would only surface after merge.

Verified that root.html is genuinely EJS-processed: the built copy on the running server contains zero <%= occurrences, so the existing <%= require(...) %> directives render.

Verifying after deploy

A normal refresh should be enough. Confirm the built root.html carries the query:

grep -o "<link[^>]*slash-theme[^>]*>" /opt/mattermost/client/root.html
Fixes #26. ## The bug `root.html` referenced the theme stylesheet by a stable filename: ``` <link rel='stylesheet' href='/static/slash-theme.css'> ``` and Mattermost serves `/static/` with: ``` Cache-Control: max-age=31556926, public ``` Every other asset is content-hashed by webpack (`main.9035023454baa0d7cd87.js`), so a new build invalidates it. `slash-theme.css` is copied verbatim by CopyPlugin and keeps its name forever, so **an existing browser keeps its cached copy for up to a year** and no theme change ever reaches an already-onboarded user. ## How it surfaced The AI-icon fix (#20 / PR #21) verified clean server-side — correct rule in the deployed CSS, `/static/ai-agent-icon.svg` returning 200 — while the reporting user still saw the old icon after deploy. A hard refresh fixed it instantly. The deploy had been correct the whole time. The nastier part is that this **breaks verification, not just delivery**: a theme change can be deployed, checked, and confirmed correct while being invisible to every existing user. Fresh browser profiles always show the new file, so testing that way actively misleads. ## The fix ```html <link rel='stylesheet' href='/static/slash-theme.css?bt=<%= typeof compilation !== "undefined" ? compilation.hash : Date.now() %>'> ``` - `?bt=` matches the convention already used in this codebase for `remote_entry.js`. - `compilation` is a default html-webpack-plugin template parameter, so no webpack config change is needed. - The `typeof` guard is deliberate. If that parameter ever goes away, this degrades to a timestamp instead of failing the whole webapp build — which matters while PRs are not compiled (#23), because a broken template would only surface *after* merge. Verified that `root.html` is genuinely EJS-processed: the built copy on the running server contains zero `<%=` occurrences, so the existing `<%= require(...) %>` directives render. ## Verifying after deploy A normal refresh should be enough. Confirm the built `root.html` carries the query: ``` grep -o "<link[^>]*slash-theme[^>]*>" /opt/mattermost/client/root.html ```
fix(theme): cache-bust slash-theme.css so theme changes reach existing users
All checks were successful
Build Slash Webapp / branding-audit (pull_request) Successful in 34s
Build Slash Webapp / build-webapp (pull_request) Has been skipped
641a366ca3
slash-theme.css is referenced from root.html by a stable filename and is
served with Cache-Control: max-age=31556926 (one year). Every other webapp
asset is content-hashed by webpack; this one is copied verbatim by
CopyPlugin and keeps its name forever.

The effect is that no change to the Slash theme has ever reached an already-
onboarded user until they hard-refreshed. New users and fresh browser
profiles get the new file, which makes a broken deploy look fine.

This was found when the AI agent icon fix (#20 / PR #21) verified clean
server-side - correct rule in the deployed CSS, asset returning 200 - while
the reporting user still saw the old icon. A hard refresh fixed it
immediately. It also retroactively explains why the 2026-07-09 theme fix
needed a second person to confirm before it was believed.

Uses `?bt=` to match the convention already used for remote_entry.js.
The `typeof` guard keeps a template-parameter change from failing the
entire webapp build - relevant while PRs are not built (#23).

Closes #26
mohlec merged commit 9f8e3f38c3 into slash/main 2026-08-05 02:32:00 +00:00
mohlec deleted branch fix/theme-css-cache-busting 2026-08-05 02:32:01 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
slash/slash-server!27
No description provided.