host: relations-as-posts slice 2.5 — picker title reads are O(page), not O(pool)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s

relate-candidates computes the available candidate SLUGS (slug-sorted, no per-candidate
read), then reads titles only for the page it returns. On the unfiltered path (q="" —
the initial picker load AND every editor server-fill, the common case) that's ~limit
durable reads instead of one-per-post, cutting the http-listen suspend/resume churn. A
filter (q≠"") still resolves titles across the pool since it matches on the title.

(A boot slug→title cache would make the filter O(1)-perform too, but it's blocked: no
bulk KV read, and a per-post host/blog-get loop at boot hits the JIT 'durable read in a
boot loop drops all-but-first' bug — see plans/relations-as-posts.md.)

conformance 291/291, run-picker-check 3/3 (incl. the title filter + paging).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 07:50:31 +00:00
parent 90190346aa
commit f94b9d0b93
2 changed files with 43 additions and 25 deletions

View File

@@ -418,24 +418,34 @@
(fn (kind other)
(contains? (host/blog--candidate-pool kind) other)))
(define host/blog--title (fn (s) (get (host/blog-get s) :title))) ;; one durable read
;; One PAGE of candidates (records {:slug :title}) for relating `slug` under `kind`.
;; Slice 2.5 — title reads are O(page), not O(pool): the available candidate SLUGS are
;; computed + slug-sorted with NO per-candidate read; then titles are fetched only for
;; the rows actually returned. On the unfiltered path (q="" — the initial picker load
;; AND every editor server-fill) that's ~`limit` reads instead of one-per-post, which
;; was the durable-read churn under http-listen. A filter (q≠"") still resolves titles
;; across the pool, since it matches on the title — but that's the interactive path.
(define host/blog--relate-candidates
(fn (slug q kind)
(let ((spec (host/blog--kind-spec kind)))
(let ((pool (host/blog--candidate-pool kind))
(already (host/blog-out slug kind))
(ql (lower (or q ""))))
;; pool is slugs; resolve titles, drop self + already-linked, filter by q
(let ((cands
(filter
(fn (p)
(or (= ql "")
(contains? (lower (get p :title)) ql)
(contains? (get p :slug) ql)))
(map (fn (s) {:slug s :title (get (host/blog-get s) :title)})
(filter (fn (s) (and (not (= s slug)) (not (contains? already s)))) pool)))))
;; title-sort via [title slug] pairs (sort compares the title first)
(map (fn (pair) {:slug (nth pair 1) :title (nth pair 0)})
(sort (map (fn (p) (list (get p :title) (get p :slug))) cands))))))))
(fn (slug q kind offset limit)
(let ((pool (host/blog--candidate-pool kind))
(already (host/blog-out slug kind))
(ql (lower (or q ""))))
(let ((avail (sort (filter (fn (s) (and (not (= s slug)) (not (contains? already s)))) pool))))
(if (= ql "")
;; no filter: page by slug, then read titles for just the page
(map (fn (s) {:slug s :title (host/blog--title s)})
(take (drop avail offset) limit))
;; filter: resolve titles, match on title|slug, then page
(let ((recs (map (fn (s) {:slug s :title (host/blog--title s)}) avail)))
(take
(drop
(filter (fn (r) (or (contains? (lower (get r :title)) ql)
(contains? (get r :slug) ql)))
recs)
offset)
limit)))))))
;; One candidate row: a tiny form whose button adds the relation under `kind`.
(define host/blog--picker-item
@@ -492,8 +502,7 @@
;; so a filter like "Item 13" arrives as "Item%2013" — decode it.
(q (dr/url-decode (or (dream-query-param req "q") "")))
(offset (host/query-int req "offset" 0)))
(let ((page (take (drop (host/blog--relate-candidates slug q kind) offset)
host/blog--picker-limit)))
(let ((page (host/blog--relate-candidates slug q kind offset host/blog--picker-limit)))
(let ((rows (join "" (map (fn (p) (render-page (host/blog--picker-item slug p kind))) page)))
(more (if (= (len page) host/blog--picker-limit)
(render-page (host/blog--picker-more slug kind q (+ offset host/blog--picker-limit)))
@@ -686,8 +695,7 @@
;; li-trees splice in as children (component args would evaluate them).
(results-ul
(let ((rows (if with-cands
(let ((cands (take (host/blog--relate-candidates slug "" kind)
host/blog--picker-limit)))
(let ((cands (host/blog--relate-candidates slug "" kind 0 host/blog--picker-limit)))
(append
(map (fn (p) (host/blog--picker-item slug p kind)) cands)
(if (= (len cands) host/blog--picker-limit)