diff --git a/internal/server/server.go b/internal/server/server.go index c272811..e28c0e9 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -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) + }) +} diff --git a/web/static/style.css b/web/static/style.css index 5f1e0e4..605ca5b 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -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);