@fullstackdatasolutions/articles 1.2.1 → 1.2.2
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.
- package/CHANGELOG.md +6 -0
- package/dist/nextjs.cjs +1 -1
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.js +1 -1
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/server-articles.test.ts +4 -3
- package/src/server-articles.ts +1 -3
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/server-articles.ts","../src/authorUtils.ts","../src/markdown.ts","../src/errorReporting.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\n ? getAuthorAvatar(primaryAuthorProfile, config)\n : 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'\n\ntype LinkTargetOptions = Readonly<{\n strategy?: LinkTargetStrategy\n siteUrl?: string\n}>\n\nconst DEFAULT_LINK_TARGET_STRATEGY: LinkTargetStrategy = 'external-new-tab'\n\nfunction 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\nfunction 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\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","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('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''')\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 { ComponentType, ImgHTMLAttributes } from 'react'\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 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\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 const internalComponents = basePath ? { img: makeImgComponent(basePath) } : undefined\n const components =\n internalComponents || config?.mdxComponents\n ? { ...internalComponents, ...config?.mdxComponents }\n : undefined\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","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/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'\n\ntype LinkTargetOptions = Readonly<{\n strategy?: LinkTargetStrategy\n siteUrl?: string\n}>\n\nconst DEFAULT_LINK_TARGET_STRATEGY: LinkTargetStrategy = 'external-new-tab'\n\nfunction 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\nfunction 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\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","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('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''')\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 { ComponentType, ImgHTMLAttributes } from 'react'\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 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\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 const internalComponents = basePath ? { img: makeImgComponent(basePath) } : undefined\n const components =\n internalComponents || config?.mdxComponents\n ? { ...internalComponents, ...config?.mdxComponents }\n : undefined\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","remarkGfm","remarkGithubBlockquoteAlert","rehypeSlug","rehypePrism","import_jsx_runtime","import_jsx_runtime","path"]}
|