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:
parent
f590ff19cf
commit
270c5a4369
3 changed files with 48 additions and 15 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue