diff --git a/lib/host/blog.sx b/lib/host/blog.sx
index 528b1c79..ed48f5db 100644
--- a/lib/host/blog.sx
+++ b/lib/host/blog.sx
@@ -592,7 +592,26 @@
;; current edges read up front (a perform) — NOT inside the quasiquote, where
;; a perform would raise VmSuspended under http-listen.
(let ((spec (host/blog--kind-spec kind))
- (current (host/blog-out slug kind)))
+ (current (host/blog-out slug kind))
+ ;; the results
, server-rendered with the first page of candidates so a
+ ;; re-rendered editor's picker is never briefly empty (the load trigger then
+ ;; re-fetches the same page and morphs it in, invisibly). Built by cons so
+ ;; the candidate li-trees splice in as children (the same pattern the current
+ ;; list uses) — they can't be passed through a component arg (those evaluate).
+ (results-ul
+ (let ((cands (take (host/blog--relate-candidates slug "" kind)
+ host/blog--picker-limit)))
+ (let ((rows (append
+ (map (fn (p) (host/blog--picker-item slug p kind)) cands)
+ (if (= (len cands) host/blog--picker-limit)
+ (list (host/blog--picker-more slug kind "" host/blog--picker-limit))
+ (list)))))
+ (cons (quote ul)
+ (append
+ (quasiquote (:id (unquote (str "rp-" kind "-results"))
+ :class "rp-results"
+ :style "list-style:none;padding:0;margin:0.5em 0;border:1px solid #ddd"))
+ rows))))))
(quasiquote
;; #rel-editor-KIND wraps the WHOLE editor (current list + picker) so relate
;; and unrelate can re-render it with one outerHTML swap — keeping the two
@@ -623,12 +642,25 @@
(button :type "submit" "remove")))))
current))
(quote (p :style "opacity:0.7" "None yet."))))
- ;; The picker is now a reusable, content-addressed SX component
- ;; (lib/host/sx/relate-picker.sx). render-page expands it server-side on a
- ;; full load; on a boosted SPA nav the body serialises to the compact
- ;; (~relate-picker …) and the CLIENT expands it (the component module is
- ;; loaded content-addressed via the manifest at boot).
- (~relate-picker :slug (unquote slug) :kind (unquote kind)))))))
+ ;; The picker, rendered INLINE (not via the ~relate-picker component) so the
+ ;; first page of candidates is server-rendered into the results — the
+ ;; re-rendered editor shows them immediately, no empty flash. Same declarative
+ ;; SX-htmx form: GET relate-options, innerHTML-swap the results on a debounced
+ ;; "input" and on "load"; sx-retry self-heals a dropped fetch.
+ (form
+ :class "relate-picker"
+ :data-slug (unquote slug)
+ :data-kind (unquote kind)
+ :sx-get (unquote (str "/" slug "/relate-options"))
+ :sx-trigger "input delay:200ms, load"
+ :sx-target (unquote (str "#rp-" kind "-results"))
+ :sx-swap "innerHTML"
+ :sx-retry "exponential:1000:30000"
+ :style "margin:0"
+ (input :type "hidden" :name "kind" :value (unquote kind))
+ (input :type "text" :name "q" :class "rp-filter" :placeholder "filter…"
+ :autocomplete "off" :style "width:100%;padding:0.4em;box-sizing:border-box")
+ (unquote results-ul)))))))
;; "Is this post a tag?" toggle — marking a post a tag is just an is-a edge to the
;; "tag" type-post, so it reuses the relate/unrelate routes (no new endpoint).
diff --git a/lib/host/tests/blog.sx b/lib/host/tests/blog.sx
index 72f3cb30..4293a4aa 100644
--- a/lib/host/tests/blog.sx
+++ b/lib/host/tests/blog.sx
@@ -281,6 +281,14 @@
(contains? html "input delay:200ms, load")
(contains? html "rp-related-results")))
(list true true true))
+;; the editor server-renders the first page of candidates INTO the picker's results
+;; , so a re-rendered editor is never briefly empty (no flash). The candidate row
+;; for an existing post appears inside the results ul.
+(host-bl-test "editor server-renders the first page of candidates into the picker"
+ (let ((html (render-page (host/blog--relation-editor "alpha-post" "related"))))
+ (list (contains? html "id=\"cand-related-") ;; a candidate row is present
+ (contains? html "Beta Post"))) ;; an unrelated post is offered
+ (list true true))
;; Paging is server-driven: a full page carries a "load more" sentinel that, when
;; revealed, GETs the next page and replaces itself (outerHTML), preserving q.
(host-bl-test "load-more sentinel: revealed, outerHTML-swap, next offset, preserved q"