From a04d241ec78e004e924d632c74a90e199633f5fd Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sat, 29 Aug 2026 13:46:49 +0000 Subject: [PATCH] fix(social): the feed page heading, which the title polish missed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #159 gave a social row its canonical name in the RSS channel title and in the page metadata, and left the thing a reader actually looks at — the

— saying "reddit.com". The cause was one line away from the change: `FEED_COLS` does not select `social_ref`, so `feedBySlug` has never returned it and the feed page could not tell a social row from any other. The RSS path worked only because it goes through `feedBySocialRef`, which is `select *`. So the columns join FEED_COLS, and the page uses them. Both addresses for a row now agree — /r/programming and /r-programming show the same name — which they did not before, since only one of them knew what it was holding. Verified against a seeded database rather than by reading: a row titled "reddit.com", exactly as the OPML import leaves them, now renders "r/programming" in the heading, the and the feed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q6QEgpuS4MLamogXtr2ZX6 --- apps/web/src/app/[slug]/page.jsx | 25 +++++++++++++++++++++++-- packages/db/src/queries.js | 3 ++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/[slug]/page.jsx b/apps/web/src/app/[slug]/page.jsx index 0fd94f1..fbdb1c3 100644 --- a/apps/web/src/app/[slug]/page.jsx +++ b/apps/web/src/app/[slug]/page.jsx @@ -1,5 +1,6 @@ import { notFound } from 'next/navigation'; import { q, alerts, queue, authors as people } from '@rssamplifier/db'; +import { socialDisplayTitle, socialPathFor } from '@rssamplifier/social'; import { db, siteUrl } from '../../lib/db.js'; import { currentUser } from '../../lib/auth.js'; @@ -42,7 +43,7 @@ export async function generateMetadata({ params }) { const feed = await q.feedBySlug(db(), slug); if (!feed) return { title: 'Not found' }; - const title = String(feed.title); + const title = displayTitle(feed); const description = feed.description ? String(feed.description) : `Latest ${(CATEGORIES[String(feed.category)] ?? CATEGORIES.blog).item} from ${feed.title}.`; @@ -232,7 +233,11 @@ export default async function FeedPage({ params }) { <p className="eyebrow"> <a href={category.path}>{category.one[0].toUpperCase() + category.one.slice(1)}</a> </p> - <h1>{feed.title}</h1> + {/* The canonical name where the stored title says nothing. Most of the + 50,026 imported subreddits have not been crawled yet and carry the + bare host as their title, so this heading read "reddit.com" on every + one of them. A real title always wins; see socialDisplayTitle. */} + <h1>{displayTitle(feed)}</h1> {feed.description && <p className="lede">{feed.description}</p>} <div className="feed-meta detail"> @@ -482,6 +487,22 @@ export default async function FeedPage({ params }) { * @param {unknown} iso * @returns {string} */ +/** + * What to call this feed on the page. + * + * Only ever different for a social row whose title was never crawled — every + * other feed, and every crawled social one, gets its own title back unchanged. + * `socialPathFor` supplies the fallback because the ref already encodes the + * canonical name: `/r/programming` becomes `r/programming`. + * + * @param {{ title?: unknown, feed_url?: unknown, social_ref?: unknown, slug?: unknown }} feed + * @returns {string} + */ +function displayTitle(feed) { + if (!feed?.social_ref) return String(feed?.title ?? ''); + return socialDisplayTitle(feed, socialPathFor(feed).replace(/^\//, '')); +} + function formatDate(iso) { if (!iso) return 'undated'; const d = new Date(String(iso)); diff --git a/packages/db/src/queries.js b/packages/db/src/queries.js index bc2f7d3..9c7a007 100644 --- a/packages/db/src/queries.js +++ b/packages/db/src/queries.js @@ -58,7 +58,8 @@ const FEED_COLS = `id, slug, feed_url, site_url, title, description, language, i author, categories, kind, category, category_source, status, last_fetched_at, last_success_at, last_error, error_count, fetch_interval_minutes, next_fetch_at, item_count, last_published_at, created_at, updated_at, source_kind, card_url, card_width, card_height, card_type, authors_checked_at, - http_etag, http_last_modified, content_hash, change_log`; + http_etag, http_last_modified, content_hash, change_log, + social_network, social_ref, social_config`; /** The categories the directory is browsable by. */ export const KINDS = ['blog', 'news', 'podcast', 'music', 'video', 'comic', 'live', 'reel'];