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
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:
@@ -136,6 +136,23 @@
|
||||
"</form>"
|
||||
"</li>")))
|
||||
|
||||
;; One CURRENT-relation row, as host/blog--relation-editor renders it: an AJAX
|
||||
;; in-place remove (sx-post + sx-target=#cur-… + sx-swap=delete).
|
||||
(define cur-row-html
|
||||
(fn (slug kind other label)
|
||||
(str
|
||||
"<li id=\"cur-" kind "-" other "\">"
|
||||
"<a href=\"/" other "/\">" label "</a> "
|
||||
"<form method=\"post\" action=\"/" slug "/unrelate\""
|
||||
" sx-post=\"/" slug "/unrelate\""
|
||||
" sx-target=\"#cur-" kind "-" other "\""
|
||||
" sx-swap=\"delete\">"
|
||||
"<input type=\"hidden\" name=\"other\" value=\"" other "\">"
|
||||
"<input type=\"hidden\" name=\"kind\" value=\"" kind "\">"
|
||||
"<button type=\"submit\">remove</button>"
|
||||
"</form>"
|
||||
"</li>")))
|
||||
|
||||
;; The "load more" sentinel, as host/blog--picker-more renders it.
|
||||
(define sentinel-html
|
||||
(fn (slug kind offset)
|
||||
@@ -290,6 +307,44 @@
|
||||
(assert-equal 3 (count-candidates results))
|
||||
(clear-root! root))))
|
||||
|
||||
;; ── regression: removing a relation must not clear the relate picker ─
|
||||
;; The remove button is an AJAX in-place delete (sx-post + sx-swap=delete on its own
|
||||
;; current-relation row). Submitting it deletes ONLY that row — the sibling picker's
|
||||
;; candidate list is left intact, because #content is never re-rendered. (Bug: the
|
||||
;; old plain-boosted remove redirected and the re-rendered picker came back empty.)
|
||||
(defsuite
|
||||
"relate-picker:unrelate-keeps-picker"
|
||||
(deftest
|
||||
"removing a current relation deletes just that row, leaving the picker intact"
|
||||
(reset-fetch-mock!)
|
||||
;; the AJAX unrelate returns an empty 200 (like the host); sx-swap=delete then
|
||||
;; removes the current-relation row in place.
|
||||
(let
|
||||
((root (mk-root (str
|
||||
"<div id=\"cur-box\"><ul>"
|
||||
(cur-row-html "host" "related" "beta" "Beta")
|
||||
(cur-row-html "host" "related" "gamma" "Gamma")
|
||||
"</ul></div>"
|
||||
"<div id=\"res-box\"><ul class=\"rp-results\">"
|
||||
(row-html "host" "related" "delta" "Picker Delta")
|
||||
(row-html "host" "related" "epsilon" "Picker Epsilon")
|
||||
"</ul></div>")))
|
||||
(cur-box (dom-query "#cur-box"))
|
||||
(res-box (dom-query "#res-box")))
|
||||
(process-elements root)
|
||||
;; two current relations, two picker candidates to start
|
||||
(assert-equal 2 (len (dom-query-all cur-box "li")))
|
||||
(assert-equal 2 (len (dom-query-all res-box "li")))
|
||||
;; remove Beta — submit its in-place remove form
|
||||
(fire-event! (dom-query (dom-query "#cur-related-beta") "form") "submit")
|
||||
;; just Beta's row is gone; Gamma remains
|
||||
(assert-nil (dom-query "#cur-related-beta"))
|
||||
(assert-true (not (nil? (dom-query "#cur-related-gamma"))))
|
||||
(assert-equal 1 (len (dom-query-all cur-box "li")))
|
||||
;; and the picker's candidate list is UNTOUCHED — the bug was it cleared to 0
|
||||
(assert-equal 2 (len (dom-query-all res-box "li")))
|
||||
(clear-root! root))))
|
||||
|
||||
;; ── Phase 3: the engine drives a non-browser target (the console) ───
|
||||
;; render-to-console (web/console-render.sx) prints the live engine tree as text.
|
||||
;; These assert the picker's terminal rendering directly on a built tree — the
|
||||
|
||||
Reference in New Issue
Block a user