OXYGENHUB

Stylesheet and script URLs carry a hash of their contents (refs #74) 8e820b4ae875

They are served with long cache lifetimes (hub.js and github.css for a
year, style.css for a day), and Cloudflare in front of the hub kept
serving the old github.css after a deploy. base.html now links them as
?v=<hash>, a hash of the three files computed once, so a changed file
has a new URL and an unchanged one stays cached.
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-01 22:29 UTC
Signature
✔ signed by Andy McCall
Hash
8e820b4ae875238097b844db3c5acfc846811299075ccc5dcabceb1096adf5ac
Depends on
19bb31ff1f5c d23464c0ac60 f6346d875ee5
Depended on by
a7aa76ac52d7 17ca6cddc534 f3e5e2bc78e9
In bookmarks
api-unknown-fields cancel-landed-runs clear-waiting close-proposal-140 diffusion-pin-bookmark draft-ready image-check job-waits main oh-comment-body-file proposal-list-fast run-one-job wiki-says-so
In tags
v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
6 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified crates/hub/src/web/mod.rs +5 −0
…
313 unchanged lines
314 314
}
315 315
316 316
impl Shell {
317 +
    /// The version in the stylesheet and script URLs (`statics::assets_version`).
318 +
    pub fn assets(&self) -> &'static str {
319 +
        statics::assets_version()
320 +
    }
321 +
317 322
    pub fn new(app: &Shared, path: &str, viewer: &Viewer) -> Shell {
318 323
        Shell {
319 324
            site: app.config.site.name.clone(),
…
297 unchanged lines
modified crates/hub/src/web/statics.rs +16 −0
…
6 unchanged lines
7 7
const CSS: &str = include_str!("../../../../templates/style.css");
8 8
const FAVICON: &str = include_str!("../../../../assets/icons/oxygenhub.svg");
9 9
10 +
/// A short hash of the stylesheets and script the pages link, put in their
11 +
/// URLs (`?v=…`): they are served for a long time and cached by any proxy
12 +
/// in front of the hub (Cloudflare held a day-old stylesheet), so a new
13 +
/// version must have a new URL.
14 +
pub fn assets_version() -> &'static str {
15 +
    static V: std::sync::OnceLock<String> = std::sync::OnceLock::new();
16 +
    V.get_or_init(|| {
17 +
        let mut h = blake3::Hasher::new();
18 +
        h.update(CSS.as_bytes());
19 +
        for a in ASSETS.iter().filter(|a| a.path == "github.css" || a.path == "hub.js") {
20 +
            h.update(a.bytes);
21 +
        }
22 +
        h.finalize().to_hex()[..12].to_string()
23 +
    })
24 +
}
25 +
10 26
struct Asset {
11 27
    path: &'static str,
12 28
    mime: &'static str,
…
50 unchanged lines
modified templates/base.html +3 −3
…
3 unchanged lines
4 4
<meta charset="utf-8">
5 5
<meta name="viewport" content="width=device-width, initial-scale=1">
6 6
<title>{% block title %}{{ shell.site }}{% endblock %} · {{ shell.site }}</title>
7 -
<link rel="stylesheet" href="/style.css">
7 +
<link rel="stylesheet" href="/style.css?v={{ shell.assets() }}">
8 8
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
9 -
<link rel="stylesheet" href="/static/github.css">
9 +
<link rel="stylesheet" href="/static/github.css?v={{ shell.assets() }}">
10 10
<script>/* The look (oxygenhub#74): GitHub-like by default (assets/github.css, every rule scoped to data-look="github"); ?look=classic keeps the RetroPlay look for this browser, ?look=github returns to the default. */(function(){var q=new URLSearchParams(location.search).get("look");if(q==="classic")document.cookie="look=classic; path=/; max-age=31536000; SameSite=Lax";else if(q==="github"||q==="default")document.cookie="look=; path=/; max-age=0; SameSite=Lax";if(!/(?:^|; )look=classic/.test(document.cookie))document.documentElement.dataset.look="github";})();</script>
11 11
<script>/* Theme before first paint: the theme cookie (dark, light or auto; dark when unset), auto following the system. */(function(){var d=document.documentElement,m=document.cookie.match(/(?:^|; )theme=(light|dark|auto)/),p=m?m[1]:"dark",q=window.matchMedia&&matchMedia("(prefers-color-scheme: light)");function a(){d.dataset.theme=p==="auto"?(q&&q.matches?"light":"dark"):p;}d.dataset.themePref=p;a();if(q&&q.addEventListener)q.addEventListener("change",function(){if(d.dataset.themePref==="auto")d.dataset.theme=q.matches?"light":"dark";});})();</script>
12 12
</head>
…
45 unchanged lines
58 58
  <table><tr><td><kbd>/</kbd></td><td>Search</td></tr><tr><td><kbd>g</kbd> <kbd>c</kbd></td><td>Code</td></tr><tr><td><kbd>g</kbd> <kbd>h</kbd></td><td>History</td></tr><tr><td><kbd>g</kbd> <kbd>b</kbd></td><td>Bookmarks</td></tr><tr><td><kbd>g</kbd> <kbd>t</kbd></td><td>Trust</td></tr><tr><td><kbd>y</kbd></td><td>Permalink to this state (files and trees)</td></tr><tr><td><kbd>?</kbd></td><td>This sheet</td></tr></table>
59 59
  <p class="small"><a href="/help#keys">All keys</a> · <a href="/help">Help</a></p>
60 60
</div>
61 -
<script src="/static/hub.js" defer></script>
61 +
<script src="/static/hub.js?v={{ shell.assets() }}" defer></script>
62 62
</body>
63 63
</html>