fix(ui+server): tool cursor wins on canvas children; no-cache static assets
Issue 1 — cursor lies about armed tool. .svg-draggable { cursor: grab }
on frame/device rects beat the .canvas-wrap.tool-device #canvas {
cursor: crosshair } rule because element-level wins over descendant.
m saw "grab" hovering a frame with +Dev armed and thought the tool was
broken even though clicks routed correctly after the previous fix. Add
a descendant rule with !important so tool-armed wraps any child cursor:
.canvas-wrap.tool-frame #canvas *,
.canvas-wrap.tool-device #canvas * { cursor: crosshair !important; }
Issue 2 — stale browser cache after each redeploy. http.FileServerFS
served embedded assets with no Cache-Control header, so browsers held
on to the previous main.js/style.css until hard-reload. New noCache
middleware on the static handler emits Cache-Control: no-cache. Note:
embedded FS files have zero ModTime, so http.FileServer suppresses
Last-Modified — every fetch is a fresh 200 rather than a 304. Fine at
~30KB of JS+CSS, and fixes the staleness problem completely.
Middleware is wrapped only around the static handler. /api/* responses
write their own headers and aren't touched.
Verified locally:
curl -I /main.js → Cache-Control: no-cache
curl -I /style.css → Cache-Control: no-cache + contains the new rule
curl -I /api/healthz → unaffected (no Cache-Control from us)
go test -race ./... still green.
This commit is contained in:
@@ -46,7 +46,26 @@ func New(store *db.Store, frontend fs.FS) http.Handler {
|
||||
mux.HandleFunc("DELETE /api/projects/{pid}/devices/{id}", h.deleteDevice)
|
||||
|
||||
// Frontend (embedded). Serve "/" → index.html via http.FileServerFS.
|
||||
mux.Handle("/", http.FileServerFS(frontend))
|
||||
// Wrap in noCache so the browser revalidates with the ETag/Last-Modified
|
||||
// the file server already emits — without this, browsers cache aggressively
|
||||
// and m sees the old main.js after every redeploy until hard-reload.
|
||||
mux.Handle("/", noCache(http.FileServerFS(frontend)))
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
// noCache wraps a static handler so each response carries
|
||||
// Cache-Control: no-cache. Combined with the ETag/Last-Modified headers
|
||||
// http.FileServer(FS) already emits, this turns every fetch into a
|
||||
// cheap revalidation request — the browser uses its cached body when
|
||||
// the ETag matches but always asks first, so freshly-built assets show
|
||||
// up on the next page load without a hard-reload.
|
||||
//
|
||||
// Applied to the static-asset handler only — API responses write their
|
||||
// own headers and aren't routed through this.
|
||||
func noCache(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -203,9 +203,15 @@ body {
|
||||
.svg-draggable { cursor: grab; }
|
||||
.svg-draggable.dragging { cursor: grabbing; }
|
||||
|
||||
/* tool cursor on the empty canvas while a tool is armed */
|
||||
/* Tool cursor while a tool is armed. The `* { ... !important }` descendant
|
||||
rule is the load-bearing part: without it, the `.svg-draggable` rules
|
||||
on individual frame/device rects win by element specificity and
|
||||
override the SVG-root cursor — so hovering a frame with +Dev armed
|
||||
shows `grab`, which lies about what a click will do. */
|
||||
.canvas-wrap.tool-frame #canvas,
|
||||
.canvas-wrap.tool-device #canvas { cursor: crosshair; }
|
||||
.canvas-wrap.tool-frame #canvas *,
|
||||
.canvas-wrap.tool-device #canvas,
|
||||
.canvas-wrap.tool-device #canvas * { cursor: crosshair !important; }
|
||||
|
||||
.rubber-band {
|
||||
fill: rgba(25, 113, 194, 0.08);
|
||||
|
||||
Reference in New Issue
Block a user