Optimize feed generation and publication queries (#360) master
* Cut per-request query weight across the /lish read paths Follow-up round of feed/publication query optimization: - The /lish layout metadata query pulled every subscription row and every document's full record jsonb on every view of every publication page, then read only uri + record. Select just those two columns. - getPostPageData embedded each sibling post's full record only to derive prev/next links; project uri, sort_date, and the title/publishedAt scalars instead (publishedAt keeps normalizeDocument's gating of unpublished pub.leaflet records). - fetchPublicationForPage loaded the whole post list (full record jsonb plus three count embeds) even though most requests through it — every post URL, any page without a posts list — never render one. The publication row query no longer embeds documents; a separate fetchPublicationPostRows query runs only where a post list is actually rendered (legacy homepage, archive, posts-list blocks inside published pages). - generateFeed sorted and truncated the feed's 50 items in JS after fetching a publication's entire document history; order/limit at the database via the sort_date index and project only uri/data/sort_date. - Feed responses now send Last-Modified (the newest post's date) and honor If-Modified-Since for the readers that don't send If-None-Match; the JSON feed route gains the same conditional handling rss/atom already had. - The leaflet.pub feed-skeleton route selected entire document rows to read one post ref; project just the postRef/bskyPostRef/publishedAt fields, matching the feed service's existing fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017w9GceB5kdYvDiZtgEnsGL * Overlap independent fetches and simplify the deferred post-list plumbing Parallelization: - Publication homepage: the author profile lookup doesn't depend on the post rows, so run it alongside the rows -> bylines chain instead of serializing all three. - Archive: the /archive shadowing check and the post rows are independent; fetch them concurrently (the rows only go unused in the rare shadowed case). - PublicationPageRenderer: start the post-list query before the block-resource fetches (bsky API calls) so the two overlap. - generateFeed: kick off the react-dom/server import before the queries rather than blocking on it first. Simplification: - DefaultPublicationHomepage no longer accepts a raw documents_in_publications embed; every caller passes built posts (the theme preview builds them client-side, where bylines already resolve), removing the optional-prop fallback branch. - feedResponse expresses the If-None-Match / If-Modified-Since precedence as one not-modified decision with a single headers object. - Drop a redundant .slice() on buildPublicationPosts output (it already returns a fresh array). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017w9GceB5kdYvDiZtgEnsGL --------- Co-authored-by: Claude <noreply@anthropic.com>