host: no flash on relate/unrelate — server-render the picker's first page
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
Relating/removing re-renders the kind's editor (outerHTML); the swapped-in picker's results <ul> was empty and only filled after its 'load' fetch, so the candidate list briefly emptied (a visible flash). Render the first page of candidates INTO the results <ul> server-side (host/blog--relation-editor builds it inline via cons, the same splice pattern the current-relations list uses), so the re-rendered picker arrives already populated; the 'load' trigger then re-fetches the same page and morphs it in place — invisible. No empty state, no flash. Rendered inline rather than via the ~relate-picker component because component args are evaluated, so pre-built candidate li-trees can't be spliced through one (they'd be applied as calls). The component is left in place but unused. Server-side only — the client engine (orchestration.sxbc, last commit's re-bind fix) is unchanged. host conformance 278/278 (new: editor server-renders candidates), web engine suite 8/8, run-picker-check 3/3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <ul>, 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 <ul> — 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).
|
||||
|
||||
@@ -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
|
||||
;; <ul>, 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"
|
||||
|
||||
Reference in New Issue
Block a user