slash-theme.css has no cache-busting and is served with a 1-year max-age, so theme fixes never reach existing users #26

Closed
opened 2026-08-05 01:39:44 +00:00 by mohlec · 1 comment
Owner

Summary

slash-theme.css is referenced from root.html by a stable filename with no cache-busting, and Mattermost serves /static/ with a one-year cache lifetime:

root.html      ->  slash-theme.css          (no ?v=, no content hash)
HTTP response  ->  Cache-Control: max-age=31556926, public

Every other webapp asset is content-hashed by webpack (1748.ded9eb4e8bd335d49cf3.js), so a new build invalidates it automatically. slash-theme.css is copied verbatim by CopyPlugin and keeps its name forever.

Consequence: no change to the Slash theme ever reaches an existing user. They keep the copy their browser cached the first time they loaded Slash, for up to a year, until they hard-refresh or clear cache. New users and incognito sessions get the new file, which makes it look like the fix worked.

How this was found

PR #21 repointed the Agents app-bar icon at ai-agent-icon.svg (issue #20). Server-side verification was clean — correct rule in the deployed CSS, asset returning 200 — but the reporting user still saw the broken icon after deploy. The CSS was right; their browser was serving a year-old cached copy.

This retroactively explains the 2026-07-09 theme fix as well, which had to be confirmed by a second person before it was believed.

Why it matters beyond cosmetics

This silently invalidates verification. A theme change can be deployed, checked server-side, and confirmed correct — and still not be in front of any existing user. Every past and future slash-theme.css fix is affected.

Suggested fix

Stamp the reference in root.html with the build identity, so it invalidates exactly like the hashed bundles:

<link rel="stylesheet" href="/static/slash-theme.css?v=BUILD_HASH">

The build hash is already available (the dist artifact is named slash-<date>-<commit>-dist.tar.gz), so CI can substitute it at build time. A content hash on the file itself would work equally well.

Until then, theme changes need a hard-refresh (Ctrl+Shift+R) to verify, and "I checked and it looks right" from a fresh browser profile is not evidence that existing users got it.

## Summary `slash-theme.css` is referenced from `root.html` by a **stable filename with no cache-busting**, and Mattermost serves `/static/` with a **one-year** cache lifetime: ``` root.html -> slash-theme.css (no ?v=, no content hash) HTTP response -> Cache-Control: max-age=31556926, public ``` Every other webapp asset is content-hashed by webpack (`1748.ded9eb4e8bd335d49cf3.js`), so a new build invalidates it automatically. `slash-theme.css` is copied verbatim by CopyPlugin and keeps its name forever. **Consequence: no change to the Slash theme ever reaches an existing user.** They keep the copy their browser cached the first time they loaded Slash, for up to a year, until they hard-refresh or clear cache. New users and incognito sessions get the new file, which makes it look like the fix worked. ## How this was found PR #21 repointed the Agents app-bar icon at `ai-agent-icon.svg` (issue #20). Server-side verification was clean — correct rule in the deployed CSS, asset returning 200 — but the reporting user still saw the broken icon after deploy. The CSS was right; their browser was serving a year-old cached copy. This retroactively explains the 2026-07-09 theme fix as well, which had to be confirmed by a second person before it was believed. ## Why it matters beyond cosmetics This silently invalidates *verification*. A theme change can be deployed, checked server-side, and confirmed correct — and still not be in front of any existing user. Every past and future `slash-theme.css` fix is affected. ## Suggested fix Stamp the reference in `root.html` with the build identity, so it invalidates exactly like the hashed bundles: ```html <link rel="stylesheet" href="/static/slash-theme.css?v=BUILD_HASH"> ``` The build hash is already available (the dist artifact is named `slash-<date>-<commit>-dist.tar.gz`), so CI can substitute it at build time. A content hash on the file itself would work equally well. Until then, **theme changes need a hard-refresh (Ctrl+Shift+R) to verify**, and "I checked and it looks right" from a fresh browser profile is not evidence that existing users got it.
Author
Owner

Fixed and deployed. PR #27, webapp slash-20260805-9f8e3f38c3, deployed 2026-08-05.

Verified end to end on CT#137:

deployed root.html   ->  <link rel="stylesheet" href="/static/slash-theme.css?bt=54d67c982e779ce51fe7">
that URL             ->  200
slash-theme.css      ->  url("/static/ai-agent-icon.svg")   (icon fix intact)
app shell (/)        ->  Cache-Control: no-cache, ...

Two things worth recording:

compilation.hash resolved as intended. The value is a 20-character webpack hash, not a 13-digit timestamp, so the typeof fallback never fired. This was verified by extracting root.html from the CI artifact before deploying, rather than by deploying and hoping — which is worth doing while PRs are not compiled (#23).

The fix was able to deliver itself. The app shell at / is served no-cache, so browsers revalidate it on every load and pick up the new root.html without intervention. Had the shell been cached like the rest of /static/, this fix would have been unable to reach the users who needed it — worth knowing if anyone ever changes those headers.

One-time cost: users still need a single hard refresh to pick up the new root.html. After that, every future theme change lands on a normal refresh, and server-side verification of theme work becomes real evidence again.

**Fixed and deployed.** PR #27, webapp `slash-20260805-9f8e3f38c3`, deployed 2026-08-05. Verified end to end on CT#137: ``` deployed root.html -> <link rel="stylesheet" href="/static/slash-theme.css?bt=54d67c982e779ce51fe7"> that URL -> 200 slash-theme.css -> url("/static/ai-agent-icon.svg") (icon fix intact) app shell (/) -> Cache-Control: no-cache, ... ``` Two things worth recording: **`compilation.hash` resolved as intended.** The value is a 20-character webpack hash, not a 13-digit timestamp, so the `typeof` fallback never fired. This was verified by extracting `root.html` from the CI artifact *before* deploying, rather than by deploying and hoping — which is worth doing while PRs are not compiled (#23). **The fix was able to deliver itself.** The app shell at `/` is served `no-cache`, so browsers revalidate it on every load and pick up the new `root.html` without intervention. Had the shell been cached like the rest of `/static/`, this fix would have been unable to reach the users who needed it — worth knowing if anyone ever changes those headers. **One-time cost:** users still need a single hard refresh to pick up the new `root.html`. After that, every future theme change lands on a normal refresh, and server-side verification of theme work becomes real evidence again.
Sign in to join this conversation.
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#26
No description provided.