fix(web): load HTMX so task (+tree/dashboard/bulk/classify) forms work
ROOT CAUSE (head-diagnosed, confirmed): projax templates use hx-post/hx-get/ hx-target/hx-swap across the task, tree, dashboard, bulk and classify forms, but HTMX was NEVER loaded — layout.tmpl had only inline <script> blocks, no htmx <script src>. So every hx-post form silently no-op'd to a GET-to-self (logs: GET /i/social.mama on each 'Add' click). m hit it as 'can't add Tasks (projax), nothing happens'. Both the mBrian AND the older CalDAV task forms were dead. The style.css even comments 'HTMX-driven' — the script was just never wired. FIX: vendor htmx 1.9.12 into web/static (Tailscale-only app → vendored over CDN, matches the go:embed asset model) + one deferred <script> in layout <head>. htmx only intercepts hx-* elements, so the existing plain method=post forms are untouched. The task handlers already return section fragments for hx-swap, so the flow just works once htmx is present. Added htmx.min.js to the service-worker shell precache (CACHE_NAME v1→v2). The server-side write path was already proven green (TestMBrianTaskRoundTrip PASS with prod creds) — the bug was purely the client form never POSTing. Loading htmx closes that gap. Regression guard: TestLayoutLoadsHTMX asserts the script ships in the layout so this can't silently recur.
This commit is contained in:
@@ -185,6 +185,21 @@ func TestLayoutThemeToggleBoundToBothButtons(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLayoutLoadsHTMX guards against the Phase 7c regression: the task / tree
|
||||
// / dashboard / bulk / classify forms drive in-place swaps with hx-* attrs,
|
||||
// which are inert unless htmx is actually loaded. It went unnoticed for many
|
||||
// phases (every hx-post task form silently no-op'd to a GET-to-self). This
|
||||
// test fails the moment the vendored htmx <script> drops out of the layout.
|
||||
func TestLayoutLoadsHTMX(t *testing.T) {
|
||||
srv, pool := mustServer(t)
|
||||
defer pool.Close()
|
||||
h := srv.Routes()
|
||||
_, body := get(t, h, "/views/dashboard")
|
||||
if !strings.Contains(body, `src="/static/htmx.min.js"`) {
|
||||
t.Errorf("layout must load vendored htmx (hx-* forms are dead without it); body: %s", truncate(body, 400))
|
||||
}
|
||||
}
|
||||
|
||||
func truncate(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
|
||||
1
web/static/htmx.min.js
vendored
Normal file
1
web/static/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -12,9 +12,12 @@
|
||||
// real PWA + keep static assets warm. Mutations (CalDAV / Gitea writeback)
|
||||
// still require connectivity.
|
||||
|
||||
const CACHE_NAME = 'projax-shell-v1';
|
||||
// v2: htmx.min.js joins the shell so the HTMX-driven forms work offline too
|
||||
// (the cache name bump purges the v1 asset set on activate).
|
||||
const CACHE_NAME = 'projax-shell-v2';
|
||||
const SHELL_ASSETS = [
|
||||
'/static/style.css',
|
||||
'/static/htmx.min.js',
|
||||
'/static/manifest.webmanifest',
|
||||
'/static/icon-192.png',
|
||||
'/static/icon-512.png',
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/static/icon-192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/static/icon-512.png">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<!-- HTMX powers the in-place fragment swaps on the task / tree / dashboard /
|
||||
bulk / classify forms (hx-post/hx-get/hx-target/hx-swap). Vendored (not
|
||||
CDN) — projax is Tailscale-only and ships its assets via go:embed. Loaded
|
||||
deferred so it executes after parse but before DOMContentLoaded, where
|
||||
htmx wires every hx-* element. Plain method=post forms are untouched. -->
|
||||
<script src="/static/htmx.min.js" defer></script>
|
||||
<script>
|
||||
// Phase 5g — restore sidebar collapsed state BEFORE first paint so the
|
||||
// main-content margin doesn't flash from 220px→56px on every navigation.
|
||||
|
||||
Reference in New Issue
Block a user