From f6eda7cfa1e23dd8898464c18efe900bb751d513 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Wed, 15 Jul 2026 12:10:02 +0000 Subject: [PATCH 1/3] Register htmx swap handler immediately htmx-settings.js is loaded as a module script, and module scripts are deferred until after the document is parsed. That means document.body is available without waiting for DOMContentLoaded, and registering immediately keeps the htmx behavior ready as soon as the settings module runs. --- handlers/static/js/htmx-settings.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/handlers/static/js/htmx-settings.js b/handlers/static/js/htmx-settings.js index 2f3b1a42..fbd74ea1 100644 --- a/handlers/static/js/htmx-settings.js +++ b/handlers/static/js/htmx-settings.js @@ -8,19 +8,18 @@ htmx.config.allowEval = false; // Don't let response-targets override isError. htmx.config.responseTargetUnsetsError = false; -document.addEventListener("DOMContentLoaded", () => { - document.body.addEventListener("htmx:beforeSwap", function (evt) { - if (evt.detail.xhr.status === 204) { - evt.detail.shouldSwap = true; - } - if (evt.detail.xhr.status === 422) { - // allow 422 responses to swap as we are using this as a signal that - // a form was submitted with bad data and want to rerender with the - // errors - // - // set isError to false to avoid error logging in console - evt.detail.shouldSwap = true; - evt.detail.isError = false; - } - }); +// Module scripts run after the document is parsed, so document.body exists. +document.body.addEventListener("htmx:beforeSwap", function (evt) { + if (evt.detail.xhr.status === 204) { + evt.detail.shouldSwap = true; + } + if (evt.detail.xhr.status === 422) { + // allow 422 responses to swap as we are using this as a signal that + // a form was submitted with bad data and want to rerender with the + // errors + // + // set isError to false to avoid error logging in console + evt.detail.shouldSwap = true; + evt.detail.isError = false; + } }); From 6fc33fa4255634fb126214fc129108a237a3f67d Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Wed, 15 Jul 2026 12:10:48 +0000 Subject: [PATCH 2/3] Add htmx history and timeout defaults Disable htmx's injected indicator CSS because the application provides equivalent styles in screenjournal.css. Since htmx no longer emits that inline style block, its CSP SHA-256 hash is no longer needed. --- handlers/csp.go | 2 -- handlers/static/js/htmx-settings.js | 21 ++++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/handlers/csp.go b/handlers/csp.go index 6ef9be38..4c2fbde7 100644 --- a/handlers/csp.go +++ b/handlers/csp.go @@ -39,8 +39,6 @@ func enforceContentSecurityPolicy(next http.Handler) http.Handler { values: []string{ "'self'", "'nonce-" + nonce + "'", - // for htmx 2.0.4 inline style - "'sha256-bsV5JivYxvGywDAZ22EZJKBFip65Ng9xoJVLbBg7bdo='", }, }, { diff --git a/handlers/static/js/htmx-settings.js b/handlers/static/js/htmx-settings.js index fbd74ea1..c1a9d939 100644 --- a/handlers/static/js/htmx-settings.js +++ b/handlers/static/js/htmx-settings.js @@ -1,10 +1,29 @@ /* global htmx */ -// Tighten security. +// Keep htmx requests limited to this origin so hx-* attributes can't be used to +// send data to another site. htmx.config.selfRequestsOnly = true; + +// Server-rendered htmx responses don't need response-provided script execution. htmx.config.allowScriptTags = false; + +// Keep htmx from evaluating dynamic JavaScript strings. htmx.config.allowEval = false; +// Disable htmx's history cache to avoid stale pages and storing page contents in +// long-lived browser storage. +htmx.config.historyCacheSize = 0; + +// History restores should fetch full pages, not HX-Request partial responses. +htmx.config.historyRestoreAsHxRequest = false; + +// Indicator CSS lives in screenjournal.css, so htmx shouldn't inject its own +// inline style tag. +htmx.config.includeIndicatorStyles = false; + +// Fail stalled requests instead of leaving controls disabled indefinitely. +htmx.config.timeout = 5000; + // Don't let response-targets override isError. htmx.config.responseTargetUnsetsError = false; From 216ded90dc5706d18ec584caa8cac474b287e919 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Wed, 15 Jul 2026 12:11:13 +0000 Subject: [PATCH 3/3] Use htmx response handling config --- handlers/static/js/htmx-settings.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/handlers/static/js/htmx-settings.js b/handlers/static/js/htmx-settings.js index c1a9d939..824f53ac 100644 --- a/handlers/static/js/htmx-settings.js +++ b/handlers/static/js/htmx-settings.js @@ -27,18 +27,16 @@ htmx.config.timeout = 5000; // Don't let response-targets override isError. htmx.config.responseTargetUnsetsError = false; -// Module scripts run after the document is parsed, so document.body exists. -document.body.addEventListener("htmx:beforeSwap", function (evt) { - if (evt.detail.xhr.status === 204) { - evt.detail.shouldSwap = true; - } - if (evt.detail.xhr.status === 422) { - // allow 422 responses to swap as we are using this as a signal that - // a form was submitted with bad data and want to rerender with the - // errors - // - // set isError to false to avoid error logging in console - evt.detail.shouldSwap = true; - evt.detail.isError = false; - } -}); +htmx.config.responseHandling = [ + // Empty 204 responses from delete endpoints should clear their target. + { code: "204", swap: true }, + + // Validation errors should swap normally without console error noise. + { code: "422", swap: true, error: false }, + + // Successful non-empty responses should swap normally. + { code: "[23]..", swap: true }, + + // Let response-targets route error responses to hx-target-error elements. + { code: "[45]..", swap: false, error: true }, +];