Version static URLs by mtime; flatter tabs

The stylesheet and bundle were served with Last-Modified only, so a browser
could pair new JavaScript with a stale app.css and render the tab strip and
thumbnails unstyled. Their URLs now carry the file's mtime. Tabs are flat on
the chrome with the open one underlined; the close button shows on hover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Olive Vaughn 2026-09-29 13:08:38 -04:00
parent f590ff19cf
commit 270c5a4369
3 changed files with 48 additions and 15 deletions

View file

@ -20,12 +20,12 @@ and PUTs with ordinary CSRF protection — no endpoint in this app is exempt.
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>arthur</title> <title>arthur</title>
<link rel="stylesheet" href="{% static 'arthur/app.css' %}"> <link rel="stylesheet" href="{% static 'arthur/app.css' %}?v={{ css_version }}">
</head> </head>
<body> <body>
{% csrf_token %} {% csrf_token %}
<div id="app"></div> <div id="app"></div>
<script src="{% static 'mediapipe/vision_bundle.js' %}"></script> <script src="{% static 'mediapipe/vision_bundle.js' %}"></script>
<script src="{% static 'arthur/js/main.js' %}"></script> <script src="{% static 'arthur/js/main.js' %}?v={{ js_version }}"></script>
</body> </body>
</html> </html>

View file

@ -119,10 +119,27 @@ def _crop_blob(chunks):
# the page # the page
def _asset_version(relative):
"""A static file's modification time, for its URL.
The stylesheet and the bundle are served with `Last-Modified` and nothing
else, so a browser is free to keep a stale copy on heuristic freshness — and
new JavaScript over an old stylesheet renders a pane the stylesheet has never
heard of as bare elements. A version in the URL makes each edit a new URL."""
for root in settings.STATICFILES_DIRS:
path = Path(root) / relative
if path.exists():
return str(int(path.stat().st_mtime))
return "0"
def page(request): def page(request):
"""The host page. This replaced `frontend/public/index.html` at step 9, and """The host page. This replaced `frontend/public/index.html` at step 9, and
`:dev-http` in shadow-cljs.edn went away with it.""" `:dev-http` in shadow-cljs.edn went away with it."""
return render(request, "clips/index.html") return render(request, "clips/index.html", {
"css_version": _asset_version("arthur/app.css"),
"js_version": _asset_version("arthur/js/main.js"),
})
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View file

@ -336,31 +336,47 @@ input[type="range"] { width: 100%; accent-color: var(--sel); }
overflow: hidden; overflow: hidden;
} }
/* One tab per open symbol. Flat, like the pane heads, with the open one lifted /* One tab per open symbol: flat, on the chrome, with the open one marked by the
to the palette bar's colour so it reads as the thing the bar below belongs to. */ selection colour underneath rather than by a raised shape. */
.tabs { .tabs {
display: flex; display: flex;
gap: 1px; align-items: stretch;
padding: 3px 6px 0; height: 24px;
background: var(--line); flex: 0 0 24px;
padding: 0 4px;
background: var(--chrome);
border-bottom: 1px solid var(--line);
overflow-x: auto; overflow-x: auto;
} }
.tab { .tab {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 4px;
padding: 2px 8px; padding: 0 6px 0 10px;
background: var(--sunk);
color: var(--dim); color: var(--dim);
border-radius: 3px 3px 0 0; border-right: 1px solid var(--hair);
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
} }
.tab.on { background: var(--chrome); color: var(--fg); } .tab:hover { color: var(--fg); background: var(--sunk); }
.tab .close { padding: 0 3px; border: 0; background: none; color: var(--dim); line-height: 1; } .tab.on { color: var(--fg); background: var(--pane); box-shadow: inset 0 -2px 0 var(--sel); }
.tab .close:hover { color: var(--fg); }
.tab .close {
width: 16px;
height: 16px;
padding: 0;
border: 0;
border-radius: 2px;
background: none;
color: var(--dim);
line-height: 16px;
visibility: hidden;
}
.tab:hover .close, .tab.on .close { visibility: visible; }
.tab .close:hover { background: var(--hair); color: var(--fg); }
.palette-bar { .palette-bar {
display: flex; display: flex;