host: relate removes just the picked candidate row in place (no reload)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s

Picking a candidate to relate it no longer does a full POST -> 303 -> reload.
The candidate <li> now carries an id and its relate form is an AJAX sx-post
(sx-target="#cand-<kind>-<other>", sx-swap="delete"): on success the engine
deletes just that one row — the item is now related, so it leaves the candidate
pool with no reload and no candidate-list refetch. host/blog-relate-submit returns
an empty 200 for an SX request (so the delete swap fires) and still 303s for a
plain POST (no-JS fallback via the form's method+action).

relate-picker.spec.js test 4 updated to assert the in-place row delete + no reload
+ the relation still persists (shows on the post page). 6/6 + conformance 272/272.

(Symmetric unrelate-in-place was prototyped but backed out: the current-links
form, bound via boot's process-elements rather than post-swap, didn't fire the
AJAX delete despite identical markup — a binding quirk to chase separately. Unrelate
keeps its plain POST -> reload for now, no regression.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 15:49:03 +00:00
parent b21ae05e8f
commit c0007740e7
2 changed files with 25 additions and 14 deletions

View File

@@ -362,13 +362,18 @@
(define host/blog--picker-item
(fn (slug p kind)
(quasiquote
(li :style "border-bottom:1px solid #eee"
;; sx-disable: this relate form is a plain POST -> 303 -> full reload (the
;; engine swaps the picker rows in, which would otherwise boost this form;
;; a boosted POST+redirect into #content swaps unreliably). A relate is a
;; deliberate action, so a clean reload that re-renders the editor is right.
(form :method "post" :style "margin:0" :sx-disable "true"
(li :id (unquote (str "cand-" kind "-" (get p :slug)))
:style "border-bottom:1px solid #eee"
;; AJAX relate: sx-post the relation, then sx-swap="delete" removes THIS
;; candidate row (its sx-target) — it's now related, so it leaves the pool
;; without a reload or a list refetch. method+action stay for the no-JS
;; fallback (plain POST -> 303 -> reload); the engine prevents the double
;; submit when it handles sx-post.
(form :method "post" :style "margin:0"
:action (unquote (str "/" slug "/relate"))
:sx-post (unquote (str "/" slug "/relate"))
:sx-target (unquote (str "#cand-" kind "-" (get p :slug)))
:sx-swap "delete"
(input :type "hidden" :name "other" :value (unquote (get p :slug)))
(input :type "hidden" :name "kind" :value (unquote kind))
(button :type "submit"
@@ -866,7 +871,12 @@
(when (and other (not (= other "")) (not (= other slug))
(host/blog--kind-spec kind) (host/blog-exists? other))
(host/blog-relate! slug other kind))
(dream-redirect (str "/" slug "/edit")))))))
;; AJAX (the picker's sx-post): return an empty 200 so the candidate
;; form's sx-swap="delete" removes just that one row — no full reload,
;; no candidate-list refetch. Plain POST (no-JS) still redirects.
(if (host/blog--spa-req? req)
(dream-html "")
(dream-redirect (str "/" slug "/edit"))))))))
;; POST /<slug>/unrelate — remove the relation to `other` under `kind` (default
;; "related"). Idempotent; redirects back to the edit page.