From c1260cfa68dd6dcad9b8dd91d3f446c0e44948f9 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Date: Mon, 25 May 2026 03:54:35 -0300 Subject: [PATCH] W6.1: batch-narrate all 122 documents + magazine titles from body H1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User wants every case in the archive documented in the narrated format. Queued a case_report for all 122 documents (slug = doc_id, scoped to that doc's chunks). The LISTEN worker drains 2 at a time (~5h, ~$18 OAuth). 5 reports already existed; ~122 new jobs queued, skipping any already queued/running/complete by slug. Title rendering: the batch seeds `topic` from the auto-derived canonical_title ("Doc 18 100754 General 1946 7 Vol 2"), which is ugly as a headline. The narrator writes a proper magazine H1 in the body, so: - /c/[slug]: extract the two body H1s (EN + PT-BR), use the locale one as the headline, strip both leading H1s from the rendered body so the title isn't duplicated. Falls back to frontmatter topic. - case-library.tsx + featured-case.tsx: same H1 extraction for card titles, so /bureau and the homepage show real titles not "Doc 18…". - featured-case: pin the flagship (green-fireballs-narrative, which has a curated hero painting) to the featured slot instead of most-recent, so the batch's random completions don't take over the homepage hero. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/app/c/[slug]/page.tsx | 13 +++++++++++-- web/components/case-library.tsx | 9 +++++++-- web/components/featured-case.tsx | 10 ++++++++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/web/app/c/[slug]/page.tsx b/web/app/c/[slug]/page.tsx index a8d2c85..c39ba57 100644 --- a/web/app/c/[slug]/page.tsx +++ b/web/app/c/[slug]/page.tsx @@ -121,9 +121,18 @@ export default async function CaseReportPage({ const locale = (await getLocale()) === "en" ? "en" : "pt-br"; const c = await loadCase(slug); if (!c) notFound(); - const { fm, body } = c; + const { fm } = c; - const title = locale === "pt-br" ? (fm.topic_pt_br ?? fm.topic ?? slug) : (fm.topic ?? slug); + // The narrator writes two H1s in the body: "# " then + // "# <Título PT-BR>". Prefer those over the generic frontmatter topic + // (which is often an auto-derived "Doc 18 100754 …"). Pick by locale and + // strip BOTH leading H1s from the body so the headline isn't duplicated. + const h1s = [...c.body.matchAll(/^#\s+(.+)$/gm)].map((m) => m[1].trim()); + const bodyTitle = locale === "pt-br" ? (h1s[1] ?? h1s[0]) : h1s[0]; + // Strip H1 lines ("# Title") — the "## section" headers survive since the + // regex requires whitespace after a single #. Avoids duplicating the title. + const body = c.body.replace(/^#\s+.+$\n?/gm, "").replace(/^\s+/, ""); + const title = bodyTitle ?? (locale === "pt-br" ? (fm.topic_pt_br ?? fm.topic ?? slug) : (fm.topic ?? slug)); const dateLabel = fm.created_at ? new Date(fm.created_at).toLocaleDateString(locale === "pt-br" ? "pt-BR" : "en-US", { day: "numeric", month: "long", year: "numeric" }) : null; const canonical = `${SITE_URL}/c/${slug}`; const lead = pickLead(body, locale).slice(0, 280); diff --git a/web/components/case-library.tsx b/web/components/case-library.tsx index edcd76a..3f8b8d2 100644 --- a/web/components/case-library.tsx +++ b/web/components/case-library.tsx @@ -93,10 +93,15 @@ async function loadCases(locale: "pt-br" | "en"): Promise<CaseFile[]> { const md = await readFile(full, "utf-8"); const st = await stat(full); const { fm, body } = parseFrontmatter(md); + // Prefer the narrator's body H1 (magazine title) over the generic + // auto-derived frontmatter topic. Two H1s: EN then PT-BR. + const h1s = [...body.matchAll(/^#\s+(.+)$/gm)].map((m) => m[1].trim()); + const enTitle = h1s[0] ?? fm.topic ?? f; + const ptTitle = h1s[1] ?? h1s[0] ?? fm.topic_pt_br ?? fm.topic ?? f; items.push({ slug: f.replace(/\.md$/, ""), - topic: fm.topic ?? f, - topic_pt_br: fm.topic_pt_br ?? null, + topic: enTitle, + topic_pt_br: ptTitle, opening: pickOpening(body, locale), mtimeMs: st.mtimeMs, }); diff --git a/web/components/featured-case.tsx b/web/components/featured-case.tsx index 601a487..d8fd5d9 100644 --- a/web/components/featured-case.tsx +++ b/web/components/featured-case.tsx @@ -81,10 +81,11 @@ async function loadFeatured(locale: "pt-br" | "en"): Promise<FeaturedCaseData | const { fm, body } = parseFrontmatter(md); const docRef = extractFirstDocRef(body); const slug = f.replace(/\.md$/, ""); + const h1s = [...body.matchAll(/^#\s+(.+)$/gm)].map((m) => m[1].trim()); items.push({ slug, - topic: fm.topic ?? f, - topic_pt_br: fm.topic_pt_br ?? null, + topic: h1s[0] ?? fm.topic ?? f, + topic_pt_br: h1s[1] ?? h1s[0] ?? fm.topic_pt_br ?? null, opening: pickOpening(body, locale), hero_doc_id: docRef?.doc_id ?? null, hero_page: docRef?.page ?? null, @@ -92,6 +93,11 @@ async function loadFeatured(locale: "pt-br" | "en"): Promise<FeaturedCaseData | mtimeMs: st.mtimeMs, }); } + // Pin a flagship to the featured slot (it has a curated hero painting). + // Falls back to most-recent if the flagship isn't present. + const FLAGSHIP = "green-fireballs-narrative"; + const flagship = items.find((i) => i.slug === FLAGSHIP); + if (flagship) return flagship; items.sort((a, b) => b.mtimeMs - a.mtimeMs); return items[0] ?? null; } catch {