host: removing a related post no longer clears the relate picker
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 34s

Bug: the edit page's remove button (on a current relation) was a plain boosted
form — POST /unrelate -> 303 redirect -> the engine re-rendered #content, and the
freshly-swapped relate picker came back EMPTY ("the list of posts to relate" was
cleared).

Fix: make the remove button an AJAX in-place delete, exactly like the relate
candidate rows — each current-relation <li> gets an id and its form carries
sx-post + sx-target=#cur-<kind>-<other> + sx-swap=delete. unrelate-submit returns
an empty 200 for that request so the engine deletes just that one row; #content is
never re-rendered, so the picker is untouched. method+action stay for no-JS.

The empty-200 is gated on the SX-Target header (sent only by the sx-post form), so
a plain boosted form / no-JS POST still redirects + re-renders — the is-a-tag
toggle and graceful degradation are unaffected.

Tests (all red before the fix):
 - lib/host/playwright/relate-picker.spec.js: the remove-button test now asserts
   the picker still has candidates after a removal (the reproduction).
 - web/tests/test-relate-picker.sx: an SX engine test — removing a current relation
   deletes just that row and leaves the sibling picker's list intact.
 - lib/host/tests/blog.sx: the relation-editor renders the AJAX delete attrs;
   unrelate returns empty-200 with SX-Target and 303 without.

Verified: host conformance 275/275, web engine suite 8/8, run-picker-check 2/2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 19:15:11 +00:00
parent 53de29158b
commit 09465f4483
4 changed files with 120 additions and 5 deletions

View File

@@ -295,6 +295,37 @@
(host-bl-test "relate-options omits the load-more sentinel on a short last page"
(contains? (dream-resp-body (host-bl-app (host-bl-req "/alpha-post/relate-options"))) "rp-more")
false)
;; -- unrelate: AJAX in-place row delete (regression: removing a related post must
;; NOT clear the relate picker). The remove button is now an sx-post form that
;; deletes just its own current-relation row (sx-target=#cur-…, sx-swap=delete),
;; so #content is never re-rendered and the picker is left intact. --
(host/blog-relate! "alpha-post" "beta-post" "related")
(host-bl-test "relation-editor remove button is an AJAX in-place delete"
(let ((html (render-page (host/blog--relation-editor "alpha-post" "related"))))
(list (contains? html "id=\"cur-related-beta-post\"") ;; row has a target id
(contains? html "sx-post=\"/alpha-post/unrelate\"") ;; AJAX, not plain post
(contains? html "sx-target=\"#cur-related-beta-post\"")
(contains? html "sx-swap=\"delete\"")))
(list true true true true))
;; the AJAX remove (carries SX-Target) returns an empty 200 so only the row is
;; swapped out — no redirect, no #content re-render that would blank the picker.
(host-bl-test "unrelate (AJAX, SX-Target) returns an empty 200"
(let ((resp (host/blog-unrelate-submit
(dream-request "POST" "/alpha-post/unrelate"
{:sx-request "true" :sx-target "#cur-related-beta-post"}
"other=beta-post&kind=related"))))
(list (dream-status resp) (dream-resp-body resp)))
(list 200 ""))
;; a plain boosted form / no-JS POST (no SX-Target) still redirects + re-renders,
;; so the is-a-tag toggle and graceful degradation are unaffected.
(host/blog-relate! "alpha-post" "beta-post" "related")
(host-bl-test "unrelate (plain boosted / no-JS, no SX-Target) still redirects"
(dream-status (host/blog-unrelate-submit
(dream-request "POST" "/alpha-post/unrelate"
{:sx-request "true"} "other=beta-post&kind=related")))
303)
(host/blog-unrelate! "alpha-post" "beta-post" "related")
(host/blog-put! "hint-post" "Hint Post" "(p \"h\")" "published")
(host-bl-test "relations section: hint when logged-in + no relations"
(contains? (str (host/blog--relations-or-hint "hint-post" true)) "add some") true)