diff --git a/bb.edn b/bb.edn index adc7a02..e95c7c3 100644 --- a/bb.edn +++ b/bb.edn @@ -16,7 +16,15 @@ (let [adapter (slurp "dev/theme-adapter.js")] (spit "dev/replicant/public/theme-adapter.js" adapter) (spit "dev/squint/public/theme-adapter.js" adapter) - (println "Copied theme-adapter.js to dev targets")))} + (println "Copied theme-adapter.js to dev targets")) + (let [lr (slurp "dev/css-live-reload.js")] + (spit "dev/replicant/public/css-live-reload.js" lr) + (spit "dev/squint/public/css-live-reload.js" lr) + (println "Copied css-live-reload.js to dev targets")))} + + watch-theme + {:doc "Watch src/ui/*.css and tokens.edn, rebuild theme on changes" + :task (load-file "scripts/watch-theme.bb")} test {:doc "Run all unit tests" @@ -125,7 +133,7 @@ session "ui-dev"] (shell {:continue true} "tmux kill-session -t" session) (shell "tmux new-session -d -s" session - (str "PORT=" hport " bb dev-hiccup")) + (str "bash -c 'bb watch-theme & PORT=" hport " bb dev-hiccup'")) (shell "tmux split-window -h -t" session (str "bash -c 'cd dev/replicant && SHADOW_HTTP_PORT=" rport " npx shadow-cljs watch app'")) (shell "tmux split-window -v -t" session diff --git a/dev/css-live-reload.js b/dev/css-live-reload.js new file mode 100644 index 0000000..b8891dc --- /dev/null +++ b/dev/css-live-reload.js @@ -0,0 +1,42 @@ +// CSS Live Reload — polls /theme.css and hot-swaps the stylesheet on change. +// No full page reload needed. Included automatically in dev pages. +(function() { + 'use strict'; + var POLL_MS = 500; + var link = document.querySelector('link[rel="stylesheet"][href*="theme"]'); + if (!link) return; + + var prevHash = 0; + + function djb2(str) { + var h = 5381; + for (var i = 0; i < str.length; i++) { + h = ((h << 5) + h + str.charCodeAt(i)) | 0; + } + return h; + } + + function check() { + fetch('/theme.css', { cache: 'no-store' }) + .then(function(r) { return r.text(); }) + .then(function(css) { + var h = djb2(css); + if (prevHash && h !== prevHash) { + // Clone link and swap — avoids FOUC + var next = link.cloneNode(); + next.href = '/theme.css?t=' + Date.now(); + link.parentNode.insertBefore(next, link.nextSibling); + next.onload = function() { + link.parentNode.removeChild(link); + link = next; + }; + console.log('[css-live-reload] theme.css updated'); + } + prevHash = h; + }) + .catch(function() { /* ignore network errors */ }); + } + + check(); + setInterval(check, POLL_MS); +})(); diff --git a/dev/hiccup/src/dev/hiccup.clj b/dev/hiccup/src/dev/hiccup.clj index da9d0f2..da6a4b9 100644 --- a/dev/hiccup/src/dev/hiccup.clj +++ b/dev/hiccup/src/dev/hiccup.clj @@ -503,6 +503,7 @@ [:script (h/raw theme-persistence-script)]] [:body [:script {:src "/theme-adapter.js" :defer true}] + [:script {:src "/css-live-reload.js" :defer true}] (sidebar/sidebar-layout {} (app-sidebar active-page port) (sidebar/sidebar-overlay {}) @@ -534,6 +535,11 @@ :headers {"Content-Type" "application/javascript"} :body (slurp "dev/theme-adapter.js")} + (= path "/css-live-reload.js") + {:status 200 + :headers {"Content-Type" "application/javascript"} + :body (slurp "dev/css-live-reload.js")} + (resolve-page path) {:status 200 :headers {"Content-Type" "text/html; charset=utf-8"} diff --git a/dev/replicant/public/index.html b/dev/replicant/public/index.html index 2e0b2a2..00c75ed 100644 --- a/dev/replicant/public/index.html +++ b/dev/replicant/public/index.html @@ -47,5 +47,6 @@
+