@fullstackdatasolutions/articles 1.2.3 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +313 -1
  3. package/dist/index.cjs +308 -79
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +267 -16
  6. package/dist/index.d.ts +267 -16
  7. package/dist/index.js +300 -79
  8. package/dist/index.js.map +1 -1
  9. package/dist/nextjs.cjs +325 -31
  10. package/dist/nextjs.cjs.map +1 -1
  11. package/dist/nextjs.d.cts +179 -2
  12. package/dist/nextjs.d.ts +179 -2
  13. package/dist/nextjs.js +325 -31
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +660 -50
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +333 -12
  18. package/dist/server.d.ts +333 -12
  19. package/dist/server.js +645 -50
  20. package/dist/server.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/ArticleAnswer.tsx +35 -0
  23. package/src/ArticleSchemas.tsx +263 -23
  24. package/src/AuthorArticlesPage.tsx +38 -8
  25. package/src/__tests__/ArticleAnswer.test.tsx +25 -0
  26. package/src/__tests__/ArticleSchemas.test.tsx +516 -0
  27. package/src/__tests__/AuthorArticlesPage.test.tsx +76 -0
  28. package/src/__tests__/authorUtils.test.ts +50 -0
  29. package/src/__tests__/markdown.test.ts +77 -1
  30. package/src/__tests__/nextjs.test.ts +31 -15
  31. package/src/__tests__/seoUtils.test.ts +279 -0
  32. package/src/__tests__/server-articles.test.ts +434 -1
  33. package/src/__tests__/validateArticles.test.ts +167 -6
  34. package/src/articleTypes.ts +57 -0
  35. package/src/articlesConfig.ts +176 -1
  36. package/src/authorUtils.ts +19 -1
  37. package/src/errorReporting.ts +1 -0
  38. package/src/index.ts +17 -1
  39. package/src/markdown.ts +100 -1
  40. package/src/nextjs.ts +7 -4
  41. package/src/seoUtils.ts +247 -26
  42. package/src/server-articles.ts +385 -25
  43. package/src/server.ts +35 -4
  44. package/src/validateArticles.ts +157 -12
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/server-articles.ts","../src/authorUtils.ts","../src/markdown.ts","../src/errorReporting.ts","../src/linkClassification.ts","../src/articlesConfig.ts","../src/pagination.ts","../src/seoUtils.ts","../src/renderMdx.tsx","../src/ArticleContent.tsx","../src/ArticleTOC.tsx","../src/validateArticles.ts","../src/events.ts"],"sourcesContent":["// Server-only exports — uses fs/path; never import this in a client bundle\nexport {\n getAllArticles,\n getAiRobotsTxtRules,\n getArticleAiHeaders,\n getArticleMarkdown,\n getArticleMarkdownResponse,\n getArticleMarkdownUrl,\n getArticleMetadata,\n getArticleAuthors,\n getAvailableArticleSlugs,\n getAdjacentArticles,\n getAdjacentArticlesInSeries,\n getArticlesBySeries,\n getRelatedArticlesByCategory,\n getRelatedContent,\n getPath,\n getPathArticles,\n getAllAuthors,\n searchArticles,\n getAuthorBySlug,\n getAllCategories,\n getArticlesByAuthor,\n getArticlesByCategory,\n categoryToSlug,\n sanitizeImagePath,\n} from './server-articles'\n\nexport {\n generateRssFeed,\n generateArticleStaticParams,\n generateCategoryStaticParams,\n generateSeriesStaticParams,\n generateAuthorStaticParams,\n generateArticlesIndexMetadata,\n generateArticlesIndexPageMetadata,\n generateArticleMetadata,\n generateCategoryMetadata,\n generateCategoryPageMetadata,\n generateSeriesMetadata,\n generateAuthorMetadata,\n generateAuthorPageMetadata,\n buildArticleBreadcrumbs,\n buildCategoryBreadcrumbs,\n buildAuthorBreadcrumbs,\n resolveAuthorAvatar,\n resolveSearchMetadata,\n resolveSocialMetadata,\n getArticleSitemapEntries,\n} from './seoUtils'\n\nexport {\n getTotalPages,\n paginateArticles,\n buildPageUrl,\n buildPaginationLinks,\n generateListingPageStaticParams,\n parsePageParam,\n isPageOutOfRange,\n} from './pagination'\n\nexport { markdownToHtml, extractToc, getContentSlotBoundaries } from './markdown'\nexport { setArticlesErrorHandler } from './errorReporting'\nexport { getBreadcrumbsConfig } from './articlesConfig'\nexport { ArticleContent } from './ArticleContent'\nexport { ArticleTOC } from './ArticleTOC'\nexport { validateArticles, validateAllArticles } from './validateArticles'\nexport { emitArticleEvent } from './events'\n\nexport type {\n Article,\n AuthorProfile,\n AuthorSocial,\n BreadcrumbItem,\n CategoryInfo,\n PathDefinition,\n TocItem,\n} from './articleTypes'\nexport type { ArticlesConfig, LinkTargetStrategy, ListingPagination } from './articlesConfig'\nexport type { PaginatedArticles, PaginationLinks, ListingPaginationContext } from './pagination'\nexport type { ContentSlotBoundaries } from './markdown'\nexport type { ArticleSlotContext, ArticleSlotContent } from './ArticleContent'\nexport type { RelatedContentResult, RelatedContentSource } from './server-articles'\nexport type { ValidationIssue, ValidationResult, ValidationSeverity } from './validateArticles'\nexport type {\n ArticleEvent,\n ArticleEventHandler,\n ArticleEventName,\n ArticleViewedEvent,\n MeaningfulReadEvent,\n AuthorClickedEvent,\n CtaViewedEvent,\n CtaClickedEvent,\n SharedEvent,\n RelatedArticleClickedEvent,\n PathStepAdvancedEvent,\n} from './events'\nexport type {\n ArticlesErrorCode,\n ArticlesErrorContext,\n ArticlesErrorHandler,\n ArticlesErrorReport,\n} from './errorReporting'\n","import { cache } from 'react'\nimport matter from 'gray-matter'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport readingTime from 'reading-time'\nimport { getAuthorAvatar } from './authorUtils'\nimport { markdownToHtml, extractToc } from './markdown'\nimport type {\n Article,\n AuthorProfile,\n CategoryInfo,\n FaqItem,\n HowToStep,\n PathDefinition,\n} from './articleTypes'\nimport type { ArticlesConfig } from './articlesConfig'\nimport { reportArticlesError } from './errorReporting'\n\nconst articlesDirectory = path.join(/* turbopackIgnore: true */ process.cwd(), 'public/articles')\n\nfunction getReadingStats(content: string): { readTime: string; wordCount: number } {\n const stats = readingTime(content)\n return { readTime: stats.text, wordCount: stats.words }\n}\n\nfunction findArticleImage(slug: string): string | null {\n try {\n const articleDir = path.join(articlesDirectory, slug)\n const entries: (fs.Dirent | string)[] = fs.readdirSync(articleDir, { withFileTypes: true })\n const names = entries\n .map((entry) => {\n if (typeof entry === 'string') return entry\n if (entry && typeof entry.name === 'string') return entry.name\n return null\n })\n .filter((name): name is string => Boolean(name))\n const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp']\n const imageFileName = names.find((name) =>\n imageExtensions.some((ext) => name.toLowerCase().endsWith(ext))\n )\n return imageFileName ? sanitizeImagePath(imageFileName, slug) : null\n } catch {\n return null\n }\n}\n\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') return null\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) return cleanPath\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected unsafe article image path.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected article image path with invalid characters.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n if (cleanPath.includes('/')) {\n const normalizedPath = path.normalize(cleanPath)\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected article image path traversal attempt.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n return `/articles/${articleSlug}/${cleanPath}`\n }\n return `/articles/${articleSlug}/${cleanPath}`\n}\n\nfunction findArticleFile(slug: string): { filePath: string; contentType: 'md' | 'mdx' } | null {\n const mdPath = path.join(articlesDirectory, slug, 'article.md')\n const mdxPath = path.join(articlesDirectory, slug, 'article.mdx')\n if (fs.existsSync(mdPath)) return { filePath: mdPath, contentType: 'md' }\n if (fs.existsSync(mdxPath)) return { filePath: mdxPath, contentType: 'mdx' }\n return null\n}\n\nexport function getAvailableArticleSlugs(): string[] {\n try {\n if (!fs.existsSync(articlesDirectory)) return []\n return walkArticleDir(articlesDirectory, '')\n } catch (error) {\n reportArticlesError({\n code: 'article-directory-read-failed',\n message: 'Unable to read articles directory.',\n error,\n context: { directory: articlesDirectory },\n })\n return []\n }\n}\n\nfunction walkArticleDir(dir: string, baseSlug: string): string[] {\n const slugs: string[] = []\n try {\n const items = fs.readdirSync(dir, { withFileTypes: true })\n for (const item of items) {\n if (!item.isDirectory()) continue\n const slug = baseSlug ? `${baseSlug}/${item.name}` : item.name\n if (findArticleFile(slug) !== null) {\n slugs.push(slug)\n }\n slugs.push(...walkArticleDir(path.join(dir, item.name), slug))\n }\n } catch {\n // ignore unreadable directories\n }\n return slugs\n}\n\nfunction parseDateField(rawDate: unknown): string | undefined {\n if (!rawDate) return undefined\n try {\n const parsed = new Date(rawDate as string)\n if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split('T')[0]\n } catch {\n // ignore invalid dates\n }\n return undefined\n}\n\nfunction resolveFeaturedImage(rawImage: unknown, slug: string): string {\n const img = typeof rawImage === 'string' ? rawImage : ''\n if (img && !img.startsWith('http')) return sanitizeImagePath(img, slug) || '/placeholder-logo.png'\n if (!img) return findArticleImage(slug) || '/placeholder-logo.png'\n return img\n}\n\nfunction parseFaqItems(raw: unknown): FaqItem[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const items = raw.filter(\n (item): item is FaqItem =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as FaqItem).question === 'string' &&\n typeof (item as FaqItem).answer === 'string'\n )\n return items.length ? items : undefined\n}\n\nfunction parseHowToSteps(raw: unknown): HowToStep[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const steps = raw.filter(\n (item): item is HowToStep =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as HowToStep).name === 'string' &&\n typeof (item as HowToStep).text === 'string'\n )\n return steps.length ? steps : undefined\n}\n\n// Trims and drops empty strings - the same \"unset if blank\" rule applied to\n// every other optional string frontmatter field in this file\n// (canonicalUrl/articleType/series). Used for all five discovery overrides\n// (Phase 27F) so a stray `searchTitle: \"\"` in frontmatter behaves exactly\n// like omitting the key, rather than becoming an empty <title> override.\nfunction parseOptionalString(raw: unknown): string | undefined {\n return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : undefined\n}\n\nfunction parseSeriesOrder(raw: unknown): number | undefined {\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined\n}\n\nfunction parsePrimaryAction(raw: unknown): { actionId: string } | undefined {\n if (typeof raw === 'string') {\n const actionId = raw.trim()\n return actionId ? { actionId } : undefined\n }\n if (\n typeof raw === 'object' &&\n raw !== null &&\n typeof (raw as { actionId?: unknown }).actionId === 'string'\n ) {\n const actionId = (raw as { actionId: string }).actionId.trim()\n return actionId ? { actionId } : undefined\n }\n return undefined\n}\n\nfunction parseAuthors(raw: unknown): string[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const authors = raw.filter(\n (author): author is string => typeof author === 'string' && author.trim().length > 0\n )\n return authors\n}\n\nexport function getAuthorBySlug(slug: string, config: ArticlesConfig): AuthorProfile | null {\n const profile = config.authors?.[slug]\n if (!profile) return null\n return {\n ...profile,\n url: profile.url ?? `${config.siteUrl.replace(/\\/$/, '')}/articles/authors/${profile.slug}`,\n }\n}\n\nfunction getConfiguredAuthorByName(name: string, config: ArticlesConfig): AuthorProfile | null {\n const normalizedName = name.trim().toLowerCase()\n const profile = Object.values(config.authors ?? {}).find(\n (author) => author.name.toLowerCase() === normalizedName\n )\n return profile ? getAuthorBySlug(profile.slug, config) : null\n}\n\nfunction resolveArticleAuthorName(\n rawAuthor: unknown,\n rawAuthors: unknown,\n config?: ArticlesConfig\n): string {\n const authorArray = parseAuthors(rawAuthors)\n const firstAuthor = authorArray?.[0]\n const rawAuthorValue = typeof rawAuthor === 'string' && rawAuthor.trim() ? rawAuthor : undefined\n const author = firstAuthor ?? rawAuthorValue\n const resolved = author ?? config?.defaultAuthor\n if (!resolved) return ''\n if (!config) return resolved\n return (\n getAuthorBySlug(resolved, config)?.name ??\n getConfiguredAuthorByName(resolved, config)?.name ??\n resolved\n )\n}\n\nexport function getArticleAuthors(article: Article, config: ArticlesConfig): AuthorProfile[] {\n const fallbackAuthors = Array.from(\n new Set(\n [article.author, config.defaultAuthor].filter(\n (author): author is string => typeof author === 'string' && author.trim().length > 0\n )\n )\n )\n const authorValues = article.authors ?? fallbackAuthors\n if (authorValues.length === 0) return []\n const resolvedAuthors = authorValues\n .map((author) => getAuthorBySlug(author, config) ?? getConfiguredAuthorByName(author, config))\n .filter((author): author is AuthorProfile => author !== null)\n .filter((author, index, all) => all.findIndex((a) => a.slug === author.slug) === index)\n\n if (resolvedAuthors.length > 0) return resolvedAuthors\n\n return authorValues.map((fallbackName) => ({\n name: fallbackName,\n slug: categoryToSlug(fallbackName),\n bio: '',\n }))\n}\n\nexport function getAllAuthors(config: ArticlesConfig): AuthorProfile[] {\n return Object.keys(config.authors ?? {})\n .map((slug) => getAuthorBySlug(slug, config))\n .filter((author): author is AuthorProfile => author !== null)\n}\n\nasync function getArticleSummary(slug: string, config?: ArticlesConfig): Promise<Article | null> {\n try {\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { data, content: markdownContent } = matter(fileContent)\n const { readTime, wordCount } = getReadingStats(markdownContent)\n const allTags: string[] = Array.isArray(data.tags)\n ? data.tags.filter((t: unknown) => typeof t === 'string' && String(t).trim())\n : []\n const categories: string[] =\n allTags.length > 0 ? allTags.map((t: string) => t.replaceAll('-', ' ').trim()) : ['Campaigns']\n const author = resolveArticleAuthorName(data.author, data.authors, config)\n const authors = parseAuthors(data.authors)\n // Resolve the primary author's profile (if configured) so cards can\n // render an avatar/link without needing `config` client-side. Reuses\n // getArticleAuthors' existing slug/name resolution chain instead of\n // duplicating it - only the two fields it reads (author/authors) exist\n // on this partial yet.\n const primaryAuthorProfile = config\n ? getArticleAuthors({ author, authors } as Article, config)[0]\n : undefined\n return {\n slug,\n title: data.title || slug.replaceAll('-', ' '),\n excerpt: data.excerpt || '',\n date: parseDateField(data.date),\n lastmod: parseDateField(data.lastmod),\n author,\n authors,\n authorSlug: primaryAuthorProfile?.slug,\n authorAvatar: primaryAuthorProfile ? getAuthorAvatar(primaryAuthorProfile) : undefined,\n category: categories[0],\n categories,\n readTime,\n wordCount,\n featuredImage: resolveFeaturedImage(data.featuredImage, slug),\n tags: data.tags || [],\n contentType: found.contentType,\n draft: data.draft === true,\n faq: parseFaqItems(data.faq),\n howTo: parseHowToSteps(data.howTo),\n canonicalUrl: typeof data.canonicalUrl === 'string' ? data.canonicalUrl : undefined,\n articleType: typeof data.articleType === 'string' ? data.articleType : undefined,\n series: typeof data.series === 'string' ? data.series : undefined,\n seriesSlug: parseOptionalString(data.seriesSlug),\n seriesOrder: parseSeriesOrder(data.seriesOrder),\n aiCrawl: data.aiCrawl === true,\n searchTitle: parseOptionalString(data.searchTitle),\n searchDescription: parseOptionalString(data.searchDescription),\n socialTitle: parseOptionalString(data.socialTitle),\n socialDescription: parseOptionalString(data.socialDescription),\n socialImage: parseOptionalString(data.socialImage),\n primaryAction: parsePrimaryAction(data.primaryAction),\n }\n } catch (error) {\n reportArticlesError({\n code: 'article-load-failed',\n message: 'Unable to load article summary.',\n error,\n context: { slug },\n })\n return null\n }\n}\n\nexport const getArticleMetadata = cache(\n async (slug: string, config?: ArticlesConfig): Promise<Article | null> => {\n try {\n const summary = await getArticleSummary(slug, config)\n if (!summary) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n const toc = await extractToc(markdownContent)\n let htmlContent: string | undefined\n let mdxSource: string | undefined\n if (found.contentType === 'mdx') {\n mdxSource = markdownContent\n } else {\n htmlContent = await markdownToHtml(markdownContent, slug, config)\n }\n return { ...summary, content: markdownContent, htmlContent, mdxSource, toc }\n } catch (error) {\n reportArticlesError({\n code: 'article-load-failed',\n message: 'Unable to load article metadata.',\n error,\n context: { slug },\n })\n return null\n }\n }\n)\n\nexport const getAllArticles = cache(async (config?: ArticlesConfig): Promise<Article[]> => {\n const slugs = getAvailableArticleSlugs()\n const articles = await Promise.all(slugs.map((slug) => getArticleSummary(slug, config)))\n const currentDate = new Date().toISOString().split('T')[0]\n return articles\n .filter((article): article is Article => article !== null)\n .filter((article) => !article.date || article.date <= currentDate)\n .filter((article) => !(article.draft && process.env.NODE_ENV === 'production'))\n .sort((a, b) => {\n if (!a.date && !b.date) return 0\n if (!a.date) return 1\n if (!b.date) return -1\n return new Date(b.date).getTime() - new Date(a.date).getTime()\n })\n})\n\nexport async function getAdjacentArticles(\n currentSlug: string\n): Promise<{ previous: Article | null; next: Article | null }> {\n const allArticles = await getAllArticles()\n const currentIndex = allArticles.findIndex((article) => article.slug === currentSlug)\n if (currentIndex === -1) return { previous: null, next: null }\n const previous = currentIndex < allArticles.length - 1 ? allArticles[currentIndex + 1] : null\n const next = currentIndex > 0 ? allArticles[currentIndex - 1] : null\n return { previous, next }\n}\n\nexport async function getArticleMarkdown(slug: string): Promise<string | null> {\n try {\n const summary = await getArticleSummary(slug)\n if (!summary?.aiCrawl) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n return markdownContent\n } catch (error) {\n reportArticlesError({\n code: 'article-markdown-load-failed',\n message: 'Unable to load article markdown.',\n error,\n context: { slug },\n })\n return null\n }\n}\n\nexport async function getArticleMarkdownResponse(\n slug: string,\n config: ArticlesConfig\n): Promise<Response> {\n const markdown = await getArticleMarkdown(slug)\n if (markdown === null) return new Response('Not Found', { status: 404 })\n const article = await getArticleMetadata(slug)\n return new Response(markdown, {\n headers: {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n ...(article ? getArticleAiHeaders(article, config) : {}),\n },\n })\n}\n\nexport function getArticleMarkdownUrl(\n article: Pick<Article, 'slug' | 'aiCrawl'>,\n config?: Pick<ArticlesConfig, 'siteUrl'>\n): string | undefined {\n if (article.aiCrawl !== true) return undefined\n const pathname = `/articles/${article.slug}.md`\n if (!config) return pathname\n return `${config.siteUrl.replace(/\\/$/, '')}${pathname}`\n}\n\nexport function getArticleAiHeaders(\n article: Pick<Article, 'slug' | 'aiCrawl'>,\n config?: Pick<ArticlesConfig, 'siteUrl'>\n): Record<string, string> {\n const markdownUrl = getArticleMarkdownUrl(article, config)\n if (markdownUrl) {\n return {\n Link: `<${markdownUrl}>; rel=\"alternate\"; type=\"text/markdown\"`,\n }\n }\n return {\n 'X-Robots-Tag': 'noai, noimageai',\n }\n}\n\nexport async function getAiRobotsTxtRules(): Promise<string> {\n const articles = await getAllArticles()\n const blockedArticles = articles.filter((article) => article.aiCrawl !== true)\n if (blockedArticles.length === 0) return ''\n\n const aiCrawlers = [\n 'GPTBot',\n 'ChatGPT-User',\n 'CCBot',\n 'ClaudeBot',\n 'Claude-User',\n 'PerplexityBot',\n 'Google-Extended',\n ]\n const disallowRules = blockedArticles\n .map((article) => `Disallow: /articles/${article.slug}`)\n .join('\\n')\n\n return aiCrawlers\n .map((crawler) => [`User-agent: ${crawler}`, disallowRules].join('\\n'))\n .join('\\n\\n')\n}\n\nexport async function searchArticles(query: string, config?: ArticlesConfig): Promise<Article[]> {\n if (!query?.trim()) return getAllArticles(config)\n const articles = await getAllArticles(config)\n const searchTerm = query.toLowerCase().trim()\n const includeAuthor = config?.showAuthor !== false\n return articles.filter((article) => {\n const matchesTitle = article.title.toLowerCase().includes(searchTerm)\n const matchesExcerpt = article.excerpt.toLowerCase().includes(searchTerm)\n const matchesAuthor = includeAuthor && article.author.toLowerCase().includes(searchTerm)\n const matchesCategory = article.categories.some((cat) => cat.toLowerCase().includes(searchTerm))\n const matchesTags = article.tags?.some((tag) => tag.toLowerCase().includes(searchTerm))\n return (\n matchesTitle || matchesExcerpt || matchesAuthor || matchesCategory || Boolean(matchesTags)\n )\n })\n}\n\nexport function categoryToSlug(category: string): string {\n return category\n .toLowerCase()\n .replaceAll(/\\s+/g, '-')\n .replaceAll(/[^a-z0-9-]/g, '')\n}\n\nexport async function getAllCategories(): Promise<CategoryInfo[]> {\n const articles = await getAllArticles()\n const categoryMap = new Map<string, { count: number; featuredImage: string }>()\n for (const article of articles) {\n for (const cat of article.categories) {\n if (!categoryMap.has(cat)) {\n categoryMap.set(cat, { count: 0, featuredImage: article.featuredImage })\n }\n categoryMap.get(cat)!.count++\n }\n }\n return Array.from(categoryMap.entries())\n .map(([name, { count, featuredImage }]) => ({\n name,\n slug: categoryToSlug(name),\n count,\n featuredImage,\n }))\n .sort((a, b) => b.count - a.count)\n}\n\nexport async function getArticlesByCategory(\n categorySlug: string,\n config?: ArticlesConfig\n): Promise<Article[]> {\n const articles = await getAllArticles(config)\n return articles.filter((article) =>\n article.categories.some((cat) => categoryToSlug(cat) === categorySlug)\n )\n}\n\n// Built on getArticlesByCategory (same slug-matching filter, no duplicated\n// logic) rather than getAdjacentArticles' global date-order walk, so an\n// article detail page can link to other articles in the same category\n// instead of just the two chronologically-nearest articles overall.\nexport async function getRelatedArticlesByCategory(\n currentSlug: string,\n category: string,\n limit = 3,\n config?: ArticlesConfig\n): Promise<Article[]> {\n const articles = await getArticlesByCategory(categoryToSlug(category), config)\n return articles.filter((article) => article.slug !== currentSlug).slice(0, limit)\n}\n\nexport async function getArticlesByAuthor(\n authorSlug: string,\n config: ArticlesConfig\n): Promise<Article[]> {\n const articles = await getAllArticles(config)\n return articles.filter((article) =>\n getArticleAuthors(article, config).some((author) => author.slug === authorSlug)\n )\n}\n\n// Sorted by `seriesOrder` ascending (undefined pushed to the end); ties fall\n// back to the date-descending order `getAllArticles` already applies, since\n// `Array.prototype.sort` is stable - matching `getArticlesByCategory`'s\n// \"build on the existing filter, don't duplicate `getAllArticles`\" pattern.\n// The label-only `series` string field is untouched by this function.\nexport async function getArticlesBySeries(\n seriesSlug: string,\n config?: ArticlesConfig\n): Promise<Article[]> {\n const articles = await getAllArticles(config)\n return articles\n .filter((article) => article.seriesSlug === seriesSlug)\n .sort((a, b) => {\n const orderA = a.seriesOrder ?? Number.POSITIVE_INFINITY\n const orderB = b.seriesOrder ?? Number.POSITIVE_INFINITY\n return orderA - orderB\n })\n}\n\n/**\n * Series-aware sibling of `getAdjacentArticles`: walks `seriesOrder` within\n * one series instead of global date order. `previous`/`next` follow series\n * order (ascending), not chronology.\n */\nexport async function getAdjacentArticlesInSeries(\n currentSlug: string,\n seriesSlug: string,\n config?: ArticlesConfig\n): Promise<{ previous: Article | null; next: Article | null }> {\n const seriesArticles = await getArticlesBySeries(seriesSlug, config)\n const currentIndex = seriesArticles.findIndex((article) => article.slug === currentSlug)\n if (currentIndex === -1) return { previous: null, next: null }\n return {\n previous: currentIndex > 0 ? seriesArticles[currentIndex - 1] : null,\n next: currentIndex < seriesArticles.length - 1 ? seriesArticles[currentIndex + 1] : null,\n }\n}\n\n/** Looks up one configured `PathDefinition` by its app-chosen key. */\nexport function getPath(pathKey: string, config: ArticlesConfig): PathDefinition | null {\n return config.paths?.[pathKey] ?? null\n}\n\n/** Resolves a path's ordered slugs against the real article set, dropping any that don't resolve (e.g. a draft filtered out of `getAllArticles` in production) rather than throwing - use `validateArticles` to catch broken references before publishing. */\nexport async function getPathArticles(pathKey: string, config: ArticlesConfig): Promise<Article[]> {\n const path = getPath(pathKey, config)\n if (!path) return []\n const articles = await getAllArticles(config)\n const bySlug = new Map(articles.map((article) => [article.slug, article]))\n return path.articles\n .map((slug) => bySlug.get(slug))\n .filter((article): article is Article => Boolean(article))\n}\n\nfunction findPathForArticle(\n slug: string,\n config: ArticlesConfig\n): { key: string; path: PathDefinition } | null {\n for (const [key, path] of Object.entries(config.paths ?? {})) {\n if (path.articles.includes(slug)) return { key, path }\n }\n return null\n}\n\nexport type RelatedContentSource = 'path' | 'series' | 'category'\n\nexport interface RelatedContentResult {\n source: RelatedContentSource\n /** Heading for a related-content UI - the path's `name`, the article's `series` label, or \"More in {category}\". */\n heading: string\n articles: Article[]\n /** Set only when `source === 'path'`. */\n pathKey?: string\n /** Set only when `source === 'path'` - the path's one configured next action. */\n nextAction?: { label: string; href: string }\n}\n\n/**\n * Reusable related-content selection (Phase 27F): prefers a configured\n * `Path` containing this article first, then the article's `seriesSlug`,\n * falling back to 27B's `getRelatedArticlesByCategory` (imported, not\n * reimplemented) when neither a path nor a series applies - the plain\n * chronological-within-category behavior stays the fallback, not a full\n * replacement.\n */\nexport async function getRelatedContent(\n article: Article,\n config: ArticlesConfig,\n limit = 3\n): Promise<RelatedContentResult> {\n const matchedPath = findPathForArticle(article.slug, config)\n if (matchedPath) {\n const pathArticles = await getPathArticles(matchedPath.key, config)\n return {\n source: 'path',\n heading: matchedPath.path.name,\n articles: pathArticles.filter((a) => a.slug !== article.slug),\n pathKey: matchedPath.key,\n nextAction: matchedPath.path.nextAction,\n }\n }\n if (article.seriesSlug) {\n const seriesArticles = await getArticlesBySeries(article.seriesSlug, config)\n return {\n source: 'series',\n heading: article.series ?? 'This series',\n articles: seriesArticles.filter((a) => a.slug !== article.slug),\n }\n }\n const categoryArticles = await getRelatedArticlesByCategory(\n article.slug,\n article.category,\n limit,\n config\n )\n return { source: 'category', heading: `More in ${article.category}`, articles: categoryArticles }\n}\n\nexport { sanitizeImagePath }\n","import type { ArticlesConfig } from './articlesConfig'\nimport type { AuthorProfile, AuthorSocial } from './articleTypes'\n\nexport function getAuthorUrl(author: AuthorProfile): string {\n return author.url ?? `/articles/authors/${author.slug}`\n}\n\nexport function getAuthorAvatar(\n author: AuthorProfile,\n config?: ArticlesConfig\n): string | undefined {\n if (!author.avatar) return undefined\n if (author.avatar.startsWith('http://') || author.avatar.startsWith('https://')) {\n return author.avatar\n }\n const path = `/articles/authors/${author.slug}/${author.avatar.replace(/^\\/+/, '')}`\n if (!config) return path\n return `${config.siteUrl.replace(/\\/$/, '')}${path}`\n}\n\nexport function getAuthorSameAs(author: AuthorProfile): string[] {\n return getAuthorSocialLinks(author).map((link) => link.href)\n}\n\nexport interface AuthorSocialLink {\n label: string\n href: string\n}\n\nfunction normalizeHandle(value: string): string {\n return value.replace(/^@/, '')\n}\n\nfunction normalizeUrl(value: string, baseUrl?: string): string {\n if (value.startsWith('http://') || value.startsWith('https://')) return value\n if (!baseUrl) return value\n return `${baseUrl}${normalizeHandle(value)}`\n}\n\nfunction getConfiguredSocialLinks(social: AuthorSocial): AuthorSocialLink[] {\n return [\n { label: 'Website', href: social.website ?? '' },\n {\n label: 'Facebook',\n href: social.facebook ? normalizeUrl(social.facebook, 'https://www.facebook.com/') : '',\n },\n {\n label: 'Twitter',\n href: social.twitter ? normalizeUrl(social.twitter, 'https://twitter.com/') : '',\n },\n { label: 'X', href: social.x ? normalizeUrl(social.x, 'https://x.com/') : '' },\n {\n label: 'LinkedIn',\n href: social.linkedin ? normalizeUrl(social.linkedin, 'https://www.linkedin.com/in/') : '',\n },\n {\n label: 'Instagram',\n href: social.instagram ? normalizeUrl(social.instagram, 'https://www.instagram.com/') : '',\n },\n {\n label: 'YouTube',\n href: social.youtube ? normalizeUrl(social.youtube, 'https://www.youtube.com/') : '',\n },\n {\n label: 'TikTok',\n href: social.tiktok ? normalizeUrl(social.tiktok, 'https://www.tiktok.com/@') : '',\n },\n {\n label: 'GitHub',\n href: social.github ? normalizeUrl(social.github, 'https://github.com/') : '',\n },\n {\n label: 'Bluesky',\n href: social.bluesky ? normalizeUrl(social.bluesky, 'https://bsky.app/profile/') : '',\n },\n {\n label: 'Threads',\n href: social.threads ? normalizeUrl(social.threads, 'https://www.threads.net/@') : '',\n },\n { label: 'Mastodon', href: social.mastodon ? normalizeUrl(social.mastodon) : '' },\n {\n label: 'Medium',\n href: social.medium ? normalizeUrl(social.medium, 'https://medium.com/@') : '',\n },\n { label: 'Newsletter', href: social.newsletter ?? '' },\n ]\n}\n\nexport function getAuthorSocialLinks(author: AuthorProfile): AuthorSocialLink[] {\n const social = author.social\n if (!social) return []\n const configuredLinks = getConfiguredSocialLinks(social)\n const otherLinks = Object.entries(social.other ?? {}).map(([label, href]) => ({ label, href }))\n return [...configuredLinks, ...otherLinks].filter((link) => link.href.trim().length > 0)\n}\n","import type { Element, Root, ElementContent } from 'hast'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSanitize from 'rehype-sanitize'\nimport rehypeSlug from 'rehype-slug'\nimport rehypeStringify from 'rehype-stringify'\nimport { remark } from 'remark'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport remarkParse from 'remark-parse'\nimport remarkRehype from 'remark-rehype'\nimport { Plugin } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport type { TocItem } from './articleTypes'\nimport type { ArticlesConfig, LinkTargetStrategy } from './articlesConfig'\nimport { reportArticlesError } from './errorReporting'\nimport { isExternalHttpLink, isNonBrowserNavigationLink } from './linkClassification'\n\nexport { isExternalHttpLink, isNonBrowserNavigationLink }\n\ntype LinkTargetOptions = Readonly<{\n strategy?: LinkTargetStrategy\n siteUrl?: string\n}>\n\nconst DEFAULT_LINK_TARGET_STRATEGY: LinkTargetStrategy = 'external-new-tab'\n\nfunction shouldOpenInNewTab(href: string, options: LinkTargetOptions = {}): boolean {\n if (!href || href.startsWith('#') || isNonBrowserNavigationLink(href)) return false\n\n const strategy = options.strategy ?? DEFAULT_LINK_TARGET_STRATEGY\n if (strategy === 'same-tab') return false\n if (strategy === 'all-new-tab') return true\n return isExternalHttpLink(href, options.siteUrl)\n}\n\nfunction applyLinkTarget(props: Record<string, unknown>, options?: LinkTargetOptions): void {\n const href = typeof props.href === 'string' ? props.href : ''\n if (shouldOpenInNewTab(href, options)) {\n props.target = '_blank'\n props.rel = 'noopener noreferrer'\n return\n }\n delete props.target\n delete props.rel\n}\n\nfunction getLinkTargetOptions(config?: ArticlesConfig): LinkTargetOptions {\n return {\n strategy: config?.linkTargetStrategy,\n siteUrl: config?.siteUrl,\n }\n}\n\n// Import the sanitizeImagePath function\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') {\n return null\n }\n\n // Remove any null bytes or control characters\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n\n // Check for absolute URLs (http/https)\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) {\n // For external URLs, just return as-is (they're safe)\n return cleanPath\n }\n\n // For relative paths, ensure they don't contain dangerous patterns\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected unsafe markdown image path.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n\n // Only allow alphanumeric characters, hyphens, underscores, dots, and forward slashes\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected markdown image path with invalid characters.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n\n // Construct safe path within articles directory\n if (cleanPath.includes('/')) {\n // Relative path, ensure it's within the article directory\n const normalizedPath = cleanPath.replaceAll('\\\\', '/')\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected markdown image path traversal attempt.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n return `/articles/${articleSlug}/${normalizedPath}`\n } else {\n // Just a filename, construct full path\n return `/articles/${articleSlug}/${cleanPath}`\n }\n}\n\nfunction styleFootnoteLinks(nodes: ElementContent[]): void {\n nodes.forEach((n) => {\n if (n.type !== 'element') return\n const el = n as Element\n if (el.tagName === 'a') {\n const isBackRef =\n el.properties?.['dataFootnoteBackref'] !== undefined ||\n (el.children[0]?.type === 'text' && el.children[0].value === '↩')\n if (isBackRef) {\n el.properties.className = 'text-primary hover:underline ml-1'\n if (typeof el.properties.href === 'string') {\n el.properties.href = el.properties.href.replaceAll('#user-content-fnref-', '#ref-')\n }\n } else {\n el.properties.className = 'text-primary hover:underline break-all'\n }\n }\n if (el.children) styleFootnoteLinks(el.children)\n })\n}\n\nfunction processFootnoteRef(node: Element): void {\n if (\n node.tagName !== 'sup' ||\n node.children?.[0]?.type !== 'element' ||\n (node.children[0] as Element).tagName !== 'a'\n )\n return\n\n const link = node.children[0] as Element\n const href = link.properties?.href\n if (typeof href !== 'string' || !href.startsWith('#user-content-fn-')) return\n\n delete link.properties.target\n delete link.properties.rel\n link.properties.className = 'text-primary hover:underline'\n link.properties.href = href.replaceAll('#user-content-fn-', '#footnote-')\n\n if (typeof node.properties?.id === 'string') {\n link.properties.id = node.properties.id.replaceAll('user-content-fnref-', 'ref-')\n delete node.properties.id\n }\n\n if (link.children?.[0]?.type === 'text') {\n link.children[0].value = `[${link.children[0].value}]`\n }\n}\n\nfunction processFootnotesSection(node: Element): void {\n const cls = node.properties?.className\n const isFootnotes =\n node.tagName === 'section' &&\n (Array.isArray(cls) ? cls.includes('footnotes') : cls === 'footnotes')\n if (!isFootnotes) return\n\n const olCandidate = node.children.find(\n (child) => child.type === 'element' && (child as Element).tagName === 'ol'\n )\n if (olCandidate?.type !== 'element') return\n\n const ol = olCandidate as Element\n ol.properties.className = 'list-decimal ml-6 space-y-2 text-sm text-muted-foreground'\n\n ol.children.forEach((li) => {\n if (li.type !== 'element' || li.tagName !== 'li') return\n const liEl = li as Element\n\n if (liEl.properties) {\n liEl.properties.className = 'pl-2'\n if (typeof liEl.properties.id === 'string') {\n liEl.properties.id = liEl.properties.id.replaceAll('user-content-fn-', 'footnote-')\n }\n }\n\n const pIndex = liEl.children.findIndex(\n (child) => child.type === 'element' && (child as Element).tagName === 'p'\n )\n if (pIndex !== -1) {\n const p = liEl.children[pIndex] as Element\n liEl.children.splice(pIndex, 1, ...p.children)\n }\n\n styleFootnoteLinks(liEl.children)\n })\n\n const hr: Element = {\n type: 'element',\n tagName: 'hr',\n properties: { className: 'my-8 border-border' },\n children: [],\n }\n const h3: Element = {\n type: 'element',\n tagName: 'h3',\n properties: { className: 'text-lg font-semibold mb-4' },\n children: [{ type: 'text', value: 'References' }],\n }\n\n node.children = [hr, h3, ol]\n if (node.properties) node.properties.className = undefined\n}\n\nexport const customRenderer: Plugin<[LinkTargetOptions?], Root> = (linkTargetOptions = {}) => {\n return (tree: Root) => {\n // First pass: Apply general styles\n visit(tree, 'element', (node: Element) => {\n if (node.tagName) {\n const props = node.properties || {}\n\n switch (node.tagName) {\n case 'h1':\n props.className = 'text-3xl font-bold text-foreground mt-8 mb-4 scroll-mt-20'\n break\n case 'h2':\n props.className = 'text-2xl font-semibold text-foreground mt-6 mb-3 scroll-mt-20'\n break\n case 'h3':\n props.className = 'text-xl font-semibold text-foreground mt-4 mb-2 scroll-mt-20'\n break\n case 'h4':\n props.className = 'text-lg font-semibold text-foreground mt-3 mb-2 scroll-mt-20'\n break\n case 'p':\n props.className = 'text-muted-foreground leading-relaxed mb-4'\n break\n case 'a': {\n props.className = 'text-primary hover:underline transition-colors duration-200'\n applyLinkTarget(props, linkTargetOptions)\n break\n }\n case 'ul':\n props.className = 'list-disc list-inside mb-4 space-y-2 ml-4'\n break\n case 'ol':\n props.className = 'list-decimal list-inside mb-4 space-y-2 ml-4'\n break\n case 'li':\n props.className = 'mb-1'\n break\n case 'blockquote':\n props.className = 'border-l-4 border-primary pl-4 italic my-4 text-muted-foreground'\n break\n case 'code':\n props.className =\n (props.className ? props.className + ' ' : '') +\n 'bg-muted px-1 py-0.5 rounded text-sm font-mono'\n break\n case 'pre':\n props.className = 'bg-muted rounded-lg p-4 overflow-x-auto my-4'\n break\n case 'img':\n props.className = 'rounded-lg my-6 w-full max-w-2xl mx-auto'\n break\n case 'table':\n props.className = 'border-collapse border border-border my-4 w-full'\n break\n case 'th':\n props.className = 'border border-border px-2 py-1 bg-muted font-semibold'\n break\n case 'td':\n props.className = 'border border-border px-2 py-1'\n break\n case 'hr':\n props.className = 'my-8 border-border'\n break\n default:\n break\n }\n\n node.properties = props\n }\n })\n\n // Second pass: Fix footnotes and references\n visit(tree, 'element', (node: Element) => {\n processFootnoteRef(node)\n processFootnotesSection(node)\n })\n }\n}\n\n// Custom rehype plugin to process image URLs\nconst rehypeProcessImages: Plugin<[{ articleSlug?: string }], Root> = (options = {}) => {\n return (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n if (node.tagName === 'img' && node.properties) {\n const src = node.properties.src\n if (src && typeof src === 'string' && options.articleSlug) {\n // Sanitize the image path\n const sanitizedSrc = sanitizeImagePath(src, options.articleSlug)\n if (sanitizedSrc) {\n node.properties.src = sanitizedSrc\n } else {\n // If sanitization fails, use a placeholder\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Using placeholder for unsafe markdown image path.',\n context: { articleSlug: options.articleSlug, path: src },\n })\n node.properties.src = '/placeholder-logo.png'\n }\n }\n }\n })\n }\n}\n\nexport async function markdownToHtml(\n markdown: string,\n articleSlug?: string,\n config?: ArticlesConfig\n) {\n try {\n // Start building the remark processor\n let processor = remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkGithubBlockquoteAlert)\n .use(remarkRehype)\n .use(customRenderer, getLinkTargetOptions(config))\n .use(rehypeSlug)\n // @ts-ignore\n .use(rehypePrism)\n .use(rehypeSanitize, {\n attributes: {\n '*': ['className', 'class', 'id'],\n a: ['href', 'target', 'rel', 'id'],\n img: ['src', 'alt'],\n },\n })\n\n // Add image processing plugin if articleSlug is provided\n if (articleSlug) {\n processor = processor.use(rehypeProcessImages, { articleSlug })\n }\n\n const result = await processor\n .use(rehypeStringify)\n .process(stripInlineTagsFromHeadings(markdown))\n\n return result.toString()\n } catch (error) {\n reportArticlesError({\n code: 'markdown-conversion-failed',\n message: 'Unable to convert markdown to HTML.',\n error,\n context: { articleSlug },\n })\n // Return the original markdown as fallback\n return markdown\n }\n}\n\n/**\n * Strips inline HTML/JSX tags from heading lines only, keeping their inner\n * text, before the markdown reaches a plain (non-MDX) remark parse.\n *\n * Article headings commonly carry a reader-facing rating dot written as JSX,\n * e.g. `### Rage <span style={{ color: '#3b82f6' }}>●</span>`. `renderMdxSource`\n * (real MDX compilation via `@mdx-js/mdx`) parses that correctly and renders\n * a real `<span>` element. But `extractToc` and `markdownToHtml` both run\n * headings through plain `remark-parse` with no MDX support, and CommonMark's\n * raw-inline-HTML grammar does not accept a JSX object-literal attribute\n * expression like `style={{ ... }}` - remark's HTML tokenizer fails to match\n * it as a tag and falls back to treating the whole thing as literal text.\n * That garbled text then (a) becomes the visible TOC label, and (b) feeds\n * `rehype-slug`, producing a slug built from the raw markup instead of the\n * heading's real words - which does not match the id `renderMdxSource`'s\n * correctly-parsed pipeline assigns to the same heading in the live page, so\n * the TOC entry silently links to an id that does not exist in the DOM.\n *\n * Stripping tags (not their inner content) from heading lines before parsing\n * keeps the extracted text and generated slug consistent with what the real\n * MDX render puts in the page, for any heading-level inline markup - not\n * only the rating-dot convention that surfaced the bug.\n */\nfunction stripInlineTagsFromHeadings(markdown: string): string {\n return markdown.replace(/^(#{1,6}[ \\t].*)$/gm, (line) =>\n line.replace(/<\\/?[a-zA-Z][^<>\\n]*>/g, '')\n )\n}\n\n/**\n * Recursively extracts a node's text content, including text nested inside\n * child elements (for example a markdown link's `<a>Dwarf</a>` inside a\n * heading like `### [Dwarf](/link)`). A shallow, direct-children-only check\n * here previously dropped link text from every heading that used a link as\n * part of its heading text - a distinct bug from the JSX-in-heading garbling\n * `stripInlineTagsFromHeadings` fixes, but with a worse symptom: the heading\n * was silently omitted from the TOC entirely (both `id` and `text` came back\n * empty, so `extractHeadingItem` returned `null`) rather than merely garbled.\n */\nfunction nodeTextValue(c: ElementContent): string {\n if (c.type === 'text') return (c as { value: string }).value\n if (c.type === 'element' && 'children' in c) {\n return (c as Element).children.map(nodeTextValue).join('')\n }\n return ''\n}\n\nfunction extractHeadingItem(node: Element): TocItem | null {\n const match = /^h([1-6])$/.exec(node.tagName)\n if (!match) return null\n const id = typeof node.properties?.id === 'string' ? node.properties.id : ''\n const text = node.children.map(nodeTextValue).join('')\n if (!id || !text) return null\n return { id, depth: Number.parseInt(match[1], 10), text }\n}\n\nexport interface ContentSlotBoundaries {\n /** Character offset (into the raw markdown source) right after the first paragraph - the \"intro\" boundary. */\n introEnd: number\n /** Character offset right after the middle paragraph - the \"mid content\" boundary. */\n mid: number\n /** Total top-level paragraph count found. */\n paragraphCount: number\n}\n\n/**\n * Resolves deterministic `afterIntro`/`midContent` split points from the\n * raw markdown/MDX source's parsed AST (mdast paragraph node offsets) -\n * never from string-splitting rendered HTML, which is fragile by\n * construction (see Phase 27F plan notes). Returns `null` when the source\n * has no top-level paragraphs, or fails to parse (e.g. MDX with JSX syntax\n * remark-parse doesn't understand) - callers should treat `null` as \"only\n * `afterHero`/`afterContent` are available for this article\", not throw.\n */\ninterface MdastNode {\n type: string\n position?: { start: { offset?: number }; end: { offset?: number } }\n children?: MdastNode[]\n}\n\nexport function getContentSlotBoundaries(markdown: string): ContentSlotBoundaries | null {\n try {\n const tree = remark().use(remarkParse).use(remarkGfm).parse(markdown) as unknown as MdastNode\n const paragraphs = (tree.children ?? []).filter(\n (node): node is MdastNode & { position: NonNullable<MdastNode['position']> } =>\n node.type === 'paragraph' && Boolean(node.position)\n )\n if (paragraphs.length === 0) return null\n const introEnd = paragraphs[0].position.end.offset ?? 0\n const midIndex = Math.floor(paragraphs.length / 2)\n const mid = paragraphs[midIndex].position.end.offset ?? introEnd\n return { introEnd, mid: Math.max(mid, introEnd), paragraphCount: paragraphs.length }\n } catch {\n return null\n }\n}\n\nexport async function extractToc(markdown: string): Promise<TocItem[]> {\n const headings: TocItem[] = []\n const collectHeadings: Plugin<[], Root> = () => (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n const item = extractHeadingItem(node)\n if (item) headings.push(item)\n })\n }\n await remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype)\n .use(rehypeSlug)\n .use(collectHeadings)\n .use(rehypeStringify)\n .process(stripInlineTagsFromHeadings(markdown))\n return headings\n}\n","export type ArticlesErrorCode =\n | 'article-directory-read-failed'\n | 'article-load-failed'\n | 'article-markdown-load-failed'\n | 'markdown-conversion-failed'\n | 'unsafe-image-path'\n\nexport type ArticlesErrorContext = Readonly<Record<string, string | number | boolean | undefined>>\n\nexport type ArticlesErrorReport = Readonly<{\n code: ArticlesErrorCode\n message: string\n error?: unknown\n context?: ArticlesErrorContext\n}>\n\nexport type ArticlesErrorHandler = (report: ArticlesErrorReport) => void\n\nfunction defaultArticlesErrorHandler(report: ArticlesErrorReport): void {\n if (process.env.NODE_ENV === 'production') return\n const context = report.context ? ` ${JSON.stringify(report.context)}` : ''\n const detail = report.error instanceof Error ? `: ${report.error.message}` : ''\n console.warn(`[articles:${report.code}] ${report.message}${context}${detail}`)\n}\n\nlet articlesErrorHandler: ArticlesErrorHandler = defaultArticlesErrorHandler\n\nexport function setArticlesErrorHandler(handler?: ArticlesErrorHandler): void {\n articlesErrorHandler = handler ?? defaultArticlesErrorHandler\n}\n\nexport function reportArticlesError(report: ArticlesErrorReport): void {\n articlesErrorHandler(report)\n}\n","// Pure href classification helpers with no dependency on the rehype/hast\n// pipeline - kept separate from markdown.ts (which pulls in ESM-only\n// rehype/remark plugins Jest can't transform without extra config) so\n// consumers that only need \"is this link internal/external/navigable\"\n// (like renderMdx.tsx's next/link routing decision) don't have to import\n// that whole transitive dependency chain, in production or in tests.\n\nexport function isNonBrowserNavigationLink(href: string): boolean {\n return (\n /^[a-zA-Z][a-zA-Z\\d+.-]*:/.test(href) &&\n !href.startsWith('http://') &&\n !href.startsWith('https://')\n )\n}\n\nfunction getOrigin(url: string | undefined): string | null {\n if (!url) return null\n try {\n return new URL(url).origin\n } catch {\n return null\n }\n}\n\nexport function isExternalHttpLink(href: string, siteUrl?: string): boolean {\n if (!href.startsWith('http://') && !href.startsWith('https://')) return false\n const siteOrigin = getOrigin(siteUrl)\n if (!siteOrigin) return true\n return getOrigin(href) !== siteOrigin\n}\n","import type { ComponentType } from 'react'\nimport type { AuthorProfile, PathDefinition } from './articleTypes'\nimport type { ArticleEventHandler } from './events'\n\n/** Keys for each renderable section of the articles listing page. */\nexport type ArticlesSection =\n | 'hero'\n | 'search'\n | 'featured'\n | 'latest'\n | 'categories'\n | 'newsletter'\n\n/**\n * CSS custom-property overrides applied to the library's wrapper div.\n * All fields are optional; omitted fields fall back to the consuming app's Tailwind theme.\n */\nexport interface ArticlesTheme {\n /** Font family for the articles section. Example: `\"'Inter', sans-serif\"` */\n fontFamily?: string\n /**\n * Font family for headings only (article title, card titles, section\n * headings) - falls back to `fontFamily` when omitted. Lets a site use a\n * distinct display face for headings (e.g. a serif) while keeping a\n * separate body font, without hardcoding either into the package.\n * Example: `\"'Cinzel', serif\"`\n */\n headerFontFamily?: string\n /** Color for article card titles and section headings. Example: `'#111827'` */\n headerColor?: string\n /** Color for body and excerpt text. Example: `'#6b7280'` */\n textColor?: string\n /** Background color for sections and cards. Example: `'#ffffff'` */\n backgroundColor?: string\n /** Color for \"Read More\" links and inline links. Example: `'#4f46e5'` */\n linkColor?: string\n /** Hover color for links. Example: `'#4338ca'` */\n linkHoverColor?: string\n /** Text decoration for links. Values: `'underline'` | `'none'` */\n linkTextDecoration?: string\n /** Font weight for headings. Example: `700` */\n headerFontWeight?: string | number\n /** Font size for article card titles. Example: `'1.25rem'` */\n headerFontSize?: string\n /** Font size for body text. Example: `'1rem'` */\n bodyFontSize?: string\n /** Line height for body text. Example: `'1.75'` */\n lineHeight?: string\n /** Border radius for cards. Example: `'0.5rem'` */\n borderRadius?: string\n}\n\n/** Per-category text used in meta descriptions and on the category hero page. */\nexport interface CategoryDescription {\n /** Short description — used in meta description and category grid card. */\n short: string\n /** Long description — rendered as a hero paragraph on the category page. */\n long?: string\n}\n\n/** Controls the comments feature. Attach to `ArticlesConfig.comments`. */\nexport interface CommentsConfig {\n /** Set to `true` to enable comments globally across all articles. */\n enabled: boolean\n /** Maximum reply nesting depth. Default: `1` (replies to top-level only). */\n maxDepth?: number\n /**\n * Per-article overrides keyed by slug.\n * Example: `{ 'sensitive-article': false }` disables comments on that article only.\n */\n perArticleOverride?: Record<string, boolean>\n}\n\n/** Text content for the hero section at the top of the articles listing page. */\nexport interface HeroConfig {\n /** Main heading displayed in the hero. Default: `'Vox Populus Insights'` */\n title?: string\n /** Subheading paragraph displayed beneath the title. Default: the built-in description. */\n description?: string\n}\n\n/** Controls how article body links set target/rel attributes. */\nexport type LinkTargetStrategy = 'external-new-tab' | 'all-new-tab' | 'same-tab'\n\n/**\n * Controls how listing pages (the articles index, category pages, author\n * pages) surface articles beyond the first `pageSize`.\n * - `'load-more'` (default): client-only \"Load more\" button, no URL change.\n * Byte-for-byte identical to pre-27D behavior.\n * - `'pages'`: real, directly-navigable paginated routes (`/articles/page/2`,\n * `/articles/category/[category]/page/2`, `/articles/authors/[author]/page/2`)\n * with SSR content, prev/next links, and per-page canonical metadata. The\n * route *files* live in the consuming app - see the pagination primitives\n * exported from `./server` and the `PaginationNav` component.\n */\nexport type ListingPagination = 'load-more' | 'pages'\n\n/** React components that article MDX bodies can reference by JSX tag name. */\nexport type MdxComponents = Record<string, ComponentType<never>>\n\nexport type ArticleBreadcrumbToken =\n | 'home'\n | 'articles'\n | 'primaryCategory'\n | 'folderPath'\n | 'articleTitle'\nexport type CategoryBreadcrumbToken = 'home' | 'articles' | 'category'\nexport type AuthorBreadcrumbToken = 'home' | 'articles' | 'authors' | 'authorName'\n\nexport interface CustomBreadcrumbItem {\n /** Label displayed in the breadcrumb trail. */\n name: string\n /** Custom URL. Relative paths are resolved against `siteUrl` by server builders. */\n url: string\n}\n\nexport type ArticleBreadcrumbEntry = ArticleBreadcrumbToken | CustomBreadcrumbItem\nexport type CategoryBreadcrumbEntry = CategoryBreadcrumbToken | CustomBreadcrumbItem\nexport type AuthorBreadcrumbEntry = AuthorBreadcrumbToken | CustomBreadcrumbItem\n\nexport interface BreadcrumbLabels {\n home?: string\n articles?: string\n authors?: string\n}\n\nexport interface BreadcrumbsConfig {\n /** Set to false to hide visible breadcrumbs and breadcrumb JSON-LD generated by the helper builders. */\n show?: boolean\n /** Separator used by the visible Breadcrumb component. Default: '>'. */\n separator?: string\n /** Set to false to render visible breadcrumbs without JSON-LD. Default: true. */\n showSchema?: boolean\n /** Article breadcrumb trail. Example: ['primaryCategory', { name: 'Guides', url: '/guides' }, 'articleTitle']. */\n article?: ArticleBreadcrumbEntry[]\n /** Category breadcrumb trail. Default: ['home', 'articles', 'category']. */\n category?: CategoryBreadcrumbEntry[]\n /** Author breadcrumb trail. Default: ['home', 'articles', 'authors', 'authorName']. */\n author?: AuthorBreadcrumbEntry[]\n /** Optional label overrides for built-in breadcrumb items. */\n labels?: BreadcrumbLabels\n}\n\n/** Top-level configuration object. Pass one instance to every library component. */\nexport interface ArticlesConfig {\n /** Canonical base URL of the site, used in metadata and JSON-LD. Example: `'https://yoursite.com'` */\n siteUrl: string\n /** Site name shown in metadata titles and JSON-LD publisher fields. Example: `'Vox Populus'` */\n siteName: string\n /** Number of articles shown per page in the grid and loaded on each \"Load more\" click. Default: `6` */\n pageSize?: number\n /** Number of category cards shown before a \"Load more categories\" button appears. Default: `8` */\n categoriesPageSize?: number\n /**\n * Ordered list of sections to render on the articles listing page.\n * Omit a key to hide that section entirely. Default: `['hero','search','featured','latest','categories']`\n */\n layout?: ArticlesSection[]\n /** CSS custom-property overrides for colors, fonts, and spacing. All fields optional. */\n theme?: ArticlesTheme\n /**\n * Descriptive text for each category, keyed by category slug.\n * A plain string is treated as the short description only.\n * Example: `{ 'campaigns': { short: 'Campaign tips', long: 'Full paragraph...' } }`\n */\n categoryDescriptions?: Record<string, string | CategoryDescription>\n /** Comments configuration. Omit or set `enabled: false` to hide comments entirely. */\n comments?: CommentsConfig\n /** Hero section title and description. Omit to use the built-in defaults. */\n hero?: HeroConfig\n /** Short description used in the RSS feed channel. Falls back to siteName if omitted. */\n description?: string\n /** Set to false to hide the table of contents on article detail pages. Default: true. */\n showToc?: boolean\n /** Set to false to hide the \"Back to Articles\" navigation link on article detail pages. Default: true. */\n showBackToArticles?: boolean\n /** Set to false to hide author names from UI and metadata. Default: true. */\n showAuthor?: boolean\n /** Author profiles keyed by slug. Omit to keep plain string author display. */\n authors?: Record<string, AuthorProfile>\n /** Author slug used when article frontmatter omits author fields. */\n defaultAuthor?: string\n /** Set to false to disable copied author page routes in consuming apps. Default: true. */\n showAuthorPage?: boolean\n /** Set to false to disable breadcrumbs, or pass a config object to customize breadcrumb trails. */\n breadcrumbs?: false | BreadcrumbsConfig\n /** Article body link target behavior. Default: `'external-new-tab'`. */\n linkTargetStrategy?: LinkTargetStrategy\n /** Extra components exposed to article MDX bodies by JSX tag name. */\n mdxComponents?: MdxComponents\n /**\n * Chooses how listing pages surface articles beyond the first `pageSize`.\n * Default: `'load-more'` (unchanged pre-27D behavior). Set to `'pages'` to\n * opt into real, crawlable paginated routes instead.\n */\n listingPagination?: ListingPagination\n /**\n * \"Start here\" curated reader journeys, keyed by an app-chosen path key.\n * Distinct from the label-only `series` field/`seriesSlug` pair - a path\n * can cross series and categories. Every `PathDefinition.articles` slug\n * must exist and not be `draft: true`; validate with `validateArticles`\n * before publishing, since a broken reference produces a dead journey\n * step rather than a build-time failure otherwise.\n */\n paths?: Record<string, PathDefinition>\n /**\n * Vendor-neutral event callback (Phase 27F). Fired by components/hooks at\n * meaningful reader-journey moments (see `ArticleEvent` in `events.ts`).\n * No PII in any payload. The package never talks to an analytics/email\n * vendor directly - translate events to PostHog/etc. in this callback.\n */\n onEvent?: ArticleEventHandler\n}\n\nexport const DEFAULT_PAGE_SIZE = 6\nexport const DEFAULT_CATEGORIES_PAGE_SIZE = 8\n\nexport const DEFAULT_LAYOUT: ArticlesSection[] = [\n 'hero',\n 'search',\n 'featured',\n 'latest',\n 'categories',\n]\n\nexport function breadcrumbsAreEnabled(config: ArticlesConfig): boolean {\n return config.breadcrumbs !== false && config.breadcrumbs?.show !== false\n}\n\nexport function getBreadcrumbsConfig(config: ArticlesConfig): BreadcrumbsConfig {\n if (config.breadcrumbs === false) return {}\n return config.breadcrumbs ?? {}\n}\n","// Pure pagination math shared by both the server data-prep side (route files\n// slicing articles per page, generateStaticParams) and the client-safe\n// PaginationNav component (building prev/next hrefs). No fs/next dependency\n// so it's safe to import from both `index.ts` and `server.ts` entry points.\nimport type { Article } from './articleTypes'\n\nexport interface PaginatedArticles {\n /** Articles belonging to this page only (already sliced). */\n articles: Article[]\n /** Clamped to the range `[1, totalPages]`. */\n page: number\n totalPages: number\n hasPrevious: boolean\n hasNext: boolean\n}\n\n/** Context threaded from a listing page component down into `LatestArticles`/`PaginationNav` in `'pages'` mode. */\nexport interface ListingPaginationContext {\n page: number\n totalPages: number\n /** Un-paginated route path for this listing, e.g. `/articles` or `/articles/category/campaigns`. */\n basePath: string\n}\n\nexport interface PaginationLinks {\n /** This page's own canonical URL - never points back to page 1 for page > 1. */\n canonicalUrl: string\n prevUrl: string | null\n nextUrl: string | null\n}\n\nexport function getTotalPages(totalCount: number, pageSize: number): number {\n if (totalCount <= 0 || pageSize <= 0) return 1\n return Math.max(1, Math.ceil(totalCount / pageSize))\n}\n\n/** Slices `articles` to the requested page, clamping out-of-range page numbers into `[1, totalPages]`. */\nexport function paginateArticles(\n articles: Article[],\n page: number,\n pageSize: number\n): PaginatedArticles {\n const totalPages = getTotalPages(articles.length, pageSize)\n const requestedPage = Math.trunc(page) || 1\n const clampedPage = Math.min(Math.max(requestedPage, 1), totalPages)\n const start = (clampedPage - 1) * pageSize\n return {\n articles: articles.slice(start, start + pageSize),\n page: clampedPage,\n totalPages,\n hasPrevious: clampedPage > 1,\n hasNext: clampedPage < totalPages,\n }\n}\n\n/** Page 1 is the un-suffixed `basePath` itself; page N>1 is `${basePath}/page/${N}`. */\nexport function buildPageUrl(basePath: string, page: number): string {\n const base = basePath.replace(/\\/$/, '')\n return page > 1 ? `${base}/page/${page}` : base\n}\n\nexport function buildPaginationLinks(\n basePath: string,\n page: number,\n totalPages: number\n): PaginationLinks {\n return {\n canonicalUrl: buildPageUrl(basePath, page),\n prevUrl: page > 1 ? buildPageUrl(basePath, page - 1) : null,\n nextUrl: page < totalPages ? buildPageUrl(basePath, page + 1) : null,\n }\n}\n\n/**\n * Static params for pages 2..totalPages (page 1 has no `/page/1` route - it's\n * served by the un-paginated base route). For nested dynamic segments (e.g.\n * `/articles/category/[category]/page/[page]`), combine this per-category in\n * the consuming app's `generateStaticParams` - see README.\n */\nexport function generateListingPageStaticParams(totalPages: number): { page: string }[] {\n const params: { page: string }[] = []\n for (let page = 2; page <= totalPages; page++) params.push({ page: String(page) })\n return params\n}\n\nexport function parsePageParam(raw: string | undefined | null): number {\n const parsed = Number.parseInt(raw ?? '', 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 1\n}\n\nexport function isPageOutOfRange(page: number, totalPages: number): boolean {\n return page < 1 || page > totalPages\n}\n","import type { Metadata, MetadataRoute } from 'next'\nimport {\n getArticleMetadata,\n getAllArticles,\n getAllAuthors,\n getAllCategories,\n getArticleAuthors,\n getArticlesByCategory,\n getArticlesBySeries,\n getAuthorBySlug,\n getArticleMarkdownUrl,\n getAvailableArticleSlugs,\n categoryToSlug,\n} from './server-articles'\nimport {\n breadcrumbsAreEnabled,\n getBreadcrumbsConfig,\n type ArticlesConfig,\n type ArticleBreadcrumbEntry,\n type AuthorBreadcrumbEntry,\n type CategoryBreadcrumbEntry,\n type CustomBreadcrumbItem,\n} from './articlesConfig'\nimport { buildPaginationLinks } from './pagination'\nimport type { Article, AuthorProfile, BreadcrumbItem } from './articleTypes'\n\nfunction escapeXml(str: string): string {\n return str\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&apos;')\n}\n\nexport function generateRssFeed(articles: Article[], config: ArticlesConfig): string {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const showAuthor = config.showAuthor !== false\n\n const items = articles\n .map((article) => {\n const url = `${siteUrl}/articles/${article.slug}`\n const pubDate = article.date ? new Date(article.date).toUTCString() : ''\n const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : ''\n\n return [\n ' <item>',\n ` <title><![CDATA[${article.title}]]></title>`,\n ` <link>${url}</link>`,\n ` <guid isPermaLink=\"true\">${url}</guid>`,\n pubDate ? ` <pubDate>${pubDate}</pubDate>` : '',\n article.excerpt ? ` <description><![CDATA[${article.excerpt}]]></description>` : '',\n showAuthor && article.author ? ` <author>${escapeXml(article.author)}</author>` : '',\n article.category ? ` <category><![CDATA[${article.category}]]></category>` : '',\n imageUrl\n ? ` <media:content url=\"${imageUrl}\" medium=\"image\" width=\"1200\" height=\"630\"/>`\n : '',\n ' </item>',\n ]\n .filter(Boolean)\n .join('\\n')\n })\n .join('\\n')\n\n const description = config.description ?? `${config.siteName} articles`\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:media=\"http://search.yahoo.com/mrss/\">\n <channel>\n <title><![CDATA[${config.siteName}]]></title>\n <link>${siteUrl}/articles</link>\n <description><![CDATA[${description}]]></description>\n <language>en</language>\n <atom:link href=\"${siteUrl}/articles/feed.xml\" rel=\"self\" type=\"application/rss+xml\" />\n${items}\n </channel>\n</rss>`\n}\n\nexport function generateArticleStaticParams(): { slug: string }[] {\n return getAvailableArticleSlugs().map((slug) => ({ slug }))\n}\n\nexport async function generateCategoryStaticParams(): Promise<{ category: string }[]> {\n const categories = await getAllCategories()\n return categories.map((cat) => ({ category: cat.slug }))\n}\n\nexport function generateAuthorStaticParams(config: ArticlesConfig): { author: string }[] {\n if (config.showAuthorPage === false) return []\n return getAllAuthors(config).map((author) => ({ author: author.slug }))\n}\n\n/** Static params for `/articles/series/[series]` - one entry per distinct `seriesSlug` found across all articles. */\nexport async function generateSeriesStaticParams(\n config?: ArticlesConfig\n): Promise<{ series: string }[]> {\n const articles = await getAllArticles(config)\n const seriesSlugs = new Set(\n articles.map((article) => article.seriesSlug).filter((slug): slug is string => Boolean(slug))\n )\n return [...seriesSlugs].map((series) => ({ series }))\n}\n\nfunction resolveImageUrl(featuredImage: string, siteUrl: string): string {\n const base = siteUrl.replace(/\\/$/, '')\n if (featuredImage.startsWith('http://') || featuredImage.startsWith('https://')) {\n return featuredImage\n }\n return `${base}/${featuredImage.replace(/^\\/+/, '')}`\n}\n\n// Discovery metadata channel separation (Phase 27F): `searchTitle`/\n// `searchDescription` feed the `<title>`/meta-description channel ONLY,\n// `socialTitle`/`socialDescription`/`socialImage` feed Open Graph/Twitter\n// Card ONLY. Canonical URLs, JSON-LD (`ArticleSEO`), `ArticleCard`, and RSS\n// all keep reading `title`/`excerpt`/`featuredImage` directly and are\n// untouched by either override - each surface pulls from exactly one\n// source, never \"apply every override everywhere\".\nexport function resolveSearchMetadata(\n article: Pick<Article, 'title' | 'excerpt' | 'searchTitle' | 'searchDescription'>,\n config: Pick<ArticlesConfig, 'siteName'>\n): { title: string; description: string } {\n return {\n title: article.searchTitle ?? article.title,\n description:\n article.searchDescription ??\n article.excerpt ??\n `Read ${article.title} on ${config.siteName}.`,\n }\n}\n\nexport function resolveSocialMetadata(\n article: Pick<\n Article,\n 'title' | 'excerpt' | 'featuredImage' | 'socialTitle' | 'socialDescription' | 'socialImage'\n >,\n siteUrl: string\n): { title: string; description: string; imageUrl: string } {\n const image = article.socialImage ?? article.featuredImage\n return {\n title: article.socialTitle ?? article.title,\n description: article.socialDescription ?? article.excerpt ?? '',\n imageUrl: image ? resolveImageUrl(image, siteUrl) : `${siteUrl}/placeholder-logo.png`,\n }\n}\n\nexport async function generateArticleMetadata(\n slug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const article = await getArticleMetadata(slug, config)\n\n if (!article) {\n return {\n title: 'Article Not Found',\n description: 'The requested article could not be found.',\n }\n }\n\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const articleUrl = `${siteUrl}/articles/${slug}`\n const canonicalUrl = article.canonicalUrl ?? articleUrl\n const search = resolveSearchMetadata(article, config)\n const social = resolveSocialMetadata(article, siteUrl)\n const description = search.description\n const showAuthor = config.showAuthor !== false\n const markdownUrl = getArticleMarkdownUrl(article, config)\n const authorNames = getArticleAuthors(article, config).map((author) => author.name)\n\n return {\n title: `${search.title} | ${config.siteName}`,\n description,\n keywords: [...(article.tags ?? []).map((tag) => tag.toLowerCase())].join(', '),\n openGraph: {\n title: social.title,\n description: social.description || description,\n url: articleUrl,\n siteName: config.siteName,\n images: [{ url: social.imageUrl, width: 1200, height: 630, alt: social.title }],\n locale: 'en_US',\n type: 'article',\n ...(article.date && { publishedTime: article.date }),\n ...(article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }),\n ...(showAuthor && authorNames.length > 0 && { authors: authorNames }),\n tags: article.tags ?? [],\n },\n twitter: {\n card: 'summary_large_image',\n title: social.title,\n description: social.description || description,\n images: [social.imageUrl],\n },\n alternates: {\n canonical: canonicalUrl,\n ...(markdownUrl && {\n types: {\n 'text/markdown': markdownUrl,\n },\n }),\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n other: {\n ...(showAuthor && authorNames.length > 0 && { 'article:author': authorNames.join(', ') }),\n ...(article.date && {\n 'article:published_time': new Date(article.date).toISOString(),\n }),\n ...(article.lastmod && {\n 'article:modified_time': new Date(article.lastmod).toISOString(),\n }),\n 'article:section': article.category,\n 'article:tag': article.tags?.join(',') ?? '',\n 'linkedin:owner': process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID ?? '',\n },\n }\n}\n\nexport function generateArticlesIndexMetadata(config: ArticlesConfig): Metadata {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const indexUrl = `${siteUrl}/articles`\n const title = `Articles | ${config.siteName}`\n const description =\n config.hero?.description ?? `Expert analysis and insights from ${config.siteName}.`\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n url: indexUrl,\n siteName: config.siteName,\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n },\n alternates: {\n canonical: indexUrl,\n types: {\n 'application/rss+xml': `${siteUrl}/articles/feed.xml`,\n },\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function generateCategoryMetadata(\n categorySlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const articles = await getArticlesByCategory(categorySlug)\n\n if (articles.length === 0) return { title: 'Category Not Found' }\n\n const categoryName = articles[0].category\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const categoryUrl = `${siteUrl}/articles/category/${categorySlug}`\n const raw = config.categoryDescriptions?.[categorySlug]\n const fallback = `Browse ${articles.length} article${articles.length === 1 ? '' : 's'} in the ${categoryName} category.`\n const description = typeof raw === 'string' ? raw : (raw?.short ?? fallback)\n\n const title = `${categoryName} Articles | ${config.siteName}`\n return {\n title,\n description,\n openGraph: {\n title: `${categoryName} Articles`,\n description,\n url: categoryUrl,\n siteName: config.siteName,\n images: [{ url: articles[0].featuredImage }],\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title: `${categoryName} Articles`,\n description,\n },\n alternates: {\n canonical: categoryUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\n/** Metadata for a series landing page, analogous to `generateCategoryMetadata`. Series display name comes from the first matching article's label-only `series` string, falling back to `seriesSlug` itself. */\nexport async function generateSeriesMetadata(\n seriesSlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const articles = await getArticlesBySeries(seriesSlug, config)\n\n if (articles.length === 0) return { title: 'Series Not Found' }\n\n const seriesName = articles[0].series ?? seriesSlug\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const seriesUrl = `${siteUrl}/articles/series/${seriesSlug}`\n const description = `Follow the ${seriesName} series - ${articles.length} article${articles.length === 1 ? '' : 's'} on ${config.siteName}.`\n const title = `${seriesName} Series | ${config.siteName}`\n\n return {\n title,\n description,\n openGraph: {\n title: `${seriesName} Series`,\n description,\n url: seriesUrl,\n siteName: config.siteName,\n images: [{ url: articles[0].featuredImage }],\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title: `${seriesName} Series`,\n description,\n },\n alternates: {\n canonical: seriesUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function generateAuthorMetadata(\n authorSlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const author = getAuthorBySlug(authorSlug, config)\n\n if (!author || config.showAuthorPage === false) return { title: 'Author Not Found' }\n\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const authorUrl = author.url ?? `${siteUrl}/articles/authors/${author.slug}`\n const title = `${author.name} Articles | ${config.siteName}`\n\n return {\n title,\n description: author.bio,\n openGraph: {\n title,\n description: author.bio,\n url: authorUrl,\n siteName: config.siteName,\n type: 'profile',\n locale: 'en_US',\n ...(author.avatar && { images: [{ url: resolveAuthorAvatar(author, config) }] }),\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description: author.bio,\n ...(author.avatar && { images: [resolveAuthorAvatar(author, config)] }),\n },\n alternates: {\n canonical: authorUrl,\n },\n robots: {\n index: true,\n follow: true,\n },\n }\n}\n\n// Google Search Central's pagination guidance no longer treats rel=next/prev\n// as an indexing or ranking signal (confirmed dropped in 2019) - the\n// documented current recommendation is a unique, self-referencing canonical\n// per paginated page (never pointing page 2+ back to page 1) plus real\n// crawlable <a href> links between pages (handled by `PaginationNav`), which\n// is what these functions and that component together provide. rel=next/prev\n// itself is still valid HTML and still read by Bing and some third-party\n// tools/crawlers, so `PaginationNav` still emits it - it's just not what\n// makes these pages indexable to Google. Paginated pages stay index/follow\n// (inherited from the wrapped `generate*Metadata` call below) - the point of\n// this feature is making page 2+ indexable, not excluding it.\nfunction withPaginationMeta(\n base: Metadata,\n basePath: string,\n page: number,\n totalPages: number\n): Metadata {\n // Not-found responses from the wrapped generate*Metadata call (e.g.\n // \"Category Not Found\") never set `alternates` - leave them untouched\n // rather than decorating an error title/canonical with page info.\n if (!base.alternates) return base\n\n const { canonicalUrl } = buildPaginationLinks(basePath, page, totalPages)\n const pageSuffix = page > 1 ? ` - Page ${page}` : ''\n const title = typeof base.title === 'string' ? `${base.title}${pageSuffix}` : base.title\n const openGraph = base.openGraph\n ? {\n ...base.openGraph,\n title:\n typeof base.openGraph.title === 'string'\n ? `${base.openGraph.title}${pageSuffix}`\n : base.openGraph.title,\n url: canonicalUrl,\n }\n : base.openGraph\n const twitter = base.twitter\n ? {\n ...base.twitter,\n title:\n typeof base.twitter.title === 'string'\n ? `${base.twitter.title}${pageSuffix}`\n : base.twitter.title,\n }\n : base.twitter\n\n return {\n ...base,\n title,\n openGraph,\n twitter,\n alternates: { ...base.alternates, canonical: canonicalUrl },\n }\n}\n\n/** Per-page metadata for `/articles/page/[page]` in `listingPagination: 'pages'` mode. Page 1 is identical to `generateArticlesIndexMetadata`. */\nexport function generateArticlesIndexPageMetadata(\n page: number,\n totalPages: number,\n config: ArticlesConfig\n): Metadata {\n const base = generateArticlesIndexMetadata(config)\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n return withPaginationMeta(base, `${siteUrl}/articles`, page, totalPages)\n}\n\n/** Per-page metadata for `/articles/category/[category]/page/[page]` in `listingPagination: 'pages'` mode. */\nexport async function generateCategoryPageMetadata(\n categorySlug: string,\n page: number,\n totalPages: number,\n config: ArticlesConfig\n): Promise<Metadata> {\n const base = await generateCategoryMetadata(categorySlug, config)\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n return withPaginationMeta(base, `${siteUrl}/articles/category/${categorySlug}`, page, totalPages)\n}\n\n/** Per-page metadata for `/articles/authors/[author]/page/[page]` in `listingPagination: 'pages'` mode. */\nexport async function generateAuthorPageMetadata(\n authorSlug: string,\n page: number,\n totalPages: number,\n config: ArticlesConfig\n): Promise<Metadata> {\n const base = await generateAuthorMetadata(authorSlug, config)\n const author = getAuthorBySlug(authorSlug, config)\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const basePath = author?.url ?? `${siteUrl}/articles/authors/${authorSlug}`\n return withPaginationMeta(base, basePath, page, totalPages)\n}\n\nfunction formatCategoryName(category: string): string {\n return category\n .split('-')\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nexport function buildArticleBreadcrumbs(\n article: Pick<Article, 'slug' | 'title' | 'category'>,\n config: ArticlesConfig\n): BreadcrumbItem[] {\n if (!breadcrumbsAreEnabled(config)) return []\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const breadcrumbConfig = getBreadcrumbsConfig(config)\n const labels = breadcrumbConfig.labels ?? {}\n const categorySlug = categoryToSlug(article.category)\n const trail = breadcrumbConfig.article ?? ['home', 'articles', 'primaryCategory', 'articleTitle']\n const folderSegments = article.slug.split('/').filter(Boolean).slice(0, -1)\n return trail.flatMap((token): BreadcrumbItem[] =>\n buildArticleBreadcrumbToken(token, {\n article,\n siteUrl,\n categorySlug,\n folderSegments,\n labels,\n })\n )\n}\n\nexport function buildCategoryBreadcrumbs(\n category: string,\n config: ArticlesConfig,\n categoryName = formatCategoryName(category)\n): BreadcrumbItem[] {\n if (!breadcrumbsAreEnabled(config)) return []\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const breadcrumbConfig = getBreadcrumbsConfig(config)\n const labels = breadcrumbConfig.labels ?? {}\n const trail = breadcrumbConfig.category ?? ['home', 'articles', 'category']\n return trail.flatMap((entry): BreadcrumbItem[] =>\n buildCategoryBreadcrumbEntry(entry, { categoryName, siteUrl, labels })\n )\n}\n\nexport function buildAuthorBreadcrumbs(\n author: AuthorProfile,\n config: ArticlesConfig\n): BreadcrumbItem[] {\n if (!breadcrumbsAreEnabled(config)) return []\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const breadcrumbConfig = getBreadcrumbsConfig(config)\n const labels = breadcrumbConfig.labels ?? {}\n const trail = breadcrumbConfig.author ?? ['home', 'articles', 'authors', 'authorName']\n return trail.flatMap((entry): BreadcrumbItem[] =>\n buildAuthorBreadcrumbEntry(entry, { author, siteUrl, labels })\n )\n}\n\ntype BreadcrumbLabels = NonNullable<ReturnType<typeof getBreadcrumbsConfig>['labels']>\n\nfunction isCustomBreadcrumbItem(entry: unknown): entry is CustomBreadcrumbItem {\n return typeof entry === 'object' && entry !== null && 'name' in entry && 'url' in entry\n}\n\nfunction resolveCustomBreadcrumbItem(item: CustomBreadcrumbItem, siteUrl: string): BreadcrumbItem {\n if (item.url.startsWith('/')) return { name: item.name, url: `${siteUrl}${item.url}` }\n return { name: item.name, url: item.url }\n}\n\nfunction buildArticleBreadcrumbToken(\n entry: ArticleBreadcrumbEntry,\n context: Readonly<{\n article: Pick<Article, 'slug' | 'title' | 'category'>\n siteUrl: string\n categorySlug: string\n folderSegments: string[]\n labels: BreadcrumbLabels\n }>\n): BreadcrumbItem[] {\n if (isCustomBreadcrumbItem(entry)) {\n return [resolveCustomBreadcrumbItem(entry, context.siteUrl)]\n }\n if (entry === 'home') return [{ name: context.labels.home ?? 'Home', url: context.siteUrl }]\n if (entry === 'articles') {\n return [{ name: context.labels.articles ?? 'Articles', url: `${context.siteUrl}/articles` }]\n }\n if (entry === 'primaryCategory') {\n return [\n {\n name: context.article.category,\n url: `${context.siteUrl}/articles/category/${context.categorySlug}`,\n },\n ]\n }\n if (entry === 'folderPath') {\n return context.folderSegments.map((segment, index) => ({\n name: formatCategoryName(segment),\n url: `${context.siteUrl}/articles/${context.folderSegments.slice(0, index + 1).join('/')}`,\n }))\n }\n return [{ name: context.article.title }]\n}\n\nfunction buildCategoryBreadcrumbEntry(\n entry: CategoryBreadcrumbEntry,\n context: Readonly<{ categoryName: string; siteUrl: string; labels: BreadcrumbLabels }>\n): BreadcrumbItem[] {\n if (isCustomBreadcrumbItem(entry)) {\n return [resolveCustomBreadcrumbItem(entry, context.siteUrl)]\n }\n if (entry === 'home') return [{ name: context.labels.home ?? 'Home', url: context.siteUrl }]\n if (entry === 'articles') {\n return [{ name: context.labels.articles ?? 'Articles', url: `${context.siteUrl}/articles` }]\n }\n return [{ name: context.categoryName }]\n}\n\nfunction buildAuthorBreadcrumbEntry(\n entry: AuthorBreadcrumbEntry,\n context: Readonly<{ author: AuthorProfile; siteUrl: string; labels: BreadcrumbLabels }>\n): BreadcrumbItem[] {\n if (isCustomBreadcrumbItem(entry)) {\n return [resolveCustomBreadcrumbItem(entry, context.siteUrl)]\n }\n if (entry === 'home') return [{ name: context.labels.home ?? 'Home', url: context.siteUrl }]\n if (entry === 'articles') {\n return [{ name: context.labels.articles ?? 'Articles', url: `${context.siteUrl}/articles` }]\n }\n if (entry === 'authors') {\n return [\n {\n name: context.labels.authors ?? 'Authors',\n url: `${context.siteUrl}/articles/authors`,\n },\n ]\n }\n return [{ name: context.author.name }]\n}\n\nexport function resolveAuthorAvatar(author: AuthorProfile, config: ArticlesConfig): string {\n if (!author.avatar) return ''\n if (author.avatar.startsWith('http://') || author.avatar.startsWith('https://')) {\n return author.avatar\n }\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n return `${siteUrl}/articles/authors/${author.slug}/${author.avatar.replace(/^\\/+/, '')}`\n}\n\nexport async function getArticleSitemapEntries(\n baseUrlOrConfig: string | ArticlesConfig\n): Promise<MetadataRoute.Sitemap> {\n const baseUrl = (\n typeof baseUrlOrConfig === 'string' ? baseUrlOrConfig : baseUrlOrConfig.siteUrl\n ).replace(/\\/$/, '')\n\n try {\n const [articles, categories] = await Promise.all([\n getAllArticles(typeof baseUrlOrConfig === 'string' ? undefined : baseUrlOrConfig),\n getAllCategories(),\n ])\n\n const articleEntries: MetadataRoute.Sitemap = articles.map((article) => {\n const dateStr = article.lastmod ?? article.date\n const lastModified = dateStr ? new Date(dateStr) : undefined\n return {\n url: `${baseUrl}/articles/${article.slug}`,\n lastModified,\n changeFrequency: 'weekly' as const,\n priority: 0.8,\n }\n })\n\n const categoryEntries: MetadataRoute.Sitemap = categories.map((cat) => ({\n url: `${baseUrl}/articles/category/${cat.slug}`,\n lastModified: new Date(),\n changeFrequency: 'weekly' as const,\n priority: 0.7,\n }))\n\n const authorEntries: MetadataRoute.Sitemap =\n typeof baseUrlOrConfig === 'string' || baseUrlOrConfig.showAuthorPage === false\n ? []\n : getAllAuthors(baseUrlOrConfig).map((author) => ({\n url: `${baseUrl}/articles/authors/${author.slug}`,\n changeFrequency: 'monthly' as const,\n priority: 0.6,\n }))\n\n return [...articleEntries, ...categoryEntries, ...authorEntries]\n } catch {\n return []\n }\n}\n","// MDX files must use JSX prop syntax:\n// Correct: <span style={{ color: '#3b82f6' }}>Text</span>\n// Incorrect: <span style=\"color: #3b82f6\">Text</span>\n// Correct: className=\"...\"\n// Incorrect: class=\"...\"\nimport React from 'react'\nimport type { AnchorHTMLAttributes, ComponentType, ImgHTMLAttributes } from 'react'\nimport Link from 'next/link'\nimport * as devRuntime from 'react/jsx-dev-runtime'\nimport * as runtime from 'react/jsx-runtime'\nimport { evaluate } from '@mdx-js/mdx'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSlug from 'rehype-slug'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport { customRenderer } from './markdown'\nimport { isExternalHttpLink, isNonBrowserNavigationLink } from './linkClassification'\nimport type { ArticlesConfig } from './articlesConfig'\n\ntype MdxContent = ComponentType<{\n components?: Record<string, ComponentType<unknown>>\n}>\n\nfunction makeImgComponent(basePath: string) {\n return function MdxImage({ src, alt, ...props }: ImgHTMLAttributes<HTMLImageElement>) {\n const resolvedSrc =\n typeof src === 'string' && !src.startsWith('http') && !src.startsWith('/')\n ? `${basePath}/${src}`\n : src\n return React.createElement('img', { src: resolvedSrc, alt, ...props })\n }\n}\n\n// Same internal/external classification `customRenderer`'s rehype pass\n// already used to decide target=\"_blank\" (see `applyLinkTarget` in\n// markdown.ts) - reused rather than reimplemented so the two decisions\n// can never drift apart. An in-page anchor (#section) or a non-browser\n// scheme (mailto:, tel:) is never routed through next/link either; only a\n// same-origin, browser-navigable href gets client-side routing.\nfunction isInternalNavigableHref(href: string, siteUrl?: string): boolean {\n if (!href || href.startsWith('#') || isNonBrowserNavigationLink(href)) return false\n return !isExternalHttpLink(href, siteUrl)\n}\n\nfunction makeLinkComponent(siteUrl?: string) {\n return function MdxLink({ href, children, ...props }: AnchorHTMLAttributes<HTMLAnchorElement>) {\n if (typeof href === 'string' && isInternalNavigableHref(href, siteUrl)) {\n // React.createElement, not JSX, for the same reason makeImgComponent\n // above uses it: next/link's own (duplicate) @types/react copy in\n // this monorepo's node_modules is structurally incompatible with\n // this file's DOM attribute types, and JSX's prop-checking is\n // stricter about that mismatch than createElement's is.\n return React.createElement(Link, { href, ...props } as never, children)\n }\n return (\n <a href={href} {...props}>\n {children}\n </a>\n )\n }\n}\n\nexport async function renderMdxSource(source: string, basePath?: string, config?: ArticlesConfig) {\n const isDevelopment = process.env.NODE_ENV === 'development'\n\n const mdxModule = await evaluate(source, {\n ...(isDevelopment ? devRuntime : runtime),\n development: isDevelopment,\n remarkPlugins: [remarkGfm, remarkGithubBlockquoteAlert],\n rehypePlugins: [\n [customRenderer, { strategy: config?.linkTargetStrategy, siteUrl: config?.siteUrl }],\n rehypeSlug,\n // @ts-ignore\n rehypePrism,\n ],\n })\n\n const Content = mdxModule.default as MdxContent\n // `a` is always overridden - unlike `img`, internal-link routing isn't\n // conditional on basePath being provided. Every article's markdown\n // links otherwise compile to a plain `<a>` (a full page reload on\n // click, since @mdx-js/mdx's evaluate() has no knowledge of Next's\n // router), which was silently forcing a hard navigation - and a fresh\n // same-site document.referrer - on every single in-article link click.\n const internalComponents = {\n a: makeLinkComponent(config?.siteUrl),\n ...(basePath ? { img: makeImgComponent(basePath) } : {}),\n }\n const components = { ...internalComponents, ...config?.mdxComponents }\n return <Content components={components as Record<string, ComponentType<unknown>>} />\n}\n","import type { ReactNode } from 'react'\nimport { renderMdxSource } from './renderMdx'\nimport { markdownToHtml, getContentSlotBoundaries } from './markdown'\nimport type { Article } from './articleTypes'\nimport type { ArticlesConfig } from './articlesConfig'\n\n/**\n * Sanitized, non-PII context passed into `ArticleContent`'s slot render\n * props - deliberately a narrow subset of `Article`, not the whole object\n * (no raw `content`/`mdxSource`, no author email or anything author-PII).\n */\nexport interface ArticleSlotContext {\n slug: string\n title: string\n category: string\n tags: string[]\n readTime: string\n wordCount?: number\n authorSlug?: string\n seriesSlug?: string\n primaryActionId?: string\n}\n\nexport type ArticleSlotContent = ReactNode | ((context: ArticleSlotContext) => ReactNode)\n\nfunction buildSlotContext(article: Article): ArticleSlotContext {\n return {\n slug: article.slug,\n title: article.title,\n category: article.category,\n tags: article.tags ?? [],\n readTime: article.readTime,\n wordCount: article.wordCount,\n authorSlug: article.authorSlug,\n seriesSlug: article.seriesSlug,\n primaryActionId: article.primaryAction?.actionId,\n }\n}\n\nfunction resolveSlot(slot: ArticleSlotContent | undefined, context: ArticleSlotContext): ReactNode {\n if (slot === undefined) return null\n return typeof slot === 'function' ? slot(context) : slot\n}\n\ntype ArticleContentProps = Readonly<{\n article: Article\n className?: string\n config?: ArticlesConfig\n /** Rendered immediately before the article body - the \"around the body, not inside it\" counterpart to `config.mdxComponents` (which places content *inside* MDX bodies). */\n afterHero?: ArticleSlotContent\n /** Rendered right after the first paragraph, resolved deterministically from the parsed AST (see `getContentSlotBoundaries`). Falls back to not rendering (never a brittle string split) when the source has no detectable paragraphs, e.g. MDX using JSX-heavy syntax remark-parse can't read as plain markdown. */\n afterIntro?: ArticleSlotContent\n /** Rendered after roughly the middle paragraph. Same fallback behavior as `afterIntro`. */\n midContent?: ArticleSlotContent\n /** Rendered immediately after the article body. */\n afterContent?: ArticleSlotContent\n}>\n\nasync function renderSegment(\n markdown: string,\n contentType: Article['contentType'],\n slug: string,\n config?: ArticlesConfig\n): Promise<ReactNode> {\n if (!markdown.trim()) return null\n if (contentType === 'mdx') {\n return renderMdxSource(markdown, `/articles/${slug}`, config)\n }\n const html = await markdownToHtml(markdown, slug, config)\n return <div dangerouslySetInnerHTML={{ __html: html }} />\n}\n\nexport async function ArticleContent({\n article,\n className,\n config,\n afterHero,\n afterIntro,\n midContent,\n afterContent,\n}: ArticleContentProps) {\n // Legacy path (Phase 27F: zero slot props passed) reproduces the exact\n // pre-27F markup - a single outer div, `dangerouslySetInnerHTML` set\n // directly on it for the HTML path - rather than the slot-aware wrapper\n // below, so existing consumers/tests see byte-for-byte identical output.\n const hasAnySlot =\n afterHero !== undefined ||\n afterIntro !== undefined ||\n midContent !== undefined ||\n afterContent !== undefined\n if (!hasAnySlot) {\n if (article.contentType === 'mdx' && article.mdxSource) {\n const content = await renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config)\n return <div className={className}>{content}</div>\n }\n return (\n <div className={className} dangerouslySetInnerHTML={{ __html: article.htmlContent || '' }} />\n )\n }\n\n const slotContext = buildSlotContext(article)\n const heroNode = resolveSlot(afterHero, slotContext)\n const introNode = resolveSlot(afterIntro, slotContext)\n const midNode = resolveSlot(midContent, slotContext)\n const contentNode = resolveSlot(afterContent, slotContext)\n\n const needsSplit = Boolean(introNode || midNode)\n const rawSource =\n article.contentType === 'mdx' ? article.mdxSource : (article.content ?? undefined)\n\n if (needsSplit && rawSource) {\n const boundaries = getContentSlotBoundaries(rawSource)\n if (boundaries) {\n try {\n const introSegment = rawSource.slice(0, boundaries.introEnd)\n const midSegment = rawSource.slice(boundaries.introEnd, boundaries.mid)\n const restSegment = rawSource.slice(boundaries.mid)\n const [introHtml, midHtml, restHtml] = await Promise.all([\n renderSegment(introSegment, article.contentType, article.slug, config),\n renderSegment(midSegment, article.contentType, article.slug, config),\n renderSegment(restSegment, article.contentType, article.slug, config),\n ])\n return (\n <div className={className}>\n {heroNode}\n {introHtml}\n {introNode}\n {midHtml}\n {midNode}\n {restHtml}\n {contentNode}\n </div>\n )\n } catch {\n // Splitting the MDX source failed to evaluate (e.g. a JSX block\n // straddled a paragraph boundary) - fall through to the\n // whole-document render below rather than throwing. `afterIntro`/\n // `midContent` are silently omitted for this article; `afterHero`/\n // `afterContent` still render.\n }\n }\n }\n\n const wholeBody =\n article.contentType === 'mdx' && article.mdxSource ? (\n await renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config)\n ) : (\n <div dangerouslySetInnerHTML={{ __html: article.htmlContent || '' }} />\n )\n\n return (\n <div className={className}>\n {heroNode}\n {wholeBody}\n {contentNode}\n </div>\n )\n}\n","import type { TocItem } from './articleTypes'\n\ntype ArticleTOCProps = Readonly<{ toc: TocItem[]; className?: string }>\n\nexport function ArticleTOC({ toc, className }: ArticleTOCProps) {\n if (!toc.length) return null\n return (\n <nav\n aria-label=\"Table of contents\"\n className={`mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className ?? ''}`}\n >\n <p className=\"mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground\">\n On this page\n </p>\n <ul className=\"space-y-1 text-sm\">\n {toc.map((item) => (\n <li key={item.id} style={{ paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }}>\n <a\n href={`#${item.id}`}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n >\n {item.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n )\n}\n","// Package validator (Phase 27F). A pure function operating on an already\n// loaded `Article[]`/`ArticlesConfig` - no `fs` access here, so it's\n// directly unit-testable with fixture data. `validateAllArticles` below is\n// the thin, fs-dependent convenience wrapper (`server`-only, like the rest\n// of this file) for a consuming app's own validation script.\nimport {\n getAllArticles,\n getArticleAuthors,\n getAuthorBySlug,\n categoryToSlug,\n} from './server-articles'\nimport type { Article } from './articleTypes'\nimport type { ArticlesConfig } from './articlesConfig'\n\nexport type ValidationSeverity = 'error' | 'warning'\n\nexport interface ValidationIssue {\n severity: ValidationSeverity\n /** Stable machine-readable code, e.g. `'duplicate-canonical-url'`. */\n code: string\n message: string\n articleSlug?: string\n pathKey?: string\n}\n\nexport interface ValidationResult {\n ok: boolean\n errors: ValidationIssue[]\n warnings: ValidationIssue[]\n}\n\nconst UNSAFE_URL_SCHEME = /^\\s*(javascript|data|vbscript):/i\n\nconst SEARCH_TITLE_MAX = 60\nconst SEARCH_DESCRIPTION_MAX = 160\nconst SOCIAL_TITLE_MAX = 95\nconst SOCIAL_DESCRIPTION_MAX = 200\n\nfunction isUnsafeUrl(href: string): boolean {\n return UNSAFE_URL_SCHEME.test(href)\n}\n\nfunction checkDuplicateCanonicalUrls(articles: Article[]): ValidationIssue[] {\n const seen = new Map<string, string>()\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n if (!article.canonicalUrl) continue\n const owner = seen.get(article.canonicalUrl)\n if (owner) {\n issues.push({\n severity: 'error',\n code: 'duplicate-canonical-url',\n message: `canonicalUrl \"${article.canonicalUrl}\" is also used by \"${owner}\".`,\n articleSlug: article.slug,\n })\n } else {\n seen.set(article.canonicalUrl, article.slug)\n }\n }\n return issues\n}\n\nfunction checkAuthorReferences(articles: Article[], config: ArticlesConfig): ValidationIssue[] {\n if (!config.authors || Object.keys(config.authors).length === 0) return []\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n for (const resolved of getArticleAuthors(article, config)) {\n if (!getAuthorBySlug(resolved.slug, config)) {\n issues.push({\n severity: 'error',\n code: 'unknown-author-reference',\n message: `Author \"${resolved.name}\" does not match any entry in config.authors.`,\n articleSlug: article.slug,\n })\n }\n }\n }\n return issues\n}\n\nfunction checkSeriesCollisions(articles: Article[]): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n const seenSlugOrder = new Map<string, string>()\n for (const article of articles) {\n if (!article.seriesSlug || article.seriesOrder === undefined) continue\n const key = `${article.seriesSlug}::${article.seriesOrder}`\n const owner = seenSlugOrder.get(key)\n if (owner) {\n issues.push({\n severity: 'error',\n code: 'duplicate-series-order',\n message: `seriesOrder ${article.seriesOrder} in series \"${article.seriesSlug}\" collides with \"${owner}\".`,\n articleSlug: article.slug,\n })\n } else {\n seenSlugOrder.set(key, article.slug)\n }\n }\n return issues\n}\n\nfunction checkPaths(articles: Article[], config: ArticlesConfig): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n const bySlug = new Map(articles.map((article) => [article.slug, article]))\n for (const [pathKey, path] of Object.entries(config.paths ?? {})) {\n if (path.articles.length === 0) {\n issues.push({\n severity: 'error',\n code: 'empty-path',\n message: `Path \"${pathKey}\" has no articles.`,\n pathKey,\n })\n }\n for (const slug of path.articles) {\n const referenced = bySlug.get(slug)\n if (!referenced) {\n issues.push({\n severity: 'error',\n code: 'path-missing-article',\n message: `Path \"${pathKey}\" references missing article \"${slug}\".`,\n pathKey,\n articleSlug: slug,\n })\n } else if (referenced.draft) {\n issues.push({\n severity: 'error',\n code: 'path-references-draft',\n message: `Path \"${pathKey}\" references unpublished (draft) article \"${slug}\".`,\n pathKey,\n articleSlug: slug,\n })\n }\n }\n if (isUnsafeUrl(path.nextAction.href)) {\n issues.push({\n severity: 'error',\n code: 'unsafe-url',\n message: `Path \"${pathKey}\" nextAction.href uses an unsafe URL scheme.`,\n pathKey,\n })\n }\n }\n return issues\n}\n\nfunction checkAuthorCtaUrls(config: ArticlesConfig): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n for (const author of Object.values(config.authors ?? {})) {\n if (author.primaryCta && isUnsafeUrl(author.primaryCta.href)) {\n issues.push({\n severity: 'error',\n code: 'unsafe-url',\n message: `Author \"${author.slug}\" primaryCta.href uses an unsafe URL scheme.`,\n })\n }\n }\n return issues\n}\n\nfunction checkRequiredFrontmatter(articles: Article[]): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n if (!article.excerpt) {\n issues.push({\n severity: 'warning',\n code: 'missing-excerpt',\n message: 'Article has no excerpt.',\n articleSlug: article.slug,\n })\n }\n if (!article.date) {\n issues.push({\n severity: 'warning',\n code: 'missing-date',\n message: 'Article has no date.',\n articleSlug: article.slug,\n })\n }\n }\n return issues\n}\n\nfunction checkDiscoveryFieldLengths(articles: Article[]): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n const checks: [string | undefined, string, number][] = [\n [article.searchTitle, 'search-title-too-long', SEARCH_TITLE_MAX],\n [article.searchDescription, 'search-description-too-long', SEARCH_DESCRIPTION_MAX],\n [article.socialTitle, 'social-title-too-long', SOCIAL_TITLE_MAX],\n [article.socialDescription, 'social-description-too-long', SOCIAL_DESCRIPTION_MAX],\n ]\n for (const [value, code, max] of checks) {\n if (value && value.length > max) {\n issues.push({\n severity: 'warning',\n code,\n message: `${code.replaceAll('-', ' ')} (${value.length} > ${max} recommended chars).`,\n articleSlug: article.slug,\n })\n }\n }\n }\n return issues\n}\n\nfunction checkCategorySlugs(articles: Article[]): ValidationIssue[] {\n // Two differently-cased/spaced category labels that collapse to the same\n // slug silently merge on `/articles/category/[slug]` - surfaced as a\n // warning (not an error) since it may be intentional (e.g. \"Game\n // Masters\" and \"game-masters\" tags both meaning the same category).\n const issues: ValidationIssue[] = []\n const slugToNames = new Map<string, Set<string>>()\n for (const article of articles) {\n for (const category of article.categories) {\n const slug = categoryToSlug(category)\n const names = slugToNames.get(slug) ?? new Set<string>()\n names.add(category)\n slugToNames.set(slug, names)\n }\n }\n for (const [slug, names] of slugToNames) {\n if (names.size > 1) {\n issues.push({\n severity: 'warning',\n code: 'category-slug-collision',\n message: `Categories [${[...names].join(', ')}] all collapse to slug \"${slug}\".`,\n })\n }\n }\n return issues\n}\n\n/**\n * Validates a loaded article set + config. Warnings cover optional\n * discovery-field issues (missing excerpt/date, over-length search/social\n * fields, category slug collisions); errors cover broken reader journeys\n * (duplicate canonical URLs, unknown author references, series order\n * collisions, missing/draft path references, unsafe URL schemes).\n */\nexport function validateArticles(articles: Article[], config: ArticlesConfig): ValidationResult {\n const errors = [\n ...checkDuplicateCanonicalUrls(articles),\n ...checkAuthorReferences(articles, config),\n ...checkSeriesCollisions(articles),\n ...checkPaths(articles, config),\n ...checkAuthorCtaUrls(config),\n ]\n const warnings = [\n ...checkRequiredFrontmatter(articles),\n ...checkDiscoveryFieldLengths(articles),\n ...checkCategorySlugs(articles),\n ]\n return { ok: errors.length === 0, errors, warnings }\n}\n\n/** Convenience wrapper: loads every article via `getAllArticles(config)` (fs-dependent) then validates. Suitable for a consuming app's own `scripts/validate-articles.ts` invoked in CI before publish. */\nexport async function validateAllArticles(config: ArticlesConfig): Promise<ValidationResult> {\n const articles = await getAllArticles(config)\n return validateArticles(articles, config)\n}\n","// Vendor-neutral article event contract (Phase 27F). The package emits\n// these typed events at the right places in existing components/hooks and\n// hands them to `config.onEvent` - it never talks to PostHog/Plunk/any\n// analytics or email vendor directly (see package.json dependencies, which\n// stay clean of them). No PII in any payload: only slugs/IDs/enums, never\n// emails, names-as-identifiers, or free text.\n\nexport type ArticleEventName =\n | 'article_viewed'\n | 'meaningful_read'\n | 'author_clicked'\n | 'cta_viewed'\n | 'cta_clicked'\n | 'shared'\n | 'related_article_clicked'\n | 'path_step_advanced'\n\ninterface ArticleEventBase<Name extends ArticleEventName> {\n name: Name\n /** `Date.now()` at emit time. */\n timestamp: number\n}\n\nexport interface ArticleViewedEvent extends ArticleEventBase<'article_viewed'> {\n articleSlug: string\n category?: string\n seriesSlug?: string\n}\n\n/** Fired once per view after the reader has spent roughly half the article's estimated read time on the page (see `ArticleViewTracker`). */\nexport interface MeaningfulReadEvent extends ArticleEventBase<'meaningful_read'> {\n articleSlug: string\n}\n\nexport interface AuthorClickedEvent extends ArticleEventBase<'author_clicked'> {\n articleSlug: string\n authorSlug: string\n}\n\n/** `ctaId` is `primaryAction.actionId`, an `AuthorProfile.primaryCta` slug, or a `PathDefinition` key - always an app-chosen ID, never label text. */\nexport interface CtaViewedEvent extends ArticleEventBase<'cta_viewed'> {\n ctaId: string\n articleSlug?: string\n}\n\nexport interface CtaClickedEvent extends ArticleEventBase<'cta_clicked'> {\n ctaId: string\n articleSlug?: string\n}\n\nexport interface SharedEvent extends ArticleEventBase<'shared'> {\n articleSlug: string\n /** Share channel key, e.g. `'linkedin'`, `'copy-link'` - never the shared URL/message text. */\n channel: string\n}\n\nexport interface RelatedArticleClickedEvent extends ArticleEventBase<'related_article_clicked'> {\n fromSlug: string\n toSlug: string\n source: 'path' | 'series' | 'category'\n}\n\nexport interface PathStepAdvancedEvent extends ArticleEventBase<'path_step_advanced'> {\n pathKey: string\n fromSlug: string\n toSlug: string\n direction: 'previous' | 'next'\n}\n\nexport type ArticleEvent =\n | ArticleViewedEvent\n | MeaningfulReadEvent\n | AuthorClickedEvent\n | CtaViewedEvent\n | CtaClickedEvent\n | SharedEvent\n | RelatedArticleClickedEvent\n | PathStepAdvancedEvent\n\n/** Register this on `ArticlesConfig.onEvent` to receive every emitted event and translate it to your own analytics stack. */\nexport type ArticleEventHandler = (event: ArticleEvent) => void\n\n// `Omit<ArticleEvent, 'timestamp'>` alone would collapse the discriminated\n// union to its common keys (TypeScript computes `keyof` on a union as the\n// intersection of each member's keys), losing every event-specific field.\n// A distributive conditional type over the naked `T` preserves each\n// member's own shape instead.\ntype DistributiveOmitTimestamp<T> = T extends ArticleEvent ? Omit<T, 'timestamp'> : never\n\n/**\n * Safely invokes `handler` with `event`, stamping `timestamp`. Swallows any\n * error thrown by the consuming app's handler - a broken analytics\n * integration must never break article rendering.\n */\nexport function emitArticleEvent(\n handler: ArticleEventHandler | undefined,\n event: DistributiveOmitTimestamp<ArticleEvent>\n): void {\n if (!handler) return\n try {\n handler({ ...event, timestamp: Date.now() } as ArticleEvent)\n } catch {\n // consuming app's handler errors must never break rendering\n }\n}\n"],"mappings":"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","names":["sanitizeImagePath","path","remarkParse","remarkGfm","remarkGithubBlockquoteAlert","remarkRehype","rehypeSlug","rehypePrism","rehypeSanitize","rehypeStringify","path","readingTime","fs","sanitizeImagePath","_a","matter","import_react","import_rehype_prism_plus","import_rehype_slug","import_remark_gfm","import_remark_github_blockquote_alert","React","Link","remarkGfm","remarkGithubBlockquoteAlert","rehypeSlug","rehypePrism","import_jsx_runtime","import_jsx_runtime","path"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/server-articles.ts","../src/authorUtils.ts","../src/markdown.ts","../src/errorReporting.ts","../src/linkClassification.ts","../src/articlesConfig.ts","../src/pagination.ts","../src/seoUtils.ts","../src/renderMdx.tsx","../src/ArticleContent.tsx","../src/ArticleTOC.tsx","../src/ArticleAnswer.tsx","../src/validateArticles.ts","../src/events.ts"],"sourcesContent":["// Server-only exports — uses fs/path; never import this in a client bundle\nexport {\n getAllArticles,\n getAiRobotsTxtRules,\n buildMarkdownTwinHeader,\n getMarkdownTwinResponse,\n getCategoryMarkdown,\n getAuthorMarkdown,\n getSeriesMarkdown,\n matchAiCrawler,\n AI_CRAWLERS,\n getArticleAiHeaders,\n getArticleMarkdown,\n getArticleMarkdownResponse,\n getArticleMarkdownUrl,\n getArticleMetadata,\n getArticleAuthors,\n getAvailableArticleSlugs,\n getAdjacentArticles,\n getAdjacentArticlesInSeries,\n getArticlesBySeries,\n getRelatedArticlesByCategory,\n getRelatedContent,\n getPath,\n getPathArticles,\n getAllAuthors,\n searchArticles,\n getAuthorBySlug,\n getAllCategories,\n getArticlesByAuthor,\n getArticlesByCategory,\n categoryToSlug,\n sanitizeImagePath,\n} from './server-articles'\n\nexport {\n generateRssFeed,\n generateLlmsTxt,\n generateLlmsFullTxt,\n generateArticleStaticParams,\n generateCategoryStaticParams,\n generateSeriesStaticParams,\n generateAuthorStaticParams,\n generateArticlesIndexMetadata,\n generateArticlesIndexPageMetadata,\n generateArticleMetadata,\n generateCategoryMetadata,\n generateCategoryPageMetadata,\n generateSeriesMetadata,\n generateAuthorMetadata,\n generateAuthorPageMetadata,\n buildArticleBreadcrumbs,\n buildCategoryBreadcrumbs,\n buildAuthorBreadcrumbs,\n resolveAuthorAvatar,\n resolveSearchMetadata,\n resolveSocialMetadata,\n getArticleSitemapEntries,\n} from './seoUtils'\n\nexport {\n getTotalPages,\n paginateArticles,\n buildPageUrl,\n buildPaginationLinks,\n generateListingPageStaticParams,\n parsePageParam,\n isPageOutOfRange,\n} from './pagination'\n\nexport {\n markdownToHtml,\n extractToc,\n getContentSlotBoundaries,\n deriveFaqFromHeadings,\n} from './markdown'\nexport { setArticlesErrorHandler } from './errorReporting'\nexport {\n formatPageTitle,\n getBreadcrumbsConfig,\n getOrganizationId,\n getPersonId,\n getWebSiteId,\n} from './articlesConfig'\nexport { ArticleContent } from './ArticleContent'\nexport { ArticleTOC } from './ArticleTOC'\nexport { ArticleAnswer } from './ArticleAnswer'\nexport { validateArticles, validateAllArticles } from './validateArticles'\nexport { emitArticleEvent } from './events'\n\nexport type {\n Article,\n AuthorProfile,\n AuthorSocial,\n BreadcrumbItem,\n CategoryInfo,\n CitationReference,\n EntityReference,\n FaqItem,\n HowToStep,\n PathDefinition,\n TocItem,\n} from './articleTypes'\nexport type {\n AiCrawlEvent,\n ArticlesConfig,\n LinkTargetStrategy,\n ListingPagination,\n OrganizationConfig,\n} from './articlesConfig'\nexport type { PaginatedArticles, PaginationLinks, ListingPaginationContext } from './pagination'\nexport type { ContentSlotBoundaries } from './markdown'\nexport type { ArticleSlotContext, ArticleSlotContent } from './ArticleContent'\nexport type { RelatedContentResult, RelatedContentSource, RequestHeaders } from './server-articles'\nexport type { ValidationIssue, ValidationResult, ValidationSeverity } from './validateArticles'\nexport type {\n ArticleEvent,\n ArticleEventHandler,\n ArticleEventName,\n ArticleViewedEvent,\n MeaningfulReadEvent,\n AuthorClickedEvent,\n CtaViewedEvent,\n CtaClickedEvent,\n SharedEvent,\n RelatedArticleClickedEvent,\n PathStepAdvancedEvent,\n} from './events'\nexport type {\n ArticlesErrorCode,\n ArticlesErrorContext,\n ArticlesErrorHandler,\n ArticlesErrorReport,\n} from './errorReporting'\n","import { cache } from 'react'\nimport matter from 'gray-matter'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport readingTime from 'reading-time'\nimport { getAuthorAvatar } from './authorUtils'\nimport { markdownToHtml, extractToc, deriveFaqFromHeadings } from './markdown'\nimport type {\n Article,\n AuthorProfile,\n CategoryInfo,\n CitationReference,\n EntityReference,\n FaqItem,\n HowToStep,\n PathDefinition,\n} from './articleTypes'\nimport type { ArticlesConfig } from './articlesConfig'\nimport { reportArticlesError } from './errorReporting'\n\nconst articlesDirectory = path.join(/* turbopackIgnore: true */ process.cwd(), 'public/articles')\n\nfunction getReadingStats(content: string): { readTime: string; wordCount: number } {\n const stats = readingTime(content)\n return { readTime: stats.text, wordCount: stats.words }\n}\n\nfunction findArticleImage(slug: string): string | null {\n try {\n const articleDir = path.join(articlesDirectory, slug)\n const entries: (fs.Dirent | string)[] = fs.readdirSync(articleDir, { withFileTypes: true })\n const names = entries\n .map((entry) => {\n if (typeof entry === 'string') return entry\n if (entry && typeof entry.name === 'string') return entry.name\n return null\n })\n .filter((name): name is string => Boolean(name))\n const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp']\n const imageFileName = names.find((name) =>\n imageExtensions.some((ext) => name.toLowerCase().endsWith(ext))\n )\n return imageFileName ? sanitizeImagePath(imageFileName, slug) : null\n } catch {\n return null\n }\n}\n\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') return null\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) return cleanPath\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected unsafe article image path.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected article image path with invalid characters.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n if (cleanPath.includes('/')) {\n const normalizedPath = path.normalize(cleanPath)\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected article image path traversal attempt.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n return `/articles/${articleSlug}/${cleanPath}`\n }\n return `/articles/${articleSlug}/${cleanPath}`\n}\n\nfunction findArticleFile(slug: string): { filePath: string; contentType: 'md' | 'mdx' } | null {\n const mdPath = path.join(articlesDirectory, slug, 'article.md')\n const mdxPath = path.join(articlesDirectory, slug, 'article.mdx')\n if (fs.existsSync(mdPath)) return { filePath: mdPath, contentType: 'md' }\n if (fs.existsSync(mdxPath)) return { filePath: mdxPath, contentType: 'mdx' }\n return null\n}\n\nexport function getAvailableArticleSlugs(): string[] {\n try {\n if (!fs.existsSync(articlesDirectory)) return []\n return walkArticleDir(articlesDirectory, '')\n } catch (error) {\n reportArticlesError({\n code: 'article-directory-read-failed',\n message: 'Unable to read articles directory.',\n error,\n context: { directory: articlesDirectory },\n })\n return []\n }\n}\n\nfunction walkArticleDir(dir: string, baseSlug: string): string[] {\n const slugs: string[] = []\n try {\n const items = fs.readdirSync(dir, { withFileTypes: true })\n for (const item of items) {\n if (!item.isDirectory()) continue\n const slug = baseSlug ? `${baseSlug}/${item.name}` : item.name\n if (findArticleFile(slug) !== null) {\n slugs.push(slug)\n }\n slugs.push(...walkArticleDir(path.join(dir, item.name), slug))\n }\n } catch {\n // ignore unreadable directories\n }\n return slugs\n}\n\nfunction parseDateField(rawDate: unknown): string | undefined {\n if (!rawDate) return undefined\n try {\n const parsed = new Date(rawDate as string)\n if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split('T')[0]\n } catch {\n // ignore invalid dates\n }\n return undefined\n}\n\nfunction resolveFeaturedImage(rawImage: unknown, slug: string): string {\n const img = typeof rawImage === 'string' ? rawImage : ''\n if (img && !img.startsWith('http')) return sanitizeImagePath(img, slug) || '/placeholder-logo.png'\n if (!img) return findArticleImage(slug) || '/placeholder-logo.png'\n return img\n}\n\nfunction parseFaqItems(raw: unknown): FaqItem[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const items = raw.filter(\n (item): item is FaqItem =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as FaqItem).question === 'string' &&\n typeof (item as FaqItem).answer === 'string'\n )\n return items.length ? items : undefined\n}\n\nfunction parseHowToSteps(raw: unknown): HowToStep[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const steps = raw.filter(\n (item): item is HowToStep =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as HowToStep).name === 'string' &&\n typeof (item as HowToStep).text === 'string'\n )\n return steps.length ? steps : undefined\n}\n\n// Trims and drops empty strings - the same \"unset if blank\" rule applied to\n// every other optional string frontmatter field in this file\n// (canonicalUrl/articleType/series). Used for all five discovery overrides\n// (Phase 27F) so a stray `searchTitle: \"\"` in frontmatter behaves exactly\n// like omitting the key, rather than becoming an empty <title> override.\nfunction parseOptionalString(raw: unknown): string | undefined {\n return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : undefined\n}\n\n// `aiCrawl` is the one frontmatter flag with a config-level default\n// (`ArticlesConfig.aiCrawlDefault`). Explicit `true`/`false` in frontmatter\n// always wins; only an omitted/non-boolean key falls through to the config.\n// Omitting both reproduces the original \"blocked unless opted in\" behavior.\n// Explicit `faq` frontmatter always wins - derivation only fills the gap\n// for articles that never declared one, and only when the site opted in.\nfunction deriveFaq(markdownContent: string, config?: ArticlesConfig): FaqItem[] | undefined {\n if (config?.deriveFaqFromHeadings !== true) return undefined\n const derived = deriveFaqFromHeadings(markdownContent)\n return derived.length ? derived : undefined\n}\n\n// Frontmatter `lastmod` always wins. Only `'fileMtime'` produces a value\n// here; `'published'` (the default) and `'none'` differ in what\n// `ArticleSEO` does with an absent `lastmod` - reuse `datePublished`, or\n// omit `dateModified` rather than repeat a stale date.\nfunction resolveLastmod(\n rawLastmod: unknown,\n rawDate: unknown,\n filePath: string,\n config?: ArticlesConfig\n): string | undefined {\n const declared = parseDateField(rawLastmod)\n if (declared) return declared\n if (config?.lastmodFallback !== 'fileMtime') return undefined\n try {\n return parseDateField(fs.statSync(filePath).mtime)\n } catch {\n return parseDateField(rawDate)\n }\n}\n\nfunction resolveAiCrawl(raw: unknown, config?: ArticlesConfig): boolean {\n if (typeof raw === 'boolean') return raw\n return config?.aiCrawlDefault === true\n}\n\n// A bare string resolves through `config.entities` first, so a corpus can\n// share one canonical name/`sameAs` pair per topic instead of each article\n// spelling it out (and spelling it differently). Unregistered strings still\n// work as plain names - `validateArticles` warns about them rather than\n// dropping the entry.\nfunction parseEntityReferences(\n raw: unknown,\n config?: ArticlesConfig\n): EntityReference[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const items = raw\n .map((item) => {\n if (typeof item === 'string') {\n const key = item.trim()\n if (!key) return null\n return config?.entities?.[key] ?? { name: key }\n }\n if (\n typeof item === 'object' &&\n item !== null &&\n typeof (item as EntityReference).name === 'string'\n ) {\n const entity = item as EntityReference\n const name = entity.name.trim()\n if (!name) return null\n return entity.sameAs ? { name, sameAs: entity.sameAs } : { name }\n }\n return null\n })\n .filter((item): item is EntityReference => item !== null)\n return items.length ? items : undefined\n}\n\nfunction parseCitations(raw: unknown): CitationReference[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const items = raw\n .map((item) => {\n if (typeof item === 'string') return item.trim() ? { name: item.trim() } : null\n if (\n typeof item === 'object' &&\n item !== null &&\n typeof (item as CitationReference).name === 'string'\n ) {\n const citation = item as CitationReference\n const name = citation.name.trim()\n if (!name) return null\n return citation.url ? { name, url: citation.url } : { name }\n }\n return null\n })\n .filter((item): item is CitationReference => item !== null)\n return items.length ? items : undefined\n}\n\nfunction parseSeriesOrder(raw: unknown): number | undefined {\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined\n}\n\nfunction parsePrimaryAction(raw: unknown): { actionId: string } | undefined {\n if (typeof raw === 'string') {\n const actionId = raw.trim()\n return actionId ? { actionId } : undefined\n }\n if (\n typeof raw === 'object' &&\n raw !== null &&\n typeof (raw as { actionId?: unknown }).actionId === 'string'\n ) {\n const actionId = (raw as { actionId: string }).actionId.trim()\n return actionId ? { actionId } : undefined\n }\n return undefined\n}\n\nfunction parseAuthors(raw: unknown): string[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const authors = raw.filter(\n (author): author is string => typeof author === 'string' && author.trim().length > 0\n )\n return authors\n}\n\nexport function getAuthorBySlug(slug: string, config: ArticlesConfig): AuthorProfile | null {\n const profile = config.authors?.[slug]\n if (!profile) return null\n return {\n ...profile,\n url: profile.url ?? `${config.siteUrl.replace(/\\/$/, '')}/articles/authors/${profile.slug}`,\n }\n}\n\nfunction getConfiguredAuthorByName(name: string, config: ArticlesConfig): AuthorProfile | null {\n const normalizedName = name.trim().toLowerCase()\n const profile = Object.values(config.authors ?? {}).find(\n (author) => author.name.toLowerCase() === normalizedName\n )\n return profile ? getAuthorBySlug(profile.slug, config) : null\n}\n\nfunction resolveArticleAuthorName(\n rawAuthor: unknown,\n rawAuthors: unknown,\n config?: ArticlesConfig\n): string {\n const authorArray = parseAuthors(rawAuthors)\n const firstAuthor = authorArray?.[0]\n const rawAuthorValue = typeof rawAuthor === 'string' && rawAuthor.trim() ? rawAuthor : undefined\n const author = firstAuthor ?? rawAuthorValue\n const resolved = author ?? config?.defaultAuthor\n if (!resolved) return ''\n if (!config) return resolved\n return (\n getAuthorBySlug(resolved, config)?.name ??\n getConfiguredAuthorByName(resolved, config)?.name ??\n resolved\n )\n}\n\nexport function getArticleAuthors(article: Article, config: ArticlesConfig): AuthorProfile[] {\n const fallbackAuthors = Array.from(\n new Set(\n [article.author, config.defaultAuthor].filter(\n (author): author is string => typeof author === 'string' && author.trim().length > 0\n )\n )\n )\n const authorValues = article.authors ?? fallbackAuthors\n if (authorValues.length === 0) return []\n const resolvedAuthors = authorValues\n .map((author) => getAuthorBySlug(author, config) ?? getConfiguredAuthorByName(author, config))\n .filter((author): author is AuthorProfile => author !== null)\n .filter((author, index, all) => all.findIndex((a) => a.slug === author.slug) === index)\n\n if (resolvedAuthors.length > 0) return resolvedAuthors\n\n return authorValues.map((fallbackName) => ({\n name: fallbackName,\n slug: categoryToSlug(fallbackName),\n bio: '',\n }))\n}\n\nexport function getAllAuthors(config: ArticlesConfig): AuthorProfile[] {\n return Object.keys(config.authors ?? {})\n .map((slug) => getAuthorBySlug(slug, config))\n .filter((author): author is AuthorProfile => author !== null)\n}\n\nasync function getArticleSummary(slug: string, config?: ArticlesConfig): Promise<Article | null> {\n try {\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { data, content: markdownContent } = matter(fileContent)\n const { readTime, wordCount } = getReadingStats(markdownContent)\n const allTags: string[] = Array.isArray(data.tags)\n ? data.tags.filter((t: unknown) => typeof t === 'string' && String(t).trim())\n : []\n const categories: string[] =\n allTags.length > 0 ? allTags.map((t: string) => t.replaceAll('-', ' ').trim()) : ['Campaigns']\n const author = resolveArticleAuthorName(data.author, data.authors, config)\n const authors = parseAuthors(data.authors)\n // Resolve the primary author's profile (if configured) so cards can\n // render an avatar/link without needing `config` client-side. Reuses\n // getArticleAuthors' existing slug/name resolution chain instead of\n // duplicating it - only the two fields it reads (author/authors) exist\n // on this partial yet.\n const primaryAuthorProfile = config\n ? getArticleAuthors({ author, authors } as Article, config)[0]\n : undefined\n return {\n slug,\n title: data.title || slug.replaceAll('-', ' '),\n excerpt: data.excerpt || '',\n date: parseDateField(data.date),\n lastmod: resolveLastmod(data.lastmod, data.date, found.filePath, config),\n author,\n authors,\n authorSlug: primaryAuthorProfile?.slug,\n authorAvatar: primaryAuthorProfile ? getAuthorAvatar(primaryAuthorProfile) : undefined,\n category: categories[0],\n categories,\n readTime,\n wordCount,\n featuredImage: resolveFeaturedImage(data.featuredImage, slug),\n tags: data.tags || [],\n contentType: found.contentType,\n draft: data.draft === true,\n faq: parseFaqItems(data.faq) ?? deriveFaq(markdownContent, config),\n howTo: parseHowToSteps(data.howTo),\n answer: parseOptionalString(data.answer),\n about: parseEntityReferences(data.about, config),\n citation: parseCitations(data.citation),\n canonicalUrl: typeof data.canonicalUrl === 'string' ? data.canonicalUrl : undefined,\n articleType: typeof data.articleType === 'string' ? data.articleType : undefined,\n series: typeof data.series === 'string' ? data.series : undefined,\n seriesSlug: parseOptionalString(data.seriesSlug),\n seriesOrder: parseSeriesOrder(data.seriesOrder),\n aiCrawl: resolveAiCrawl(data.aiCrawl, config),\n searchTitle: parseOptionalString(data.searchTitle),\n searchDescription: parseOptionalString(data.searchDescription),\n socialTitle: parseOptionalString(data.socialTitle),\n socialDescription: parseOptionalString(data.socialDescription),\n socialImage: parseOptionalString(data.socialImage),\n primaryAction: parsePrimaryAction(data.primaryAction),\n }\n } catch (error) {\n reportArticlesError({\n code: 'article-load-failed',\n message: 'Unable to load article summary.',\n error,\n context: { slug },\n })\n return null\n }\n}\n\nexport const getArticleMetadata = cache(\n async (slug: string, config?: ArticlesConfig): Promise<Article | null> => {\n try {\n const summary = await getArticleSummary(slug, config)\n if (!summary) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n const toc = await extractToc(markdownContent)\n let htmlContent: string | undefined\n let mdxSource: string | undefined\n if (found.contentType === 'mdx') {\n mdxSource = markdownContent\n } else {\n htmlContent = await markdownToHtml(markdownContent, slug, config)\n }\n return { ...summary, content: markdownContent, htmlContent, mdxSource, toc }\n } catch (error) {\n reportArticlesError({\n code: 'article-load-failed',\n message: 'Unable to load article metadata.',\n error,\n context: { slug },\n })\n return null\n }\n }\n)\n\nexport const getAllArticles = cache(async (config?: ArticlesConfig): Promise<Article[]> => {\n const slugs = getAvailableArticleSlugs()\n const articles = await Promise.all(slugs.map((slug) => getArticleSummary(slug, config)))\n const currentDate = new Date().toISOString().split('T')[0]\n return articles\n .filter((article): article is Article => article !== null)\n .filter((article) => !article.date || article.date <= currentDate)\n .filter((article) => !(article.draft && process.env.NODE_ENV === 'production'))\n .sort((a, b) => {\n if (!a.date && !b.date) return 0\n if (!a.date) return 1\n if (!b.date) return -1\n return new Date(b.date).getTime() - new Date(a.date).getTime()\n })\n})\n\nexport async function getAdjacentArticles(\n currentSlug: string\n): Promise<{ previous: Article | null; next: Article | null }> {\n const allArticles = await getAllArticles()\n const currentIndex = allArticles.findIndex((article) => article.slug === currentSlug)\n if (currentIndex === -1) return { previous: null, next: null }\n const previous = currentIndex < allArticles.length - 1 ? allArticles[currentIndex + 1] : null\n const next = currentIndex > 0 ? allArticles[currentIndex - 1] : null\n return { previous, next }\n}\n\nexport async function getArticleMarkdown(\n slug: string,\n config?: ArticlesConfig\n): Promise<string | null> {\n try {\n const summary = await getArticleSummary(slug, config)\n if (!summary?.aiCrawl) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n return markdownContent\n } catch (error) {\n reportArticlesError({\n code: 'article-markdown-load-failed',\n message: 'Unable to load article markdown.',\n error,\n context: { slug },\n })\n return null\n }\n}\n\n/**\n * Crawlers that read content on behalf of an answer engine. Used both to\n * write robots.txt rules and to classify markdown-twin fetches for\n * `ArticlesConfig.onAiCrawl` - one list, so the two can never disagree about\n * what counts as an AI crawler.\n */\nexport const AI_CRAWLERS = [\n 'GPTBot',\n 'ChatGPT-User',\n 'OAI-SearchBot',\n 'CCBot',\n 'ClaudeBot',\n 'Claude-User',\n 'Claude-SearchBot',\n 'anthropic-ai',\n 'PerplexityBot',\n 'Perplexity-User',\n 'Google-Extended',\n 'Applebot-Extended',\n 'Bytespider',\n 'Amazonbot',\n 'meta-externalagent',\n 'cohere-ai',\n 'DuckAssistBot',\n 'MistralAI-User',\n] as const\n\n/** Matches a `User-Agent` against `AI_CRAWLERS`, returning the crawler name or `null`. */\nexport function matchAiCrawler(userAgent: string): string | null {\n if (!userAgent) return null\n const normalized = userAgent.toLowerCase()\n return AI_CRAWLERS.find((crawler) => normalized.includes(crawler.toLowerCase())) ?? null\n}\n\n/**\n * Attribution header prepended to an article's markdown twin.\n *\n * The twin is what an AI crawler actually reads, and `matter()` strips every\n * frontmatter field before it is served - so without this the model gets an\n * anonymous body with no title, date, author, or link back to the canonical\n * page. Skips its own `# {title}` line when the body already opens with the\n * same H1, so the common \"body repeats the title\" layout doesn't end up with\n * two.\n */\nexport function buildMarkdownTwinHeader(\n article: Article,\n config: ArticlesConfig,\n body: string\n): string {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const firstLine = body.trimStart().split('\\n', 1)[0]?.trim() ?? ''\n const bodyRepeatsTitle = firstLine.toLowerCase() === `# ${article.title}`.toLowerCase()\n\n const facts = [\n `Source: ${siteUrl}/articles/${article.slug}`,\n article.date ? `Published: ${article.date}` : '',\n article.lastmod ? `Updated: ${article.lastmod}` : '',\n config.showAuthor !== false && article.author ? `Author: ${article.author}` : '',\n `Site: ${config.siteName}`,\n ].filter(Boolean)\n\n const blocks = [\n bodyRepeatsTitle ? '' : `# ${article.title}`,\n article.excerpt ? `> ${article.excerpt}` : '',\n facts.join('\\n'),\n article.answer ? `**Short answer:** ${article.answer}` : '',\n '---',\n ].filter((block) => block !== '')\n\n return `${blocks.join('\\n\\n')}\\n\\n`\n}\n\n/**\n * Reports a markdown-twin fetch to `config.onAiCrawl`. Never lets a consumer\n * callback break the response - a telemetry handler throwing must not turn a\n * served article into a 500.\n */\nfunction reportAiCrawl(slug: string, config: ArticlesConfig, headers?: RequestHeaders): void {\n if (!config.onAiCrawl) return\n const userAgent = headers?.get('user-agent') ?? ''\n try {\n config.onAiCrawl({ slug, crawler: matchAiCrawler(userAgent) ?? 'unknown', userAgent })\n } catch (error) {\n reportArticlesError({\n code: 'ai-crawl-handler-failed',\n message: 'onAiCrawl handler threw.',\n error,\n context: { slug },\n })\n }\n}\n\n/** Minimal shape of a request's headers - avoids depending on `next/server` here. */\nexport type RequestHeaders = Readonly<{ get(name: string): string | null }>\n\n/**\n * Markdown twin for a listing surface - a category, author, or series.\n *\n * Article twins alone leave a model with a bag of pages and no map: the\n * listing surfaces are what answer \"what does this site cover, and who\n * writes it\". Only articles resolved to `aiCrawl: true` are listed, so a\n * blocked article stays invisible here too.\n */\nfunction buildListingMarkdown(\n heading: string,\n intro: string[],\n articles: readonly Article[],\n config: ArticlesConfig\n): string {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const crawlable = articles.filter((article) => article.aiCrawl === true)\n const entries = crawlable.map((article) => {\n const summary = article.answer ?? article.excerpt\n const line = `- [${article.title}](${siteUrl}/articles/${article.slug}.md)`\n return summary ? `${line}: ${summary}` : line\n })\n\n return [\n `# ${heading}`,\n '',\n ...intro.flatMap((line) => [line, '']),\n `Source: ${siteUrl}`,\n `Site: ${config.siteName}`,\n '',\n '---',\n '',\n ...(entries.length > 0 ? entries : ['_No articles available._']),\n '',\n ].join('\\n')\n}\n\n/** Markdown twin for `/articles/category/[category]`. `null` when the category has no articles. */\nexport async function getCategoryMarkdown(\n categorySlug: string,\n config: ArticlesConfig\n): Promise<string | null> {\n const articles = await getArticlesByCategory(categorySlug, config)\n if (articles.length === 0) return null\n const name =\n articles[0].categories.find((c) => categoryToSlug(c) === categorySlug) ?? categorySlug\n const description = resolveCategoryDescription(categorySlug, config)\n return buildListingMarkdown(name, description ? [description] : [], articles, config)\n}\n\nfunction resolveCategoryDescription(\n categorySlug: string,\n config: ArticlesConfig\n): string | undefined {\n const entry = config.categoryDescriptions?.[categorySlug]\n if (!entry) return undefined\n return typeof entry === 'string' ? entry : (entry.long ?? entry.short)\n}\n\n/**\n * Markdown twin for `/articles/authors/[author]`.\n *\n * Carries the author's bio, promise, principles, and sourced proof alongside\n * their article list - the \"who is this and why trust them\" context that\n * otherwise exists only inside React components, and the exact question asked\n * before anything they wrote gets cited. `credentials` are included as the\n * author's own stated claims; unlike JSON-LD, prose can attribute a claim\n * without asserting it as a verified fact.\n */\nexport async function getAuthorMarkdown(\n authorSlug: string,\n config: ArticlesConfig\n): Promise<string | null> {\n const author = getAuthorBySlug(authorSlug, config)\n if (!author) return null\n const articles = await getArticlesByAuthor(authorSlug, config)\n\n const intro = [\n author.promise ?? '',\n author.bio ?? '',\n ...(author.servesWho?.length ? [`Writes for: ${author.servesWho.join(', ')}`] : []),\n ...(author.knowsAbout?.length ? [`Writes about: ${author.knowsAbout.join(', ')}`] : []),\n ...(author.credentials?.length\n ? ['## Stated experience', ...author.credentials.map((item) => `- ${item}`)]\n : []),\n ...(author.proof?.length\n ? [\n '## Proof points',\n ...author.proof.map((item) =>\n item.url ? `- [${item.claim}](${item.url})` : `- ${item.claim}`\n ),\n ]\n : []),\n ...(author.originStory?.length\n ? [\n '## Background',\n ...author.originStory.flatMap((section) => [\n ...(section.heading ? [`### ${section.heading}`] : []),\n ...section.paragraphs,\n ]),\n ]\n : []),\n ].filter((line) => line.trim() !== '')\n\n return buildListingMarkdown(author.name, intro, articles, config)\n}\n\n/** Markdown twin for `/articles/series/[series]`. `null` when the series has no articles. */\nexport async function getSeriesMarkdown(\n seriesSlug: string,\n config: ArticlesConfig\n): Promise<string | null> {\n const articles = await getArticlesBySeries(seriesSlug, config)\n if (articles.length === 0) return null\n const name = articles[0].series ?? seriesSlug\n return buildListingMarkdown(name, [], articles, config)\n}\n\n/**\n * Serves the markdown twin for any `/articles/...` path - an article, or a\n * category/author/series listing.\n *\n * Dispatching on the slug prefix here rather than adding three more app\n * routes keeps the existing single rewrite (`/articles/:path*.md`) working\n * unchanged: without it, `/articles/category/campaigns.md` matches that\n * rewrite, reaches the article handler, and 404s.\n */\nexport async function getMarkdownTwinResponse(\n slug: string,\n config: ArticlesConfig,\n options?: Readonly<{ headers?: RequestHeaders }>\n): Promise<Response> {\n const listing = await resolveListingMarkdown(slug, config)\n if (listing !== undefined) {\n if (listing === null) return new Response('Not Found', { status: 404 })\n reportAiCrawl(slug, config, options?.headers)\n return new Response(listing, { headers: LISTING_MARKDOWN_HEADERS })\n }\n return getArticleMarkdownResponse(slug, config, options)\n}\n\nconst LISTING_MARKDOWN_HEADERS = {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n}\n\n// `undefined` means \"not a listing path\" (fall through to the article\n// handler); `null` means \"a listing path that resolved to nothing\" (404).\nasync function resolveListingMarkdown(\n slug: string,\n config: ArticlesConfig\n): Promise<string | null | undefined> {\n const [prefix, ...rest] = slug.split('/')\n const key = rest.join('/')\n if (!key) return undefined\n if (prefix === 'category') return getCategoryMarkdown(key, config)\n if (prefix === 'authors') return getAuthorMarkdown(key, config)\n if (prefix === 'series') return getSeriesMarkdown(key, config)\n return undefined\n}\n\nexport async function getArticleMarkdownResponse(\n slug: string,\n config: ArticlesConfig,\n options?: Readonly<{ headers?: RequestHeaders }>\n): Promise<Response> {\n const markdown = await getArticleMarkdown(slug, config)\n if (markdown === null) return new Response('Not Found', { status: 404 })\n const article = await getArticleMetadata(slug, config)\n reportAiCrawl(slug, config, options?.headers)\n const body =\n article && config.markdownTwinHeader !== false\n ? `${buildMarkdownTwinHeader(article, config, markdown)}${markdown.trimStart()}`\n : markdown\n // `getArticleAiHeaders` builds a `rel=\"alternate\"` link *to* the twin, which\n // is correct on the HTML page and wrong here - on the twin's own response it\n // pointed at the URL being requested, telling a client the alternate of\n // `/articles/x.md` is `/articles/x.md`. `getArticleMarkdown` already returned\n // null for anything not opted in, so that helper's other branch\n // (`X-Robots-Tag: noai`) was unreachable from this call site anyway. Point\n // back at the HTML article instead, which is the relationship a client\n // fetching the twin actually needs: this is a representation of that page.\n const canonicalUrl = `${config.siteUrl.replace(/\\/$/, '')}/articles/${slug}`\n return new Response(body, {\n headers: {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n Link: `<${canonicalUrl}>; rel=\"canonical\"`,\n },\n })\n}\n\nexport function getArticleMarkdownUrl(\n article: Pick<Article, 'slug' | 'aiCrawl'>,\n config?: Pick<ArticlesConfig, 'siteUrl'>\n): string | undefined {\n if (article.aiCrawl !== true) return undefined\n const pathname = `/articles/${article.slug}.md`\n if (!config) return pathname\n return `${config.siteUrl.replace(/\\/$/, '')}${pathname}`\n}\n\nexport function getArticleAiHeaders(\n article: Pick<Article, 'slug' | 'aiCrawl'>,\n config?: Pick<ArticlesConfig, 'siteUrl'>\n): Record<string, string> {\n const markdownUrl = getArticleMarkdownUrl(article, config)\n if (markdownUrl) {\n return {\n Link: `<${markdownUrl}>; rel=\"alternate\"; type=\"text/markdown\"`,\n }\n }\n return {\n 'X-Robots-Tag': 'noai, noimageai',\n }\n}\n\nexport async function getAiRobotsTxtRules(config?: ArticlesConfig): Promise<string> {\n const articles = await getAllArticles(config)\n const blockedArticles = articles.filter((article) => article.aiCrawl !== true)\n if (blockedArticles.length === 0) return ''\n\n const disallowRules = blockedArticles\n .map((article) => `Disallow: /articles/${article.slug}`)\n .join('\\n')\n\n return AI_CRAWLERS.map((crawler) => [`User-agent: ${crawler}`, disallowRules].join('\\n')).join(\n '\\n\\n'\n )\n}\n\nexport async function searchArticles(query: string, config?: ArticlesConfig): Promise<Article[]> {\n if (!query?.trim()) return getAllArticles(config)\n const articles = await getAllArticles(config)\n const searchTerm = query.toLowerCase().trim()\n const includeAuthor = config?.showAuthor !== false\n return articles.filter((article) => {\n const matchesTitle = article.title.toLowerCase().includes(searchTerm)\n const matchesExcerpt = article.excerpt.toLowerCase().includes(searchTerm)\n const matchesAuthor = includeAuthor && article.author.toLowerCase().includes(searchTerm)\n const matchesCategory = article.categories.some((cat) => cat.toLowerCase().includes(searchTerm))\n const matchesTags = article.tags?.some((tag) => tag.toLowerCase().includes(searchTerm))\n return (\n matchesTitle || matchesExcerpt || matchesAuthor || matchesCategory || Boolean(matchesTags)\n )\n })\n}\n\nexport function categoryToSlug(category: string): string {\n return category\n .toLowerCase()\n .replaceAll(/\\s+/g, '-')\n .replaceAll(/[^a-z0-9-]/g, '')\n}\n\nexport async function getAllCategories(): Promise<CategoryInfo[]> {\n const articles = await getAllArticles()\n const categoryMap = new Map<string, { count: number; featuredImage: string }>()\n for (const article of articles) {\n for (const cat of article.categories) {\n if (!categoryMap.has(cat)) {\n categoryMap.set(cat, { count: 0, featuredImage: article.featuredImage })\n }\n categoryMap.get(cat)!.count++\n }\n }\n return Array.from(categoryMap.entries())\n .map(([name, { count, featuredImage }]) => ({\n name,\n slug: categoryToSlug(name),\n count,\n featuredImage,\n }))\n .sort((a, b) => b.count - a.count)\n}\n\nexport async function getArticlesByCategory(\n categorySlug: string,\n config?: ArticlesConfig\n): Promise<Article[]> {\n const articles = await getAllArticles(config)\n return articles.filter((article) =>\n article.categories.some((cat) => categoryToSlug(cat) === categorySlug)\n )\n}\n\n// Built on getArticlesByCategory (same slug-matching filter, no duplicated\n// logic) rather than getAdjacentArticles' global date-order walk, so an\n// article detail page can link to other articles in the same category\n// instead of just the two chronologically-nearest articles overall.\nexport async function getRelatedArticlesByCategory(\n currentSlug: string,\n category: string,\n limit = 3,\n config?: ArticlesConfig\n): Promise<Article[]> {\n const articles = await getArticlesByCategory(categoryToSlug(category), config)\n return articles.filter((article) => article.slug !== currentSlug).slice(0, limit)\n}\n\nexport async function getArticlesByAuthor(\n authorSlug: string,\n config: ArticlesConfig\n): Promise<Article[]> {\n const articles = await getAllArticles(config)\n return articles.filter((article) =>\n getArticleAuthors(article, config).some((author) => author.slug === authorSlug)\n )\n}\n\n// Sorted by `seriesOrder` ascending (undefined pushed to the end); ties fall\n// back to the date-descending order `getAllArticles` already applies, since\n// `Array.prototype.sort` is stable - matching `getArticlesByCategory`'s\n// \"build on the existing filter, don't duplicate `getAllArticles`\" pattern.\n// The label-only `series` string field is untouched by this function.\nexport async function getArticlesBySeries(\n seriesSlug: string,\n config?: ArticlesConfig\n): Promise<Article[]> {\n const articles = await getAllArticles(config)\n return articles\n .filter((article) => article.seriesSlug === seriesSlug)\n .sort((a, b) => {\n const orderA = a.seriesOrder ?? Number.POSITIVE_INFINITY\n const orderB = b.seriesOrder ?? Number.POSITIVE_INFINITY\n return orderA - orderB\n })\n}\n\n/**\n * Series-aware sibling of `getAdjacentArticles`: walks `seriesOrder` within\n * one series instead of global date order. `previous`/`next` follow series\n * order (ascending), not chronology.\n */\nexport async function getAdjacentArticlesInSeries(\n currentSlug: string,\n seriesSlug: string,\n config?: ArticlesConfig\n): Promise<{ previous: Article | null; next: Article | null }> {\n const seriesArticles = await getArticlesBySeries(seriesSlug, config)\n const currentIndex = seriesArticles.findIndex((article) => article.slug === currentSlug)\n if (currentIndex === -1) return { previous: null, next: null }\n return {\n previous: currentIndex > 0 ? seriesArticles[currentIndex - 1] : null,\n next: currentIndex < seriesArticles.length - 1 ? seriesArticles[currentIndex + 1] : null,\n }\n}\n\n/** Looks up one configured `PathDefinition` by its app-chosen key. */\nexport function getPath(pathKey: string, config: ArticlesConfig): PathDefinition | null {\n return config.paths?.[pathKey] ?? null\n}\n\n/** Resolves a path's ordered slugs against the real article set, dropping any that don't resolve (e.g. a draft filtered out of `getAllArticles` in production) rather than throwing - use `validateArticles` to catch broken references before publishing. */\nexport async function getPathArticles(pathKey: string, config: ArticlesConfig): Promise<Article[]> {\n const path = getPath(pathKey, config)\n if (!path) return []\n const articles = await getAllArticles(config)\n const bySlug = new Map(articles.map((article) => [article.slug, article]))\n return path.articles\n .map((slug) => bySlug.get(slug))\n .filter((article): article is Article => Boolean(article))\n}\n\nfunction findPathForArticle(\n slug: string,\n config: ArticlesConfig\n): { key: string; path: PathDefinition } | null {\n for (const [key, path] of Object.entries(config.paths ?? {})) {\n if (path.articles.includes(slug)) return { key, path }\n }\n return null\n}\n\nexport type RelatedContentSource = 'path' | 'series' | 'category'\n\nexport interface RelatedContentResult {\n source: RelatedContentSource\n /** Heading for a related-content UI - the path's `name`, the article's `series` label, or \"More in {category}\". */\n heading: string\n articles: Article[]\n /** Set only when `source === 'path'`. */\n pathKey?: string\n /** Set only when `source === 'path'` - the path's one configured next action. */\n nextAction?: { label: string; href: string }\n}\n\n/**\n * Reusable related-content selection (Phase 27F): prefers a configured\n * `Path` containing this article first, then the article's `seriesSlug`,\n * falling back to 27B's `getRelatedArticlesByCategory` (imported, not\n * reimplemented) when neither a path nor a series applies - the plain\n * chronological-within-category behavior stays the fallback, not a full\n * replacement.\n */\nexport async function getRelatedContent(\n article: Article,\n config: ArticlesConfig,\n limit = 3\n): Promise<RelatedContentResult> {\n const matchedPath = findPathForArticle(article.slug, config)\n if (matchedPath) {\n const pathArticles = await getPathArticles(matchedPath.key, config)\n return {\n source: 'path',\n heading: matchedPath.path.name,\n articles: pathArticles.filter((a) => a.slug !== article.slug),\n pathKey: matchedPath.key,\n nextAction: matchedPath.path.nextAction,\n }\n }\n if (article.seriesSlug) {\n const seriesArticles = await getArticlesBySeries(article.seriesSlug, config)\n return {\n source: 'series',\n heading: article.series ?? 'This series',\n articles: seriesArticles.filter((a) => a.slug !== article.slug),\n }\n }\n const categoryArticles = await getRelatedArticlesByCategory(\n article.slug,\n article.category,\n limit,\n config\n )\n return { source: 'category', heading: `More in ${article.category}`, articles: categoryArticles }\n}\n\nexport { sanitizeImagePath }\n","import type { ArticlesConfig } from './articlesConfig'\nimport type { AuthorProfile, AuthorSocial } from './articleTypes'\n\n/**\n * The canonical identity URL a Person `@id` derives from. Prefers the\n * explicit cross-site `identityUrl`, then `url`, then this site's own author\n * page - so an author who configures nothing keeps a per-site identity, and\n * one who sets `identityUrl` gets the same `@id` on every site.\n */\nexport function getAuthorIdentityUrl(\n author: AuthorProfile,\n config?: Pick<ArticlesConfig, 'siteUrl'>\n): string | undefined {\n if (author.identityUrl) return author.identityUrl\n if (author.url) return author.url\n if (!config) return undefined\n return `${config.siteUrl.replace(/\\/$/, '')}/articles/authors/${author.slug}`\n}\n\nexport function getAuthorUrl(author: AuthorProfile): string {\n return author.url ?? `/articles/authors/${author.slug}`\n}\n\nexport function getAuthorAvatar(\n author: AuthorProfile,\n config?: ArticlesConfig\n): string | undefined {\n if (!author.avatar) return undefined\n if (author.avatar.startsWith('http://') || author.avatar.startsWith('https://')) {\n return author.avatar\n }\n const path = `/articles/authors/${author.slug}/${author.avatar.replace(/^\\/+/, '')}`\n if (!config) return path\n return `${config.siteUrl.replace(/\\/$/, '')}${path}`\n}\n\nexport function getAuthorSameAs(author: AuthorProfile): string[] {\n const derived = getAuthorSocialLinks(author).map((link) => link.href)\n const explicit = author.sameAs?.filter((url) => url.trim() !== '') ?? []\n return [...new Set([...derived, ...explicit])]\n}\n\nexport interface AuthorSocialLink {\n label: string\n href: string\n}\n\nfunction normalizeHandle(value: string): string {\n return value.replace(/^@/, '')\n}\n\nfunction normalizeUrl(value: string, baseUrl?: string): string {\n if (value.startsWith('http://') || value.startsWith('https://')) return value\n if (!baseUrl) return value\n return `${baseUrl}${normalizeHandle(value)}`\n}\n\nfunction getConfiguredSocialLinks(social: AuthorSocial): AuthorSocialLink[] {\n return [\n { label: 'Website', href: social.website ?? '' },\n {\n label: 'Facebook',\n href: social.facebook ? normalizeUrl(social.facebook, 'https://www.facebook.com/') : '',\n },\n {\n label: 'Twitter',\n href: social.twitter ? normalizeUrl(social.twitter, 'https://twitter.com/') : '',\n },\n { label: 'X', href: social.x ? normalizeUrl(social.x, 'https://x.com/') : '' },\n {\n label: 'LinkedIn',\n href: social.linkedin ? normalizeUrl(social.linkedin, 'https://www.linkedin.com/in/') : '',\n },\n {\n label: 'Instagram',\n href: social.instagram ? normalizeUrl(social.instagram, 'https://www.instagram.com/') : '',\n },\n {\n label: 'YouTube',\n href: social.youtube ? normalizeUrl(social.youtube, 'https://www.youtube.com/') : '',\n },\n {\n label: 'TikTok',\n href: social.tiktok ? normalizeUrl(social.tiktok, 'https://www.tiktok.com/@') : '',\n },\n {\n label: 'GitHub',\n href: social.github ? normalizeUrl(social.github, 'https://github.com/') : '',\n },\n {\n label: 'Bluesky',\n href: social.bluesky ? normalizeUrl(social.bluesky, 'https://bsky.app/profile/') : '',\n },\n {\n label: 'Threads',\n href: social.threads ? normalizeUrl(social.threads, 'https://www.threads.net/@') : '',\n },\n { label: 'Mastodon', href: social.mastodon ? normalizeUrl(social.mastodon) : '' },\n {\n label: 'Medium',\n href: social.medium ? normalizeUrl(social.medium, 'https://medium.com/@') : '',\n },\n { label: 'Newsletter', href: social.newsletter ?? '' },\n ]\n}\n\nexport function getAuthorSocialLinks(author: AuthorProfile): AuthorSocialLink[] {\n const social = author.social\n if (!social) return []\n const configuredLinks = getConfiguredSocialLinks(social)\n const otherLinks = Object.entries(social.other ?? {}).map(([label, href]) => ({ label, href }))\n return [...configuredLinks, ...otherLinks].filter((link) => link.href.trim().length > 0)\n}\n","import type { Element, Root, ElementContent } from 'hast'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSanitize from 'rehype-sanitize'\nimport rehypeSlug from 'rehype-slug'\nimport rehypeStringify from 'rehype-stringify'\nimport { remark } from 'remark'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport remarkParse from 'remark-parse'\nimport remarkRehype from 'remark-rehype'\nimport { Plugin } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport type { FaqItem, TocItem } from './articleTypes'\nimport type { ArticlesConfig, LinkTargetStrategy } from './articlesConfig'\nimport { reportArticlesError } from './errorReporting'\nimport { isExternalHttpLink, isNonBrowserNavigationLink } from './linkClassification'\n\nexport { isExternalHttpLink, isNonBrowserNavigationLink }\n\ntype LinkTargetOptions = Readonly<{\n strategy?: LinkTargetStrategy\n siteUrl?: string\n}>\n\nconst DEFAULT_LINK_TARGET_STRATEGY: LinkTargetStrategy = 'external-new-tab'\n\nfunction shouldOpenInNewTab(href: string, options: LinkTargetOptions = {}): boolean {\n if (!href || href.startsWith('#') || isNonBrowserNavigationLink(href)) return false\n\n const strategy = options.strategy ?? DEFAULT_LINK_TARGET_STRATEGY\n if (strategy === 'same-tab') return false\n if (strategy === 'all-new-tab') return true\n return isExternalHttpLink(href, options.siteUrl)\n}\n\nfunction applyLinkTarget(props: Record<string, unknown>, options?: LinkTargetOptions): void {\n const href = typeof props.href === 'string' ? props.href : ''\n if (shouldOpenInNewTab(href, options)) {\n props.target = '_blank'\n props.rel = 'noopener noreferrer'\n return\n }\n delete props.target\n delete props.rel\n}\n\nfunction getLinkTargetOptions(config?: ArticlesConfig): LinkTargetOptions {\n return {\n strategy: config?.linkTargetStrategy,\n siteUrl: config?.siteUrl,\n }\n}\n\n// Import the sanitizeImagePath function\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') {\n return null\n }\n\n // Remove any null bytes or control characters\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n\n // Check for absolute URLs (http/https)\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) {\n // For external URLs, just return as-is (they're safe)\n return cleanPath\n }\n\n // For relative paths, ensure they don't contain dangerous patterns\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected unsafe markdown image path.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n\n // Only allow alphanumeric characters, hyphens, underscores, dots, and forward slashes\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected markdown image path with invalid characters.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n\n // Construct safe path within articles directory\n if (cleanPath.includes('/')) {\n // Relative path, ensure it's within the article directory\n const normalizedPath = cleanPath.replaceAll('\\\\', '/')\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected markdown image path traversal attempt.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n return `/articles/${articleSlug}/${normalizedPath}`\n } else {\n // Just a filename, construct full path\n return `/articles/${articleSlug}/${cleanPath}`\n }\n}\n\nfunction styleFootnoteLinks(nodes: ElementContent[]): void {\n nodes.forEach((n) => {\n if (n.type !== 'element') return\n const el = n as Element\n if (el.tagName === 'a') {\n const isBackRef =\n el.properties?.['dataFootnoteBackref'] !== undefined ||\n (el.children[0]?.type === 'text' && el.children[0].value === '↩')\n if (isBackRef) {\n el.properties.className = 'text-primary hover:underline ml-1'\n if (typeof el.properties.href === 'string') {\n el.properties.href = el.properties.href.replaceAll('#user-content-fnref-', '#ref-')\n }\n } else {\n el.properties.className = 'text-primary hover:underline break-all'\n }\n }\n if (el.children) styleFootnoteLinks(el.children)\n })\n}\n\nfunction processFootnoteRef(node: Element): void {\n if (\n node.tagName !== 'sup' ||\n node.children?.[0]?.type !== 'element' ||\n (node.children[0] as Element).tagName !== 'a'\n )\n return\n\n const link = node.children[0] as Element\n const href = link.properties?.href\n if (typeof href !== 'string' || !href.startsWith('#user-content-fn-')) return\n\n delete link.properties.target\n delete link.properties.rel\n link.properties.className = 'text-primary hover:underline'\n link.properties.href = href.replaceAll('#user-content-fn-', '#footnote-')\n\n if (typeof node.properties?.id === 'string') {\n link.properties.id = node.properties.id.replaceAll('user-content-fnref-', 'ref-')\n delete node.properties.id\n }\n\n if (link.children?.[0]?.type === 'text') {\n link.children[0].value = `[${link.children[0].value}]`\n }\n}\n\nfunction processFootnotesSection(node: Element): void {\n const cls = node.properties?.className\n const isFootnotes =\n node.tagName === 'section' &&\n (Array.isArray(cls) ? cls.includes('footnotes') : cls === 'footnotes')\n if (!isFootnotes) return\n\n const olCandidate = node.children.find(\n (child) => child.type === 'element' && (child as Element).tagName === 'ol'\n )\n if (olCandidate?.type !== 'element') return\n\n const ol = olCandidate as Element\n ol.properties.className = 'list-decimal ml-6 space-y-2 text-sm text-muted-foreground'\n\n ol.children.forEach((li) => {\n if (li.type !== 'element' || li.tagName !== 'li') return\n const liEl = li as Element\n\n if (liEl.properties) {\n liEl.properties.className = 'pl-2'\n if (typeof liEl.properties.id === 'string') {\n liEl.properties.id = liEl.properties.id.replaceAll('user-content-fn-', 'footnote-')\n }\n }\n\n const pIndex = liEl.children.findIndex(\n (child) => child.type === 'element' && (child as Element).tagName === 'p'\n )\n if (pIndex !== -1) {\n const p = liEl.children[pIndex] as Element\n liEl.children.splice(pIndex, 1, ...p.children)\n }\n\n styleFootnoteLinks(liEl.children)\n })\n\n const hr: Element = {\n type: 'element',\n tagName: 'hr',\n properties: { className: 'my-8 border-border' },\n children: [],\n }\n const h3: Element = {\n type: 'element',\n tagName: 'h3',\n properties: { className: 'text-lg font-semibold mb-4' },\n children: [{ type: 'text', value: 'References' }],\n }\n\n node.children = [hr, h3, ol]\n if (node.properties) node.properties.className = undefined\n}\n\nexport const customRenderer: Plugin<[LinkTargetOptions?], Root> = (linkTargetOptions = {}) => {\n return (tree: Root) => {\n // First pass: Apply general styles\n visit(tree, 'element', (node: Element) => {\n if (node.tagName) {\n const props = node.properties || {}\n\n switch (node.tagName) {\n case 'h1':\n props.className = 'text-3xl font-bold text-foreground mt-8 mb-4 scroll-mt-20'\n break\n case 'h2':\n props.className = 'text-2xl font-semibold text-foreground mt-6 mb-3 scroll-mt-20'\n break\n case 'h3':\n props.className = 'text-xl font-semibold text-foreground mt-4 mb-2 scroll-mt-20'\n break\n case 'h4':\n props.className = 'text-lg font-semibold text-foreground mt-3 mb-2 scroll-mt-20'\n break\n case 'p':\n props.className = 'text-muted-foreground leading-relaxed mb-4'\n break\n case 'a': {\n props.className = 'text-primary hover:underline transition-colors duration-200'\n applyLinkTarget(props, linkTargetOptions)\n break\n }\n case 'ul':\n props.className = 'list-disc list-inside mb-4 space-y-2 ml-4'\n break\n case 'ol':\n props.className = 'list-decimal list-inside mb-4 space-y-2 ml-4'\n break\n case 'li':\n props.className = 'mb-1'\n break\n case 'blockquote':\n props.className = 'border-l-4 border-primary pl-4 italic my-4 text-muted-foreground'\n break\n case 'code':\n props.className =\n (props.className ? props.className + ' ' : '') +\n 'bg-muted px-1 py-0.5 rounded text-sm font-mono'\n break\n case 'pre':\n props.className = 'bg-muted rounded-lg p-4 overflow-x-auto my-4'\n break\n case 'img':\n props.className = 'rounded-lg my-6 w-full max-w-2xl mx-auto'\n break\n case 'table':\n props.className = 'border-collapse border border-border my-4 w-full'\n break\n case 'th':\n props.className = 'border border-border px-2 py-1 bg-muted font-semibold'\n break\n case 'td':\n props.className = 'border border-border px-2 py-1'\n break\n case 'hr':\n props.className = 'my-8 border-border'\n break\n default:\n break\n }\n\n node.properties = props\n }\n })\n\n // Second pass: Fix footnotes and references\n visit(tree, 'element', (node: Element) => {\n processFootnoteRef(node)\n processFootnotesSection(node)\n })\n }\n}\n\n// Custom rehype plugin to process image URLs\nconst rehypeProcessImages: Plugin<[{ articleSlug?: string }], Root> = (options = {}) => {\n return (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n if (node.tagName === 'img' && node.properties) {\n const src = node.properties.src\n if (src && typeof src === 'string' && options.articleSlug) {\n // Sanitize the image path\n const sanitizedSrc = sanitizeImagePath(src, options.articleSlug)\n if (sanitizedSrc) {\n node.properties.src = sanitizedSrc\n } else {\n // If sanitization fails, use a placeholder\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Using placeholder for unsafe markdown image path.',\n context: { articleSlug: options.articleSlug, path: src },\n })\n node.properties.src = '/placeholder-logo.png'\n }\n }\n }\n })\n }\n}\n\nexport async function markdownToHtml(\n markdown: string,\n articleSlug?: string,\n config?: ArticlesConfig\n) {\n try {\n // Start building the remark processor\n let processor = remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkGithubBlockquoteAlert)\n .use(remarkRehype)\n .use(customRenderer, getLinkTargetOptions(config))\n .use(rehypeSlug)\n // @ts-ignore\n .use(rehypePrism)\n .use(rehypeSanitize, {\n attributes: {\n '*': ['className', 'class', 'id'],\n a: ['href', 'target', 'rel', 'id'],\n img: ['src', 'alt'],\n },\n })\n\n // Add image processing plugin if articleSlug is provided\n if (articleSlug) {\n processor = processor.use(rehypeProcessImages, { articleSlug })\n }\n\n const result = await processor\n .use(rehypeStringify)\n .process(stripInlineTagsFromHeadings(markdown))\n\n return result.toString()\n } catch (error) {\n reportArticlesError({\n code: 'markdown-conversion-failed',\n message: 'Unable to convert markdown to HTML.',\n error,\n context: { articleSlug },\n })\n // Return the original markdown as fallback\n return markdown\n }\n}\n\n/**\n * Strips inline HTML/JSX tags from heading lines only, keeping their inner\n * text, before the markdown reaches a plain (non-MDX) remark parse.\n *\n * Article headings commonly carry a reader-facing rating dot written as JSX,\n * e.g. `### Rage <span style={{ color: '#3b82f6' }}>●</span>`. `renderMdxSource`\n * (real MDX compilation via `@mdx-js/mdx`) parses that correctly and renders\n * a real `<span>` element. But `extractToc` and `markdownToHtml` both run\n * headings through plain `remark-parse` with no MDX support, and CommonMark's\n * raw-inline-HTML grammar does not accept a JSX object-literal attribute\n * expression like `style={{ ... }}` - remark's HTML tokenizer fails to match\n * it as a tag and falls back to treating the whole thing as literal text.\n * That garbled text then (a) becomes the visible TOC label, and (b) feeds\n * `rehype-slug`, producing a slug built from the raw markup instead of the\n * heading's real words - which does not match the id `renderMdxSource`'s\n * correctly-parsed pipeline assigns to the same heading in the live page, so\n * the TOC entry silently links to an id that does not exist in the DOM.\n *\n * Stripping tags (not their inner content) from heading lines before parsing\n * keeps the extracted text and generated slug consistent with what the real\n * MDX render puts in the page, for any heading-level inline markup - not\n * only the rating-dot convention that surfaced the bug.\n */\nfunction stripInlineTagsFromHeadings(markdown: string): string {\n return markdown.replace(/^(#{1,6}[ \\t].*)$/gm, (line) =>\n line.replace(/<\\/?[a-zA-Z][^<>\\n]*>/g, '')\n )\n}\n\n/**\n * Recursively extracts a node's text content, including text nested inside\n * child elements (for example a markdown link's `<a>Dwarf</a>` inside a\n * heading like `### [Dwarf](/link)`). A shallow, direct-children-only check\n * here previously dropped link text from every heading that used a link as\n * part of its heading text - a distinct bug from the JSX-in-heading garbling\n * `stripInlineTagsFromHeadings` fixes, but with a worse symptom: the heading\n * was silently omitted from the TOC entirely (both `id` and `text` came back\n * empty, so `extractHeadingItem` returned `null`) rather than merely garbled.\n */\nfunction nodeTextValue(c: ElementContent): string {\n if (c.type === 'text') return (c as { value: string }).value\n if (c.type === 'element' && 'children' in c) {\n return (c as Element).children.map(nodeTextValue).join('')\n }\n return ''\n}\n\nfunction extractHeadingItem(node: Element): TocItem | null {\n const match = /^h([1-6])$/.exec(node.tagName)\n if (!match) return null\n const id = typeof node.properties?.id === 'string' ? node.properties.id : ''\n const text = node.children.map(nodeTextValue).join('')\n if (!id || !text) return null\n return { id, depth: Number.parseInt(match[1], 10), text }\n}\n\nexport interface ContentSlotBoundaries {\n /** Character offset (into the raw markdown source) right after the first paragraph - the \"intro\" boundary. */\n introEnd: number\n /** Character offset right after the middle paragraph - the \"mid content\" boundary. */\n mid: number\n /** Total top-level paragraph count found. */\n paragraphCount: number\n}\n\n/**\n * Resolves deterministic `afterIntro`/`midContent` split points from the\n * raw markdown/MDX source's parsed AST (mdast paragraph node offsets) -\n * never from string-splitting rendered HTML, which is fragile by\n * construction (see Phase 27F plan notes). Returns `null` when the source\n * has no top-level paragraphs, or fails to parse (e.g. MDX with JSX syntax\n * remark-parse doesn't understand) - callers should treat `null` as \"only\n * `afterHero`/`afterContent` are available for this article\", not throw.\n */\ninterface MdastNode {\n type: string\n position?: { start: { offset?: number }; end: { offset?: number } }\n children?: MdastNode[]\n}\n\nexport function getContentSlotBoundaries(markdown: string): ContentSlotBoundaries | null {\n try {\n const tree = remark().use(remarkParse).use(remarkGfm).parse(markdown) as unknown as MdastNode\n const paragraphs = (tree.children ?? []).filter(\n (node): node is MdastNode & { position: NonNullable<MdastNode['position']> } =>\n node.type === 'paragraph' && Boolean(node.position)\n )\n if (paragraphs.length === 0) return null\n const introEnd = paragraphs[0].position.end.offset ?? 0\n const midIndex = Math.floor(paragraphs.length / 2)\n const mid = paragraphs[midIndex].position.end.offset ?? introEnd\n return { introEnd, mid: Math.max(mid, introEnd), paragraphCount: paragraphs.length }\n } catch {\n return null\n }\n}\n\nexport async function extractToc(markdown: string): Promise<TocItem[]> {\n const headings: TocItem[] = []\n const collectHeadings: Plugin<[], Root> = () => (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n const item = extractHeadingItem(node)\n if (item) headings.push(item)\n })\n }\n await remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype)\n .use(rehypeSlug)\n .use(collectHeadings)\n .use(rehypeStringify)\n .process(stripInlineTagsFromHeadings(markdown))\n return headings\n}\n\n// Interrogatives that open a genuine reader question. Deliberately a closed\n// list rather than \"any heading ending in ?\" - a rhetorical heading like\n// \"Sound familiar?\" ends in a question mark too, and promoting it to a\n// published Q&A pair would be worse than omitting it.\nconst QUESTION_OPENERS =\n /^(what|how|why|when|where|who|which|can|should|does|do|is|are|will|would|must)\\b/i\n\n/**\n * Derives `FaqItem`s from question-shaped `##` headings and the prose that\n * follows each one, for `ArticlesConfig.deriveFaqFromHeadings`.\n *\n * Line-based on purpose: it runs for every article at summary time, and the\n * shapes it must reject (fenced code, a heading with no prose under it) are\n * cheaper to detect by scanning than by walking a parsed AST.\n */\nexport function deriveFaqFromHeadings(markdown: string): FaqItem[] {\n const items: FaqItem[] = []\n const collector = createFaqCollector(items)\n let inFence = false\n\n for (const line of markdown.split('\\n')) {\n if (FENCE.test(line)) {\n inFence = !inFence\n continue\n }\n if (inFence) continue\n collector.consume(line)\n }\n collector.flush()\n\n return items\n}\n\nconst FENCE = /^\\s*(```|~~~)/\nconst ANY_HEADING = /^#{1,6}\\s/\n\nfunction isSpace(char: string | undefined): boolean {\n return char === ' ' || char === '\\t'\n}\n\n/** Drops closing-hash decoration (`## Heading ##`) without a `#+$` scan. */\nfunction trimTrailingHashes(value: string): string {\n let end = value.length\n while (end > 0 && value[end - 1] === '#') end--\n return value.slice(0, end).trimEnd()\n}\n\n/**\n * Extracts a question-shaped h2's text, or `null` for any other line.\n *\n * Deliberately string operations rather than a capture regex: every anchored\n * form of this (`(.*\\S)\\s*$`, `\\s+(.*)$`, `#+$`) backtracks super-linearly,\n * and this runs over every line of every article.\n */\nfunction readQuestionHeading(line: string): string | null {\n if (!line.startsWith('##') || line.startsWith('###')) return null\n if (!isSpace(line[2])) return null\n const text = trimTrailingHashes(line.slice(3).trim())\n if (!text.endsWith('?') || !QUESTION_OPENERS.test(text)) return null\n return text\n}\n\n/**\n * Line-at-a-time state machine pairing a question heading with the prose\n * under it. Split out of `deriveFaqFromHeadings` so the loop there stays a\n * flat fence check plus a delegation.\n */\nfunction createFaqCollector(items: FaqItem[]) {\n let pending: string | null = null\n let buffer: string[] = []\n\n const flush = (): void => {\n if (pending && buffer.length > 0) {\n items.push({ question: pending, answer: buffer.join(' ').trim() })\n }\n pending = null\n buffer = []\n }\n\n const consume = (line: string): void => {\n if (ANY_HEADING.test(line)) {\n flush()\n pending = readQuestionHeading(line)\n return\n }\n if (!pending) return\n // A blank line only ends an answer that has already started - the blank\n // between a heading and its first paragraph must not discard the pending\n // question.\n if (line.trim() === '') {\n if (buffer.length > 0) flush()\n return\n }\n buffer.push(line.trim())\n }\n\n return { consume, flush }\n}\n","export type ArticlesErrorCode =\n | 'article-directory-read-failed'\n | 'article-load-failed'\n | 'article-markdown-load-failed'\n | 'ai-crawl-handler-failed'\n | 'markdown-conversion-failed'\n | 'unsafe-image-path'\n\nexport type ArticlesErrorContext = Readonly<Record<string, string | number | boolean | undefined>>\n\nexport type ArticlesErrorReport = Readonly<{\n code: ArticlesErrorCode\n message: string\n error?: unknown\n context?: ArticlesErrorContext\n}>\n\nexport type ArticlesErrorHandler = (report: ArticlesErrorReport) => void\n\nfunction defaultArticlesErrorHandler(report: ArticlesErrorReport): void {\n if (process.env.NODE_ENV === 'production') return\n const context = report.context ? ` ${JSON.stringify(report.context)}` : ''\n const detail = report.error instanceof Error ? `: ${report.error.message}` : ''\n console.warn(`[articles:${report.code}] ${report.message}${context}${detail}`)\n}\n\nlet articlesErrorHandler: ArticlesErrorHandler = defaultArticlesErrorHandler\n\nexport function setArticlesErrorHandler(handler?: ArticlesErrorHandler): void {\n articlesErrorHandler = handler ?? defaultArticlesErrorHandler\n}\n\nexport function reportArticlesError(report: ArticlesErrorReport): void {\n articlesErrorHandler(report)\n}\n","// Pure href classification helpers with no dependency on the rehype/hast\n// pipeline - kept separate from markdown.ts (which pulls in ESM-only\n// rehype/remark plugins Jest can't transform without extra config) so\n// consumers that only need \"is this link internal/external/navigable\"\n// (like renderMdx.tsx's next/link routing decision) don't have to import\n// that whole transitive dependency chain, in production or in tests.\n\nexport function isNonBrowserNavigationLink(href: string): boolean {\n return (\n /^[a-zA-Z][a-zA-Z\\d+.-]*:/.test(href) &&\n !href.startsWith('http://') &&\n !href.startsWith('https://')\n )\n}\n\nfunction getOrigin(url: string | undefined): string | null {\n if (!url) return null\n try {\n return new URL(url).origin\n } catch {\n return null\n }\n}\n\nexport function isExternalHttpLink(href: string, siteUrl?: string): boolean {\n if (!href.startsWith('http://') && !href.startsWith('https://')) return false\n const siteOrigin = getOrigin(siteUrl)\n if (!siteOrigin) return true\n return getOrigin(href) !== siteOrigin\n}\n","import type { ComponentType } from 'react'\nimport type { AuthorProfile, EntityReference, PathDefinition } from './articleTypes'\nimport type { ArticleEventHandler } from './events'\n\n/** Keys for each renderable section of the articles listing page. */\nexport type ArticlesSection =\n | 'hero'\n | 'search'\n | 'featured'\n | 'latest'\n | 'categories'\n | 'newsletter'\n\n/**\n * CSS custom-property overrides applied to the library's wrapper div.\n * All fields are optional; omitted fields fall back to the consuming app's Tailwind theme.\n */\nexport interface ArticlesTheme {\n /** Font family for the articles section. Example: `\"'Inter', sans-serif\"` */\n fontFamily?: string\n /**\n * Font family for headings only (article title, card titles, section\n * headings) - falls back to `fontFamily` when omitted. Lets a site use a\n * distinct display face for headings (e.g. a serif) while keeping a\n * separate body font, without hardcoding either into the package.\n * Example: `\"'Cinzel', serif\"`\n */\n headerFontFamily?: string\n /** Color for article card titles and section headings. Example: `'#111827'` */\n headerColor?: string\n /** Color for body and excerpt text. Example: `'#6b7280'` */\n textColor?: string\n /** Background color for sections and cards. Example: `'#ffffff'` */\n backgroundColor?: string\n /** Color for \"Read More\" links and inline links. Example: `'#4f46e5'` */\n linkColor?: string\n /** Hover color for links. Example: `'#4338ca'` */\n linkHoverColor?: string\n /** Text decoration for links. Values: `'underline'` | `'none'` */\n linkTextDecoration?: string\n /** Font weight for headings. Example: `700` */\n headerFontWeight?: string | number\n /** Font size for article card titles. Example: `'1.25rem'` */\n headerFontSize?: string\n /** Font size for body text. Example: `'1rem'` */\n bodyFontSize?: string\n /** Line height for body text. Example: `'1.75'` */\n lineHeight?: string\n /** Border radius for cards. Example: `'0.5rem'` */\n borderRadius?: string\n}\n\n/** Per-category text used in meta descriptions and on the category hero page. */\nexport interface CategoryDescription {\n /** Short description — used in meta description and category grid card. */\n short: string\n /** Long description — rendered as a hero paragraph on the category page. */\n long?: string\n}\n\n/** Controls the comments feature. Attach to `ArticlesConfig.comments`. */\nexport interface CommentsConfig {\n /** Set to `true` to enable comments globally across all articles. */\n enabled: boolean\n /** Maximum reply nesting depth. Default: `1` (replies to top-level only). */\n maxDepth?: number\n /**\n * Per-article overrides keyed by slug.\n * Example: `{ 'sensitive-article': false }` disables comments on that article only.\n */\n perArticleOverride?: Record<string, boolean>\n}\n\n/** Text content for the hero section at the top of the articles listing page. */\nexport interface HeroConfig {\n /** Main heading displayed in the hero. Default: `'Vox Populus Insights'` */\n title?: string\n /** Subheading paragraph displayed beneath the title. Default: the built-in description. */\n description?: string\n}\n\n/** Controls how article body links set target/rel attributes. */\nexport type LinkTargetStrategy = 'external-new-tab' | 'all-new-tab' | 'same-tab'\n\n/**\n * Controls how listing pages (the articles index, category pages, author\n * pages) surface articles beyond the first `pageSize`.\n * - `'load-more'` (default): client-only \"Load more\" button, no URL change.\n * Byte-for-byte identical to pre-27D behavior.\n * - `'pages'`: real, directly-navigable paginated routes (`/articles/page/2`,\n * `/articles/category/[category]/page/2`, `/articles/authors/[author]/page/2`)\n * with SSR content, prev/next links, and per-page canonical metadata. The\n * route *files* live in the consuming app - see the pagination primitives\n * exported from `./server` and the `PaginationNav` component.\n */\nexport type ListingPagination = 'load-more' | 'pages'\n\n/** React components that article MDX bodies can reference by JSX tag name. */\nexport type MdxComponents = Record<string, ComponentType<never>>\n\nexport type ArticleBreadcrumbToken =\n | 'home'\n | 'articles'\n | 'primaryCategory'\n | 'folderPath'\n | 'articleTitle'\nexport type CategoryBreadcrumbToken = 'home' | 'articles' | 'category'\nexport type AuthorBreadcrumbToken = 'home' | 'articles' | 'authors' | 'authorName'\n\nexport interface CustomBreadcrumbItem {\n /** Label displayed in the breadcrumb trail. */\n name: string\n /** Custom URL. Relative paths are resolved against `siteUrl` by server builders. */\n url: string\n}\n\nexport type ArticleBreadcrumbEntry = ArticleBreadcrumbToken | CustomBreadcrumbItem\nexport type CategoryBreadcrumbEntry = CategoryBreadcrumbToken | CustomBreadcrumbItem\nexport type AuthorBreadcrumbEntry = AuthorBreadcrumbToken | CustomBreadcrumbItem\n\nexport interface BreadcrumbLabels {\n home?: string\n articles?: string\n authors?: string\n}\n\nexport interface BreadcrumbsConfig {\n /** Set to false to hide visible breadcrumbs and breadcrumb JSON-LD generated by the helper builders. */\n show?: boolean\n /** Separator used by the visible Breadcrumb component. Default: '>'. */\n separator?: string\n /** Set to false to render visible breadcrumbs without JSON-LD. Default: true. */\n showSchema?: boolean\n /** Article breadcrumb trail. Example: ['primaryCategory', { name: 'Guides', url: '/guides' }, 'articleTitle']. */\n article?: ArticleBreadcrumbEntry[]\n /** Category breadcrumb trail. Default: ['home', 'articles', 'category']. */\n category?: CategoryBreadcrumbEntry[]\n /** Author breadcrumb trail. Default: ['home', 'articles', 'authors', 'authorName']. */\n author?: AuthorBreadcrumbEntry[]\n /** Optional label overrides for built-in breadcrumb items. */\n labels?: BreadcrumbLabels\n}\n\n/** Payload passed to `ArticlesConfig.onAiCrawl`. */\nexport interface AiCrawlEvent {\n /** Article slug whose markdown twin was fetched. */\n slug: string\n /** Matched crawler name (e.g. `'GPTBot'`), or `'unknown'` when the agent is not recognized. */\n crawler: string\n /** Raw `User-Agent` header, or an empty string when absent. */\n userAgent: string\n}\n\n/**\n * The publishing entity behind the site. Drives `OrganizationSchema`,\n * `WebSiteSchema`, and the `publisher` reference on every article - so all\n * three point at one `@id` instead of repeating an inline, unlinked\n * `Organization` stub per page.\n */\nexport interface OrganizationConfig {\n /** schema.org type. Use `'Person'` for a personal brand. Default: `'Organization'`. */\n type?: 'Organization' | 'Person'\n /** Entity name. Falls back to `siteName`. */\n name?: string\n /** Entity homepage. Falls back to `siteUrl`. */\n url?: string\n /** Logo URL. Site-relative paths are resolved against `siteUrl`. */\n logo?: string\n /** Entity description. Falls back to `ArticlesConfig.description`. */\n description?: string\n /** Profile URLs that identify the same entity elsewhere (social, Crunchbase, Wikidata). */\n sameAs?: string[]\n /**\n * The umbrella entity this site belongs to, for a network of sites run by\n * one publisher. Emitted as `parentOrganization` - the link that lets\n * authority earned by the network attach to each site in it, instead of\n * each site standing alone.\n */\n parentOrganization?: {\n name: string\n url: string\n }\n /**\n * Search URL template for the `WebSite` `SearchAction`, e.g.\n * `'/search?q={search_term_string}'`. Omitted by default - the library's\n * own search is client-side with no crawlable results URL, so declaring one\n * that does not exist would be a false claim. Only set this if the app\n * actually serves search results at that URL. Must contain the literal\n * `{search_term_string}` placeholder.\n */\n searchUrlTemplate?: string\n}\n\n/** Top-level configuration object. Pass one instance to every library component. */\nexport interface ArticlesConfig {\n /** Canonical base URL of the site, used in metadata and JSON-LD. Example: `'https://yoursite.com'` */\n siteUrl: string\n /** Site name shown in metadata titles and JSON-LD publisher fields. Example: `'Vox Populus'` */\n siteName: string\n /** Number of articles shown per page in the grid and loaded on each \"Load more\" click. Default: `6` */\n pageSize?: number\n /** Number of category cards shown before a \"Load more categories\" button appears. Default: `8` */\n categoriesPageSize?: number\n /**\n * Ordered list of sections to render on the articles listing page.\n * Omit a key to hide that section entirely. Default: `['hero','search','featured','latest','categories']`\n */\n layout?: ArticlesSection[]\n /** CSS custom-property overrides for colors, fonts, and spacing. All fields optional. */\n theme?: ArticlesTheme\n /**\n * Descriptive text for each category, keyed by category slug.\n * A plain string is treated as the short description only.\n * Example: `{ 'campaigns': { short: 'Campaign tips', long: 'Full paragraph...' } }`\n */\n categoryDescriptions?: Record<string, string | CategoryDescription>\n /** Comments configuration. Omit or set `enabled: false` to hide comments entirely. */\n comments?: CommentsConfig\n /** Hero section title and description. Omit to use the built-in defaults. */\n hero?: HeroConfig\n /** Short description used in the RSS feed channel. Falls back to siteName if omitted. */\n description?: string\n /** Set to false to hide the table of contents on article detail pages. Default: true. */\n showToc?: boolean\n /** Set to false to hide the \"Back to Articles\" navigation link on article detail pages. Default: true. */\n showBackToArticles?: boolean\n /** Set to false to hide author names from UI and metadata. Default: true. */\n showAuthor?: boolean\n /** Author profiles keyed by slug. Omit to keep plain string author display. */\n authors?: Record<string, AuthorProfile>\n /** Author slug used when article frontmatter omits author fields. */\n defaultAuthor?: string\n /** Set to false to disable copied author page routes in consuming apps. Default: true. */\n showAuthorPage?: boolean\n /** Set to false to disable breadcrumbs, or pass a config object to customize breadcrumb trails. */\n breadcrumbs?: false | BreadcrumbsConfig\n /** Article body link target behavior. Default: `'external-new-tab'`. */\n linkTargetStrategy?: LinkTargetStrategy\n /** Extra components exposed to article MDX bodies by JSX tag name. */\n mdxComponents?: MdxComponents\n /**\n * Chooses how listing pages surface articles beyond the first `pageSize`.\n * Default: `'load-more'` (unchanged pre-27D behavior). Set to `'pages'` to\n * opt into real, crawlable paginated routes instead.\n */\n listingPagination?: ListingPagination\n /**\n * \"Start here\" curated reader journeys, keyed by an app-chosen path key.\n * Distinct from the label-only `series` field/`seriesSlug` pair - a path\n * can cross series and categories. Every `PathDefinition.articles` slug\n * must exist and not be `draft: true`; validate with `validateArticles`\n * before publishing, since a broken reference produces a dead journey\n * step rather than a build-time failure otherwise.\n */\n paths?: Record<string, PathDefinition>\n /**\n * Template for the `<title>` tag on article, category, series, and author\n * pages. Supports `{title}` and `{siteName}` placeholders.\n * Default: `'{title} | {siteName}'`.\n *\n * Google truncates a result title around 60 characters, and a site name\n * suffix spends that budget on every page. Set `'{title}'` to drop it when\n * your titles are already long and your brand draws little search volume -\n * the suffix is only earning its characters if people search for the brand.\n */\n titleTemplate?: string\n /**\n * BCP 47 language tag for the site's content. Emitted as the Article\n * schema's `inLanguage` and the RSS channel `<language>`. Default: `'en'`.\n */\n language?: string\n /**\n * Set to `false` when articles sit behind a paywall or registration wall.\n * Default: `true`, emitted as the Article schema's `isAccessibleForFree` -\n * an explicit \"this is readable\" signal, since consumers that cannot tell\n * tend to skip suspected-paywalled sources.\n */\n isAccessibleForFree?: boolean\n /**\n * CSS selectors marking the parts of an article suitable for text-to-speech,\n * emitted as the Article schema's `speakable`. Omitted by default - the\n * correct selectors depend on the consuming app's own markup, and guessing\n * them would point at elements that may not exist.\n */\n speakableSelectors?: string[]\n /**\n * Set to `true` to derive `FAQPage` entries from question-shaped `##`\n * headings and the paragraph that follows each one. Default: `false` -\n * turning prose into structured data without the author's intent can\n * promote a rhetorical heading into a published Q&A pair, so this is\n * opt-in. Explicit `faq` frontmatter always wins over derived entries.\n */\n deriveFaqFromHeadings?: boolean\n /**\n * Shared entity vocabulary, keyed by an app-chosen slug. Article `about`\n * entries may reference a key here instead of repeating a name/`sameAs`\n * pair - the point of `about` is that a consumer can resolve one entity\n * across a corpus, which free-text names spelled three different ways\n * defeat. `validateArticles` warns on `about` keys with no registry entry.\n */\n entities?: Record<string, EntityReference>\n /**\n * Where `lastmod` comes from when frontmatter omits it.\n * - `'published'` (default): reuse the publish date, as before 1.3.0.\n * - `'none'`: leave `lastmod` unset, so `dateModified` is omitted rather\n * than repeating a stale publish date.\n * - `'fileMtime'`: read the article file's modification time. Accurate\n * locally; on a CI runner that clones fresh, every file's mtime is the\n * checkout time, which would report the whole corpus as updated today.\n * Only use it where the build preserves mtimes.\n */\n lastmodFallback?: 'published' | 'none' | 'fileMtime'\n /**\n * Called when an AI crawler fetches an article's markdown twin. The one\n * choke point where those requests land, so it is the only place a site\n * can measure whether any of its AI-readable content is being read, and\n * by which bot. No PII: the payload carries the slug, the matched crawler\n * name, and the raw user agent string only.\n */\n onAiCrawl?: (event: AiCrawlEvent) => void\n /**\n * The publishing entity behind the site. Omit to keep the pre-1.3.0 inline\n * `{'@type':'Organization', name: siteName}` publisher stub on articles.\n * Set it to emit `OrganizationSchema`/`WebSiteSchema` in the root layout and\n * have every article, author, and collection page reference the same `@id`.\n */\n organization?: OrganizationConfig\n /**\n * Default `aiCrawl` value for articles whose frontmatter omits the key.\n * Default: `false` (every article stays opted out unless it sets\n * `aiCrawl: true`). Set to `true` on a site whose goal is being cited by\n * answer engines to opt the whole corpus in at once; per-article\n * `aiCrawl: false` still wins and keeps that article blocked.\n */\n aiCrawlDefault?: boolean\n /**\n * Set to `false` to serve article markdown twins as the bare body, with no\n * attribution header. Default: `true` - the twin is prefixed with the\n * title, excerpt, canonical source URL, dates, author, and site name so a\n * model reading `/articles/[slug].md` can attribute it. Only applies when\n * a config is available (i.e. via `getArticleMarkdownResponse`).\n */\n markdownTwinHeader?: boolean\n /**\n * Vendor-neutral event callback (Phase 27F). Fired by components/hooks at\n * meaningful reader-journey moments (see `ArticleEvent` in `events.ts`).\n * No PII in any payload. The package never talks to an analytics/email\n * vendor directly - translate events to PostHog/etc. in this callback.\n */\n onEvent?: ArticleEventHandler\n}\n\nexport const DEFAULT_PAGE_SIZE = 6\nexport const DEFAULT_CATEGORIES_PAGE_SIZE = 8\n\nexport const DEFAULT_LAYOUT: ArticlesSection[] = [\n 'hero',\n 'search',\n 'featured',\n 'latest',\n 'categories',\n]\n\nexport function breadcrumbsAreEnabled(config: ArticlesConfig): boolean {\n return config.breadcrumbs !== false && config.breadcrumbs?.show !== false\n}\n\nexport function getBreadcrumbsConfig(config: ArticlesConfig): BreadcrumbsConfig {\n if (config.breadcrumbs === false) return {}\n return config.breadcrumbs ?? {}\n}\n\n/** Stable `@id` for the site's publishing entity. */\nexport function getOrganizationId(config: Pick<ArticlesConfig, 'siteUrl'>): string {\n return `${config.siteUrl.replace(/\\/$/, '')}/#organization`\n}\n\n/** Stable `@id` for the site itself. */\nexport function getWebSiteId(config: Pick<ArticlesConfig, 'siteUrl'>): string {\n return `${config.siteUrl.replace(/\\/$/, '')}/#website`\n}\n\n/** Stable `@id` for an author, so every article by them resolves to one Person. */\nexport function getPersonId(authorUrl: string): string {\n return `${authorUrl.replace(/\\/$/, '')}#person`\n}\n\n/** Resolves a possibly site-relative asset path against `siteUrl`. */\nexport function resolveEntityUrl(value: string, config: Pick<ArticlesConfig, 'siteUrl'>): string {\n if (/^https?:\\/\\//.test(value)) return value\n return `${config.siteUrl.replace(/\\/$/, '')}/${value.replace(/^\\/+/, '')}`\n}\n\nexport const DEFAULT_TITLE_TEMPLATE = '{title} | {siteName}'\n\n/**\n * Applies `ArticlesConfig.titleTemplate` to a page title. Kept here rather\n * than inlined at each call site so article, category, series, and author\n * pages can never drift apart on how the site name is appended.\n */\nexport function formatPageTitle(\n title: string,\n config: Pick<ArticlesConfig, 'siteName' | 'titleTemplate'>\n): string {\n return (config.titleTemplate ?? DEFAULT_TITLE_TEMPLATE)\n .replaceAll('{title}', title)\n .replaceAll('{siteName}', config.siteName)\n}\n","// Pure pagination math shared by both the server data-prep side (route files\n// slicing articles per page, generateStaticParams) and the client-safe\n// PaginationNav component (building prev/next hrefs). No fs/next dependency\n// so it's safe to import from both `index.ts` and `server.ts` entry points.\nimport type { Article } from './articleTypes'\n\nexport interface PaginatedArticles {\n /** Articles belonging to this page only (already sliced). */\n articles: Article[]\n /** Clamped to the range `[1, totalPages]`. */\n page: number\n totalPages: number\n hasPrevious: boolean\n hasNext: boolean\n}\n\n/** Context threaded from a listing page component down into `LatestArticles`/`PaginationNav` in `'pages'` mode. */\nexport interface ListingPaginationContext {\n page: number\n totalPages: number\n /** Un-paginated route path for this listing, e.g. `/articles` or `/articles/category/campaigns`. */\n basePath: string\n}\n\nexport interface PaginationLinks {\n /** This page's own canonical URL - never points back to page 1 for page > 1. */\n canonicalUrl: string\n prevUrl: string | null\n nextUrl: string | null\n}\n\nexport function getTotalPages(totalCount: number, pageSize: number): number {\n if (totalCount <= 0 || pageSize <= 0) return 1\n return Math.max(1, Math.ceil(totalCount / pageSize))\n}\n\n/** Slices `articles` to the requested page, clamping out-of-range page numbers into `[1, totalPages]`. */\nexport function paginateArticles(\n articles: Article[],\n page: number,\n pageSize: number\n): PaginatedArticles {\n const totalPages = getTotalPages(articles.length, pageSize)\n const requestedPage = Math.trunc(page) || 1\n const clampedPage = Math.min(Math.max(requestedPage, 1), totalPages)\n const start = (clampedPage - 1) * pageSize\n return {\n articles: articles.slice(start, start + pageSize),\n page: clampedPage,\n totalPages,\n hasPrevious: clampedPage > 1,\n hasNext: clampedPage < totalPages,\n }\n}\n\n/** Page 1 is the un-suffixed `basePath` itself; page N>1 is `${basePath}/page/${N}`. */\nexport function buildPageUrl(basePath: string, page: number): string {\n const base = basePath.replace(/\\/$/, '')\n return page > 1 ? `${base}/page/${page}` : base\n}\n\nexport function buildPaginationLinks(\n basePath: string,\n page: number,\n totalPages: number\n): PaginationLinks {\n return {\n canonicalUrl: buildPageUrl(basePath, page),\n prevUrl: page > 1 ? buildPageUrl(basePath, page - 1) : null,\n nextUrl: page < totalPages ? buildPageUrl(basePath, page + 1) : null,\n }\n}\n\n/**\n * Static params for pages 2..totalPages (page 1 has no `/page/1` route - it's\n * served by the un-paginated base route). For nested dynamic segments (e.g.\n * `/articles/category/[category]/page/[page]`), combine this per-category in\n * the consuming app's `generateStaticParams` - see README.\n */\nexport function generateListingPageStaticParams(totalPages: number): { page: string }[] {\n const params: { page: string }[] = []\n for (let page = 2; page <= totalPages; page++) params.push({ page: String(page) })\n return params\n}\n\nexport function parsePageParam(raw: string | undefined | null): number {\n const parsed = Number.parseInt(raw ?? '', 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 1\n}\n\nexport function isPageOutOfRange(page: number, totalPages: number): boolean {\n return page < 1 || page > totalPages\n}\n","import type { Metadata, MetadataRoute } from 'next'\nimport {\n getArticleMetadata,\n getAllArticles,\n getAllAuthors,\n getAllCategories,\n getArticleAuthors,\n getArticlesByCategory,\n getArticlesBySeries,\n getAuthorBySlug,\n getArticleMarkdown,\n getArticleMarkdownUrl,\n getAvailableArticleSlugs,\n buildMarkdownTwinHeader,\n categoryToSlug,\n} from './server-articles'\nimport {\n breadcrumbsAreEnabled,\n formatPageTitle,\n getBreadcrumbsConfig,\n DEFAULT_PAGE_SIZE,\n type ArticlesConfig,\n type ArticleBreadcrumbEntry,\n type AuthorBreadcrumbEntry,\n type CategoryBreadcrumbEntry,\n type CustomBreadcrumbItem,\n} from './articlesConfig'\nimport { buildPageUrl, buildPaginationLinks, getTotalPages } from './pagination'\nimport type { Article, AuthorProfile, BreadcrumbItem } from './articleTypes'\n\nfunction escapeXml(str: string): string {\n return str\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&apos;')\n}\n\n/**\n * `options.fullContent` adds `<content:encoded>` with each article's rendered\n * HTML, for feeds meant to be ingested rather than previewed - an\n * excerpt-only feed gives a consumer nothing to work with. Off by default:\n * it requires articles loaded with `htmlContent` (i.e. via\n * `getArticleMetadata`, not `getAllArticles`' summaries), and articles\n * without it are simply emitted without the element.\n */\nexport function generateRssFeed(\n articles: Article[],\n config: ArticlesConfig,\n options?: Readonly<{ fullContent?: boolean }>\n): string {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const showAuthor = config.showAuthor !== false\n const fullContent = options?.fullContent === true\n\n const items = articles\n .map((article) => {\n const url = `${siteUrl}/articles/${article.slug}`\n const pubDate = article.date ? new Date(article.date).toUTCString() : ''\n const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : ''\n\n return [\n ' <item>',\n ` <title><![CDATA[${article.title}]]></title>`,\n ` <link>${url}</link>`,\n ` <guid isPermaLink=\"true\">${url}</guid>`,\n pubDate ? ` <pubDate>${pubDate}</pubDate>` : '',\n article.excerpt ? ` <description><![CDATA[${article.excerpt}]]></description>` : '',\n fullContent && article.htmlContent\n ? ` <content:encoded><![CDATA[${article.htmlContent}]]></content:encoded>`\n : '',\n showAuthor && article.author ? ` <author>${escapeXml(article.author)}</author>` : '',\n article.category ? ` <category><![CDATA[${article.category}]]></category>` : '',\n imageUrl\n ? ` <media:content url=\"${imageUrl}\" medium=\"image\" width=\"1200\" height=\"630\"/>`\n : '',\n ' </item>',\n ]\n .filter(Boolean)\n .join('\\n')\n })\n .join('\\n')\n\n const description = config.description ?? `${config.siteName} articles`\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:media=\"http://search.yahoo.com/mrss/\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title><![CDATA[${config.siteName}]]></title>\n <link>${siteUrl}/articles</link>\n <description><![CDATA[${description}]]></description>\n <language>${config.language ?? 'en'}</language>\n <atom:link href=\"${siteUrl}/articles/feed.xml\" rel=\"self\" type=\"application/rss+xml\" />\n${items}\n </channel>\n</rss>`\n}\n\n/** Shared `# name` / `> summary` preamble for both llms.txt variants. */\nfunction buildLlmsHeader(config: ArticlesConfig): string[] {\n const summary = config.description ?? `${config.siteName} articles`\n return [`# ${config.siteName}`, '', `> ${summary}`, '']\n}\n\n/**\n * `llms.txt` index - the emerging convention for pointing an LLM at a site's\n * canonical, markdown-native content (https://llmstxt.org). Lists only\n * articles opted in via `aiCrawl` (see `ArticlesConfig.aiCrawlDefault`),\n * grouped by category, linking to each article's `.md` twin rather than its\n * HTML page.\n *\n * Wire it up in the consuming app as `app/llms.txt/route.ts`:\n * export async function GET() {\n * return new Response(generateLlmsTxt(await getAllArticles(siteConfig), siteConfig), {\n * headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n * })\n * }\n */\nexport function generateLlmsTxt(articles: Article[], config: ArticlesConfig): string {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const crawlable = articles.filter((article) => article.aiCrawl === true)\n\n const byCategory = new Map<string, Article[]>()\n for (const article of crawlable) {\n const category = article.category || 'Articles'\n const existing = byCategory.get(category)\n if (existing) existing.push(article)\n else byCategory.set(category, [article])\n }\n\n const sections = [...byCategory.entries()].map(([category, categoryArticles]) => {\n const lines = categoryArticles.map((article) => {\n const url = `${siteUrl}/articles/${article.slug}.md`\n const summary = article.excerpt ? `: ${article.excerpt}` : ''\n return `- [${article.title}](${url})${summary}`\n })\n return [`## ${category}`, '', ...lines].join('\\n')\n })\n\n return [\n ...buildLlmsHeader(config),\n ...(sections.length > 0 ? sections : ['## Articles', '', '_No articles available._']),\n ...buildLlmsListingSection(crawlable, config),\n '',\n ].join('\\n')\n}\n\n/**\n * Links the category, author, and series twins alongside the article list.\n * Without them a model gets a flat bag of pages; these are the surfaces that\n * answer \"what does this site cover, and who writes it\".\n */\nfunction buildLlmsListingSection(articles: Article[], config: ArticlesConfig): string[] {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const categories = [...new Set(articles.flatMap((a) => a.categories ?? []))].filter(Boolean)\n const series = [...new Set(articles.map((a) => a.seriesSlug).filter(Boolean))] as string[]\n const authors = Object.values(config.authors ?? {})\n\n const lines = [\n ...categories.map(\n (name) => `- [${name}](${siteUrl}/articles/category/${categoryToSlug(name)}.md)`\n ),\n ...(config.showAuthorPage === false\n ? []\n : authors.map((a) => `- [${a.name}](${siteUrl}/articles/authors/${a.slug}.md)`)),\n ...series.map((slug) => `- [${slug}](${siteUrl}/articles/series/${slug}.md)`),\n ]\n if (lines.length === 0) return []\n return ['## Collections', '', ...lines]\n}\n\n/**\n * `llms-full.txt` - every opted-in article's full markdown twin, headers\n * included, concatenated into one document. Larger and slower to build than\n * `generateLlmsTxt`; generate it in a route handler or at build time, not on\n * every request.\n */\nexport async function generateLlmsFullTxt(\n articles: Article[],\n config: ArticlesConfig\n): Promise<string> {\n const crawlable = articles.filter((article) => article.aiCrawl === true)\n const documents = await Promise.all(\n crawlable.map(async (article) => {\n const body = await getArticleMarkdown(article.slug, config)\n if (body === null) return null\n return `${buildMarkdownTwinHeader(article, config, body)}${body.trimStart()}`\n })\n )\n\n return [\n ...buildLlmsHeader(config),\n ...documents.filter((doc): doc is string => doc !== null),\n ].join('\\n')\n}\n\nexport function generateArticleStaticParams(): { slug: string }[] {\n return getAvailableArticleSlugs().map((slug) => ({ slug }))\n}\n\nexport async function generateCategoryStaticParams(): Promise<{ category: string }[]> {\n const categories = await getAllCategories()\n return categories.map((cat) => ({ category: cat.slug }))\n}\n\nexport function generateAuthorStaticParams(config: ArticlesConfig): { author: string }[] {\n if (config.showAuthorPage === false) return []\n return getAllAuthors(config).map((author) => ({ author: author.slug }))\n}\n\n/** Static params for `/articles/series/[series]` - one entry per distinct `seriesSlug` found across all articles. */\nexport async function generateSeriesStaticParams(\n config?: ArticlesConfig\n): Promise<{ series: string }[]> {\n const articles = await getAllArticles(config)\n const seriesSlugs = new Set(\n articles.map((article) => article.seriesSlug).filter((slug): slug is string => Boolean(slug))\n )\n return [...seriesSlugs].map((series) => ({ series }))\n}\n\nfunction resolveImageUrl(featuredImage: string, siteUrl: string): string {\n const base = siteUrl.replace(/\\/$/, '')\n if (featuredImage.startsWith('http://') || featuredImage.startsWith('https://')) {\n return featuredImage\n }\n return `${base}/${featuredImage.replace(/^\\/+/, '')}`\n}\n\n// Discovery metadata channel separation (Phase 27F): `searchTitle`/\n// `searchDescription` feed the `<title>`/meta-description channel ONLY,\n// `socialTitle`/`socialDescription`/`socialImage` feed Open Graph/Twitter\n// Card ONLY. Canonical URLs, JSON-LD (`ArticleSEO`), `ArticleCard`, and RSS\n// all keep reading `title`/`excerpt`/`featuredImage` directly and are\n// untouched by either override - each surface pulls from exactly one\n// source, never \"apply every override everywhere\".\nexport function resolveSearchMetadata(\n article: Pick<Article, 'title' | 'excerpt' | 'searchTitle' | 'searchDescription'>,\n config: Pick<ArticlesConfig, 'siteName'>\n): { title: string; description: string } {\n return {\n title: article.searchTitle ?? article.title,\n description:\n article.searchDescription ??\n article.excerpt ??\n `Read ${article.title} on ${config.siteName}.`,\n }\n}\n\nexport function resolveSocialMetadata(\n article: Pick<\n Article,\n 'title' | 'excerpt' | 'featuredImage' | 'socialTitle' | 'socialDescription' | 'socialImage'\n >,\n siteUrl: string\n): { title: string; description: string; imageUrl: string } {\n const image = article.socialImage ?? article.featuredImage\n return {\n title: article.socialTitle ?? article.title,\n description: article.socialDescription ?? article.excerpt ?? '',\n imageUrl: image ? resolveImageUrl(image, siteUrl) : `${siteUrl}/placeholder-logo.png`,\n }\n}\n\nexport async function generateArticleMetadata(\n slug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const article = await getArticleMetadata(slug, config)\n\n if (!article) {\n return {\n title: 'Article Not Found',\n description: 'The requested article could not be found.',\n }\n }\n\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const articleUrl = `${siteUrl}/articles/${slug}`\n const canonicalUrl = article.canonicalUrl ?? articleUrl\n const search = resolveSearchMetadata(article, config)\n const social = resolveSocialMetadata(article, siteUrl)\n const description = search.description\n const showAuthor = config.showAuthor !== false\n const markdownUrl = getArticleMarkdownUrl(article, config)\n const authorNames = getArticleAuthors(article, config).map((author) => author.name)\n\n return {\n title: formatPageTitle(search.title, config),\n description,\n keywords: [...(article.tags ?? []).map((tag) => tag.toLowerCase())].join(', '),\n openGraph: {\n title: social.title,\n description: social.description || description,\n url: articleUrl,\n siteName: config.siteName,\n images: [{ url: social.imageUrl, width: 1200, height: 630, alt: social.title }],\n locale: 'en_US',\n type: 'article',\n ...(article.date && { publishedTime: article.date }),\n ...(article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }),\n ...(showAuthor && authorNames.length > 0 && { authors: authorNames }),\n tags: article.tags ?? [],\n },\n twitter: {\n card: 'summary_large_image',\n title: social.title,\n description: social.description || description,\n images: [social.imageUrl],\n },\n alternates: {\n canonical: canonicalUrl,\n ...(markdownUrl && {\n types: {\n 'text/markdown': markdownUrl,\n },\n }),\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n other: {\n ...(showAuthor && authorNames.length > 0 && { 'article:author': authorNames.join(', ') }),\n ...(article.date && {\n 'article:published_time': new Date(article.date).toISOString(),\n }),\n ...(article.lastmod && {\n 'article:modified_time': new Date(article.lastmod).toISOString(),\n }),\n 'article:section': article.category,\n 'article:tag': article.tags?.join(',') ?? '',\n 'linkedin:owner': process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID ?? '',\n },\n }\n}\n\nexport function generateArticlesIndexMetadata(config: ArticlesConfig): Metadata {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const indexUrl = `${siteUrl}/articles`\n const title = formatPageTitle('Articles', config)\n const description =\n config.hero?.description ?? `Expert analysis and insights from ${config.siteName}.`\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n url: indexUrl,\n siteName: config.siteName,\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n },\n alternates: {\n canonical: indexUrl,\n types: {\n 'application/rss+xml': `${siteUrl}/articles/feed.xml`,\n },\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function generateCategoryMetadata(\n categorySlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const articles = await getArticlesByCategory(categorySlug)\n\n if (articles.length === 0) return { title: 'Category Not Found' }\n\n const categoryName = articles[0].category\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const categoryUrl = `${siteUrl}/articles/category/${categorySlug}`\n const raw = config.categoryDescriptions?.[categorySlug]\n const fallback = `Browse ${articles.length} article${articles.length === 1 ? '' : 's'} in the ${categoryName} category.`\n const description = typeof raw === 'string' ? raw : (raw?.short ?? fallback)\n\n const title = formatPageTitle(`${categoryName} Articles`, config)\n return {\n title,\n description,\n openGraph: {\n title: `${categoryName} Articles`,\n description,\n url: categoryUrl,\n siteName: config.siteName,\n images: [{ url: articles[0].featuredImage }],\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title: `${categoryName} Articles`,\n description,\n },\n alternates: {\n canonical: categoryUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\n/** Metadata for a series landing page, analogous to `generateCategoryMetadata`. Series display name comes from the first matching article's label-only `series` string, falling back to `seriesSlug` itself. */\nexport async function generateSeriesMetadata(\n seriesSlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const articles = await getArticlesBySeries(seriesSlug, config)\n\n if (articles.length === 0) return { title: 'Series Not Found' }\n\n const seriesName = articles[0].series ?? seriesSlug\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const seriesUrl = `${siteUrl}/articles/series/${seriesSlug}`\n const description = `Follow the ${seriesName} series - ${articles.length} article${articles.length === 1 ? '' : 's'} on ${config.siteName}.`\n const title = formatPageTitle(`${seriesName} Series`, config)\n\n return {\n title,\n description,\n openGraph: {\n title: `${seriesName} Series`,\n description,\n url: seriesUrl,\n siteName: config.siteName,\n images: [{ url: articles[0].featuredImage }],\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title: `${seriesName} Series`,\n description,\n },\n alternates: {\n canonical: seriesUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function generateAuthorMetadata(\n authorSlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const author = getAuthorBySlug(authorSlug, config)\n\n if (!author || config.showAuthorPage === false) return { title: 'Author Not Found' }\n\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const authorUrl = author.url ?? `${siteUrl}/articles/authors/${author.slug}`\n const title = formatPageTitle(`${author.name} Articles`, config)\n\n return {\n title,\n description: author.bio,\n openGraph: {\n title,\n description: author.bio,\n url: authorUrl,\n siteName: config.siteName,\n type: 'profile',\n locale: 'en_US',\n ...(author.avatar && { images: [{ url: resolveAuthorAvatar(author, config) }] }),\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description: author.bio,\n ...(author.avatar && { images: [resolveAuthorAvatar(author, config)] }),\n },\n alternates: {\n canonical: authorUrl,\n },\n robots: {\n index: true,\n follow: true,\n },\n }\n}\n\n// Google Search Central's pagination guidance no longer treats rel=next/prev\n// as an indexing or ranking signal (confirmed dropped in 2019) - the\n// documented current recommendation is a unique, self-referencing canonical\n// per paginated page (never pointing page 2+ back to page 1) plus real\n// crawlable <a href> links between pages (handled by `PaginationNav`), which\n// is what these functions and that component together provide. rel=next/prev\n// itself is still valid HTML and still read by Bing and some third-party\n// tools/crawlers, so `PaginationNav` still emits it - it's just not what\n// makes these pages indexable to Google. Paginated pages stay index/follow\n// (inherited from the wrapped `generate*Metadata` call below) - the point of\n// this feature is making page 2+ indexable, not excluding it.\nfunction withPaginationMeta(\n base: Metadata,\n basePath: string,\n page: number,\n totalPages: number\n): Metadata {\n // Not-found responses from the wrapped generate*Metadata call (e.g.\n // \"Category Not Found\") never set `alternates` - leave them untouched\n // rather than decorating an error title/canonical with page info.\n if (!base.alternates) return base\n\n const { canonicalUrl } = buildPaginationLinks(basePath, page, totalPages)\n const pageSuffix = page > 1 ? ` - Page ${page}` : ''\n const title = typeof base.title === 'string' ? `${base.title}${pageSuffix}` : base.title\n const openGraph = base.openGraph\n ? {\n ...base.openGraph,\n title:\n typeof base.openGraph.title === 'string'\n ? `${base.openGraph.title}${pageSuffix}`\n : base.openGraph.title,\n url: canonicalUrl,\n }\n : base.openGraph\n const twitter = base.twitter\n ? {\n ...base.twitter,\n title:\n typeof base.twitter.title === 'string'\n ? `${base.twitter.title}${pageSuffix}`\n : base.twitter.title,\n }\n : base.twitter\n\n return {\n ...base,\n title,\n openGraph,\n twitter,\n alternates: { ...base.alternates, canonical: canonicalUrl },\n }\n}\n\n/** Per-page metadata for `/articles/page/[page]` in `listingPagination: 'pages'` mode. Page 1 is identical to `generateArticlesIndexMetadata`. */\nexport function generateArticlesIndexPageMetadata(\n page: number,\n totalPages: number,\n config: ArticlesConfig\n): Metadata {\n const base = generateArticlesIndexMetadata(config)\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n return withPaginationMeta(base, `${siteUrl}/articles`, page, totalPages)\n}\n\n/** Per-page metadata for `/articles/category/[category]/page/[page]` in `listingPagination: 'pages'` mode. */\nexport async function generateCategoryPageMetadata(\n categorySlug: string,\n page: number,\n totalPages: number,\n config: ArticlesConfig\n): Promise<Metadata> {\n const base = await generateCategoryMetadata(categorySlug, config)\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n return withPaginationMeta(base, `${siteUrl}/articles/category/${categorySlug}`, page, totalPages)\n}\n\n/** Per-page metadata for `/articles/authors/[author]/page/[page]` in `listingPagination: 'pages'` mode. */\nexport async function generateAuthorPageMetadata(\n authorSlug: string,\n page: number,\n totalPages: number,\n config: ArticlesConfig\n): Promise<Metadata> {\n const base = await generateAuthorMetadata(authorSlug, config)\n const author = getAuthorBySlug(authorSlug, config)\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const basePath = author?.url ?? `${siteUrl}/articles/authors/${authorSlug}`\n return withPaginationMeta(base, basePath, page, totalPages)\n}\n\nfunction formatCategoryName(category: string): string {\n return category\n .split('-')\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nexport function buildArticleBreadcrumbs(\n article: Pick<Article, 'slug' | 'title' | 'category'>,\n config: ArticlesConfig\n): BreadcrumbItem[] {\n if (!breadcrumbsAreEnabled(config)) return []\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const breadcrumbConfig = getBreadcrumbsConfig(config)\n const labels = breadcrumbConfig.labels ?? {}\n const categorySlug = categoryToSlug(article.category)\n const trail = breadcrumbConfig.article ?? ['home', 'articles', 'primaryCategory', 'articleTitle']\n const folderSegments = article.slug.split('/').filter(Boolean).slice(0, -1)\n return trail.flatMap((token): BreadcrumbItem[] =>\n buildArticleBreadcrumbToken(token, {\n article,\n siteUrl,\n categorySlug,\n folderSegments,\n labels,\n })\n )\n}\n\nexport function buildCategoryBreadcrumbs(\n category: string,\n config: ArticlesConfig,\n categoryName = formatCategoryName(category)\n): BreadcrumbItem[] {\n if (!breadcrumbsAreEnabled(config)) return []\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const breadcrumbConfig = getBreadcrumbsConfig(config)\n const labels = breadcrumbConfig.labels ?? {}\n const trail = breadcrumbConfig.category ?? ['home', 'articles', 'category']\n return trail.flatMap((entry): BreadcrumbItem[] =>\n buildCategoryBreadcrumbEntry(entry, { categoryName, siteUrl, labels })\n )\n}\n\nexport function buildAuthorBreadcrumbs(\n author: AuthorProfile,\n config: ArticlesConfig\n): BreadcrumbItem[] {\n if (!breadcrumbsAreEnabled(config)) return []\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const breadcrumbConfig = getBreadcrumbsConfig(config)\n const labels = breadcrumbConfig.labels ?? {}\n const trail = breadcrumbConfig.author ?? ['home', 'articles', 'authors', 'authorName']\n return trail.flatMap((entry): BreadcrumbItem[] =>\n buildAuthorBreadcrumbEntry(entry, { author, siteUrl, labels })\n )\n}\n\ntype BreadcrumbLabels = NonNullable<ReturnType<typeof getBreadcrumbsConfig>['labels']>\n\nfunction isCustomBreadcrumbItem(entry: unknown): entry is CustomBreadcrumbItem {\n return typeof entry === 'object' && entry !== null && 'name' in entry && 'url' in entry\n}\n\nfunction resolveCustomBreadcrumbItem(item: CustomBreadcrumbItem, siteUrl: string): BreadcrumbItem {\n if (item.url.startsWith('/')) return { name: item.name, url: `${siteUrl}${item.url}` }\n return { name: item.name, url: item.url }\n}\n\nfunction buildArticleBreadcrumbToken(\n entry: ArticleBreadcrumbEntry,\n context: Readonly<{\n article: Pick<Article, 'slug' | 'title' | 'category'>\n siteUrl: string\n categorySlug: string\n folderSegments: string[]\n labels: BreadcrumbLabels\n }>\n): BreadcrumbItem[] {\n if (isCustomBreadcrumbItem(entry)) {\n return [resolveCustomBreadcrumbItem(entry, context.siteUrl)]\n }\n if (entry === 'home') return [{ name: context.labels.home ?? 'Home', url: context.siteUrl }]\n if (entry === 'articles') {\n return [{ name: context.labels.articles ?? 'Articles', url: `${context.siteUrl}/articles` }]\n }\n if (entry === 'primaryCategory') {\n return [\n {\n name: context.article.category,\n url: `${context.siteUrl}/articles/category/${context.categorySlug}`,\n },\n ]\n }\n if (entry === 'folderPath') {\n return context.folderSegments.map((segment, index) => ({\n name: formatCategoryName(segment),\n url: `${context.siteUrl}/articles/${context.folderSegments.slice(0, index + 1).join('/')}`,\n }))\n }\n return [{ name: context.article.title }]\n}\n\nfunction buildCategoryBreadcrumbEntry(\n entry: CategoryBreadcrumbEntry,\n context: Readonly<{ categoryName: string; siteUrl: string; labels: BreadcrumbLabels }>\n): BreadcrumbItem[] {\n if (isCustomBreadcrumbItem(entry)) {\n return [resolveCustomBreadcrumbItem(entry, context.siteUrl)]\n }\n if (entry === 'home') return [{ name: context.labels.home ?? 'Home', url: context.siteUrl }]\n if (entry === 'articles') {\n return [{ name: context.labels.articles ?? 'Articles', url: `${context.siteUrl}/articles` }]\n }\n return [{ name: context.categoryName }]\n}\n\nfunction buildAuthorBreadcrumbEntry(\n entry: AuthorBreadcrumbEntry,\n context: Readonly<{ author: AuthorProfile; siteUrl: string; labels: BreadcrumbLabels }>\n): BreadcrumbItem[] {\n if (isCustomBreadcrumbItem(entry)) {\n return [resolveCustomBreadcrumbItem(entry, context.siteUrl)]\n }\n if (entry === 'home') return [{ name: context.labels.home ?? 'Home', url: context.siteUrl }]\n if (entry === 'articles') {\n return [{ name: context.labels.articles ?? 'Articles', url: `${context.siteUrl}/articles` }]\n }\n if (entry === 'authors') {\n return [\n {\n name: context.labels.authors ?? 'Authors',\n url: `${context.siteUrl}/articles/authors`,\n },\n ]\n }\n return [{ name: context.author.name }]\n}\n\nexport function resolveAuthorAvatar(author: AuthorProfile, config: ArticlesConfig): string {\n if (!author.avatar) return ''\n if (author.avatar.startsWith('http://') || author.avatar.startsWith('https://')) {\n return author.avatar\n }\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n return `${siteUrl}/articles/authors/${author.slug}/${author.avatar.replace(/^\\/+/, '')}`\n}\n\n/** Newest `lastmod`/`date` across a set of articles, or `undefined` if none carry one. */\nfunction newestArticleDate(articles: readonly Article[]): Date | undefined {\n let newest: Date | undefined\n for (const article of articles) {\n const stamp = article.lastmod ?? article.date\n if (!stamp) continue\n const parsed = new Date(stamp)\n if (Number.isNaN(parsed.getTime())) continue\n if (!newest || parsed > newest) newest = parsed\n }\n return newest\n}\n\n/**\n * Paginated listing routes, emitted only in `listingPagination: 'pages'` mode.\n * Page 1 is the listing URL itself, already emitted by the caller, so this\n * starts at page 2. Without these, the paginated routes exist and carry\n * correct canonical/prev/next metadata but appear in no sitemap.\n */\nfunction paginationEntries(\n basePath: string,\n itemCount: number,\n pageSize: number,\n lastModified: Date | undefined,\n priority: number\n): MetadataRoute.Sitemap {\n const totalPages = getTotalPages(itemCount, pageSize)\n const entries: MetadataRoute.Sitemap = []\n for (let page = 2; page <= totalPages; page++) {\n entries.push({\n url: buildPageUrl(basePath, page),\n lastModified,\n changeFrequency: 'weekly' as const,\n priority,\n })\n }\n return entries\n}\n\nexport async function getArticleSitemapEntries(\n baseUrlOrConfig: string | ArticlesConfig\n): Promise<MetadataRoute.Sitemap> {\n const config = typeof baseUrlOrConfig === 'string' ? undefined : baseUrlOrConfig\n const baseUrl = (config?.siteUrl ?? (baseUrlOrConfig as string)).replace(/\\/$/, '')\n\n try {\n const [articles, categories] = await Promise.all([getAllArticles(config), getAllCategories()])\n\n const articleEntries: MetadataRoute.Sitemap = articles.map((article) => {\n const dateStr = article.lastmod ?? article.date\n const lastModified = dateStr ? new Date(dateStr) : undefined\n return {\n url: `${baseUrl}/articles/${article.slug}`,\n lastModified,\n changeFrequency: 'weekly' as const,\n priority: 0.8,\n }\n })\n\n // Derived from the newest article in each category, not `new Date()` -\n // stamping \"now\" told every crawl that every category changed today,\n // which is exactly the freshness signal a sitemap exists to carry.\n const categoryEntries: MetadataRoute.Sitemap = categories.map((cat) => ({\n url: `${baseUrl}/articles/category/${cat.slug}`,\n lastModified: newestArticleDate(\n articles.filter((article) =>\n (article.categories ?? []).some((name) => categoryToSlug(name) === cat.slug)\n )\n ),\n changeFrequency: 'weekly' as const,\n priority: 0.7,\n }))\n\n const authors = config && config.showAuthorPage !== false ? getAllAuthors(config) : []\n const authorEntries: MetadataRoute.Sitemap = authors.map((author) => ({\n url: `${baseUrl}/articles/authors/${author.slug}`,\n lastModified: newestArticleDate(\n articles.filter((article) =>\n getArticleAuthors(article, config!).some((profile) => profile.slug === author.slug)\n )\n ),\n changeFrequency: 'monthly' as const,\n priority: 0.6,\n }))\n\n // Series routes (`/articles/series/[series]`) are real - they have static\n // params, metadata, and a markdown twin - but were absent from the sitemap.\n const seriesSlugs = [...new Set(articles.map((a) => a.seriesSlug).filter(Boolean))] as string[]\n const seriesEntries: MetadataRoute.Sitemap = seriesSlugs.map((seriesSlug) => ({\n url: `${baseUrl}/articles/series/${seriesSlug}`,\n lastModified: newestArticleDate(articles.filter((a) => a.seriesSlug === seriesSlug)),\n changeFrequency: 'weekly' as const,\n priority: 0.6,\n }))\n\n const pageEntries: MetadataRoute.Sitemap = []\n if (config?.listingPagination === 'pages') {\n const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE\n pageEntries.push(\n ...paginationEntries(\n `${baseUrl}/articles`,\n articles.length,\n pageSize,\n newestArticleDate(articles),\n 0.5\n )\n )\n for (const cat of categories) {\n const inCategory = articles.filter((article) =>\n (article.categories ?? []).some((name) => categoryToSlug(name) === cat.slug)\n )\n pageEntries.push(\n ...paginationEntries(\n `${baseUrl}/articles/category/${cat.slug}`,\n inCategory.length,\n pageSize,\n newestArticleDate(inCategory),\n 0.4\n )\n )\n }\n for (const author of authors) {\n const byAuthor = articles.filter((article) =>\n getArticleAuthors(article, config).some((profile) => profile.slug === author.slug)\n )\n pageEntries.push(\n ...paginationEntries(\n `${baseUrl}/articles/authors/${author.slug}`,\n byAuthor.length,\n pageSize,\n newestArticleDate(byAuthor),\n 0.4\n )\n )\n }\n }\n\n return [\n ...articleEntries,\n ...categoryEntries,\n ...authorEntries,\n ...seriesEntries,\n ...pageEntries,\n ]\n } catch {\n return []\n }\n}\n","// MDX files must use JSX prop syntax:\n// Correct: <span style={{ color: '#3b82f6' }}>Text</span>\n// Incorrect: <span style=\"color: #3b82f6\">Text</span>\n// Correct: className=\"...\"\n// Incorrect: class=\"...\"\nimport React from 'react'\nimport type { AnchorHTMLAttributes, ComponentType, ImgHTMLAttributes } from 'react'\nimport Link from 'next/link'\nimport * as devRuntime from 'react/jsx-dev-runtime'\nimport * as runtime from 'react/jsx-runtime'\nimport { evaluate } from '@mdx-js/mdx'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSlug from 'rehype-slug'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport { customRenderer } from './markdown'\nimport { isExternalHttpLink, isNonBrowserNavigationLink } from './linkClassification'\nimport type { ArticlesConfig } from './articlesConfig'\n\ntype MdxContent = ComponentType<{\n components?: Record<string, ComponentType<unknown>>\n}>\n\nfunction makeImgComponent(basePath: string) {\n return function MdxImage({ src, alt, ...props }: ImgHTMLAttributes<HTMLImageElement>) {\n const resolvedSrc =\n typeof src === 'string' && !src.startsWith('http') && !src.startsWith('/')\n ? `${basePath}/${src}`\n : src\n return React.createElement('img', { src: resolvedSrc, alt, ...props })\n }\n}\n\n// Same internal/external classification `customRenderer`'s rehype pass\n// already used to decide target=\"_blank\" (see `applyLinkTarget` in\n// markdown.ts) - reused rather than reimplemented so the two decisions\n// can never drift apart. An in-page anchor (#section) or a non-browser\n// scheme (mailto:, tel:) is never routed through next/link either; only a\n// same-origin, browser-navigable href gets client-side routing.\nfunction isInternalNavigableHref(href: string, siteUrl?: string): boolean {\n if (!href || href.startsWith('#') || isNonBrowserNavigationLink(href)) return false\n return !isExternalHttpLink(href, siteUrl)\n}\n\nfunction makeLinkComponent(siteUrl?: string) {\n return function MdxLink({ href, children, ...props }: AnchorHTMLAttributes<HTMLAnchorElement>) {\n if (typeof href === 'string' && isInternalNavigableHref(href, siteUrl)) {\n // React.createElement, not JSX, for the same reason makeImgComponent\n // above uses it: next/link's own (duplicate) @types/react copy in\n // this monorepo's node_modules is structurally incompatible with\n // this file's DOM attribute types, and JSX's prop-checking is\n // stricter about that mismatch than createElement's is.\n return React.createElement(Link, { href, ...props } as never, children)\n }\n return (\n <a href={href} {...props}>\n {children}\n </a>\n )\n }\n}\n\nexport async function renderMdxSource(source: string, basePath?: string, config?: ArticlesConfig) {\n const isDevelopment = process.env.NODE_ENV === 'development'\n\n const mdxModule = await evaluate(source, {\n ...(isDevelopment ? devRuntime : runtime),\n development: isDevelopment,\n remarkPlugins: [remarkGfm, remarkGithubBlockquoteAlert],\n rehypePlugins: [\n [customRenderer, { strategy: config?.linkTargetStrategy, siteUrl: config?.siteUrl }],\n rehypeSlug,\n // @ts-ignore\n rehypePrism,\n ],\n })\n\n const Content = mdxModule.default as MdxContent\n // `a` is always overridden - unlike `img`, internal-link routing isn't\n // conditional on basePath being provided. Every article's markdown\n // links otherwise compile to a plain `<a>` (a full page reload on\n // click, since @mdx-js/mdx's evaluate() has no knowledge of Next's\n // router), which was silently forcing a hard navigation - and a fresh\n // same-site document.referrer - on every single in-article link click.\n const internalComponents = {\n a: makeLinkComponent(config?.siteUrl),\n ...(basePath ? { img: makeImgComponent(basePath) } : {}),\n }\n const components = { ...internalComponents, ...config?.mdxComponents }\n return <Content components={components as Record<string, ComponentType<unknown>>} />\n}\n","import type { ReactNode } from 'react'\nimport { renderMdxSource } from './renderMdx'\nimport { markdownToHtml, getContentSlotBoundaries } from './markdown'\nimport type { Article } from './articleTypes'\nimport type { ArticlesConfig } from './articlesConfig'\n\n/**\n * Sanitized, non-PII context passed into `ArticleContent`'s slot render\n * props - deliberately a narrow subset of `Article`, not the whole object\n * (no raw `content`/`mdxSource`, no author email or anything author-PII).\n */\nexport interface ArticleSlotContext {\n slug: string\n title: string\n category: string\n tags: string[]\n readTime: string\n wordCount?: number\n authorSlug?: string\n seriesSlug?: string\n primaryActionId?: string\n}\n\nexport type ArticleSlotContent = ReactNode | ((context: ArticleSlotContext) => ReactNode)\n\nfunction buildSlotContext(article: Article): ArticleSlotContext {\n return {\n slug: article.slug,\n title: article.title,\n category: article.category,\n tags: article.tags ?? [],\n readTime: article.readTime,\n wordCount: article.wordCount,\n authorSlug: article.authorSlug,\n seriesSlug: article.seriesSlug,\n primaryActionId: article.primaryAction?.actionId,\n }\n}\n\nfunction resolveSlot(slot: ArticleSlotContent | undefined, context: ArticleSlotContext): ReactNode {\n if (slot === undefined) return null\n return typeof slot === 'function' ? slot(context) : slot\n}\n\ntype ArticleContentProps = Readonly<{\n article: Article\n className?: string\n config?: ArticlesConfig\n /** Rendered immediately before the article body - the \"around the body, not inside it\" counterpart to `config.mdxComponents` (which places content *inside* MDX bodies). */\n afterHero?: ArticleSlotContent\n /** Rendered right after the first paragraph, resolved deterministically from the parsed AST (see `getContentSlotBoundaries`). Falls back to not rendering (never a brittle string split) when the source has no detectable paragraphs, e.g. MDX using JSX-heavy syntax remark-parse can't read as plain markdown. */\n afterIntro?: ArticleSlotContent\n /** Rendered after roughly the middle paragraph. Same fallback behavior as `afterIntro`. */\n midContent?: ArticleSlotContent\n /** Rendered immediately after the article body. */\n afterContent?: ArticleSlotContent\n}>\n\nasync function renderSegment(\n markdown: string,\n contentType: Article['contentType'],\n slug: string,\n config?: ArticlesConfig\n): Promise<ReactNode> {\n if (!markdown.trim()) return null\n if (contentType === 'mdx') {\n return renderMdxSource(markdown, `/articles/${slug}`, config)\n }\n const html = await markdownToHtml(markdown, slug, config)\n return <div dangerouslySetInnerHTML={{ __html: html }} />\n}\n\nexport async function ArticleContent({\n article,\n className,\n config,\n afterHero,\n afterIntro,\n midContent,\n afterContent,\n}: ArticleContentProps) {\n // Legacy path (Phase 27F: zero slot props passed) reproduces the exact\n // pre-27F markup - a single outer div, `dangerouslySetInnerHTML` set\n // directly on it for the HTML path - rather than the slot-aware wrapper\n // below, so existing consumers/tests see byte-for-byte identical output.\n const hasAnySlot =\n afterHero !== undefined ||\n afterIntro !== undefined ||\n midContent !== undefined ||\n afterContent !== undefined\n if (!hasAnySlot) {\n if (article.contentType === 'mdx' && article.mdxSource) {\n const content = await renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config)\n return <div className={className}>{content}</div>\n }\n return (\n <div className={className} dangerouslySetInnerHTML={{ __html: article.htmlContent || '' }} />\n )\n }\n\n const slotContext = buildSlotContext(article)\n const heroNode = resolveSlot(afterHero, slotContext)\n const introNode = resolveSlot(afterIntro, slotContext)\n const midNode = resolveSlot(midContent, slotContext)\n const contentNode = resolveSlot(afterContent, slotContext)\n\n const needsSplit = Boolean(introNode || midNode)\n const rawSource =\n article.contentType === 'mdx' ? article.mdxSource : (article.content ?? undefined)\n\n if (needsSplit && rawSource) {\n const boundaries = getContentSlotBoundaries(rawSource)\n if (boundaries) {\n try {\n const introSegment = rawSource.slice(0, boundaries.introEnd)\n const midSegment = rawSource.slice(boundaries.introEnd, boundaries.mid)\n const restSegment = rawSource.slice(boundaries.mid)\n const [introHtml, midHtml, restHtml] = await Promise.all([\n renderSegment(introSegment, article.contentType, article.slug, config),\n renderSegment(midSegment, article.contentType, article.slug, config),\n renderSegment(restSegment, article.contentType, article.slug, config),\n ])\n return (\n <div className={className}>\n {heroNode}\n {introHtml}\n {introNode}\n {midHtml}\n {midNode}\n {restHtml}\n {contentNode}\n </div>\n )\n } catch {\n // Splitting the MDX source failed to evaluate (e.g. a JSX block\n // straddled a paragraph boundary) - fall through to the\n // whole-document render below rather than throwing. `afterIntro`/\n // `midContent` are silently omitted for this article; `afterHero`/\n // `afterContent` still render.\n }\n }\n }\n\n const wholeBody =\n article.contentType === 'mdx' && article.mdxSource ? (\n await renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config)\n ) : (\n <div dangerouslySetInnerHTML={{ __html: article.htmlContent || '' }} />\n )\n\n return (\n <div className={className}>\n {heroNode}\n {wholeBody}\n {contentNode}\n </div>\n )\n}\n","import type { TocItem } from './articleTypes'\n\ntype ArticleTOCProps = Readonly<{ toc: TocItem[]; className?: string }>\n\nexport function ArticleTOC({ toc, className }: ArticleTOCProps) {\n if (!toc.length) return null\n return (\n <nav\n aria-label=\"Table of contents\"\n className={`mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className ?? ''}`}\n >\n <p className=\"mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground\">\n On this page\n </p>\n <ul className=\"space-y-1 text-sm\">\n {toc.map((item) => (\n <li key={item.id} style={{ paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }}>\n <a\n href={`#${item.id}`}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n >\n {item.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n )\n}\n","import type { Article } from './articleTypes'\n\ntype ArticleAnswerProps = Readonly<{\n article: Pick<Article, 'answer'>\n /** Heading shown above the answer. Default: `'The short answer'`. */\n label?: string\n className?: string\n}>\n\n/**\n * Renders `article.answer` as a callout above the article body.\n *\n * The same text is emitted as the Article schema's `abstract` and placed at\n * the top of the article's markdown twin, so the passage an answer engine is\n * most likely to lift is also the one a reader sees first. Returns `null`\n * when the article has no `answer`, so it is safe to render unconditionally.\n */\nexport function ArticleAnswer({\n article,\n label = 'The short answer',\n className,\n}: ArticleAnswerProps) {\n if (!article.answer?.trim()) return null\n return (\n <aside\n className={`mb-8 rounded-lg border-l-4 border-primary bg-muted/40 px-6 py-4 ${className ?? ''}`}\n style={{ borderLeftWidth: '4px' }}\n >\n <p className=\"mb-2 text-sm font-semibold uppercase tracking-wide text-muted-foreground\">\n {label}\n </p>\n <p className=\"text-base leading-relaxed\">{article.answer}</p>\n </aside>\n )\n}\n","// Package validator (Phase 27F). A pure function operating on an already\n// loaded `Article[]`/`ArticlesConfig` - no `fs` access here, so it's\n// directly unit-testable with fixture data. `validateAllArticles` below is\n// the thin, fs-dependent convenience wrapper (`server`-only, like the rest\n// of this file) for a consuming app's own validation script.\nimport {\n getAllArticles,\n getArticleAuthors,\n getAuthorBySlug,\n categoryToSlug,\n} from './server-articles'\nimport type { Article } from './articleTypes'\nimport { formatPageTitle, type ArticlesConfig } from './articlesConfig'\n\nexport type ValidationSeverity = 'error' | 'warning'\n\nexport interface ValidationIssue {\n severity: ValidationSeverity\n /** Stable machine-readable code, e.g. `'duplicate-canonical-url'`. */\n code: string\n message: string\n articleSlug?: string\n pathKey?: string\n}\n\nexport interface ValidationResult {\n ok: boolean\n errors: ValidationIssue[]\n warnings: ValidationIssue[]\n}\n\nconst UNSAFE_URL_SCHEME = /^\\s*(javascript|data|vbscript):/i\n\nconst THIN_CONTENT_WORDS = 300\nconst STALE_CONTENT_MONTHS = 18\n\nconst SEARCH_TITLE_MAX = 60\nconst SEARCH_DESCRIPTION_MAX = 160\nconst SOCIAL_TITLE_MAX = 95\nconst SOCIAL_DESCRIPTION_MAX = 200\n\nfunction isUnsafeUrl(href: string): boolean {\n return UNSAFE_URL_SCHEME.test(href)\n}\n\nfunction checkDuplicateCanonicalUrls(articles: Article[]): ValidationIssue[] {\n const seen = new Map<string, string>()\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n if (!article.canonicalUrl) continue\n const owner = seen.get(article.canonicalUrl)\n if (owner) {\n issues.push({\n severity: 'error',\n code: 'duplicate-canonical-url',\n message: `canonicalUrl \"${article.canonicalUrl}\" is also used by \"${owner}\".`,\n articleSlug: article.slug,\n })\n } else {\n seen.set(article.canonicalUrl, article.slug)\n }\n }\n return issues\n}\n\nfunction checkAuthorReferences(articles: Article[], config: ArticlesConfig): ValidationIssue[] {\n if (!config.authors || Object.keys(config.authors).length === 0) return []\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n for (const resolved of getArticleAuthors(article, config)) {\n if (!getAuthorBySlug(resolved.slug, config)) {\n issues.push({\n severity: 'error',\n code: 'unknown-author-reference',\n message: `Author \"${resolved.name}\" does not match any entry in config.authors.`,\n articleSlug: article.slug,\n })\n }\n }\n }\n return issues\n}\n\nfunction checkSeriesCollisions(articles: Article[]): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n const seenSlugOrder = new Map<string, string>()\n for (const article of articles) {\n if (!article.seriesSlug || article.seriesOrder === undefined) continue\n const key = `${article.seriesSlug}::${article.seriesOrder}`\n const owner = seenSlugOrder.get(key)\n if (owner) {\n issues.push({\n severity: 'error',\n code: 'duplicate-series-order',\n message: `seriesOrder ${article.seriesOrder} in series \"${article.seriesSlug}\" collides with \"${owner}\".`,\n articleSlug: article.slug,\n })\n } else {\n seenSlugOrder.set(key, article.slug)\n }\n }\n return issues\n}\n\nfunction checkPaths(articles: Article[], config: ArticlesConfig): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n const bySlug = new Map(articles.map((article) => [article.slug, article]))\n for (const [pathKey, path] of Object.entries(config.paths ?? {})) {\n if (path.articles.length === 0) {\n issues.push({\n severity: 'error',\n code: 'empty-path',\n message: `Path \"${pathKey}\" has no articles.`,\n pathKey,\n })\n }\n for (const slug of path.articles) {\n const referenced = bySlug.get(slug)\n if (!referenced) {\n issues.push({\n severity: 'error',\n code: 'path-missing-article',\n message: `Path \"${pathKey}\" references missing article \"${slug}\".`,\n pathKey,\n articleSlug: slug,\n })\n } else if (referenced.draft) {\n issues.push({\n severity: 'error',\n code: 'path-references-draft',\n message: `Path \"${pathKey}\" references unpublished (draft) article \"${slug}\".`,\n pathKey,\n articleSlug: slug,\n })\n }\n }\n if (isUnsafeUrl(path.nextAction.href)) {\n issues.push({\n severity: 'error',\n code: 'unsafe-url',\n message: `Path \"${pathKey}\" nextAction.href uses an unsafe URL scheme.`,\n pathKey,\n })\n }\n }\n return issues\n}\n\nfunction checkAuthorCtaUrls(config: ArticlesConfig): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n for (const author of Object.values(config.authors ?? {})) {\n if (author.primaryCta && isUnsafeUrl(author.primaryCta.href)) {\n issues.push({\n severity: 'error',\n code: 'unsafe-url',\n message: `Author \"${author.slug}\" primaryCta.href uses an unsafe URL scheme.`,\n })\n }\n }\n return issues\n}\n\nfunction checkRequiredFrontmatter(articles: Article[]): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n if (!article.excerpt) {\n issues.push({\n severity: 'warning',\n code: 'missing-excerpt',\n message: 'Article has no excerpt.',\n articleSlug: article.slug,\n })\n }\n if (!article.date) {\n issues.push({\n severity: 'warning',\n code: 'missing-date',\n message: 'Article has no date.',\n articleSlug: article.slug,\n })\n }\n }\n return issues\n}\n\n/**\n * Length checks against what a search result actually renders, not just the\n * optional overrides.\n *\n * Before 1.3.0 this only measured `searchTitle`/`searchDescription`, so a site\n * that never set them (the common case) got no warnings at all while every\n * one of its titles rendered over-length. The effective title is\n * `titleTemplate` applied to `searchTitle ?? title`, and the effective\n * description is `searchDescription ?? excerpt` - those are the strings a\n * person sees, so those are what get measured.\n */\nfunction checkDiscoveryFieldLengths(\n articles: Article[],\n config: ArticlesConfig\n): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n const effectiveTitle = formatPageTitle(article.searchTitle ?? article.title, config)\n const effectiveDescription = article.searchDescription ?? article.excerpt\n const checks: [string | undefined, string, number][] = [\n [effectiveTitle, 'effective-title-too-long', SEARCH_TITLE_MAX],\n [effectiveDescription, 'effective-description-too-long', SEARCH_DESCRIPTION_MAX],\n [article.socialTitle, 'social-title-too-long', SOCIAL_TITLE_MAX],\n [article.socialDescription, 'social-description-too-long', SOCIAL_DESCRIPTION_MAX],\n ]\n for (const [value, code, max] of checks) {\n if (value && value.length > max) {\n issues.push({\n severity: 'warning',\n code,\n message: `${code.replaceAll('-', ' ')} (${value.length} > ${max} recommended chars).`,\n articleSlug: article.slug,\n })\n }\n }\n }\n return issues\n}\n\n// AEO checks. All warnings, never errors: each one flags content that will\n// still build and render correctly but is unlikely to be quotable, resolvable,\n// or trusted by an answer engine.\nfunction checkAnswerability(articles: Article[]): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n const hasQuestionHeading = (article.toc ?? []).some(\n (item) => item.depth === 2 && item.text.trim().endsWith('?')\n )\n if (!article.answer && !article.faq?.length && !hasQuestionHeading) {\n issues.push({\n severity: 'warning',\n code: 'no-answer',\n message:\n 'Article has no `answer`, no `faq`, and no question-shaped heading - nothing for an answer engine to lift.',\n articleSlug: article.slug,\n })\n }\n if (article.wordCount !== undefined && article.wordCount < THIN_CONTENT_WORDS) {\n issues.push({\n severity: 'warning',\n code: 'thin-content',\n message: `Article is ${article.wordCount} words (under ${THIN_CONTENT_WORDS}).`,\n articleSlug: article.slug,\n })\n }\n if (!article.about?.length) {\n issues.push({\n severity: 'warning',\n code: 'missing-about',\n message: 'Article has no `about` entity references.',\n articleSlug: article.slug,\n })\n }\n }\n return issues\n}\n\n// `now` is injected rather than read from the clock so the check is\n// deterministic in tests and reproducible in CI.\n// `about` strings that hit no `config.entities` key still render as plain\n// names - the warning exists because that is how a corpus ends up with\n// \"Pathfinder\", \"pathfinder\", and \"PF2e\" as three separate entities.\nfunction checkEntityReferences(articles: Article[], config: ArticlesConfig): ValidationIssue[] {\n const registry = config.entities\n if (!registry) return []\n const known = new Set(Object.values(registry).map((entity) => entity.name))\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n for (const entity of article.about ?? []) {\n if (!known.has(entity.name) && !entity.sameAs) {\n issues.push({\n severity: 'warning',\n code: 'unknown-entity',\n message: `about entry \"${entity.name}\" is not in config.entities and has no sameAs.`,\n articleSlug: article.slug,\n })\n }\n }\n }\n return issues\n}\n\nfunction checkStaleContent(articles: Article[], now: Date): ValidationIssue[] {\n const cutoff = new Date(now)\n cutoff.setMonth(cutoff.getMonth() - STALE_CONTENT_MONTHS)\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n const stamp = article.lastmod ?? article.date\n if (!stamp) continue\n const parsed = new Date(stamp)\n if (Number.isNaN(parsed.getTime())) continue\n if (parsed < cutoff) {\n issues.push({\n severity: 'warning',\n code: 'stale-content',\n message: `Last updated ${stamp}, over ${STALE_CONTENT_MONTHS} months ago.`,\n articleSlug: article.slug,\n })\n }\n }\n return issues\n}\n\n// An article no other article links to is reachable only from listing pages,\n// which is the weakest possible internal signal. Matches on the article's own\n// `/articles/<slug>` path appearing in any other article's raw body, so it\n// only runs for articles loaded with their `content` (i.e. via\n// `getArticleMetadata`, not `getAllArticles`' summaries).\nfunction checkOrphanArticles(articles: Article[]): ValidationIssue[] {\n const bodies = articles.filter((article) => typeof article.content === 'string')\n if (bodies.length === 0) return []\n\n const issues: ValidationIssue[] = []\n for (const article of articles) {\n const needle = `/articles/${article.slug}`\n const linked = bodies.some(\n (other) => other.slug !== article.slug && other.content!.includes(needle)\n )\n if (!linked) {\n issues.push({\n severity: 'warning',\n code: 'orphan-article',\n message: 'No other article links to this one.',\n articleSlug: article.slug,\n })\n }\n }\n return issues\n}\n\nfunction checkCategorySlugs(articles: Article[]): ValidationIssue[] {\n // Two differently-cased/spaced category labels that collapse to the same\n // slug silently merge on `/articles/category/[slug]` - surfaced as a\n // warning (not an error) since it may be intentional (e.g. \"Game\n // Masters\" and \"game-masters\" tags both meaning the same category).\n const issues: ValidationIssue[] = []\n const slugToNames = new Map<string, Set<string>>()\n for (const article of articles) {\n for (const category of article.categories) {\n const slug = categoryToSlug(category)\n const names = slugToNames.get(slug) ?? new Set<string>()\n names.add(category)\n slugToNames.set(slug, names)\n }\n }\n for (const [slug, names] of slugToNames) {\n if (names.size > 1) {\n issues.push({\n severity: 'warning',\n code: 'category-slug-collision',\n message: `Categories [${[...names].join(', ')}] all collapse to slug \"${slug}\".`,\n })\n }\n }\n return issues\n}\n\n/**\n * Validates a loaded article set + config. Warnings cover optional\n * discovery-field issues (missing excerpt/date, over-length rendered title\n * and meta description, over-length social fields, category slug collisions) and answer-engine readiness\n * (`no-answer`, `thin-content`, `missing-about`, `unknown-entity`,\n * `stale-content`, `orphan-article`); errors cover broken reader journeys (duplicate canonical\n * URLs, unknown author references, series order collisions, missing/draft\n * path references, unsafe URL schemes).\n *\n * `options.now` overrides the clock used by the `stale-content` check.\n */\nexport function validateArticles(\n articles: Article[],\n config: ArticlesConfig,\n options?: Readonly<{ now?: Date }>\n): ValidationResult {\n const errors = [\n ...checkDuplicateCanonicalUrls(articles),\n ...checkAuthorReferences(articles, config),\n ...checkSeriesCollisions(articles),\n ...checkPaths(articles, config),\n ...checkAuthorCtaUrls(config),\n ]\n const warnings = [\n ...checkRequiredFrontmatter(articles),\n ...checkDiscoveryFieldLengths(articles, config),\n ...checkAnswerability(articles),\n ...checkEntityReferences(articles, config),\n ...checkStaleContent(articles, options?.now ?? new Date()),\n ...checkOrphanArticles(articles),\n ...checkCategorySlugs(articles),\n ]\n return { ok: errors.length === 0, errors, warnings }\n}\n\n/** Convenience wrapper: loads every article via `getAllArticles(config)` (fs-dependent) then validates. Suitable for a consuming app's own `scripts/validate-articles.ts` invoked in CI before publish. */\nexport async function validateAllArticles(\n config: ArticlesConfig,\n options?: Readonly<{ now?: Date }>\n): Promise<ValidationResult> {\n const articles = await getAllArticles(config)\n return validateArticles(articles, config, options)\n}\n","// Vendor-neutral article event contract (Phase 27F). The package emits\n// these typed events at the right places in existing components/hooks and\n// hands them to `config.onEvent` - it never talks to PostHog/Plunk/any\n// analytics or email vendor directly (see package.json dependencies, which\n// stay clean of them). No PII in any payload: only slugs/IDs/enums, never\n// emails, names-as-identifiers, or free text.\n\nexport type ArticleEventName =\n | 'article_viewed'\n | 'meaningful_read'\n | 'author_clicked'\n | 'cta_viewed'\n | 'cta_clicked'\n | 'shared'\n | 'related_article_clicked'\n | 'path_step_advanced'\n\ninterface ArticleEventBase<Name extends ArticleEventName> {\n name: Name\n /** `Date.now()` at emit time. */\n timestamp: number\n}\n\nexport interface ArticleViewedEvent extends ArticleEventBase<'article_viewed'> {\n articleSlug: string\n category?: string\n seriesSlug?: string\n}\n\n/** Fired once per view after the reader has spent roughly half the article's estimated read time on the page (see `ArticleViewTracker`). */\nexport interface MeaningfulReadEvent extends ArticleEventBase<'meaningful_read'> {\n articleSlug: string\n}\n\nexport interface AuthorClickedEvent extends ArticleEventBase<'author_clicked'> {\n articleSlug: string\n authorSlug: string\n}\n\n/** `ctaId` is `primaryAction.actionId`, an `AuthorProfile.primaryCta` slug, or a `PathDefinition` key - always an app-chosen ID, never label text. */\nexport interface CtaViewedEvent extends ArticleEventBase<'cta_viewed'> {\n ctaId: string\n articleSlug?: string\n}\n\nexport interface CtaClickedEvent extends ArticleEventBase<'cta_clicked'> {\n ctaId: string\n articleSlug?: string\n}\n\nexport interface SharedEvent extends ArticleEventBase<'shared'> {\n articleSlug: string\n /** Share channel key, e.g. `'linkedin'`, `'copy-link'` - never the shared URL/message text. */\n channel: string\n}\n\nexport interface RelatedArticleClickedEvent extends ArticleEventBase<'related_article_clicked'> {\n fromSlug: string\n toSlug: string\n source: 'path' | 'series' | 'category'\n}\n\nexport interface PathStepAdvancedEvent extends ArticleEventBase<'path_step_advanced'> {\n pathKey: string\n fromSlug: string\n toSlug: string\n direction: 'previous' | 'next'\n}\n\nexport type ArticleEvent =\n | ArticleViewedEvent\n | MeaningfulReadEvent\n | AuthorClickedEvent\n | CtaViewedEvent\n | CtaClickedEvent\n | SharedEvent\n | RelatedArticleClickedEvent\n | PathStepAdvancedEvent\n\n/** Register this on `ArticlesConfig.onEvent` to receive every emitted event and translate it to your own analytics stack. */\nexport type ArticleEventHandler = (event: ArticleEvent) => void\n\n// `Omit<ArticleEvent, 'timestamp'>` alone would collapse the discriminated\n// union to its common keys (TypeScript computes `keyof` on a union as the\n// intersection of each member's keys), losing every event-specific field.\n// A distributive conditional type over the naked `T` preserves each\n// member's own shape instead.\ntype DistributiveOmitTimestamp<T> = T extends ArticleEvent ? Omit<T, 'timestamp'> : never\n\n/**\n * Safely invokes `handler` with `event`, stamping `timestamp`. Swallows any\n * error thrown by the consuming app's handler - a broken analytics\n * integration must never break article rendering.\n */\nexport function emitArticleEvent(\n handler: ArticleEventHandler | undefined,\n event: DistributiveOmitTimestamp<ArticleEvent>\n): void {\n if (!handler) return\n try {\n handler({ ...event, timestamp: Date.now() } as ArticleEvent)\n } catch {\n // consuming app's handler errors must never break rendering\n }\n}\n"],"mappings":"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","names":["sanitizeImagePath","path","remarkParse","remarkGfm","remarkGithubBlockquoteAlert","remarkRehype","rehypeSlug","rehypePrism","rehypeSanitize","rehypeStringify","path","readingTime","fs","sanitizeImagePath","_a","matter","_a","import_react","import_rehype_prism_plus","import_rehype_slug","import_remark_gfm","import_remark_github_blockquote_alert","React","Link","remarkGfm","remarkGithubBlockquoteAlert","rehypeSlug","rehypePrism","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","path"]}