@duffcloudservices/cms 0.13.3 → 0.13.4

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/seo/schemaGraph.ts","../src/seo/headTags.ts","../src/seo/spliceHeadHtml.ts","../src/seo/sweepBakedHeadTags.ts","../src/seo/sitemap.ts","../src/seo/pagesManifest.ts","../src/seo/installSeoHead.ts","../src/seo/headHonesty.ts","../src/seo/bodyPrerender.ts","../src/seo/emittedUrls.ts","../src/seo/charsetBudget.ts","../src/seo/vitepressTransform.ts"],"names":["path","fs"],"mappings":"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","file":"chunk-W5Q6SOAP.js","sourcesContent":["/**\n * Framework-free schema.org JSON-LD builders shared by BOTH SEO emit paths.\n *\n * This is the single home for the structured-data \"completeness\" logic the DCS\n * SEO factory needs: a cross-linked `@graph` knowledge spine (Organization /\n * WebSite / LocalBusiness), `BreadcrumbList`, `BlogPosting`, `FAQPage`, and the\n * HONEST `Review` / `aggregateRating` injection. Both call sites consume it:\n *\n * - the Vue-SPA per-route emitter (`buildHeadTags` in `./headTags.ts`), and\n * - the VitePress `transformPageData` factory (`./vitepressTransform.ts`).\n *\n * Every export here is a PURE function of plain inputs (no Vue, no Vite, no\n * filesystem) returning plain `schema.org` objects, so the two paths stay\n * byte-identical and the builder logic is never duplicated.\n *\n * ── HONESTY (non-negotiable) ────────────────────────────────────────────────\n * `Review`, `aggregateRating`, and `FAQPage` are emitted ONLY from REAL data:\n * • Review/aggregateRating come from `.dcs/content.yaml` review items that\n * carry a numeric `rating`, a non-empty `text`, and an `authorName`.\n * • FAQPage comes from a structured Q&A source (frontmatter `faq: [{q,a}]` or\n * `.dcs/faq.yaml`) where each entry has a non-empty question AND answer.\n * When the real source is missing or empty the builder short-circuits to an\n * empty result — it NEVER synthesises a placeholder rating, review, or Q&A. A\n * fabricated rating is a legal + trust risk; this module makes fabrication\n * impossible by construction (no defaults, no invented counts).\n */\n\nimport type { GlobalSeoConfig, SeoSchemaConfig } from '../types/seo'\n\nconst SCHEMA_CONTEXT = 'https://schema.org'\n\n/** A plain schema.org object (already shaped for JSON-LD serialisation). */\nexport type SchemaObject = Record<string, unknown>\n\n/**\n * The schema.org `@type`s that, when present in `global.schemas`, are treated as\n * the site's LocalBusiness node and ABSORBED into the `@graph` spine (promoted\n * with an `@id` + `parentOrganization` ref) rather than emitted as a second,\n * unlinked copy. This is the union of `LocalBusiness` and its common subtypes\n * used across DCS sites (Iron Oak `HomeAndConstructionBusiness`, kept\n * `MedicalBusiness`, ktbraunlaw `LegalService`).\n */\nconst LOCAL_BUSINESS_TYPES = new Set<string>([\n 'LocalBusiness',\n 'HomeAndConstructionBusiness',\n 'GeneralContractor',\n 'HVACBusiness',\n 'Plumber',\n 'Electrician',\n 'RoofingContractor',\n 'HousePainter',\n 'MovingCompany',\n 'MedicalBusiness',\n 'Dentist',\n 'Physician',\n 'MedicalClinic',\n 'LegalService',\n 'Attorney',\n 'Notary',\n 'AccountingService',\n 'FinancialService',\n 'InsuranceAgency',\n 'RealEstateAgent',\n 'Store',\n 'ProfessionalService',\n 'AutomotiveBusiness',\n 'AutoRepair',\n 'BeautySalon',\n 'HairSalon',\n 'HealthAndBeautyBusiness',\n 'FoodEstablishment',\n 'Restaurant',\n])\n\n/** Return true when a schema `@type` is a LocalBusiness (sub)type. */\nexport function isLocalBusinessType(type: string | undefined): boolean {\n return !!type && LOCAL_BUSINESS_TYPES.has(type)\n}\n\n/**\n * A real review item, mirroring the shape stored in `.dcs/content.yaml` under a\n * `reviews.<key>.items` array (see `useReviewContent`). Only the fields the\n * honest Review/aggregateRating builder needs are typed here; extra fields are\n * ignored.\n */\nexport interface ReviewSource {\n rating?: unknown\n text?: unknown\n authorName?: unknown\n date?: unknown\n locationName?: unknown\n}\n\n/** A structured FAQ entry: `{ q, a }` (frontmatter) — honesty-gated. */\nexport interface FaqSource {\n q?: unknown\n a?: unknown\n /** Alternate keys some sources use (`question`/`answer`). */\n question?: unknown\n answer?: unknown\n}\n\n/** A single breadcrumb hop along the route to the current page. */\nexport interface BreadcrumbCrumb {\n /** Human-readable name (e.g. `Home`, `Services`, the post title). */\n name: string\n /** Absolute URL for this hop. */\n item: string\n}\n\n/** Blog-post metadata used to build a `BlogPosting`, from the SPA or VitePress. */\nexport interface BlogMeta {\n /** The headline / post title. */\n headline?: string\n /** ISO-ish publish date as authored (granularity preserved, e.g. `2026-01`). */\n datePublished?: string\n /** ISO-ish modified date, if distinct. */\n dateModified?: string\n /** Absolute canonical URL of the post (becomes `mainEntityOfPage`). */\n url?: string\n /** Header/social image URL for the post. */\n image?: string\n /** Short description / excerpt. */\n description?: string\n}\n\n// =============================================================================\n// @id derivation\n// =============================================================================\n\nfunction trimSlash(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n\n/** Stable `@id` anchors derived from the site URL (no new YAML required). */\nexport function graphIds(siteUrl: string): {\n organization: string\n website: string\n localBusiness: string\n} {\n const base = trimSlash(siteUrl)\n return {\n organization: `${base}/#organization`,\n website: `${base}/#website`,\n localBusiness: `${base}/#localbusiness`,\n }\n}\n\n// =============================================================================\n// social → sameAs\n// =============================================================================\n\n/**\n * Derive an `sameAs` array of absolute profile URLs from the global `social`\n * block. Returns `[]` when nothing is configured (so the key is omitted, never\n * emitted empty).\n */\nexport function deriveSameAs(global: GlobalSeoConfig): string[] {\n const s = global.social\n if (!s) return []\n const out: string[] = []\n const push = (u: string | undefined) => {\n if (u && u.trim()) out.push(u.trim())\n }\n // Each handle may already be a full URL or a bare slug/handle.\n if (s.facebook) push(asUrl(s.facebook, 'https://www.facebook.com/'))\n if (s.instagram) push(asUrl(stripAt(s.instagram), 'https://www.instagram.com/'))\n if (s.linkedin) push(asUrl(s.linkedin, 'https://www.linkedin.com/in/'))\n if (s.youtube) push(asUrl(s.youtube, 'https://www.youtube.com/'))\n if (s.github) push(asUrl(stripAt(s.github), 'https://github.com/'))\n if (s.twitter) push(asUrl(stripAt(s.twitter), 'https://twitter.com/'))\n return out\n}\n\nfunction stripAt(handle: string): string {\n return handle.startsWith('@') ? handle.slice(1) : handle\n}\n\nfunction asUrl(value: string, base: string): string {\n if (/^https?:\\/\\//i.test(value)) return value\n return `${base}${value.replace(/^\\/+/, '')}`\n}\n\n// =============================================================================\n// LocalBusiness extraction\n// =============================================================================\n\n/**\n * Find the LocalBusiness-subtype entry in `global.schemas`, if any. This is the\n * NAP-complete node a site already hand-authors; the graph builder absorbs it\n * (rather than letting `generateJsonLd` emit a second, unlinked copy).\n *\n * @returns the matching `SeoSchemaConfig` and its index, or `null`.\n */\nexport function findLocalBusinessSchema(\n global: GlobalSeoConfig\n): { schema: SeoSchemaConfig; index: number } | null {\n const schemas = global.schemas ?? []\n for (let i = 0; i < schemas.length; i++) {\n if (isLocalBusinessType(schemas[i]?.type)) {\n return { schema: schemas[i], index: i }\n }\n }\n return null\n}\n\n/**\n * The `PostalAddress` fields that together make a NAP a real, mappable address.\n * Order is the order a human writes one, which is the order the warning prints.\n */\nexport const REQUIRED_POSTAL_ADDRESS_FIELDS = [\n 'streetAddress',\n 'addressLocality',\n 'addressRegion',\n 'postalCode',\n 'addressCountry',\n] as const\n\n/** A LocalBusiness node that CLAIMS an address but does not finish it. */\nexport interface PostalAddressGap {\n /** The LocalBusiness `@type` as authored (e.g. `HomeAndConstructionBusiness`). */\n businessType: string\n /** Required fields absent or blank, in `REQUIRED_POSTAL_ADDRESS_FIELDS` order. */\n missing: string[]\n /** Required fields actually supplied — what makes the node look answered. */\n present: string[]\n}\n\n/**\n * Report a LocalBusiness `address` that is PRESENT BUT PARTIAL.\n *\n * `buildGlobalGraph` spreads the hand-authored node's properties verbatim\n * (`...props`), so a `PostalAddress` carrying only a locality and a region ships\n * to crawlers exactly as authored, with nothing anywhere saying it is half an\n * address. Google matches a LocalBusiness to a place using the NAP; a partial\n * address is the shape that silently fails that match while every audit that\n * only asks \"is there an address node?\" stays green.\n *\n * ## Why an ABSENT address is not reported\n *\n * A service-area business (a contractor working out of a home, a mobile trade)\n * is *supposed* to omit `address` — that is Google's own guidance, and several\n * DCS fleet sites are exactly that. Warning there would be an accusation\n * levelled at correct configuration, and a warning people learn to ignore is\n * worth less than no warning. The defect is the CLAIM that is not finished, so\n * the trigger is an address node that exists and is incomplete.\n *\n * A non-object `address` (a plain string) is also skipped: it is unstructured,\n * not partial, and belongs to a different finding.\n *\n * @returns one gap per incomplete address, or `[]` when complete/absent.\n */\nexport function findPostalAddressGaps(global: GlobalSeoConfig): PostalAddressGap[] {\n const found = findLocalBusinessSchema(global)\n if (!found) return []\n\n const address = (found.schema.properties ?? {})['address']\n if (!address || typeof address !== 'object' || Array.isArray(address)) return []\n\n const fields = address as Record<string, unknown>\n const missing: string[] = []\n const present: string[] = []\n for (const key of REQUIRED_POSTAL_ADDRESS_FIELDS) {\n const value = fields[key]\n const filled = typeof value === 'string' ? value.trim().length > 0 : value != null\n ;(filled ? present : missing).push(key)\n }\n if (missing.length === 0) return []\n\n return [{ businessType: found.schema.type ?? 'LocalBusiness', missing, present }]\n}\n\n/**\n * Render the partial-address warning. Names the type, what is there, and what is\n * not — an operator must be able to act without re-deriving the finding, and the\n * \"or drop the address entirely\" branch is stated because for a service-area\n * business that is the CORRECT fix, not a workaround.\n */\nexport function formatPostalAddressGapReport(gaps: PostalAddressGap[]): string {\n const lines: string[] = []\n for (const gap of gaps) {\n lines.push(\n `[dcs-seo] LocalBusiness (${gap.businessType}) emits a PARTIAL PostalAddress: ` +\n `missing ${gap.missing.join(', ')} (present: ${gap.present.join(', ') || 'nothing'}). ` +\n `A half-authored address ships to crawlers as if it were complete and will not ` +\n `match the business to a place. Fix: complete the address in .dcs/seo.yaml, or — ` +\n `if this is a service-area business with no public storefront — remove the ` +\n `address block entirely and rely on areaServed, which is the correct shape for it.`\n )\n }\n return lines.join('\\n')\n}\n\n/**\n * True when `buildGlobalGraph` provides this schema canonically, so it must NOT\n * also be emitted standalone: the LocalBusiness subtype it folds in, or a global\n * `Organization` / `WebSite` (the graph emits cross-linked versions of both).\n *\n * Shared by both emit paths so the de-dup rule is identical.\n *\n * INVARIANT (C-609): every schema this returns `true` for must be ABSORBED by\n * `buildGlobalGraph` — its hand-authored properties merged into the corresponding\n * `@graph` node. Absorbing here without merging there is silent data loss: the\n * node is stripped from the per-schema emission and its properties never reach\n * the crawler (the C-418 defect this pairing now prevents).\n */\nexport function graphAbsorbs(schema: SeoSchemaConfig, global: GlobalSeoConfig): boolean {\n if (schema.type === 'Organization' || schema.type === 'WebSite') return true\n const found = findLocalBusinessSchema(global)\n return !!found && found.schema === schema\n}\n\n/**\n * All hand-authored `global.schemas` entries of an exact `@type`, in document\n * order. Plural because `graphAbsorbs` absorbs EVERY `Organization` / `WebSite`\n * entry — so the graph must merge every one of them or the extras vanish.\n */\nexport function findGlobalSchemasOfType(\n global: GlobalSeoConfig,\n type: string\n): SeoSchemaConfig[] {\n return (global.schemas ?? []).filter((s) => s?.type === type)\n}\n\n/**\n * Keys the `@graph` OWNS and never takes from hand-authored `properties`.\n *\n * `@type` and `@id` are the node's identity: `@id` is the anchor the spine's\n * cross-links (`publisher`, `parentOrganization`) resolve against, so letting a\n * hand-authored property overwrite it would dangle those refs. `@context` belongs\n * on the `@graph` wrapper, not on a node inside it.\n */\nconst GRAPH_OWNED_KEYS = new Set(['@context', '@id', '@type'])\n\n/**\n * ABSORB hand-authored schema `properties` into a derived `@graph` node, in\n * place. Mirrors the LocalBusiness absorption semantics (`...props` spread over\n * the derived fields): a hand-authored property OVERRIDES the derived value for\n * the same key, and any key the graph does not derive is ADDED verbatim. Only\n * the identity keys in `GRAPH_OWNED_KEYS` are refused.\n *\n * Key ORDER is preserved for derived keys (an override rewrites in place) and\n * new hand-authored keys append — so the baked JSON-LD stays diff-stable.\n */\nfunction absorbSchemaProps(\n node: SchemaObject,\n props: Record<string, unknown> | undefined\n): void {\n if (!props) return\n for (const [key, value] of Object.entries(props)) {\n if (GRAPH_OWNED_KEYS.has(key)) continue\n node[key] = value\n }\n}\n\n// =============================================================================\n// Honest Reviews / aggregateRating\n// =============================================================================\n\n/** One normalised, REAL review (passed the honesty gate). */\nexport interface NormalisedReview {\n rating: number\n text: string\n authorName: string\n date?: string\n locationName?: string\n}\n\n/**\n * Keep ONLY real review items: a numeric `rating`, a non-empty `text`, and a\n * non-empty `authorName`. Anything missing any of the three is dropped (never\n * back-filled). Returns `[]` when nothing qualifies.\n */\nexport function filterRealReviews(items: ReviewSource[] | undefined): NormalisedReview[] {\n if (!Array.isArray(items)) return []\n const out: NormalisedReview[] = []\n for (const item of items) {\n if (!item || typeof item !== 'object') continue\n const ratingNum = Number((item as ReviewSource).rating)\n const text = typeof item.text === 'string' ? item.text.trim() : ''\n const authorName = typeof item.authorName === 'string' ? item.authorName.trim() : ''\n if (!Number.isFinite(ratingNum) || ratingNum <= 0) continue\n if (!text) continue\n if (!authorName) continue\n out.push({\n rating: ratingNum,\n text,\n authorName,\n ...(typeof item.date === 'string' && item.date.trim() ? { date: item.date.trim() } : {}),\n ...(typeof item.locationName === 'string' && item.locationName.trim()\n ? { locationName: item.locationName.trim() }\n : {}),\n })\n }\n return out\n}\n\n/** A `Review` schema.org node array + an `aggregateRating`, both honesty-gated. */\nexport interface ReviewSchemaParts {\n /** `Review` nodes (one per real item). Empty when no real reviews. */\n review: SchemaObject[]\n /** `AggregateRating` node, or `undefined` when no real reviews. */\n aggregateRating?: SchemaObject\n}\n\n/** The schema.org `bestRating`/`worstRating` bounds for DCS review ratings. */\nconst BEST_RATING = 5\nconst WORST_RATING = 1\n\n/** Clamp a rating into `[WORST_RATING, BEST_RATING]` (F3). */\nfunction clampRating(value: number): number {\n if (value < WORST_RATING) return WORST_RATING\n if (value > BEST_RATING) return BEST_RATING\n return value\n}\n\n/**\n * Build `Review[]` + `aggregateRating` from REAL review items only.\n *\n * Each `ratingValue` is CLAMPED to `[worstRating, bestRating]` (F3): a source\n * rating of 7 (or 0/-1) would otherwise emit an out-of-range, schema-invalid\n * value (and skew the aggregate). The aggregate mean is computed from the SAME\n * clamped values so the headline rating matches the displayed reviews.\n *\n * `aggregateRating.ratingValue` is the mean (rounded to one decimal),\n * `reviewCount`/`ratingCount` equal the real item count. When there are zero\n * real items, BOTH are omitted — never an invented rating or count.\n */\nexport function buildReviewSchemaParts(items: ReviewSource[] | undefined): ReviewSchemaParts {\n const real = filterRealReviews(items)\n if (real.length === 0) return { review: [] }\n\n const clamped = real.map((r) => ({ ...r, rating: clampRating(r.rating) }))\n\n const review: SchemaObject[] = clamped.map((r) => ({\n '@type': 'Review',\n reviewRating: {\n '@type': 'Rating',\n ratingValue: r.rating,\n bestRating: BEST_RATING,\n worstRating: WORST_RATING,\n },\n author: { '@type': 'Person', name: r.authorName },\n reviewBody: r.text,\n ...(r.date ? { datePublished: r.date } : {}),\n }))\n\n const sum = clamped.reduce((acc, r) => acc + r.rating, 0)\n const mean = Math.round((sum / clamped.length) * 10) / 10\n\n const aggregateRating: SchemaObject = {\n '@type': 'AggregateRating',\n ratingValue: mean,\n reviewCount: clamped.length,\n ratingCount: clamped.length,\n bestRating: BEST_RATING,\n worstRating: WORST_RATING,\n }\n\n return { review, aggregateRating }\n}\n\n// =============================================================================\n// hasCredential (trade / occupational license — honesty-gated)\n// =============================================================================\n\n/**\n * The schema.org `credentialCategory` for a trade / occupational license.\n * `hasCredential` → `EducationalOccupationalCredential` with this category is the\n * documented pattern for a professional license on an Organization /\n * LocalBusiness (see schema.org/EducationalOccupationalCredential).\n */\nconst LICENSE_CREDENTIAL_CATEGORY = 'license'\n\n/**\n * Build a single `EducationalOccupationalCredential` node for a trade license\n * from the free-text `business.license` content key, or `undefined` when the key\n * is empty / whitespace / absent.\n *\n * Shape (schema.org-correct; mirrors the portal's `Credential` editor type in\n * `portal/src/lib/jsonld-schema-types.ts`):\n *\n * { '@type': 'EducationalOccupationalCredential',\n * credentialCategory: 'license',\n * name: <verbatim business.license value> }\n *\n * HONESTY (non-negotiable, mirrors the Review/FAQ gates in this module): an\n * empty / whitespace / absent value returns `undefined` so the caller omits\n * `hasCredential` entirely — never an empty string, never a fabricated license.\n * The value is carried verbatim in `name` (the field is free text — a license\n * number, a \"Licensed & Insured\" phrase, or a credential name — so the\n * always-valid `name` slot is the safest home; nothing is parsed or invented).\n */\nexport function buildHasCredential(license: string | undefined): SchemaObject | undefined {\n const value = typeof license === 'string' ? license.trim() : ''\n if (!value) return undefined\n return {\n '@type': 'EducationalOccupationalCredential',\n credentialCategory: LICENSE_CREDENTIAL_CATEGORY,\n name: value,\n }\n}\n\n// =============================================================================\n// Global @graph spine\n// =============================================================================\n\n/**\n * Build the cross-linked global knowledge graph as ONE JSON-LD object carrying\n * a `@graph` array: `Organization`, `WebSite` (publisher → org), and the site's\n * `LocalBusiness` node (parentOrganization → org), auto-derived from\n * `global.siteName` / `global.siteUrl` / `global.images.logo` / `global.social`.\n * Requires NO new YAML.\n *\n * ALL THREE nodes ABSORB their hand-authored `global.schemas[*]` counterpart\n * (C-609) — the properties are merged onto the derived node rather than\n * discarded, which is what makes the baked `@graph` a true SUPERSET of the\n * runtime per-schema emission that `graphAbsorbs` suppresses:\n *\n * • LocalBusiness — its NAP/geo/hours/offers are preserved verbatim and the\n * node is promoted with an `@id`; honest `review` + `aggregateRating` and a\n * `hasCredential` license are merged in when supplied.\n * • Organization — e.g. the fleet default's hand-authored `logo`, which\n * `global.images.logo` does not set.\n * • WebSite — e.g. a `description` or a `potentialAction` SearchAction.\n *\n * Precedence is uniform: hand-authored properties OVERRIDE the derived value for\n * the same key and ADD any key the graph does not derive, except the identity\n * keys (`@type`, `@id`) and the spine's cross-links (`publisher`,\n * `parentOrganization`), which the graph always owns so the `@id` refs resolve.\n *\n * Returns `[]` when there is no `siteUrl` (no stable `@id` anchor possible), so\n * the existing per-schema emission is left untouched for un-configured sites.\n *\n * @param opts.reviews REAL review items (from content.yaml) for the business\n * node. Optional; when omitted/empty no Review/aggregateRating is added.\n * @param opts.license Free-text trade/occupational license (from the\n * `business.license` content key). Optional; when empty/absent no\n * `hasCredential` is added (honesty-gated — see `buildHasCredential`). Only\n * ever attached to the LocalBusiness node, never to Organization/WebSite.\n */\nexport function buildGlobalGraph(\n global: GlobalSeoConfig,\n opts: { reviews?: ReviewSource[]; license?: string } = {}\n): SchemaObject[] {\n const siteUrl = global.siteUrl ? trimSlash(global.siteUrl) : ''\n if (!siteUrl) return []\n\n const ids = graphIds(siteUrl)\n const name = global.siteName || ''\n const logo = global.images?.logo\n const sameAs = deriveSameAs(global)\n\n // ── Organization (absorb the hand-authored node, keep the derived identity) ─\n const organization: SchemaObject = {\n '@type': 'Organization',\n '@id': ids.organization,\n ...(name ? { name } : {}),\n url: `${siteUrl}/`,\n ...(logo ? { logo } : {}),\n ...(sameAs.length ? { sameAs } : {}),\n }\n // Hand-authored `Organization` properties override/extend the derived fields\n // (the fleet default authors a `logo` that `global.images.logo` never sets).\n for (const schema of findGlobalSchemasOfType(global, 'Organization')) {\n absorbSchemaProps(organization, schema.properties)\n }\n\n // ── WebSite (absorb the hand-authored node, keep the publisher cross-link) ──\n const website: SchemaObject = {\n '@type': 'WebSite',\n '@id': ids.website,\n url: `${siteUrl}/`,\n ...(name ? { name } : {}),\n }\n // Hand-authored `WebSite` properties (e.g. the site-onboard template's\n // `description` + `potentialAction` SearchAction) override/extend the derived\n // fields…\n for (const schema of findGlobalSchemasOfType(global, 'WebSite')) {\n absorbSchemaProps(website, schema.properties)\n }\n // …but the cross-link to the Organization is forced AFTER the merge, exactly\n // as `parentOrganization` is on the LocalBusiness node below: the spine is the\n // graph's to own, not the hand-authored node's to break.\n website.publisher = { '@id': ids.organization }\n\n const graph: SchemaObject[] = [organization, website]\n\n // ── LocalBusiness (absorb the hand-authored NAP node, add @id + parent) ────\n const found = findLocalBusinessSchema(global)\n if (found) {\n const props = found.schema.properties ?? {}\n const localBusiness: SchemaObject = {\n '@type': found.schema.type,\n '@id': ids.localBusiness,\n // Preserve the hand-authored NAP/geo/hours/offers verbatim…\n ...props,\n // …but ensure the cross-link to the Organization is present.\n parentOrganization: { '@id': ids.organization },\n }\n // Default url to the site root when the node doesn't set its own.\n if (localBusiness.url == null) localBusiness.url = `${siteUrl}/`\n\n // Honest reviews → merge Review[] + aggregateRating into the business node.\n const reviewParts = buildReviewSchemaParts(opts.reviews)\n if (reviewParts.review.length > 0) {\n localBusiness.review = reviewParts.review\n if (reviewParts.aggregateRating) {\n localBusiness.aggregateRating = reviewParts.aggregateRating\n }\n }\n\n // Trade/occupational license → hasCredential on the business node ONLY.\n // Honesty-gated: an empty/absent business.license emits nothing.\n const credential = buildHasCredential(opts.license)\n if (credential) {\n localBusiness.hasCredential = credential\n }\n\n graph.push(localBusiness)\n }\n\n return [\n {\n '@context': SCHEMA_CONTEXT,\n '@graph': graph,\n },\n ]\n}\n\n// =============================================================================\n// BreadcrumbList\n// =============================================================================\n\n/**\n * Build a `BreadcrumbList` from an ordered trail of crumbs (Home → … → current).\n *\n * Honesty/cleanliness rules:\n * • The home page (a trail of length ≤ 1, i.e. just \"Home\") emits NOTHING —\n * a single-item breadcrumb is noise.\n * • Positions are 1-based and contiguous.\n *\n * @returns a single-element array `[BreadcrumbList]`, or `[]` for the home page.\n */\nexport function buildBreadcrumbList(trail: BreadcrumbCrumb[]): SchemaObject[] {\n if (!Array.isArray(trail) || trail.length <= 1) return []\n\n const itemListElement = trail.map((crumb, i) => ({\n '@type': 'ListItem',\n position: i + 1,\n name: crumb.name,\n item: crumb.item,\n }))\n\n return [\n {\n '@context': SCHEMA_CONTEXT,\n '@type': 'BreadcrumbList',\n itemListElement,\n },\n ]\n}\n\n/**\n * Derive a Home → … → current breadcrumb trail from a route path.\n *\n * Each path segment becomes a crumb; the segment label comes from\n * `titles[segmentPath]` (an absolute-route → title map) when present, else a\n * slug-derived Title Case of the segment (never blank).\n *\n * @param route the page route, e.g. `/`, `/services`, `/blog/my-post`.\n * @param siteUrl normalised site URL (no trailing slash).\n * @param titles optional map of route → human title for intermediate hops\n * AND the leaf (e.g. `{ '/': 'Home', '/blog': 'Blog',\n * '/blog/my-post': 'My Post' }`). Missing entries fall back to\n * a slug-derived title.\n * @param homeName label for the root crumb (default `Home`).\n */\nexport function breadcrumbTrailFromRoute(\n route: string,\n siteUrl: string,\n titles: Record<string, string> = {},\n homeName = 'Home'\n): BreadcrumbCrumb[] {\n const base = trimSlash(siteUrl)\n const home: BreadcrumbCrumb = {\n name: titles['/'] || homeName,\n item: `${base}/`,\n }\n const normalised = (route || '/').split('?')[0].split('#')[0]\n if (normalised === '/' || normalised === '') return [home]\n\n const segments = normalised.replace(/^\\/+/, '').replace(/\\/+$/, '').split('/')\n const trail: BreadcrumbCrumb[] = [home]\n let acc = ''\n for (const seg of segments) {\n acc += `/${seg}`\n trail.push({\n name: titles[acc] || slugToTitle(seg),\n item: `${base}${acc}`,\n })\n }\n return trail\n}\n\n/** Title-case a slug segment (`my-post` → `My Post`); never blank. */\nexport function slugToTitle(slug: string): string {\n const cleaned = (slug || '').replace(/[-_]+/g, ' ').trim()\n if (!cleaned) return slug || ''\n return cleaned\n .split(/\\s+/)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ')\n}\n\n// =============================================================================\n// BlogPosting\n// =============================================================================\n\n/**\n * Build a single `BlogPosting`.\n *\n * `author`/`publisher` are emitted as SELF-CONTAINED Organization nodes — each\n * carrying its `@id`, plus a concrete `name` and `url`. This is the F2 fix: a\n * bare `{ '@id': … }` ref DANGLES when `emitBlogPosting` runs WITHOUT the global\n * `@graph` (the VitePress path with `emitGraph: false`), because nothing then\n * defines the Organization node that `@id` points at. An inline node is valid\n * standalone AND still carries the `@id`, so when the `@graph` IS present a\n * consumer merges the two by `@id` (no duplication, no dangle) either way.\n *\n * `datePublished` granularity is preserved exactly as authored (e.g. a\n * year-month `2026-01` is NOT padded to a fabricated day).\n *\n * @param global Optional global config — supplies the publisher/author `name`.\n * When omitted (or no `siteName`), the inline node still carries `@id` + `url`\n * so the reference never dangles.\n * @returns `[BlogPosting]`, or `[]` when there is no headline (nothing to emit).\n */\nexport function buildBlogPosting(\n meta: BlogMeta,\n siteUrl: string,\n global?: GlobalSeoConfig\n): SchemaObject[] {\n const headline = typeof meta.headline === 'string' ? meta.headline.trim() : ''\n if (!headline) return []\n\n const ids = graphIds(siteUrl)\n const base = trimSlash(siteUrl)\n const name = global?.siteName?.trim()\n\n // Self-contained publisher/author node: keeps the @id (graph-mergeable) AND\n // inlines name/url so it never dangles when no @graph spine is emitted.\n const orgNode: SchemaObject = {\n '@type': 'Organization',\n '@id': ids.organization,\n ...(name ? { name } : {}),\n ...(base ? { url: `${base}/` } : {}),\n }\n\n const node: SchemaObject = {\n '@context': SCHEMA_CONTEXT,\n '@type': 'BlogPosting',\n headline,\n author: orgNode,\n publisher: orgNode,\n }\n if (meta.description) node.description = meta.description\n if (meta.datePublished) node.datePublished = meta.datePublished\n if (meta.dateModified) node.dateModified = meta.dateModified\n if (meta.image) node.image = meta.image\n if (meta.url) node.mainEntityOfPage = meta.url\n\n return [node]\n}\n\n// =============================================================================\n// FAQPage (honesty-gated)\n// =============================================================================\n\n/** One normalised, REAL FAQ pair (passed the honesty gate). */\nexport interface NormalisedFaq {\n question: string\n answer: string\n}\n\n/**\n * Keep ONLY real FAQ entries: a non-empty question AND a non-empty answer.\n * Tolerant of both `{ q, a }` and `{ question, answer }` shapes. Returns `[]`\n * when nothing qualifies (so no FAQPage is emitted).\n */\nexport function filterRealFaq(entries: FaqSource[] | undefined): NormalisedFaq[] {\n if (!Array.isArray(entries)) return []\n const out: NormalisedFaq[] = []\n for (const entry of entries) {\n if (!entry || typeof entry !== 'object') continue\n const q = pickString(entry.q ?? entry.question)\n const a = pickString(entry.a ?? entry.answer)\n if (!q || !a) continue\n out.push({ question: q, answer: a })\n }\n return out\n}\n\nfunction pickString(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/**\n * Build a `FAQPage` from a STRUCTURED Q&A source ONLY (frontmatter `faq:` or a\n * `.dcs/faq.yaml`). A `<FaqPage />` Vue component is NOT a valid source — there\n * is no machine-readable Q&A to read, so this correctly emits nothing.\n *\n * @returns `[FAQPage]` with one `Question` per real entry, or `[]` when there is\n * no valid structured Q&A.\n */\nexport function buildFaqPage(entries: FaqSource[] | undefined): SchemaObject[] {\n const real = filterRealFaq(entries)\n if (real.length === 0) return []\n\n return [\n {\n '@context': SCHEMA_CONTEXT,\n '@type': 'FAQPage',\n mainEntity: real.map((f) => ({\n '@type': 'Question',\n name: f.question,\n acceptedAnswer: { '@type': 'Answer', text: f.answer },\n })),\n },\n ]\n}\n\n// =============================================================================\n// content.yaml review lookup (tolerant of per-site key naming)\n// =============================================================================\n\n/**\n * The relevant slice of `.dcs/content.yaml`: flat dotted keys live under\n * `global` and per-page maps. Reviews are stored under `reviews.<key>.items`\n * (e.g. iron-oak `reviews.reviews.items`, kept `reviews.testimonials.items`),\n * so the key differs per site — `findReviewItemsForPage` scans tolerantly.\n */\nexport interface ContentConfig {\n global?: Record<string, unknown>\n pages?: Record<string, Record<string, unknown>>\n}\n\n/** A `reviews.<key>.items` array is the canonical real-review source. */\nfunction isReviewItemsKey(key: string): boolean {\n return /^reviews\\..+\\.items$/.test(key)\n}\n\n/**\n * Find the REAL review items for a page from `.dcs/content.yaml`, tolerant of\n * the per-site key naming (`reviews.reviews.items` vs `reviews.testimonials.\n * items`). Prefers a page-scoped block, then falls back to global; within a\n * block it picks the FIRST `reviews.*.items` array (sites carry one canonical\n * source). Returns `[]` when none is present — no synthesis.\n *\n * The honesty filter still runs downstream (`buildReviewSchemaParts`), so a\n * non-empty return here is NOT yet a guarantee of emission; items without a\n * rating/text/authorName are dropped there.\n */\nexport function findReviewItemsForPage(\n content: ContentConfig | undefined,\n pageSlug: string\n): ReviewSource[] {\n if (!content) return []\n const fromBlock = (block: Record<string, unknown> | undefined): ReviewSource[] | null => {\n if (!block) return null\n for (const key of Object.keys(block)) {\n if (isReviewItemsKey(key) && Array.isArray(block[key])) {\n return block[key] as ReviewSource[]\n }\n }\n return null\n }\n const page = content.pages?.[pageSlug]\n return fromBlock(page) ?? fromBlock(content.global) ?? []\n}\n\n/** The flat content.yaml key holding a site's trade/occupational license. */\nconst BUSINESS_LICENSE_KEY = 'business.license'\n\n/**\n * Read the free-text `business.license` content key from `.dcs/content.yaml`\n * (page-scoped block first, then global — mirroring `findReviewItemsForPage`;\n * NAP identity normally lives under `global`). Returns a trimmed non-empty\n * string, or `undefined` when the key is absent or empty. A bare numeric YAML\n * value (e.g. `business.license: 123456`) is coerced to its string form. No\n * synthesis — an unset key yields `undefined`, so the emit paths stay dark.\n */\nexport function findBusinessLicense(\n content: ContentConfig | undefined,\n pageSlug: string\n): string | undefined {\n if (!content) return undefined\n const fromBlock = (block: Record<string, unknown> | undefined): string | undefined => {\n if (!block) return undefined\n const raw = block[BUSINESS_LICENSE_KEY]\n if (typeof raw === 'string') {\n const trimmed = raw.trim()\n return trimmed.length > 0 ? trimmed : undefined\n }\n if (typeof raw === 'number' && Number.isFinite(raw)) return String(raw)\n return undefined\n }\n return fromBlock(content.pages?.[pageSlug]) ?? fromBlock(content.global)\n}\n\n// =============================================================================\n// OG-URL absolutization\n// =============================================================================\n\n/**\n * Guarantee an absolute URL. When `value` is already absolute (`http(s)://`) it\n * is returned unchanged; when it is a site-relative path it is joined onto the\n * (trailing-slash-trimmed) `siteUrl`; when it is empty the `fallback` (typically\n * the already-absolute canonical) is returned.\n */\nexport function absolutizeUrl(\n value: string | undefined,\n siteUrl: string | undefined,\n fallback = ''\n): string {\n const v = (value ?? '').trim()\n if (!v) return fallback\n if (/^https?:\\/\\//i.test(v)) return v\n const base = trimSlash(siteUrl ?? '')\n if (!base) return v\n return `${base}/${v.replace(/^\\/+/, '')}`\n}\n","/**\n * Framework-agnostic SEO head-tag resolution.\n *\n * This module is the single source of truth for turning a\n * (`pageSlug`, `pagePath`, `SeoConfiguration`) triple into a plain,\n * serialisable description of the `<head>` tags a page should carry:\n * resolved title, meta[], link[], and JSON-LD script[].\n *\n * It is consumed by:\n * - `useSEO` (runtime, via `@unhead/vue`) — see `../composables/useSEO.ts`\n * - `dcsSeoPlugin`'s build-time static-HTML emitter — see\n * `../plugins/dcsSeoPlugin.ts`\n *\n * Keeping the resolution here (rather than inside the Vue composable) means\n * the runtime and the build-time emitter produce byte-identical tags from the\n * same `seo.yaml`, with no Vue/unhead dependency required at build time.\n *\n * Runtime behaviour is intentionally identical to the previous in-composable\n * logic **except** for one corrected bug: `og:title` now falls back to the\n * fully-resolved (template-applied) page title instead of the raw, untemplated\n * `page.title`. Previously `og:title` could diverge from the `<title>` element\n * (e.g. `<title>Iron Oak Contractors | Our Services</title>` but\n * `og:title = \"Our Services\"`).\n */\n\nimport type {\n SeoConfiguration,\n GlobalSeoConfig,\n SeoOpenGraphConfig,\n SeoTwitterConfig,\n SeoSchemaConfig,\n ResolvedPageSeo,\n} from '../types/seo'\nimport {\n buildGlobalGraph,\n buildBreadcrumbList,\n buildBlogPosting,\n buildFaqPage,\n graphAbsorbs,\n absolutizeUrl,\n type BreadcrumbCrumb,\n type ReviewSource,\n type FaqSource,\n type BlogMeta,\n} from './schemaGraph'\n\n// =============================================================================\n// Plain head-tag structures (no framework types)\n// =============================================================================\n\n/** A `<meta>` tag — either a `name=`/`content=` or `property=`/`content=` pair. */\nexport interface HeadMetaTag {\n name?: string\n property?: string\n content: string\n}\n\n/** A `<link>` tag (canonical, alternate, etc.). */\nexport interface HeadLinkTag {\n rel: string\n href: string\n hreflang?: string\n}\n\n/** A `<script type=\"application/ld+json\">` tag carrying serialised JSON-LD. */\nexport interface HeadScriptTag {\n type: string\n /** Pre-serialised JSON-LD string (already `JSON.stringify`-ed). */\n children: string\n}\n\n/**\n * The complete, framework-agnostic set of resolved `<head>` tags for a page.\n *\n * - `title` is the final, template-applied title (what goes in `<title>`).\n * - `meta` covers description, keywords, robots, verification, OG, Twitter.\n * - `link` covers canonical + hreflang alternates.\n * - `script` covers JSON-LD (global + page schemas).\n * - `jsonLd` is the same JSON-LD as parsed objects, for callers that want the\n * structured form (e.g. `useSEO().getSchema()`).\n */\nexport interface ResolvedHeadTags {\n title: string\n meta: HeadMetaTag[]\n link: HeadLinkTag[]\n script: HeadScriptTag[]\n jsonLd: object[]\n /** The fully-resolved page SEO (merged global + page) used to build tags. */\n resolved: ResolvedPageSeo\n}\n\n/** Optional overrides applied on top of the resolved config when building tags. */\nexport interface HeadTagOverrides {\n /** Override the resolved `<title>`. */\n title?: string\n /**\n * Page-specific title fallback used when `seo.yaml` has no `title` for this\n * page (e.g. the route `title` from `pages.yaml`). Unlike `global.defaultTitle`\n * this IS run through `titleTemplate`, so un-configured routes (blog posts,\n * etc.) get unique titles rather than the global default.\n */\n fallbackTitle?: string\n /** Override the resolved meta description. */\n description?: string\n /** Override the meta keywords value. */\n keywords?: string\n /**\n * Force the robots directive (e.g. `'noindex, nofollow'`). When supplied this\n * wins over both page- and global-level robots.\n */\n robots?: string\n /** Replace the JSON-LD schema objects entirely (already-built objects). */\n schemas?: object[]\n /** Extra meta tags appended after the generated ones. */\n meta?: HeadMetaTag[]\n /**\n * Emit a `<meta name=\"keywords\">` tag from the page's `keywords` field.\n *\n * Defaults to `false` so the `useSEO` runtime path stays byte-identical to\n * its historical output (which never emitted keywords). The static-HTML\n * emitter opts in (`true`) to surface page keywords in the baked `<head>`.\n */\n includeKeywords?: boolean\n\n // ── Schema-completeness inputs (additive; OFF unless supplied) ─────────────\n // The build-time static-HTML emitter opts into these to bake the cross-linked\n // @graph spine + BreadcrumbList + (honest) BlogPosting/FAQPage/Review. The\n // runtime `useSEO` composable does NOT pass them, so its byte output is\n // unchanged. See `./schemaGraph.ts`.\n\n /**\n * Emit the global `@graph` spine (Organization + WebSite + the promoted\n * LocalBusiness node, cross-linked by `@id`) PREPENDED before the per-schema\n * JSON-LD. When true, the LocalBusiness subtype already present in\n * `global.schemas` is ABSORBED into the graph (not emitted a second time).\n * Default `false`.\n */\n emitGraph?: boolean\n /**\n * Ordered Home → … → current breadcrumb trail (absolute item URLs). When it\n * has more than one hop a `BreadcrumbList` is emitted; the home page (≤ 1 hop)\n * emits none.\n */\n breadcrumbTrail?: BreadcrumbCrumb[]\n /**\n * REAL review items (from `.dcs/content.yaml`) for the LocalBusiness node.\n * Honest Review[] + aggregateRating are emitted ONLY for items with a numeric\n * rating + non-empty text + authorName; empty/missing ⇒ nothing.\n */\n reviews?: ReviewSource[]\n /**\n * Free-text trade/occupational license (from the `business.license`\n * content.yaml key). When non-empty a `hasCredential`\n * (`EducationalOccupationalCredential`, `credentialCategory: \"license\"`) is\n * added to the LocalBusiness node in the `@graph`; empty/missing ⇒ nothing.\n * Only consulted when `emitGraph` is true.\n */\n license?: string\n /**\n * Structured FAQ pairs (frontmatter `faq:` / `.dcs/faq.yaml`). A `FAQPage` is\n * emitted ONLY when at least one entry has a non-empty question AND answer.\n */\n faq?: FaqSource[]\n /**\n * Blog-post metadata. When `headline` is present a `BlogPosting` is emitted —\n * UNLESS the page's own schemas already hand-author a `BlogPosting` (then the\n * builder defers to the authored copy to avoid duplication).\n */\n blogMeta?: BlogMeta\n}\n\n// =============================================================================\n// Open Graph / Twitter / JSON-LD generation\n// =============================================================================\n\n/**\n * Generate Open Graph meta tags from config.\n *\n * @param resolvedTitle - the final, template-applied page title. Used as the\n * `og:title` fallback so OG stays consistent with `<title>`.\n */\nexport function generateOpenGraphMeta(\n og: SeoOpenGraphConfig,\n global: GlobalSeoConfig,\n resolvedTitle: string,\n pageDescription: string,\n canonical: string\n): Array<{ property: string; content: string }> {\n const tags: Array<{ property: string; content: string }> = []\n\n // og:title falls back to the *resolved* (templated) title — see module\n // docblock. `og.title` is already pre-resolved in `resolvePageSeo` to\n // `page.openGraph.title || resolvedTitle`, so this is consistent either way.\n tags.push({ property: 'og:title', content: og.title || resolvedTitle })\n tags.push({ property: 'og:description', content: og.description || pageDescription })\n // og:url is absolutized: an already-absolute og.url is used verbatim, a\n // site-relative og.url is joined onto the site URL, and a missing og.url falls\n // back to the (already-absolute) canonical. This stops a relative per-page\n // `openGraph.url` leaking a non-absolute og:url into the baked <head>.\n tags.push({ property: 'og:url', content: absolutizeUrl(og.url, global.siteUrl, canonical) })\n tags.push({ property: 'og:type', content: og.type || 'website' })\n\n const image = og.image || global.images?.ogDefault\n if (image) {\n tags.push({ property: 'og:image', content: image })\n if (og.imageAlt || resolvedTitle) {\n tags.push({ property: 'og:image:alt', content: og.imageAlt || resolvedTitle })\n }\n if (og.imageWidth) {\n tags.push({ property: 'og:image:width', content: String(og.imageWidth) })\n }\n if (og.imageHeight) {\n tags.push({ property: 'og:image:height', content: String(og.imageHeight) })\n }\n }\n\n if (global.siteName) {\n tags.push({ property: 'og:site_name', content: global.siteName })\n }\n\n if (global.locale) {\n tags.push({ property: 'og:locale', content: global.locale })\n }\n\n // Article-specific tags\n if (og.type === 'article') {\n if (og.publishedTime) {\n tags.push({ property: 'article:published_time', content: og.publishedTime })\n }\n if (og.modifiedTime) {\n tags.push({ property: 'article:modified_time', content: og.modifiedTime })\n }\n if (og.author) {\n tags.push({ property: 'article:author', content: og.author })\n }\n if (og.section) {\n tags.push({ property: 'article:section', content: og.section })\n }\n if (og.tags) {\n og.tags.forEach((tag) => {\n tags.push({ property: 'article:tag', content: tag })\n })\n }\n }\n\n return tags\n}\n\n/**\n * Generate Twitter Card meta tags from config.\n *\n * @param resolvedTitle - the final, template-applied page title (Twitter title\n * fallback), mirroring the OG behaviour.\n */\nexport function generateTwitterMeta(\n twitter: SeoTwitterConfig,\n global: GlobalSeoConfig,\n resolvedTitle: string,\n pageDescription: string\n): Array<{ name: string; content: string }> {\n const tags: Array<{ name: string; content: string }> = []\n\n tags.push({ name: 'twitter:card', content: twitter.card || 'summary_large_image' })\n tags.push({ name: 'twitter:title', content: twitter.title || resolvedTitle })\n tags.push({ name: 'twitter:description', content: twitter.description || pageDescription })\n\n const image = twitter.image || global.images?.twitterDefault\n if (image) {\n tags.push({ name: 'twitter:image', content: image })\n if (twitter.imageAlt || resolvedTitle) {\n tags.push({ name: 'twitter:image:alt', content: twitter.imageAlt || resolvedTitle })\n }\n }\n\n const site = twitter.site || global.social?.twitter\n if (site) {\n tags.push({ name: 'twitter:site', content: site.startsWith('@') ? site : `@${site}` })\n }\n\n if (twitter.creator) {\n tags.push({\n name: 'twitter:creator',\n content: twitter.creator.startsWith('@') ? twitter.creator : `@${twitter.creator}`,\n })\n }\n\n return tags\n}\n\n/**\n * Generate JSON-LD schema objects from schema configs, auto-populating common\n * WebSite properties from global config.\n */\nexport function generateJsonLd(schemas: SeoSchemaConfig[], global: GlobalSeoConfig): object[] {\n return schemas.map((schema) => {\n const base: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': schema.type,\n }\n\n // Merge properties\n if (schema.properties) {\n Object.assign(base, schema.properties)\n }\n\n // Auto-populate common properties from global config\n if (schema.type === 'WebSite' && global.siteUrl && !base.url) {\n base.url = global.siteUrl\n }\n if (schema.type === 'WebSite' && global.siteName && !base.name) {\n base.name = global.siteName\n }\n\n return base\n })\n}\n\n/**\n * Resolve page SEO by merging global defaults with page-specific config.\n *\n * Behavioural changes from the historical in-composable version (both bug\n * fixes):\n * 1. `openGraph.title` falls back to the **template-applied** page title, not\n * the raw `page.title`, so `og:title` matches `<title>`.\n * 2. The `titleTemplate` is applied **only** to a page-specific title\n * (`page.title`, or the `fallbackTitle` arg). It is no longer applied to\n * `global.defaultTitle`, which is already the complete brand title —\n * templating it produced `\"Brand | Default Title | Brand\"` doubling on any\n * page without its own `seo.yaml` entry.\n *\n * @param fallbackTitle - a page-specific title to use when `seo.yaml` has no\n * `title` for this page (e.g. the route `title` from `pages.yaml`). It IS run\n * through `titleTemplate`; `global.defaultTitle` is the last resort and is not.\n */\nexport function resolvePageSeo(\n pageSlug: string,\n pagePath: string | undefined,\n seoConfig: SeoConfiguration | undefined,\n fallbackTitle?: string\n): ResolvedPageSeo {\n const global = seoConfig?.global ?? {}\n const page = seoConfig?.pages?.[pageSlug] ?? {}\n\n // Build canonical URL\n let canonical = page.canonical || ''\n if (!canonical && global.siteUrl) {\n const path = pagePath ?? (pageSlug === 'home' ? '/' : `/${pageSlug}`)\n canonical = `${global.siteUrl.replace(/\\/$/, '')}${path}`\n }\n\n // Build title. The `%s` slot is for a PAGE-SPECIFIC title only (a seo.yaml\n // `page.title` or a `pages.yaml` route title). `global.defaultTitle` is\n // already brand-complete, so it is used verbatim — never re-templated.\n const pageSpecificTitle = page.title || fallbackTitle\n let title: string\n if (pageSpecificTitle) {\n title =\n page.noTitleTemplate || !global.titleTemplate\n ? pageSpecificTitle\n : global.titleTemplate.replace('%s', pageSpecificTitle)\n } else {\n title = global.defaultTitle || pageSlug\n }\n\n // Merge Open Graph. og:title now falls back to the *resolved* (templated)\n // title rather than the raw page.title, keeping it consistent with <title>.\n const openGraph: ResolvedPageSeo['openGraph'] = {\n type: page.openGraph?.type || 'website',\n title: page.openGraph?.title || title,\n description: page.openGraph?.description || page.description || global.defaultDescription || '',\n ...page.openGraph,\n }\n\n // Merge Twitter\n const twitter: ResolvedPageSeo['twitter'] = {\n card: page.twitter?.card || 'summary_large_image',\n ...page.twitter,\n }\n\n // Combine schemas (global + page)\n const schemas = [...(global.schemas ?? []), ...(page.schemas ?? [])]\n\n return {\n title,\n description: page.description || global.defaultDescription || '',\n canonical,\n robots: page.robots || global.robots || 'index, follow',\n openGraph,\n twitter,\n schemas,\n alternates: page.alternates ?? [],\n // Surface keywords on the resolved object so both runtime and emitter can\n // emit the meta tag without re-reading the raw page config.\n keywords: page.keywords,\n }\n}\n\n// =============================================================================\n// Unified head-tag builder (consumed by runtime + emitter)\n// =============================================================================\n\n/**\n * Build the complete, framework-agnostic set of `<head>` tags for a page.\n *\n * This is the function both the `useSEO` runtime and the build-time emitter\n * call, guaranteeing identical output. Pass `overrides` to mirror the\n * composable's `applyHead(overrides)` behaviour, or to force `robots` (used by\n * the emitter's `noindex` option).\n */\nexport function buildHeadTags(\n pageSlug: string,\n pagePath: string | undefined,\n seoConfig: SeoConfiguration | undefined,\n overrides?: HeadTagOverrides\n): ResolvedHeadTags {\n const resolved = resolvePageSeo(pageSlug, pagePath, seoConfig, overrides?.fallbackTitle)\n const global = seoConfig?.global ?? {}\n\n const title = overrides?.title ?? resolved.title\n const description = overrides?.description ?? resolved.description\n const robots = overrides?.robots ?? resolved.robots\n const keywords = overrides?.keywords ?? resolved.keywords\n\n // ── meta ────────────────────────────────────────────────────────────────\n const meta: HeadMetaTag[] = []\n\n meta.push({ name: 'description', content: description })\n\n // Keywords are opt-in (default off) so the runtime composable output is\n // unchanged; the static-HTML emitter passes includeKeywords: true.\n if (keywords && overrides?.includeKeywords) {\n meta.push({ name: 'keywords', content: keywords })\n }\n\n if (robots) {\n meta.push({ name: 'robots', content: robots })\n }\n\n // Verification codes\n if (global.verification?.google) {\n meta.push({ name: 'google-site-verification', content: global.verification.google })\n }\n if (global.verification?.bing) {\n meta.push({ name: 'msvalidate.01', content: global.verification.bing })\n }\n\n // Open Graph\n const ogMeta = generateOpenGraphMeta(resolved.openGraph, global, title, description, resolved.canonical)\n meta.push(...ogMeta.map((t) => ({ property: t.property, content: t.content })))\n\n // Twitter\n const twitterMeta = generateTwitterMeta(resolved.twitter, global, title, description)\n meta.push(...twitterMeta.map((t) => ({ name: t.name, content: t.content })))\n\n // Caller-supplied extra meta\n if (overrides?.meta) {\n meta.push(...overrides.meta)\n }\n\n // ── link ──────────────────────────────────────────────────────────────\n const link: HeadLinkTag[] = []\n\n if (resolved.canonical) {\n // Guarantee the canonical is absolute even if a page set a relative one.\n link.push({ rel: 'canonical', href: absolutizeUrl(resolved.canonical, global.siteUrl, resolved.canonical) })\n }\n\n resolved.alternates.forEach((alt) => {\n link.push({ rel: 'alternate', href: alt.href, hreflang: alt.hreflang })\n })\n\n // ── script (JSON-LD) ──────────────────────────────────────────────────\n const jsonLd = buildJsonLd(resolved, global, overrides)\n const script: HeadScriptTag[] = jsonLd.map((schema) => ({\n type: 'application/ld+json',\n children: JSON.stringify(schema),\n }))\n\n return { title, meta, link, script, jsonLd, resolved }\n}\n\n/**\n * Assemble the JSON-LD objects for a page, weaving in the schema-completeness\n * builders (`./schemaGraph.ts`) ONLY when the caller opts in via `overrides`.\n *\n * Order (when opted in): `@graph` spine → per-page/global schemas →\n * `BreadcrumbList` → `BlogPosting` → `FAQPage`. With no opt-in inputs this is\n * exactly the historical `overrides.schemas ?? generateJsonLd(...)`, so the\n * runtime composable's bytes are unchanged.\n *\n * Anti-duplication:\n * • When `emitGraph` absorbs the LocalBusiness subtype into the spine, that\n * subtype is excluded from the per-schema emission (no second copy).\n * • A builder `BlogPosting` is skipped when the page already hand-authors one.\n */\nfunction buildJsonLd(\n resolved: ResolvedPageSeo,\n global: GlobalSeoConfig,\n overrides?: HeadTagOverrides\n): object[] {\n // A full schema replacement still wins (back-compat with applyHead overrides).\n if (overrides?.schemas) return overrides.schemas\n\n const out: object[] = []\n\n const emitGraph = overrides?.emitGraph === true\n\n // Per-schema config to emit normally. When the graph absorbs the LocalBusiness\n // subtype, drop it here so it is not emitted twice.\n let perSchema: SeoSchemaConfig[] = resolved.schemas\n if (emitGraph) {\n const graph = buildGlobalGraph(global, {\n reviews: overrides?.reviews,\n license: overrides?.license,\n })\n if (graph.length > 0) {\n out.push(...graph)\n // Remove the schemas the @graph now provides canonically — the\n // LocalBusiness subtype it folded in, plus any global Organization/WebSite\n // (the graph emits cross-linked ones) — to avoid duplicate nodes.\n perSchema = resolved.schemas.filter((s) => !graphAbsorbs(s, global))\n }\n }\n\n out.push(...generateJsonLd(perSchema, global))\n\n // BreadcrumbList from the supplied trail (home ⇒ none).\n if (overrides?.breadcrumbTrail) {\n out.push(...buildBreadcrumbList(overrides.breadcrumbTrail))\n }\n\n // BlogPosting — only when the page does not already hand-author one.\n if (overrides?.blogMeta && global.siteUrl) {\n const alreadyAuthored = resolved.schemas.some((s) => s.type === 'BlogPosting')\n if (!alreadyAuthored) {\n out.push(...buildBlogPosting(overrides.blogMeta, global.siteUrl, global))\n }\n }\n\n // FAQPage — honesty-gated (only real structured Q&A).\n if (overrides?.faq) {\n out.push(...buildFaqPage(overrides.faq))\n }\n\n return out\n}\n","/**\n * Pure, framework-free `<head>` splicing for the build-time SEO emitter.\n *\n * Given a built `index.html` shell and a set of resolved head tags (from\n * `buildHeadTags`), this produces a new HTML string where the SEO-managed\n * tags — `<title>`, `description`, `keywords`, `robots`, `canonical`,\n * verification, all `og:*` / `article:*` properties, all `twitter:*` names,\n * and `application/ld+json` scripts — have been **replaced** (not duplicated)\n * with the resolved set.\n *\n * Design goals:\n * - **Idempotent**: running it twice yields the same output (it strips the\n * managed tags first, then re-inserts the canonical set).\n * - **Deterministic**: tag order is fixed by `renderHeadTags`.\n * - **Conservative**: only tags we own are touched. Charset, viewport, CSP,\n * theme-color, favicons, stylesheets, and the app script are left intact.\n *\n * This is intentionally regex-based (no DOM dependency) to mirror the existing\n * `dcsCdnImagePlugin` post-build HTML rewriting and to keep the emitter free of\n * heavy parser deps at build time.\n */\n\nimport type { ResolvedHeadTags } from './headTags'\n\n/** Escape a string for safe inclusion in a double-quoted HTML attribute. */\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n}\n\n/**\n * Escape a JSON-LD string for safe inclusion inside an HTML `<script>` element.\n *\n * A `<script type=\"application/ld+json\">` is a DATA block, but the HTML parser\n * still scans its raw text for `</script` (and `<!--`) — so untrusted CMS free\n * text (review bodies, FAQ Q&A, blog descriptions) carrying `</script>` could\n * break out of the script and inject markup (stored XSS).\n *\n * Per OWASP \"JSON in an HTML context\", we escape the three HTML-significant\n * characters as their `\\uXXXX` JSON escapes. Because these characters only ever\n * appear INSIDE JSON string literals (never as JSON structure), the result is\n * still byte-for-byte valid JSON that `JSON.parse` round-trips:\n * `<` → `<` (defeats `</script>` and `<!--` breakout)\n * `>` → `>`\n * `&` → `&`\n *\n * Exported so BOTH static-SEO sinks (the SPA `spliceHeadHtml` path AND the\n * VitePress `transformPageData` head emit) share one guard.\n */\nexport function escapeJsonLd(json: string): string {\n return json\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n}\n\n/** `name=` meta tags that the emitter owns and will replace. */\nconst MANAGED_META_NAMES = new Set([\n 'description',\n 'keywords',\n 'robots',\n 'google-site-verification',\n 'msvalidate.01',\n])\n\n/**\n * Property/name prefixes the emitter owns. Any meta whose `property` starts\n * with one of these (og:, article:) or whose `name` starts with `twitter:` is\n * considered managed and stripped before re-insertion.\n */\nconst MANAGED_PROPERTY_PREFIXES = ['og:', 'article:']\nconst MANAGED_NAME_PREFIXES = ['twitter:']\n\n/** `rel=` values on `<link>` that the emitter owns and will replace. */\nconst MANAGED_LINK_RELS = new Set(['canonical', 'alternate'])\n\n/**\n * IS THIS META TAG OURS? — the SINGLE definition of the managed meta set.\n *\n * WHY THIS IS EXPORTED AND WHY IT MUST STAY THE ONLY COPY (C-603 / Q-453=A).\n * `installSeoHead`'s DOM sweep removes the baked managed tags on the first\n * matched in-app navigation, and \"managed\" there has to mean EXACTLY what it\n * means here — this function is the build-time emitter's own answer, and the\n * runtime sweep is its DOM twin. The named failure mode of the sweep-once\n * design is precisely these two lists drifting: a tag the emitter starts baking\n * later would strand again, quietly, because the sweep would not recognise it.\n * A shared predicate makes that drift unrepresentable rather than merely\n * discouraged.\n *\n * Both arguments are accepted (rather than one \"identity\" string) because a\n * `<meta>` is keyed by `name` OR `property` and the two namespaces overlap:\n * `og:*` is a property, `twitter:*` is a name.\n */\nexport function isManagedMetaTag(\n name: string | null | undefined,\n property: string | null | undefined\n): boolean {\n const n = typeof name === 'string' ? name.toLowerCase() : undefined\n const p = typeof property === 'string' ? property.toLowerCase() : undefined\n\n if (n) {\n if (MANAGED_META_NAMES.has(n)) return true\n if (MANAGED_NAME_PREFIXES.some((prefix) => n.startsWith(prefix))) return true\n }\n if (p) {\n if (MANAGED_PROPERTY_PREFIXES.some((prefix) => p.startsWith(prefix))) return true\n }\n return false\n}\n\n/**\n * IS THIS LINK OURS? — the SINGLE definition of the managed link set.\n *\n * `canonical` and `alternate` only. Stylesheets, preloads, icons and manifests\n * belong to the app shell and are never touched by either sink.\n */\nexport function isManagedLinkRel(rel: string | null | undefined): boolean {\n return typeof rel === 'string' && MANAGED_LINK_RELS.has(rel.toLowerCase())\n}\n\n/**\n * Render the resolved head tags to a deterministic HTML fragment.\n * Order: title, meta (in the order produced by buildHeadTags), link, script.\n */\nexport function renderHeadTags(tags: ResolvedHeadTags, indent = ' '): string {\n const lines: string[] = []\n\n lines.push(`${indent}<title>${escapeAttr(tags.title)}</title>`)\n\n for (const m of tags.meta) {\n if (m.property !== undefined) {\n lines.push(`${indent}<meta property=\"${escapeAttr(m.property)}\" content=\"${escapeAttr(m.content)}\" />`)\n } else if (m.name !== undefined) {\n lines.push(`${indent}<meta name=\"${escapeAttr(m.name)}\" content=\"${escapeAttr(m.content)}\" />`)\n }\n }\n\n for (const l of tags.link) {\n const hreflang = l.hreflang ? ` hreflang=\"${escapeAttr(l.hreflang)}\"` : ''\n lines.push(`${indent}<link rel=\"${escapeAttr(l.rel)}\" href=\"${escapeAttr(l.href)}\"${hreflang} />`)\n }\n\n for (const s of tags.script) {\n lines.push(`${indent}<script type=\"${escapeAttr(s.type)}\">${escapeJsonLd(s.children)}</script>`)\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Strip the SEO-managed tags from a `<head>` block so they can be re-inserted\n * without duplication. Operates only within `<head>...</head>` to avoid\n * touching body content.\n */\nexport function stripManagedHeadTags(html: string): string {\n const headMatch = html.match(/<head[^>]*>([\\s\\S]*?)<\\/head>/i)\n if (!headMatch) return html\n\n let head = headMatch[1]\n\n // Remove existing <title>…</title>\n head = head.replace(/[ \\t]*<title>[\\s\\S]*?<\\/title>[ \\t]*\\r?\\n?/gi, '')\n\n // Remove existing JSON-LD scripts\n head = head.replace(\n /[ \\t]*<script[^>]*type=[\"']application\\/ld\\+json[\"'][^>]*>[\\s\\S]*?<\\/script>[ \\t]*\\r?\\n?/gi,\n ''\n )\n\n // Remove managed <meta name=\"…\"> and <meta property=\"…\"> tags.\n // Matches any <meta ...> tag, inspects its name/property, drops it if managed.\n head = head.replace(/[ \\t]*<meta\\b[^>]*>[ \\t]*\\r?\\n?/gi, (tag) => {\n const nameMatch = tag.match(/\\bname=[\"']([^\"']*)[\"']/i)\n const propMatch = tag.match(/\\bproperty=[\"']([^\"']*)[\"']/i)\n // Shared predicate — see `isManagedMetaTag`. The runtime DOM sweep in\n // `sweepBakedHeadTags.ts` asks this exact question of a real element, so\n // the build sink and the runtime sink cannot disagree about what is ours.\n return isManagedMetaTag(nameMatch?.[1], propMatch?.[1]) ? '' : tag\n })\n\n // Remove existing canonical link (alternates are also managed). Same shared\n // predicate as the meta pass — stylesheets/icons/preloads fall through.\n head = head.replace(/[ \\t]*<link\\b[^>]*>[ \\t]*\\r?\\n?/gi, (tag) => {\n const relMatch = tag.match(/\\brel=[\"']([^\"']*)[\"']/i)\n return isManagedLinkRel(relMatch?.[1]) ? '' : tag\n })\n\n return html.slice(0, headMatch.index! + headMatch[0].indexOf(headMatch[1])) +\n head +\n html.slice(headMatch.index! + headMatch[0].indexOf(headMatch[1]) + headMatch[1].length)\n}\n\n/**\n * Splice resolved SEO head tags into an HTML document.\n *\n * Strips the existing managed tags, then inserts the rendered canonical set\n * immediately before `</head>`. If no `<head>` is present the HTML is returned\n * unchanged (defensive — the emitter logs and no-ops in that case).\n *\n * Idempotent: applying twice produces identical output.\n */\nexport function spliceHeadHtml(html: string, tags: ResolvedHeadTags): string {\n if (!/<\\/head>/i.test(html)) {\n return html\n }\n\n const stripped = stripManagedHeadTags(html)\n const fragment = renderHeadTags(tags)\n\n // Insert before </head>, preserving the indentation of the closing tag.\n return stripped.replace(/([ \\t]*)<\\/head>/i, (_m, indent) => {\n return `${fragment}\\n${indent}</head>`\n })\n}\n","/**\n * THE SWEEP-ONCE HALF OF `installSeoHead` — the fix for DEFECT F-A\n * (Q-453 = A, decided 2026-08-11; C-603).\n *\n * THE DEFECT, IN ONE SENTENCE. Unhead removes only the side effects it OWNS.\n * The baked managed tags were written into the served HTML by the build-time\n * emitter before any JavaScript ran, so Unhead never owned them and never\n * removes them — which means a managed tag present in the BAKED head and ABSENT\n * from the runtime entry is left stranded in the DOM for the rest of the\n * session, while every tag the new route DOES declare moves correctly.\n * Measured live across the fleet: a stale `meta keywords` on five sites and a\n * stale `article:published_time` on iron-oak\n * (`.docs/analysis/installseohead-defect-fa-explainer-2026-08-11.md` §2).\n *\n * THE FIX. On the FIRST matched in-app navigation, remove the baked managed\n * tags that the incoming head input does not re-declare. From that moment the\n * runtime is the sole owner of the managed head and Unhead's own reconciliation\n * (proven correct by the C-362 fixture, on both shipped versions) handles every\n * subsequent navigation. One sweep is therefore not a budget — it is\n * SUFFICIENT, and §\"WHY ONCE IS ENOUGH\" below is the argument.\n *\n * ── THE FOUR REFUSALS ──────────────────────────────────────────────────────\n *\n * S1 NEVER SWEEP JSON-LD. This is the failure mode Q-453 named explicitly\n * when it chose option A. `<script type=\"application/ld+json\">` IS in the\n * emitter's managed set, but the runtime DELIBERATELY never re-asserts it\n * (the baked graph is built from inputs that do not exist at runtime:\n * content.yaml reviews, business licence, FAQ pairs, breadcrumb trails).\n * So a sweep that treated \"managed\" as \"sweepable\" would DELETE the graph\n * outright and leave the page with none — strictly worse than the stale\n * graph it replaced. Scripts are never snapshotted, so they are\n * unreachable by this module rather than merely skipped.\n *\n * S2 NEVER SWEEP A TAG THE RUNTIME IS ABOUT TO DECLARE. The sweep set is the\n * baked snapshot MINUS the identities in the incoming input. This is not\n * an optimisation — it is what makes the sweep SAFE. Unhead reconciles\n * against (and may adopt) a baked element whose identity appears in an\n * entry; ripping such an element out from under Unhead would desync its\n * side-effect bookkeeping and could leave the tag permanently missing.\n * Excluding them means this module only ever touches elements Unhead has\n * no entry for and therefore can never have adopted. It also means the\n * swept set IS exactly the strandable set, by construction.\n *\n * S3 NEVER SWEEP `<title>`. Title is emitted unconditionally by\n * `buildHeadTags`, so it is always re-asserted and can never strand; and\n * it is the one managed tag with singleton semantics in the DOM and in\n * `document.title`. It is excluded from the snapshot for the same reason\n * as S1 — unreachable beats skipped.\n *\n * S4 THE SNAPSHOT IS TAKEN AT INSTALL TIME, NOT AT SWEEP TIME. \"Baked\" means\n * \"present in the document the browser fetched\". `installSeoHead` runs at\n * app entry, after `createHead()` but BEFORE `app.mount()` — and Unhead's\n * DOM write is debounced to a later task — so `document.head` at that\n * instant holds the served tags and nothing else. Snapshotting lazily on\n * the first navigation would instead capture Unhead's own output and let\n * the sweep delete live runtime tags. The install-time snapshot is what\n * makes \"the ones the static emitter baked\" a fact rather than a guess.\n *\n * ── WHY ONCE IS ENOUGH ─────────────────────────────────────────────────────\n * After the sweep, every managed element in the head is either (a) one the\n * runtime just declared — Unhead owns it, and drops it on a later navigation\n * that omits it (the RUNTIME->RUNTIME half, already proven working by ITEM 2b),\n * or (b) gone. There is no third category left for a later navigation to\n * strand, so a second sweep would have an empty set by definition. Sweeping\n * repeatedly would also mean holding DOM references for the life of the\n * session; the snapshot is released as soon as it is spent.\n *\n * ── WHAT CRAWLERS SEE: NOTHING CHANGES ─────────────────────────────────────\n * The sweep can only fire on a MATCHED IN-APP NAVIGATION. `installSeoHead`'s R5\n * already guarantees the fetched document is never written to, and a crawler or\n * social scraper fetches each URL as an isolated page load and never performs an\n * in-app navigation. The prerender path is likewise unaffected: the Playwright\n * renderer loads each route as a fresh document, so no in-app navigation ever\n * occurs and no sweep is reachable. Baked bytes are identical before and after\n * this change — which is also why it cannot disturb the emitter-output pins in\n * `runtimeVsBakedJsonLd.test.ts` (C-418): this module changes neither emitter.\n */\n\nimport { isManagedMetaTag, isManagedLinkRel } from './spliceHeadHtml'\nimport type { HeadMetaTag, HeadLinkTag } from './headTags'\n\n/**\n * A stable identity for a managed head tag, shared by the DOM side and the\n * head-input side so the two can be compared as sets.\n *\n * The shapes mirror how the head libraries dedupe:\n * - `meta|name|<name>` — `description`, `keywords`, `twitter:*`\n * - `meta|property|<property>` — `og:*`, `article:*`\n * - `link|canonical` — the singleton canonical\n * - `link|alternate|<hreflang>` — one per hreflang; the hreflang is part of\n * the identity because a page may legitimately declare several alternates\n * and they are only interchangeable within a single language.\n */\ntype ManagedTagIdentity = string\n\nfunction metaIdentity(\n name: string | null | undefined,\n property: string | null | undefined\n): ManagedTagIdentity | null {\n if (typeof property === 'string' && property !== '') {\n return `meta|property|${property.toLowerCase()}`\n }\n if (typeof name === 'string' && name !== '') {\n return `meta|name|${name.toLowerCase()}`\n }\n return null\n}\n\nfunction linkIdentity(\n rel: string | null | undefined,\n hreflang: string | null | undefined\n): ManagedTagIdentity | null {\n if (typeof rel !== 'string' || rel === '') return null\n const r = rel.toLowerCase()\n if (r === 'alternate') {\n return `link|alternate|${typeof hreflang === 'string' ? hreflang.toLowerCase() : ''}`\n }\n return `link|${r}`\n}\n\n/**\n * The identities the runtime is ABOUT to assert — the S2 exclusion set.\n *\n * Deliberately typed against {@link HeadMetaTag}/{@link HeadLinkTag}, the exact\n * structures `buildHeadTags` returns, so a future field on either flows through\n * the same identity function the DOM side uses.\n */\nexport function headInputIdentities(input: {\n meta?: readonly HeadMetaTag[]\n link?: readonly HeadLinkTag[]\n}): Set<ManagedTagIdentity> {\n const ids = new Set<ManagedTagIdentity>()\n\n for (const m of input.meta ?? []) {\n if (!m) continue\n const id = metaIdentity(m.name, m.property)\n if (id) ids.add(id)\n }\n for (const l of input.link ?? []) {\n if (!l) continue\n const id = linkIdentity(l.rel, l.hreflang)\n if (id) ids.add(id)\n }\n\n return ids\n}\n\n/**\n * Capture the managed tags the BUILD baked into the served document (S4).\n *\n * Call this at INSTALL time. `<title>` (S3) and `ld+json` scripts (S1) are not\n * collected at all, so no later mistake can sweep them.\n *\n * Returns a plain array of live element references. It is empty — and the whole\n * feature inert — when there is no document (SSR / node build context), which is\n * the same defensive posture `readInjectedPages` takes.\n */\nexport function snapshotBakedManagedHeadTags(doc?: Document): Element[] {\n const d = doc ?? (typeof document !== 'undefined' ? document : undefined)\n if (!d || !d.head) return []\n\n const out: Element[] = []\n\n // S1/S3 by construction: only `meta` and `link` are ever considered.\n for (const el of Array.from(d.head.querySelectorAll('meta'))) {\n if (isManagedMetaTag(el.getAttribute('name'), el.getAttribute('property'))) {\n out.push(el)\n }\n }\n for (const el of Array.from(d.head.querySelectorAll('link'))) {\n if (isManagedLinkRel(el.getAttribute('rel'))) {\n out.push(el)\n }\n }\n\n return out\n}\n\n/**\n * Remove the baked managed tags the incoming head input does NOT re-declare.\n *\n * @param snapshot the install-time snapshot from\n * {@link snapshotBakedManagedHeadTags}\n * @param input the head payload about to be pushed to the head client — the S2\n * exclusion set is derived from it\n * @returns how many elements were actually removed, so the caller can REPORT\n * the sweep instead of performing it silently\n *\n * Elements that have already left the document are skipped rather than removed\n * again: a baked element can legitimately disappear between install and the\n * first navigation (a view's own `useSEO` may have caused Unhead to reconcile\n * it away), and \"still connected\" is the cheap, library-agnostic way to ask\n * whether this module is still looking at the served DOM.\n */\nexport function sweepBakedManagedHeadTags(\n snapshot: readonly Element[],\n input: { meta?: readonly HeadMetaTag[]; link?: readonly HeadLinkTag[] }\n): number {\n if (snapshot.length === 0) return 0\n\n const declared = headInputIdentities(input)\n let removed = 0\n\n for (const el of snapshot) {\n if (!el || !el.isConnected) continue\n\n const tag = el.tagName.toUpperCase()\n const identity =\n tag === 'META'\n ? metaIdentity(el.getAttribute('name'), el.getAttribute('property'))\n : tag === 'LINK'\n ? linkIdentity(el.getAttribute('rel'), el.getAttribute('hreflang'))\n : null\n\n // Unidentifiable, or the runtime is about to own it (S2) — leave it.\n if (!identity || declared.has(identity)) continue\n\n el.remove()\n removed++\n }\n\n return removed\n}\n","/**\n * Shared, PURE (fs-free) cores for the DCS site-file emitters:\n * `sitemap.xml`, `robots.txt`, and `llms.txt`.\n *\n * This is the SINGLE SOURCE OF TRUTH for the three site-wide static files. Both\n * cms's own `dcsSeoPlugin` (the \"factory\" emit path) and — as a follow-up on a\n * separate branch — kit-vite's `dcsSitemapPlugin` import these cores so their\n * outputs are byte-identical. The file name/shape deliberately mirror the\n * proven kit-vite emitter (`packages/kit-vite/src/sitemap.ts`) so the eventual\n * DRY port is a one-line re-export swap.\n *\n * These cores reuse cms's single SEO source of truth — `resolvePageSeo` for\n * each route's canonical + robots — so the `<loc>` written into `sitemap.xml`\n * (and the link written into `llms.txt`) is byte-identical to the\n * `<link rel=\"canonical\">` the SEO emitter bakes into each page's `<head>`. No\n * head-tag or manifest logic is forked here.\n *\n * Everything in this module is pure string-in / string-out and never throws.\n * The filesystem work lives in the plugin layer (`dcsSeoPlugin`), exactly like\n * the existing `headTags` / `pagesManifest` split — so these cores stay\n * Vue/Vite-free and unit-testable under jsdom.\n */\n\nimport { resolvePageSeo } from './headTags'\nimport type { PageRouteEntry } from './pagesManifest'\nimport type { SeoConfiguration } from '../types/seo'\n\n// =============================================================================\n// Shared helpers\n// =============================================================================\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\nconst URLSET_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9'\n\n/**\n * AI-crawler user agents emitted as explicit allow/deny groups in robots.txt so\n * AI discovery is opt-in-friendly (and explicitly gated off in preview). A site\n * can opt the whole tier out via `robots.aiBots: false`.\n */\nexport const AI_BOTS = [\n 'GPTBot',\n 'ClaudeBot',\n 'PerplexityBot',\n 'Google-Extended',\n 'CCBot',\n 'OAI-SearchBot',\n 'Applebot-Extended',\n] as const\n\n/** Trim a single trailing slash so joins never produce `//`. */\nfunction trimTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n\n/**\n * Sanitize a site-controlled string (title/description) before it is\n * interpolated into a single Markdown/text LINE of `llms.txt`.\n *\n * `llms.txt` is line-oriented Markdown; raw site free-text could carry newlines\n * or control chars that break the line structure (e.g. injecting a fake `##`\n * heading or a `- [x](javascript:...)` list item), or Markdown link/heading\n * sigils that hijack the line. We:\n * - collapse ALL newlines / carriage returns / control chars to a space,\n * - neutralize the line-leading Markdown sigils that would change block\n * structure (`#` heading, `>` blockquote, `-`/`*`/`+` list, `[` link),\n * - defang the `](` link sequence so `[text](javascript:...)` can't form an\n * active link,\n * then collapse runs of whitespace. Pure: string in, single-line string out.\n */\nfunction sanitizeLlmsText(value: string | undefined): string {\n if (!value) return ''\n return value\n // newlines, tabs, and other C0/C1 control chars -> space (kills breakout)\n .replace(/[\\u0000-\\u001f\\u007f-\\u009f]+/g, ' ')\n // defang any Markdown link target: `](` → `] (` so it can't resolve to a URL\n .replace(/\\]\\(/g, '] (')\n // neutralize the remaining link-open bracket so `[text]` isn't a link label\n .replace(/[[\\]]/g, ' ')\n // collapse whitespace runs and trim\n .replace(/\\s+/g, ' ')\n .trim()\n // neutralize a line-leading block sigil (heading/blockquote/list) the value\n // could still start with after trimming, so it can't open a new block\n .replace(/^([#>*+-]+)/, '\\\\$1')\n}\n\n/** XML-escape the five predefined entities for a `<loc>` value. */\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;')\n}\n\n/**\n * Convert a `pages.yaml` top-level `excluded:` glob (e.g. `/dev-*`, `/_*`,\n * `/404`) into a matcher. Only `*` is treated as a wildcard; everything else is\n * literal. Honors the coron8-shape top-level `excluded:` list, which cms's\n * manifest parser ignores.\n */\nfunction globToRegExp(glob: string): RegExp {\n const escaped = glob.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*')\n return new RegExp(`^${escaped}$`)\n}\n\n/**\n * True if `routePath` matches any glob in `excludedGlobs`. Exported so the\n * per-route HTML emitter applies the IDENTICAL exclusion semantics as the\n * sitemap/llms path (single source of truth for \"excluded\").\n */\nexport function matchesExcludedGlob(routePath: string, excludedGlobs: string[]): boolean {\n return excludedGlobs.some((g) => globToRegExp(g).test(routePath))\n}\n\n/**\n * W3C-datetime / ISO-8601 shapes the sitemaps spec accepts for `<lastmod>`:\n * `YYYY`, `YYYY-MM`, `YYYY-MM-DD`,\n * `YYYY-MM-DDThh:mmTZD`, `YYYY-MM-DDThh:mm:ssTZD`, `…ss.s+TZD`\n * where `TZD` is `Z` or `±hh:mm`. We accept the full-date and richer datetime\n * forms (the only ones sites actually author). Anything else is invalid.\n */\nconst W3C_DATETIME_RE =\n /^\\d{4}(-\\d{2}(-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})?)?)?)?$/\n\n/**\n * Validate a candidate `<lastmod>` value as a W3C datetime AND a real calendar\n * instant. Returns the trimmed value when valid, else `undefined` (caller omits\n * the `<lastmod>` rather than emitting garbage into the sitemap).\n *\n * Three gates:\n * 1. the lexical shape (W3C_DATETIME_RE),\n * 2. `Date.parse` is not NaN,\n * 3. for a `YYYY-MM-DD` prefix, the parsed UTC Y/M/D round-trips back to the\n * authored Y/M/D — this rejects shape-valid-but-impossible dates like\n * `2026-02-30`, which JS's lenient `Date.parse` would otherwise roll over\n * to March 2 (and thus silently corrupt the emitted lastmod).\n */\nfunction validW3CLastmod(value: string | undefined): string | undefined {\n if (typeof value !== 'string') return undefined\n const v = value.trim()\n if (!v) return undefined\n if (!W3C_DATETIME_RE.test(v)) return undefined\n\n const ms = Date.parse(v)\n if (Number.isNaN(ms)) return undefined\n\n // Strict calendar round-trip ONLY for a date-only `YYYY-MM-DD` value (parsed\n // as UTC midnight, so the UTC Y/M/D must equal the authored Y/M/D). This\n // rejects 02-30 / 04-31 etc. We skip this for datetimes carrying a time/zone,\n // where a timezone offset can legitimately shift the UTC calendar day.\n const dateOnly = v.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/)\n if (dateOnly) {\n const [, y, m, d] = dateOnly\n const dt = new Date(ms)\n if (\n dt.getUTCFullYear() !== Number(y) ||\n dt.getUTCMonth() + 1 !== Number(m) ||\n dt.getUTCDate() !== Number(d)\n ) {\n return undefined\n }\n }\n\n return v\n}\n\n/**\n * The SINGLE indexability predicate shared by `buildSitemapXml` and\n * `buildLlmsTxt` so the two outputs always agree on which routes appear.\n *\n * A route is NOT indexable when: its path/slug is in `exclude`, OR its\n * path/slug is in `noindex`, OR its path matches an `excludedGlobs` entry, OR\n * its resolved robots (from `seo.yaml`) matches `/noindex/i`.\n */\nexport function isRouteIndexable(\n route: PageRouteEntry,\n seoConfig: SeoConfiguration | undefined,\n opts: { exclude?: Set<string>; noindex?: Set<string>; excludedGlobs?: string[] } = {}\n): boolean {\n const { exclude, noindex, excludedGlobs = [] } = opts\n\n if (exclude && (exclude.has(route.path) || (route.slug && exclude.has(route.slug)))) return false\n if (noindex && (noindex.has(route.path) || (route.slug && noindex.has(route.slug)))) return false\n if (matchesExcludedGlob(route.path, excludedGlobs)) return false\n\n const resolved = resolvePageSeo(route.slug, route.path, seoConfig, route.title)\n if (/noindex/i.test(resolved.robots)) return false\n\n return true\n}\n\n/**\n * Resolve the absolute `<loc>`/link for a route, preferring the canonical baked\n * by the shared cms resolver (single-source reuse) and falling back to\n * `siteUrl + path`. Returns `null` when neither is derivable.\n */\nfunction resolveLoc(\n route: PageRouteEntry,\n seoConfig: SeoConfiguration | undefined,\n siteUrl: string | undefined\n): string | null {\n const resolved = resolvePageSeo(route.slug, route.path, seoConfig, route.title)\n if (resolved.canonical) return resolved.canonical\n if (siteUrl) return `${trimTrailingSlash(siteUrl)}${route.path}`\n return null\n}\n\n// =============================================================================\n// sitemap.xml\n// =============================================================================\n\nexport interface BuildSitemapParams {\n /** Route list (from `loadPagesManifest`). */\n routes: PageRouteEntry[]\n /** Canonical production origin (preferred base when no per-page canonical). */\n siteUrl?: string\n /** cms seo config (passed straight through to `resolvePageSeo`). */\n seoConfig?: SeoConfiguration\n /** Routes to skip entirely (path or slug). */\n exclude?: string[]\n /** Routes forced to noindex / omitted (path or slug). */\n noindex?: string[]\n /** `pages.yaml` top-level `excluded:` globs (coron8 parity). */\n excludedGlobs?: string[]\n /** Optional single site-wide `<lastmod>` (ISO date) — no fabricated per-page dates. */\n lastmod?: string\n}\n\n/**\n * Build the sitemap XML string from a route list. Pure: routes + siteUrl +\n * seoConfig in, XML out.\n *\n * A route is omitted when it is not indexable (see {@link isRouteIndexable}) OR\n * no absolute `<loc>` is derivable (missing siteUrl + no canonical).\n *\n * Returns the empty string when no route yields an absolute `<loc>` (the caller\n * treats this as a no-op signal — a sitemap with no absolute base is worse than\n * none).\n */\nexport function buildSitemapXml(params: BuildSitemapParams): string {\n const {\n routes,\n siteUrl,\n seoConfig,\n exclude = [],\n noindex = [],\n excludedGlobs = [],\n lastmod,\n } = params\n\n const excludeSet = new Set(exclude)\n const noindexSet = new Set(noindex)\n\n // De-duplicate <loc> values: two distinct routes can resolve to the SAME\n // canonical (e.g. `/` and `/home`, or an alias path), and duplicate <loc>\n // entries are invalid/penalized in a sitemap. First-wins preserves order.\n // Validated site-wide fallback lastmod (per-page values take precedence).\n const siteLastmod = validW3CLastmod(lastmod)\n const seen = new Set<string>()\n const entries: Array<{ loc: string; lastmod?: string }> = []\n for (const route of routes) {\n if (!isRouteIndexable(route, seoConfig, { exclude: excludeSet, noindex: noindexSet, excludedGlobs })) {\n continue\n }\n const loc = resolveLoc(route, seoConfig, siteUrl)\n if (!loc) continue\n if (seen.has(loc)) continue\n seen.add(loc)\n // Per-page <lastmod> (validated) wins; else the validated site-wide value;\n // else none. Both validated, so an authored bad date is dropped, never\n // emitted (one bad date invalidates the whole sitemap). Never fabricated.\n const perPage = validW3CLastmod(route.lastmod)\n entries.push({ loc, lastmod: perPage ?? siteLastmod })\n }\n\n // No absolute base derivable for any route → no-op signal.\n if (entries.length === 0) return ''\n\n // Only emit <lastmod> when the value is a valid W3C datetime — never emit an\n // unvalidated site-authored string (a bad date invalidates the whole sitemap).\n const urls = entries\n .map(({ loc, lastmod: lm }) => {\n const lastmodLine = lm ? `\\n <lastmod>${escapeXml(lm)}</lastmod>` : ''\n return ` <url>\\n <loc>${escapeXml(loc)}</loc>${lastmodLine}\\n </url>`\n })\n .join('\\n')\n\n return `${XML_HEADER}\\n<urlset xmlns=\"${URLSET_NS}\">\\n${urls}\\n</urlset>\\n`\n}\n\n// =============================================================================\n// robots.txt\n// =============================================================================\n\n/** robots.txt override hooks. */\nexport interface DcsRobotsOptions {\n /** Emit a robots.txt at all (default `true`). */\n enabled?: boolean\n /** `Disallow:` lines to emit (default `[]`). */\n disallow?: string[]\n /** `Allow:` lines to emit (default `['/']` in production). */\n allow?: string[]\n /** Raw lines appended verbatim after the generated directives. */\n extra?: string[]\n /**\n * Emit explicit AI-crawler allow/deny groups (GPTBot, ClaudeBot,\n * PerplexityBot, Google-Extended, CCBot, OAI-SearchBot, Applebot-Extended).\n * Default `true` — each `Allow: /` in\n * production, each `Disallow: /` in preview. Set `false` to drop the tier.\n */\n aiBots?: boolean\n /**\n * Overwrite an existing `dist/robots.txt` (e.g. a hand-authored\n * `public/robots.txt` Vite already copied). Default `false` — do not clobber.\n */\n force?: boolean\n}\n\nexport interface BuildRobotsParams {\n /** Canonical production origin (required for the absolute `Sitemap:` line). */\n siteUrl?: string\n /** Preview / staging gate: `Disallow: /`, no `Sitemap:` line. */\n preview?: boolean\n /** robots.txt override hooks. */\n robots?: DcsRobotsOptions\n /** Whether a sitemap is being emitted (drives the `Sitemap:` line). */\n hasSitemap?: boolean\n}\n\n/**\n * Build the robots.txt string. Pure: siteUrl + options in, text out.\n *\n * - **Preview mode** (`preview: true`): emits the privacy gate `User-agent: *`\n * + `Disallow: /`, the same `Disallow: /` for each AI-bot tier (when\n * enabled), and OMITS the `Sitemap:` line.\n * - **Production**: `User-agent: *`, any `disallow`/`allow` lines (default\n * `Allow: /`), explicit AI-bot allow groups (when enabled), a blank line,\n * then an absolute `Sitemap: {siteUrl}/sitemap.xml` (trailing slash trimmed).\n * The `Sitemap:` line is omitted when no sitemap is emitted or no `siteUrl`\n * is known.\n * - `extra` lines are appended verbatim.\n */\nexport function buildRobotsTxt(params: BuildRobotsParams): string {\n const { siteUrl, preview = false, robots = {}, hasSitemap = true } = params\n const { disallow = [], allow, extra = [], aiBots = true } = robots\n\n const lines: string[] = ['User-agent: *']\n\n if (preview) {\n lines.push('Disallow: /')\n if (aiBots) {\n for (const bot of AI_BOTS) {\n lines.push('')\n lines.push(`User-agent: ${bot}`)\n lines.push('Disallow: /')\n }\n }\n if (extra.length > 0) lines.push(...extra)\n return lines.join('\\n') + '\\n'\n }\n\n // Allow directives (default `Allow: /`).\n const allowLines = allow ?? ['/']\n for (const a of allowLines) lines.push(`Allow: ${a}`)\n // Disallow directives.\n for (const d of disallow) lines.push(`Disallow: ${d}`)\n\n // Explicit AI-crawler groups (opt-in-friendly discovery). Each group MUST\n // replay the operator's `disallow` (and `allow`) directives so a per-bot\n // group never becomes LESS restricted than `User-agent: *` — otherwise an\n // `Allow: /`-only AI group would re-open a path the operator disallowed for\n // everyone (e.g. `/admin`). robots.txt groups do NOT inherit, so the global\n // directives are repeated verbatim inside each AI group.\n if (aiBots) {\n for (const bot of AI_BOTS) {\n lines.push('')\n lines.push(`User-agent: ${bot}`)\n for (const a of allowLines) lines.push(`Allow: ${a}`)\n for (const d of disallow) lines.push(`Disallow: ${d}`)\n }\n }\n\n // Absolute Sitemap line (spec requires scheme+host).\n if (hasSitemap && siteUrl) {\n lines.push('')\n lines.push(`Sitemap: ${trimTrailingSlash(siteUrl)}/sitemap.xml`)\n }\n\n if (extra.length > 0) {\n lines.push(...extra)\n }\n\n return lines.join('\\n') + '\\n'\n}\n\n/**\n * Decide whether an EXISTING (hand-authored) `robots.txt` in `dist/` (one Vite\n * copied from `public/`) should be KEPT rather than overwritten by the factory.\n * It is kept when it is a REAL robots file: it carries a `User-agent: *` group\n * and is not an SPA-fallback HTML page. A bare `Sitemap:`-only stub (the\n * just-posh case) or an HTML shell fails and is OVERWRITTEN with the complete\n * factory robots (User-agent groups + the AI-bot tier + Sitemap), so AI\n * discovery is guaranteed even on a site that shipped a broken stub.\n *\n * Minimal-overwrite by design: a deliberate human robots (kduff's rich file, or\n * one with custom Disallow rules) is preserved — the factory never silently\n * drops hand-authored directives. `force` still overwrites unconditionally.\n * The \"factory-as-single-source, validated override\" gate (D6).\n *\n * Pure: string in, boolean out.\n */\nexport function isHandAuthoredRobotsAcceptable(text: string): boolean {\n if (!text || !text.trim()) return false\n // An SPA-fallback HTML page (the \"worse than a 404\" failure) starts with markup.\n if (/^\\s*</.test(text)) return false\n // Must carry the global group — a bare `Sitemap:`-only stub (just-posh) has none.\n return /^[ \\t]*User-agent:[ \\t]*\\*[ \\t]*$/im.test(text)\n}\n\n// =============================================================================\n// llms.txt\n// =============================================================================\n\nexport interface BuildLlmsParams {\n /** Route list (from `loadPagesManifest`). */\n routes: PageRouteEntry[]\n /** Canonical production origin (preferred base when no per-page canonical). */\n siteUrl?: string\n /** cms seo config (passed straight through to `resolvePageSeo`). */\n seoConfig?: SeoConfiguration\n /** Routes to skip entirely (path or slug). */\n exclude?: string[]\n /** Routes forced to noindex / omitted (path or slug). */\n noindex?: string[]\n /** `pages.yaml` top-level `excluded:` globs (coron8 parity). */\n excludedGlobs?: string[]\n}\n\n/**\n * Build the `llms.txt` plain-text body following the emerging llms.txt\n * convention (Markdown-ish): an H1 `# {siteName}`, a one-line `> {summary}`\n * blockquote, then a `## Pages` section listing each INDEXABLE route as a\n * Markdown link `- [{title}]({canonical}): {description}`.\n *\n * Sourced from the SAME inputs as the sitemap (so there is zero new resolution\n * path): `siteName`/`defaultDescription` from `seoConfig.global`, the URL list\n * filtered through the IDENTICAL {@link isRouteIndexable} predicate, and per-page\n * title/description/canonical from `resolvePageSeo`.\n *\n * Returns the empty string (no-op) when there is no `siteName` AND no indexable\n * route with a derivable link.\n */\nexport function buildLlmsTxt(params: BuildLlmsParams): string {\n const { routes, siteUrl, seoConfig, exclude = [], noindex = [], excludedGlobs = [] } = params\n\n const excludeSet = new Set(exclude)\n const noindexSet = new Set(noindex)\n\n const global = seoConfig?.global ?? {}\n // siteName/summary are site-controlled free text → sanitize before they reach\n // a Markdown line (newline/heading/link-injection guard). `siteName` keeps its\n // emptiness semantics for the no-op check below, so derive a separate display.\n const siteName = global.siteName\n const summary = sanitizeLlmsText(global.defaultDescription)\n\n type Entry = { title: string; canonical: string; description: string }\n const entries: Entry[] = []\n for (const route of routes) {\n if (!isRouteIndexable(route, seoConfig, { exclude: excludeSet, noindex: noindexSet, excludedGlobs })) {\n continue\n }\n const loc = resolveLoc(route, seoConfig, siteUrl)\n if (!loc) continue\n const resolved = resolvePageSeo(route.slug, route.path, seoConfig, route.title)\n entries.push({\n // title + description are site-controlled free text → sanitize per-line.\n title: sanitizeLlmsText(resolved.title),\n canonical: loc,\n description: sanitizeLlmsText(resolved.description),\n })\n }\n\n // No-op when there is nothing meaningful to publish.\n if (!siteName && entries.length === 0) return ''\n\n const out: string[] = []\n out.push(`# ${sanitizeLlmsText(siteName) || entries[0]?.title || 'Site'}`)\n if (summary) {\n out.push('')\n out.push(`> ${summary}`)\n }\n\n if (entries.length > 0) {\n out.push('')\n out.push('## Pages')\n out.push('')\n for (const e of entries) {\n const desc = e.description ? `: ${e.description}` : ''\n out.push(`- [${e.title}](${e.canonical})${desc}`)\n }\n }\n\n return out.join('\\n') + '\\n'\n}\n","/**\n * Loader for the `.dcs/pages.yaml` route manifest.\n *\n * `pages.yaml` is the canonical page registry maintained by the DCS portal and\n * by Copilot when scaffolding pages. For the build-time SEO emitter we only\n * need each route's `slug` and `path` (e.g. `{ slug: 'home', path: '/' }`).\n *\n * The parser is intentionally defensive: any missing/unparseable file or\n * malformed entry yields `null` (caller logs + no-ops) so a bad manifest can\n * never break a production build.\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport { matchesExcludedGlob } from './sitemap'\n\n/** A single route extracted from `.dcs/pages.yaml`. */\nexport interface PageRouteEntry {\n /** Page slug, matching an entry in `seo.yaml` `pages.<slug>` (may be absent). */\n slug: string\n /** Route path, e.g. `/`, `/services`, `/blog/my-post`. */\n path: string\n /**\n * Human title from the manifest (e.g. \"Kitchen Cabinet Refresh\"). Used as the\n * per-route title fallback when `seo.yaml` has no entry for this page, so\n * un-configured routes (e.g. blog posts) get unique titles. Optional.\n */\n title?: string\n /**\n * Per-page last-modification date for the sitemap `<lastmod>`. A W3C-datetime\n * string authored in `pages.yaml` (`lastmod` / `lastUpdated` / `dateModified`).\n * Validated + de-fabricated at emit time; an invalid or absent value falls\n * back to the site-wide lastmod. Never invented.\n */\n lastmod?: string\n}\n\n/** Shape of the relevant slice of `.dcs/pages.yaml`. */\ninterface RawPagesManifest {\n pages?: Array<{\n slug?: unknown\n path?: unknown\n title?: unknown\n lastmod?: unknown\n lastUpdated?: unknown\n dateModified?: unknown\n }>\n}\n\n/**\n * Resolve and parse `.dcs/pages.yaml` from one of the usual locations.\n *\n * Mirrors the `.dcs` path resolution used elsewhere in the package (project\n * root, parent dir for VitePress-style nesting, and cwd).\n *\n * @returns the list of `{ slug, path }` routes, or `null` if the file is\n * absent, unreadable, unparseable, or contains no usable page entries.\n */\nexport function loadPagesManifest(\n projectRoot: string,\n relativePagesPath: string,\n debug = false\n): PageRouteEntry[] | null {\n const possiblePaths = [\n path.resolve(projectRoot, relativePagesPath),\n path.resolve(projectRoot, '..', relativePagesPath),\n path.resolve(process.cwd(), relativePagesPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.warn('[dcs-seo] No pages.yaml found at:')\n possiblePaths.forEach((p) => console.warn(` - ${p}`))\n }\n return null\n }\n\n let raw: RawPagesManifest\n try {\n raw = yaml.load(fs.readFileSync(foundPath, 'utf8')) as RawPagesManifest\n } catch (error) {\n console.warn(`[dcs-seo] Failed to parse ${foundPath}:`, error)\n return null\n }\n\n const entries = parsePagesManifest(raw)\n if (!entries) {\n console.warn(`[dcs-seo] pages.yaml at ${foundPath} has no usable page entries`)\n return null\n }\n\n if (debug) {\n console.log(`[dcs-seo] Loaded ${entries.length} routes from ${foundPath}`)\n }\n return entries\n}\n\n/**\n * Extract `{ slug, path }` routes from an already-parsed manifest object.\n * Exposed separately so tests can exercise it without touching the filesystem.\n *\n * Entries missing a string `path` are skipped; a missing slug falls back to the\n * empty string (so the route still gets baked, using global SEO defaults).\n */\nexport function parsePagesManifest(raw: unknown): PageRouteEntry[] | null {\n if (!raw || typeof raw !== 'object') return null\n const pages = (raw as RawPagesManifest).pages\n if (!Array.isArray(pages)) return null\n\n const routes: PageRouteEntry[] = []\n for (const entry of pages) {\n if (!entry || typeof entry !== 'object') continue\n const p = (entry as { path?: unknown }).path\n if (typeof p !== 'string' || p.length === 0) continue\n const slug = (entry as { slug?: unknown }).slug\n const title = (entry as { title?: unknown }).title\n const lastmodRaw =\n (entry as { lastmod?: unknown }).lastmod ??\n (entry as { lastUpdated?: unknown }).lastUpdated ??\n (entry as { dateModified?: unknown }).dateModified\n const lastmod =\n typeof lastmodRaw === 'string' && lastmodRaw.length > 0 ? lastmodRaw : undefined\n routes.push({\n slug: typeof slug === 'string' ? slug : '',\n path: p,\n ...(typeof title === 'string' && title.length > 0 ? { title } : {}),\n ...(lastmod ? { lastmod } : {}),\n })\n }\n\n return routes.length > 0 ? routes : null\n}\n\n// =============================================================================\n// Which manifest routes the build actually EMITS (C-361 F5)\n// =============================================================================\n\n/**\n * The two exclusion inputs the per-route HTML emitter honours.\n *\n * `exclude` is a plugin OPTION (paths or slugs); `excludedGlobs` is the\n * `pages.yaml` top-level `excluded:` list. Neither is a file the runtime can\n * read, which is exactly why the RUNTIME MANIFEST has to be filtered before it\n * is injected rather than filtered again at lookup time.\n */\nexport interface RouteEmissionRules {\n /** `dcsSeoPlugin({ exclude })` — exact route paths OR slugs. */\n exclude?: readonly string[]\n /** `pages.yaml` top-level `excluded:` globs, e.g. `/dev-*`, `/_*`. */\n excludedGlobs?: readonly string[]\n}\n\n/** Why a manifest route is NOT emitted, or `null` when it is. */\nexport type RouteExclusionReason = 'excluded' | 'excluded-glob'\n\n/**\n * THE SINGLE EMISSION PREDICATE. Both the per-route HTML emitter and the\n * `__DCS_PAGES__` runtime manifest resolve \"is this route emitted?\" through\n * THIS function.\n *\n * WHY IT EXISTS (C-361 F5, verified). The emitter skipped `exclude` entries and\n * `excluded:` globs, while `__DCS_PAGES__` was built from the raw manifest and\n * carried EVERY loaded route. A private `/dev-*` route was therefore never baked\n * — correct — but `installSeoHead` would still runtime-assert a canonical and\n * `index, follow` over it on the first in-app navigation. The manifest has to\n * model what the emitter WRITES, not what `pages.yaml` DECLARES, and the only\n * way that survives a future edit is for there to be one predicate rather than\n * two implementations that agree today.\n */\nexport function routeExclusionReason(\n route: Pick<PageRouteEntry, 'slug' | 'path'>,\n rules: RouteEmissionRules\n): RouteExclusionReason | null {\n const exclude = rules.exclude ?? []\n if (exclude.includes(route.path)) return 'excluded'\n if (route.slug && exclude.includes(route.slug)) return 'excluded'\n if (matchesExcludedGlob(route.path, [...(rules.excludedGlobs ?? [])])) return 'excluded-glob'\n return null\n}\n\n/** `true` when the build emits a per-route `<head>` for this route. */\nexport function isRouteEmitted(\n route: Pick<PageRouteEntry, 'slug' | 'path'>,\n rules: RouteEmissionRules\n): boolean {\n return routeExclusionReason(route, rules) === null\n}\n","/**\n * ROUTER-LEVEL HEAD RE-ASSERT (C-357 clause 4 — the COVERAGE half of the\n * head-authority contract).\n *\n * WHAT PROBLEM THIS CLOSES. C-356 proved there is exactly ONE WRITER of the\n * managed head fields (`.dcs/seo.yaml`). It did NOT prove every route re-asserts\n * it. A view that writes no head at all is \"silent\": on a hard navigation it is\n * CORRECT (the baked `<head>` of the fetched document is the approved one and\n * nothing overwrites it), but on the first in-app click it leaves the PREVIOUS\n * route's title/description/canonical in the DOM. Measured on boogie-babies:\n * `/pricing` -> `/parties` still claims `canonical=/pricing`. The audit reports\n * this as `uncovered-route` and deliberately does not fail on it, because the\n * fix is one router hook in the platform, not eleven views remembering.\n *\n * WHY THIS FILE IS FULL OF REFUSALS. Turning silence from \"safe\" into \"asserts\n * whatever the lookup returns\" is a LOADED CHANGE: on every route whose baked\n * head is already correct, a wrong lookup is a live regression on a paying\n * customer. A fleet sweep on 2026-07-28 measured three ways the obvious\n * implementation breaks, and each one is encoded here as a hard rule.\n *\n * R1 DRIVE THE LOOKUP FROM `.dcs/pages.yaml`, NEVER FROM `to.meta.pageSlug`.\n * KEPT carries `meta.pageSlug` on every route, which makes the meta-driven\n * version look like the obvious choice. It is a trap: KEPT's two DYNAMIC\n * routes carry the PLACEHOLDER keys `blog-post` and `topic`, and neither\n * key exists in its `seo.yaml`. A meta-driven build therefore collapses\n * 21 blog posts + 3 topic pages to `global.defaultTitle` ON THE TOP PAYER.\n * The pages.yaml path->slug map resolves all 24 correctly, because it is\n * the SAME map the build-time emitter walked to bake them.\n * There is no read of `meta` anywhere in this file. That is the fix.\n *\n * R2 NO MATCH => TOUCH NOTHING. Not `global.defaultTitle`, not a clear, not\n * a \"best effort\" — nothing. boogie-babies' catch-all is\n * `/:pathMatch(.*)*` and its runtime path is the URL the visitor\n * MISTYPED (`/nope-xyz`), never the pages.yaml path `/404`; that repo\n * carries no `meta.pageSlug` at all. So a no-match is the NORMAL case\n * there, and any fallback write would clobber a correct baked head on\n * every 404 the site serves. No `seo.yaml` entry can fix it — the lookup\n * KEY is the wrong thing, not the value.\n *\n * R3 NORMALIZE TRAILING SLASHES ON BOTH SIDES. KEPT declares `/blog/` in\n * pages.yaml and `/blog` in router.ts. Naive path equality misses it and\n * the blog index silently joins the no-match set. The root path `/` must\n * NOT normalize to the empty string. A manifest that declares BOTH forms\n * is rejected at BUILD time (see `findDuplicateNormalizedPaths`), because\n * the emitter's last-write wins the output file while a lookup map's\n * first-write wins the runtime — a duplicate therefore guarantees\n * divergence whichever side you pick, so neither side may pick.\n *\n * R4 A FAILED NAVIGATION IS NOT A NAVIGATION. Vue Router calls\n * `afterEach(to, from, failure)` for ABORTED, CANCELLED and DUPLICATED\n * navigations, and when it fails the visible route stays `from`. A hook\n * that reads only `to` therefore asserts the head of a page the visitor is\n * NOT on: an auth guard rejecting `/pricing -> /account` leaves them on\n * pricing under the ACCOUNT head, `noindex` included. Verified at source\n * on vue-router 4.6.3 (`triggerAfterEach(toLocation, from, failure)` runs\n * unconditionally; `finalizeNavigation` — the only assignment to\n * `currentRoute` — is skipped when a failure is present) and empirically:\n * a `beforeEach` returning `false` produced `afterEach(to=/account,\n * from=/pricing, failure.type=4)` with `currentRoute` still `/pricing`.\n * So: `failure` truthy => write NOTHING. This rule exists because the\n * obvious version of THIS FIX invents its own divergence class.\n *\n * R5 THE INITIAL DOCUMENT IS NOT RE-ASSERTED. Its head is the baked,\n * owner-approved one and is correct exactly once — on that document.\n * Re-asserting buys nothing and puts the one head that is already right on\n * the same code path as the one that can be wrong. This is ENFORCED, not\n * assumed: `afterEach` alone does not guarantee it, because the generated\n * app entry installs the router BEFORE awaiting `router.isReady()`\n * (`packages/create-dcs-site/templates/vue-spa/src/main.ts`), so a hook\n * registered on the adoption line can and does observe the initial\n * navigation. The guard recognises vue-router's START location\n * structurally (`name` unset, `matched` empty, path `/`) and skips at most\n * ONE write, ever.\n *\n * S6 THE BAKED MANAGED TAGS ARE SWEPT ONCE, ON THE FIRST MATCH. See DEFECT\n * F-A immediately below and `sweepBakedHeadTags.ts` for the four refusals\n * that make the sweep safe (JSON-LD and `<title>` unreachable; never\n * sweep a tag the runtime is about to declare; snapshot at install time).\n *\n * MEASURED DEFECT F-A — **CLOSED 2026-08-11 by SWEEP ONCE** (Q-453 = A, C-603).\n * The production-shaped fixture (`installSeoHead.integration.test.ts`) put a\n * REAL Unhead client (both 1.11.20 and 2.0.19) on a REAL baked document and\n * counted `document.head`. Two facts came out of it:\n *\n * GOOD — Unhead RECONCILES against the baked static head. It does not add\n * alongside it. `<title>`, description, canonical, robots, og:* and twitter:*\n * all stayed at EXACTLY ONE element through six navigations, with the new\n * route's value. The feared \"two titles, two canonicals\" outcome does not\n * happen on either version, and is in fact unreachable: Unhead dedupes by tag\n * identity even across separate entries.\n *\n * BAD — Unhead only REMOVES side effects it OWNS, and it never owned the\n * baked tags. So a managed tag that is present in the BAKED head and ABSENT\n * from the runtime entry is left stranded in the DOM. Concretely: land on\n * baked `/blog/post-a` (which bakes `<meta name=\"keywords\">`), click through\n * to `/blog/post-b` (whose seo.yaml declares none) and post A's keywords are\n * still there while title, description and canonical have all moved. This is\n * C-361 F4 — the divergence `includeKeywords: true` was added to close —\n * surviving in its baked->runtime half. The class is EVERY OPTIONAL managed\n * tag, not just keywords: the fixture reproduces it for `link[rel=alternate]`\n * hreflang too, and `robots`/`article:*` are exposed the same way.\n *\n * It is invisible to the 610 fake-driven tests by construction: when the tag\n * was written by a PREVIOUS re-assert, Unhead owns it and drops it correctly,\n * which is all a recording fake can ever model.\n *\n * THE FIX, DECIDED BY THE OWNER RATHER THAN INVENTED HERE. Q-453 chose A —\n * SWEEP ONCE — over \"always emit\" (which structurally cannot fix hreflang or\n * `article:*`, and forks the shared `buildHeadTags` path) and \"stop baking\n * optional tags\" (the only crawler-VISIBLE option, and it changes them by\n * deletion). On the first MATCHED in-app navigation this module now removes\n * the baked managed tags the incoming input does not re-declare, so the\n * runtime owns the managed head from then on. The sweep lives in\n * `sweepBakedHeadTags.ts` and reuses the EMITTER'S OWN managed-set predicates\n * (`isManagedMetaTag` / `isManagedLinkRel` in `spliceHeadHtml.ts`) rather\n * than a second list, because the named residual of option A was those two\n * lists drifting apart.\n *\n * Crawler-visible bytes are UNCHANGED: the sweep is reachable only from a\n * matched in-app navigation, and R5 already refuses to write for the fetched\n * document. The fixture's `DEFECT F-A` assertions (ITEM 2a / 2d, plus the\n * third instance in ITEM 8a) are flipped to the swept expectation, which was\n * the acceptance test this docstring named.\n *\n * WHAT IT RE-ASSERTS, AND WHAT IT DELIBERATELY DOES NOT.\n * - `title`, `meta`, `link` — the managed fields P1 (`headHonesty.ts`) grades\n * and the ones that go stale on SPA navigation. `meta[name=keywords]` is\n * among them: the emitter passes `includeKeywords: true`, so a re-assert\n * that omitted it would leave post A's baked keywords in the DOM on\n * post A -> post B while every other managed field moved (C-361 F4).\n * - NOT `script` (JSON-LD). The baked graph is built with inputs that do not\n * exist at runtime (`content.yaml` reviews, business license, FAQ pairs,\n * breadcrumb trails). Re-asserting a POORER graph would not correct the\n * baked one, it would add a second, conflicting set of nodes — a new\n * divergence class invented by the fix. Leaving it alone is the same\n * judgement as R2: do not overwrite what you cannot faithfully reproduce.\n * Named residual: after an in-app navigation the JSON-LD still describes the\n * first document. Crawlers read the fetched document, where it is correct.\n *\n * IT RESOLVES THROUGH THE SHIPPED EMITTER (`buildHeadTags`) AND CARRIES THE TWO\n * BUILD-TIME-ONLY INPUTS. A second head-building code path would be its own\n * divergence source, so this calls the exact function the build calls. Two\n * inputs the plain runtime composable does NOT carry are threaded through here\n * because C-357 measured both as live divergences:\n * - `fallbackTitle` (the pages.yaml route title). The emitter resolves\n * `page.title || <route title>`; the runtime had no fallback and collapsed\n * to `global.defaultTitle`. mi-handyman had FOUR such routes.\n * - the `noindex` force. `dcsSeoPlugin({ noindex: [...] })` is a BUILD-TIME\n * force; the runtime `__DCS_SEO__` global is the raw seo.yaml, so a re-assert\n * without it republishes `index, follow` over a baked `noindex` — measured\n * live on iron-oak `/account` and `/projects`. Re-asserting without carrying\n * the list would ship that known regression by construction.\n *\n * NO vue-router IMPORT. The router is accepted structurally (anything with an\n * `afterEach`), so this package gains no dependency and the tests need no router\n * instance to prove the rules.\n */\n\nimport type { SeoConfiguration } from '../types/seo'\nimport { buildHeadTags, type HeadMetaTag, type HeadLinkTag } from './headTags'\nimport {\n snapshotBakedManagedHeadTags,\n sweepBakedManagedHeadTags,\n} from './sweepBakedHeadTags'\n\n// =============================================================================\n// Structural types — no vue-router, no unhead import\n// =============================================================================\n\n/**\n * What this module reads off a resolved route.\n *\n * `path` is the ONLY field consulted for the lookup. `name` and `matched` are\n * read off `from` ALONE, and only to recognise vue-router's START location for\n * R5; nothing about the head is ever derived from them.\n */\nexport interface SeoHeadRouteLike {\n path: string\n /** `from` only, R5: START location has no name. Never used for the lookup. */\n name?: unknown\n /** `from` only, R5: START location matched nothing. Never used for the lookup. */\n matched?: readonly unknown[]\n}\n\n/**\n * The slice of a vue-router instance this needs.\n *\n * The hook takes the FULL vue-router `afterEach` arity. `failure` is not\n * optional decoration — see R4: dropping it is what makes an aborted navigation\n * assert the head of a page nobody is on.\n *\n * NOTE THE ABSENCE: there is no `meta` on {@link SeoHeadRouteLike}, and that\n * absence is PINNED at compile time by {@link SEO_HEAD_ROUTE_HAS_NO_META}\n * below rather than left as friction.\n */\nexport interface SeoHeadRouterLike {\n afterEach(\n hook: (to: SeoHeadRouteLike, from?: SeoHeadRouteLike, failure?: unknown) => void\n ): unknown\n}\n\n// =============================================================================\n// The compile-time half of R1 (C-361 F7)\n// =============================================================================\n\n/**\n * COMPILE-TIME PIN: the route type this module accepts must carry NO `meta`.\n *\n * WHY A PIN AND NOT JUST AN ABSENCE. The absent property was described as\n * \"friction, not a gate\", and the cross-model review proved it: a future edit\n * adding `meta.pageSlug` as a NO-MATCH FALLBACK keeps every existing test green,\n * because the matched-route tests supply POISONED meta (so a fallback never\n * fires there) and the no-match tests supply NONE (so a fallback finds nothing).\n * Widening the interface is the mechanical prerequisite for reading `meta`, so\n * this is where R1 gets a real gate.\n *\n * Modelled on `APPLY_HEAD_TAKES_NO_ARGUMENTS` in `headContract.ts`, and it lives\n * in a CHECKED SOURCE FILE for the same reason: `packages/cms/tsconfig.json`\n * excludes `**\\/*.test.ts`, so a `@ts-expect-error` in the test file would be a\n * comment wearing a gate's clothes. Add `meta` to {@link SeoHeadRouteLike} and\n * the assignment below stops compiling — red in this package's `type-check`,\n * and through the published `.d.ts` in every site that runs one.\n *\n * HONEST LIMIT, stated rather than implied: this pins the TYPE SURFACE. It\n * cannot stop `(to as any).meta`, exactly as `APPLY_HEAD_TAKES_NO_ARGUMENTS`\n * cannot stop an `as any` argument. The behavioural half is the no-match test\n * that navigates with a poisoned meta pointing at a REAL seo.yaml slug — the\n * case the review found missing.\n */\nexport type SeoHeadRouteHasNoMeta = 'meta' extends keyof SeoHeadRouteLike ? false : true\n\nexport const SEO_HEAD_ROUTE_HAS_NO_META: SeoHeadRouteHasNoMeta = true\n\n/** A live unhead entry (`head.push(...)` result) — only `patch` is used. */\nexport interface SeoHeadEntryLike {\n patch(input: SeoHeadInput): void\n}\n\n/** The unhead client (`createHead()`), passed in explicitly — see below. */\nexport interface SeoHeadClientLike {\n push(input: SeoHeadInput, options?: Record<string, unknown>): SeoHeadEntryLike\n}\n\n/** The head payload handed to unhead. Managed fields only. */\nexport interface SeoHeadInput {\n title: string\n meta: HeadMetaTag[]\n link: HeadLinkTag[]\n}\n\n/** One route from `.dcs/pages.yaml`, as the emitter reads it. */\nexport interface SeoHeadPageRoute {\n slug: string\n path: string\n title?: string\n}\n\n/**\n * The runtime manifest injected by `dcsSeoPlugin` as `__DCS_PAGES__`.\n *\n * It carries the `noindex` list alongside the routes because that list is a\n * plugin OPTION, not a file — without it here the runtime cannot know that a\n * route was baked `noindex, nofollow`.\n */\nexport interface SeoHeadPagesManifest {\n routes: SeoHeadPageRoute[]\n noindex?: string[]\n}\n\n/** Why a navigation did or did not result in a head write. */\nexport type SeoHeadResolutionReason =\n /** Matched a manifest route; the head was re-asserted. */\n | 'applied'\n /** No manifest reached the runtime — the module is inert (never a fallback). */\n | 'no-manifest'\n /** The path is not a manifest route — R2: the baked head is left alone. */\n | 'no-match'\n /**\n * R4 — the navigation FAILED (aborted / cancelled / duplicated), so the\n * visitor is still on `from` and `to` is a page they never reached. Reported\n * rather than silent: \"the guard rejected it\" and \"the hook never ran\" must\n * not look the same from the outside.\n */\n | 'navigation-failed'\n /**\n * R5 — the initial document. Its baked head is the approved one; the hook\n * observed the navigation (installation raced `router.isReady()`) and\n * deliberately declined to write.\n */\n | 'initial-navigation'\n\nexport interface SeoHeadResolution {\n /** The path as the router reported it. */\n path: string\n /** The path after R3 normalization — what the lookup actually used. */\n normalizedPath: string\n matched: SeoHeadPageRoute | null\n reason: SeoHeadResolutionReason\n /** The resolved `<title>`, or `null` when nothing was written. */\n title: string | null\n /**\n * How many BAKED managed tags this navigation swept out of the DOM (S6).\n *\n * Non-zero on AT MOST ONE navigation per session — the first match, which\n * spends the sweep latch — and `0` on every other, including every\n * no-match / failed / initial resolution. Reported for the same reason\n * `no-match` is: \"it swept nothing\" and \"it never swept\" must not look the\n * same from the outside.\n */\n sweptBakedTags: number\n}\n\nexport interface InstallSeoHeadOptions {\n /**\n * The unhead client the app already created (`const head = createHead()`).\n *\n * REQUIRED, and it cannot be discovered instead. `injectHead()` calls Vue's\n * `inject`, which throws outside a component `setup()` — and a router\n * `afterEach` hook is exactly that. Passing it is the honest API; guessing at\n * a global would fail at runtime on the first navigation.\n */\n head: SeoHeadClientLike\n /** Route manifest. Defaults to the injected `__DCS_PAGES__.routes`. */\n pages?: SeoHeadPageRoute[]\n /** SEO config. Defaults to the injected `__DCS_SEO__`. */\n seo?: SeoConfiguration\n /** Build-time noindex list. Defaults to the injected `__DCS_PAGES__.noindex`. */\n noindex?: string[]\n /**\n * Observability hook, called once per navigation with the resolution —\n * INCLUDING the no-match case. Exists so \"it did nothing\" is distinguishable\n * from \"it never ran\", which is the vacuous-green shape this estate keeps\n * counting. Never affects behaviour.\n */\n onResolve?: (resolution: SeoHeadResolution) => void\n}\n\n// =============================================================================\n// Injected globals (mirrors `useSEO`'s `__DCS_SEO__` handling)\n// =============================================================================\n\ndeclare const __DCS_SEO__: SeoConfiguration | undefined\ndeclare const __DCS_PAGES__: SeoHeadPagesManifest | undefined\n\nfunction readInjectedSeo(): SeoConfiguration | undefined {\n try {\n if (typeof __DCS_SEO__ !== 'undefined' && __DCS_SEO__ !== null) return __DCS_SEO__\n } catch {\n /* not defined — fine */\n }\n return undefined\n}\n\nfunction readInjectedPages(): SeoHeadPagesManifest | undefined {\n try {\n if (typeof __DCS_PAGES__ !== 'undefined' && __DCS_PAGES__ !== null) return __DCS_PAGES__\n } catch {\n /* not defined — fine */\n }\n return undefined\n}\n\n// =============================================================================\n// R3 — path normalization\n// =============================================================================\n\n/**\n * Normalize a path for comparison. Applied to BOTH sides — the manifest path and\n * the router path — because normalizing only one side is how `/blog/` and\n * `/blog` stayed unequal.\n *\n * - drops `?query` and `#hash` (a router path should not carry them; a caller's\n * `fullPath` might, and silently no-matching on a query string would be a\n * surprise no one would ever debug)\n * - strips trailing slashes\n * - PRESERVES the root: `/` and `''` both normalize to `/`, never to `''`.\n * Collapsing root to the empty string makes the home route unmatchable, which\n * is the highest-traffic route on every site in the fleet.\n */\nexport function normalizeSeoHeadPath(input: string): string {\n if (typeof input !== 'string') return '/'\n let p = input\n const hash = p.indexOf('#')\n if (hash !== -1) p = p.slice(0, hash)\n const query = p.indexOf('?')\n if (query !== -1) p = p.slice(0, query)\n p = p.replace(/\\/+$/, '')\n return p === '' ? '/' : p\n}\n\n/**\n * Find manifest paths that COLLIDE after R3 normalization.\n *\n * WHY THIS IS A BUILD ERROR AND NOT A TIEBREAK (C-361 F6, verified). A manifest\n * carrying both `/blog` and `/blog/` resolves OPPOSITE ways on the two sides of\n * the contract:\n *\n * - the EMITTER walks the manifest in order and writes one file per route, so\n * for colliding paths the LAST write is the one left on disk;\n * - a lookup Map keyed on the normalized path takes the FIRST entry, because\n * that is what \"don't clobber\" means for a map.\n *\n * Whichever side is \"fixed\", the other is then wrong — the duplicate does not\n * merely risk divergence, it GUARANTEES it. There is no correct pick, so the\n * build must refuse instead of picking. The two entries have different slugs and\n * therefore different seo.yaml identities; only the site owner knows which page\n * that URL is.\n *\n * @returns the normalized paths that appear more than once, each with the\n * colliding raw entries, in first-seen order. Empty array = clean.\n */\nexport function findDuplicateNormalizedPaths(\n routes: readonly SeoHeadPageRoute[]\n): Array<{ normalizedPath: string; routes: SeoHeadPageRoute[] }> {\n const groups = new Map<string, SeoHeadPageRoute[]>()\n for (const route of routes) {\n if (!route || typeof route.path !== 'string' || route.path === '') continue\n const key = normalizeSeoHeadPath(route.path)\n const bucket = groups.get(key)\n if (bucket) bucket.push(route)\n else groups.set(key, [route])\n }\n const dupes: Array<{ normalizedPath: string; routes: SeoHeadPageRoute[] }> = []\n for (const [normalizedPath, group] of groups) {\n if (group.length > 1) dupes.push({ normalizedPath, routes: group })\n }\n return dupes\n}\n\n/** Human-readable duplicate report, shared by the build throw and the runtime log. */\nexport function formatDuplicateNormalizedPaths(\n dupes: ReadonlyArray<{ normalizedPath: string; routes: SeoHeadPageRoute[] }>\n): string {\n const lines = dupes.map(\n (d) =>\n ` ${d.normalizedPath} <= ` +\n d.routes.map((r) => `${JSON.stringify(r.path)} (slug ${JSON.stringify(r.slug)})`).join(', ')\n )\n return (\n `pages.yaml declares ${dupes.length} route path(s) that collide after trailing-slash ` +\n `normalization:\\n${lines.join('\\n')}\\n` +\n `The build-time emitter's LAST write wins the output file while the runtime lookup's ` +\n `FIRST entry wins the head — so a colliding manifest guarantees a baked/runtime ` +\n `divergence whichever side is preferred. There is no safe default: the entries carry ` +\n `different slugs and therefore different .dcs/seo.yaml identities. Delete or rename one.`\n )\n}\n\n/**\n * Build the normalized path -> route lookup.\n *\n * On a duplicate normalized path the FIRST entry wins. That is a LAST-RESORT\n * DETERMINISM RULE, not the contract: duplicates are rejected at build time by\n * `findDuplicateNormalizedPaths` (R3), and this branch only ever runs for a\n * manifest that reached a browser without passing that gate. It is deliberately\n * not \"clever\" — an arbitrary-but-stable pick is the least bad thing to do once\n * the loud failure has already been missed.\n */\nexport function buildSeoHeadRouteMap(\n routes: readonly SeoHeadPageRoute[]\n): Map<string, SeoHeadPageRoute> {\n const map = new Map<string, SeoHeadPageRoute>()\n for (const route of routes) {\n if (!route || typeof route.path !== 'string' || route.path === '') continue\n const key = normalizeSeoHeadPath(route.path)\n if (!map.has(key)) map.set(key, route)\n }\n return map\n}\n\n// =============================================================================\n// R5 — recognising the initial navigation\n// =============================================================================\n\n/**\n * Is this `from` route vue-router's START location?\n *\n * `START_LOCATION_NORMALIZED` (vue-router 4.6.3, verified at source) is\n * `{ path: '/', name: undefined, params: {}, query: {}, hash: '', fullPath: '/',\n * matched: [], meta: {}, redirectedFrom: undefined }`. The discriminating\n * triple is `name` unset + `matched` empty + path `/`: any route the visitor has\n * actually been on has matched at least one record.\n *\n * Structural on purpose — importing vue-router to compare against the exported\n * `START_LOCATION` would give this package a framework dependency for one\n * identity check, and would still be a reference comparison that a re-exported\n * or wrapped router could defeat.\n */\nfunction isStartLocation(from: SeoHeadRouteLike | undefined): boolean {\n if (!from || typeof from !== 'object') return false\n if (from.name !== undefined && from.name !== null) return false\n if (!Array.isArray(from.matched) || from.matched.length > 0) return false\n return normalizeSeoHeadPath(typeof from.path === 'string' ? from.path : '') === '/'\n}\n\n// =============================================================================\n// The install\n// =============================================================================\n\n/**\n * Re-assert the `.dcs/seo.yaml` head on every in-app navigation.\n *\n * Adoption is one line at app entry, after the head client exists:\n *\n * ```ts\n * const head = createHead()\n * app.use(head)\n * installSeoHead(router, { head })\n * ```\n *\n * DOES NOT WRITE FOR THE INITIAL DOCUMENT (R5) — and that is now IMPLEMENTED\n * rather than asserted. The previous version of this docstring claimed\n * `afterEach` \"is registered for SUBSEQUENT navigations only\"; there was no such\n * guard, and the claim is false whenever the hook is installed while the initial\n * navigation is still in flight — which is precisely what the adoption snippet\n * above does in the generated app entry, where `app.use(router)` runs BEFORE\n * `router.isReady()` is awaited. The head of the document the browser actually\n * fetched is the baked, owner-approved one and is correct exactly once, on that\n * document; re-asserting it buys nothing and puts the one head that is already\n * right on the same code path as the one that can be wrong. Installing AFTER\n * `await router.isReady()` reaches the same outcome by a different route (the\n * hook simply never sees that navigation) and is equally supported — the guard\n * exists so the outcome does not depend on remembering which.\n *\n * NAMED RESIDUAL (R5, dev only): under `vite dev` there is no baked per-route\n * `<head>` — the shell's is generic — so the skipped initial write leaves the\n * generic head in place until the first in-app navigation. Production is\n * unaffected because production HAS a baked head. The skip is reported through\n * {@link InstallSeoHeadOptions.onResolve} as `initial-navigation`, so it is\n * observable rather than mysterious.\n *\n * @returns the hook's own removal function, as vue-router's `afterEach` returns.\n */\nexport function installSeoHead(\n router: SeoHeadRouterLike,\n options: InstallSeoHeadOptions\n): unknown {\n const head = options.head\n const manifest = readInjectedPages()\n const routes = options.pages ?? manifest?.routes\n const seo = options.seo ?? readInjectedSeo()\n const noindex = options.noindex ?? manifest?.noindex ?? []\n const onResolve = options.onResolve\n\n const noindexSet = new Set(noindex)\n const routeMap =\n routes && routes.length > 0 ? buildSeoHeadRouteMap(routes) : null\n\n // R3 — a colliding manifest is a BUILD error (`dcsSeoPlugin` throws on it).\n // Reaching a browser means that gate was bypassed: say so once, loudly, and\n // carry on with the deterministic pick. This deliberately does NOT throw — by\n // the time this line runs a real visitor is looking at the page, and the worst\n // outcome of a refusal must be that they keep the owner-approved head.\n if (routes && routes.length > 0) {\n const dupes = findDuplicateNormalizedPaths(routes)\n if (dupes.length > 0) {\n console.error(`[dcs-seo] installSeoHead: ${formatDuplicateNormalizedPaths(dupes)}`)\n }\n }\n\n // ONE entry, patched in place. Pushing a new entry per navigation would pile\n // tags up for the lifetime of the session. It is created LAZILY on the first\n // MATCH, so a session that never matches never touches the head at all (R2).\n let entry: SeoHeadEntryLike | null = null\n\n // R5 — armed until the initial navigation is seen (or until the first\n // navigation proves it was installed after `isReady()`). Bounds the guard to\n // AT MOST ONE skipped write for the lifetime of the session, so a route that\n // somehow looks start-like later can never silently lose its head.\n let initialNavigationPending = true\n\n // ── S6 / S4 — the baked head, captured NOW ────────────────────────────────\n // Taken at INSTALL time on purpose: this runs at app entry, after\n // `createHead()` but before `app.mount()`, and Unhead's DOM write is\n // debounced to a later task — so `document.head` at this instant holds the\n // tags the BUILD baked and nothing else. Snapshotting later would capture\n // Unhead's own output and let the sweep delete live runtime tags.\n // `<title>` and `ld+json` are never collected (S3 / S1).\n let bakedManagedTags: Element[] = snapshotBakedManagedHeadTags()\n let bakedSweepPending = true\n\n return router.afterEach((to, from, failure) => {\n const path = to && typeof to.path === 'string' ? to.path : ''\n const normalizedPath = normalizeSeoHeadPath(path)\n\n // ── R4 — a FAILED navigation is not a navigation ─────────────────────────\n // vue-router fires `afterEach` for aborted / cancelled / duplicated\n // navigations too, and on failure the visitor is STILL ON `from`. Writing\n // `to`'s head here is the exact divergence this module exists to close,\n // just pointed the other way: the guard rejects `/pricing -> /account` and\n // the visitor reads pricing under the account head, `noindex` and all.\n // Checked BEFORE the manifest branches so it holds even when inert.\n if (failure) {\n onResolve?.({\n path,\n normalizedPath,\n matched: null,\n reason: 'navigation-failed',\n title: null,\n sweptBakedTags: 0,\n })\n return\n }\n\n // ── R5 — the initial document keeps its baked head ───────────────────────\n if (initialNavigationPending) {\n const initial = isStartLocation(from)\n initialNavigationPending = false\n if (initial) {\n onResolve?.({\n path,\n normalizedPath,\n matched: null,\n reason: 'initial-navigation',\n title: null,\n sweptBakedTags: 0,\n })\n return\n }\n }\n\n // No manifest reached the runtime (plugin absent, or an empty pages.yaml).\n // INERT, NOT FALLBACK: without the manifest there is no trustworthy key, and\n // the untrustworthy-key case is precisely R2.\n if (!routeMap) {\n onResolve?.({\n path,\n normalizedPath,\n matched: null,\n reason: 'no-manifest',\n title: null,\n sweptBakedTags: 0,\n })\n return\n }\n\n const matched = routeMap.get(normalizedPath)\n\n // ── R2 — the whole point of this branch is that it writes NOTHING ────────\n // No default title. No clear. No empty patch. `return` is the behaviour.\n if (!matched) {\n // NOTE the sweep latch is NOT spent here. A site whose visitor's first\n // click is a 404 (boogie-babies' catch-all is the normal case, R2) must\n // still get its sweep on the first REAL match, not lose it to a no-match.\n onResolve?.({\n path,\n normalizedPath,\n matched: null,\n reason: 'no-match',\n title: null,\n sweptBakedTags: 0,\n })\n return\n }\n\n // ── R1 — the slug comes from the MANIFEST, never from `to.meta` ──────────\n const forceNoindex = noindexSet.has(matched.path) || (!!matched.slug && noindexSet.has(matched.slug))\n\n // Resolve through the SHIPPED emitter, with the manifest path (not the\n // router path) so the canonical is byte-identical to the baked one — KEPT\n // bakes `/blog/`, its router says `/blog`, and the canonical must not move.\n const tags = buildHeadTags(matched.slug, matched.path, seo, {\n // MUST MATCH `dcsSeoPlugin`'s emitter call. `includeKeywords` defaults to\n // false to keep the legacy `useSEO` runtime byte-identical to its history;\n // the emitter opts in, so a re-assert that did not would leave the\n // PREVIOUS post's baked `<meta name=\"keywords\">` in the DOM while title,\n // description and canonical all moved (C-361 F4, verified at\n // dcsSeoPlugin.ts). This is a new API — the old composable's omission is\n // not a compatibility constraint on it.\n includeKeywords: true,\n fallbackTitle: matched.title,\n robots: forceNoindex ? 'noindex, nofollow' : undefined,\n })\n\n const input: SeoHeadInput = { title: tags.title, meta: tags.meta, link: tags.link }\n\n // ── S6 — SWEEP ONCE, on the FIRST MATCH (DEFECT F-A, Q-453 = A) ──────────\n // Unhead removes only what it owns, and it never owned the baked tags. So\n // remove the baked managed tags THIS input does not re-declare, handing the\n // managed head to the runtime for the rest of the session.\n //\n // BEFORE the push, so Unhead's reconciliation pass never sees the strays —\n // and the excluded set (S2) is exactly the identities in `input`, so this\n // can only ever touch elements Unhead has no entry for and therefore cannot\n // have adopted. Once is SUFFICIENT, not a budget: afterwards every managed\n // element is either runtime-owned (Unhead drops it correctly) or gone.\n let sweptBakedTags = 0\n if (bakedSweepPending) {\n bakedSweepPending = false\n sweptBakedTags = sweepBakedManagedHeadTags(bakedManagedTags, input)\n // Spent — drop the DOM references rather than hold them for the session.\n bakedManagedTags = []\n }\n\n if (entry) entry.patch(input)\n else entry = head.push(input)\n\n onResolve?.({\n path,\n normalizedPath,\n matched,\n reason: 'applied',\n title: tags.title,\n sweptBakedTags,\n })\n })\n}\n","/**\n * P1 — baked-vs-rendered `<head>` honesty rail.\n *\n * ## The defect this exists for\n *\n * `.dcs/seo.yaml` is the owner-approved, portal-managed source of truth for a\n * page's `<title>` and description, and {@link buildHeadTags} bakes it into the\n * per-route static HTML. But the SAME resolver is also reachable at runtime via\n * `useSEO().applyHead(overrides)`, and an `overrides.title` is used VERBATIM —\n * it does not go through `titleTemplate`. So a view that hardcodes\n * `applyHead({ title: 'Our Services' })` silently replaces the approved title\n * the instant the app boots.\n *\n * Measured on live production (2026-07-27, before the site-local fix):\n *\n * ```\n * route baked (non-JS crawler) runtime (Google + humans)\n * / Rochester Hills Handyman, Repairs & Carpentry | Iron Oak Iron Oak Contractors | Crafted Repairs …\n * /services Handyman, Carpentry & Home Repairs in SE Michigan | … Our Services\n * ```\n *\n * Two crawlers, two different sites. Nothing failed. Nothing logged. The\n * approved SEO decision (C-147 / Q-141=B) was live for AI crawlers and reverted\n * for Google — for the whole time since it shipped.\n *\n * ## What the rail asserts\n *\n * After the build, for every route the emitter wrote, the prerender browser is\n * already loading the page. This rail reads `document.title` and the effective\n * `meta[name=description]` from that SAME render and compares them to the values\n * baked into the file on disk. Any divergence names the route and BOTH values.\n *\n * ## Why the pass condition is stable (not a moving target)\n *\n * A site whose views call `applyHead()` with no title/description overrides\n * produces byte-identical tags at runtime and at build time — same\n * {@link buildHeadTags}, same `seo.yaml` (the plugin bakes it into\n * `__DCS_SEO__`). Zero divergence is therefore the *structural* outcome of\n * seo.yaml being the only writer, not a threshold someone has to keep tuning.\n *\n * ## Escape hatches (visible by construction)\n *\n * `mode` can be lowered to `warn`/`off` and individual routes can be listed in\n * `allow`, but both live in `.dcs/seo.yaml` (portal-owned, in git, reviewable)\n * or in an env var that prints in the build log — there is no silent way to\n * switch this off. Every allowed route is still logged.\n */\n\n/** How a divergence is reported. */\nexport type HonestyMode = 'error' | 'warn' | 'off'\n\n/** One route's baked and rendered head values. */\nexport interface HeadObservation {\n /** Route path as it appears in `pages.yaml` (e.g. `/services`). */\n route: string\n /** `<title>` as written into the emitted file (still HTML-escaped). */\n bakedTitle: string | null\n /** `document.title` after the app mounted. */\n runtimeTitle: string | null\n /** `meta[name=description]@content` as written into the emitted file. */\n bakedDescription: string | null\n /** The EFFECTIVE `meta[name=description]` in the rendered DOM (the last one). */\n runtimeDescription: string | null\n /** How many `meta[name=description]` tags the rendered DOM carried. */\n runtimeDescriptionCount?: number\n}\n\n/** A single baked≠rendered divergence. */\nexport interface HeadHonestyViolation {\n route: string\n field: 'title' | 'description'\n baked: string | null\n runtime: string | null\n}\n\n/** Configuration for the rail. */\nexport interface HeadHonestyOptions {\n /** `error` (default) fails the build, `warn` logs, `off` skips entirely. */\n mode?: HonestyMode\n /** Also compare the meta description (default `true`). */\n checkDescription?: boolean\n /**\n * Route paths exempted from the comparison. An exemption is still LOGGED, so\n * \"who turned this off for which route\" is answerable from the build output.\n */\n allow?: string[]\n}\n\n// -----------------------------------------------------------------------------\n// Normalisation\n// -----------------------------------------------------------------------------\n\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n nbsp: ' ',\n mdash: '—',\n ndash: '–',\n hellip: '…',\n copy: '©',\n reg: '®',\n trade: '™',\n rsquo: '’',\n lsquo: '‘',\n ldquo: '“',\n rdquo: '”',\n}\n\n/**\n * Decode the HTML character references the emitter can produce.\n *\n * This is NOT a general-purpose entity decoder — it covers the set\n * `spliceHeadHtml`'s `escapeAttr` emits (`& \" < >`) plus numeric references and\n * the handful of named ones that appear in real CMS copy. Anything unrecognised\n * is left verbatim, which is the safe direction: an unknown entity can only ever\n * cause a *reported* divergence to be investigated, never a real one to be\n * silently swallowed.\n */\nexport function decodeHtmlEntities(value: string): string {\n return value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/g, (whole, body: string) => {\n if (body[0] === '#') {\n const isHex = body[1] === 'x' || body[1] === 'X'\n const code = Number.parseInt(isHex ? body.slice(2) : body.slice(1), isHex ? 16 : 10)\n if (!Number.isFinite(code) || code < 0 || code > 0x10ffff) return whole\n try {\n return String.fromCodePoint(code)\n } catch {\n return whole\n }\n }\n const named = NAMED_ENTITIES[body.toLowerCase()]\n return named ?? whole\n })\n}\n\n/**\n * Put a baked (HTML-source) value and a rendered (DOM) value on the same\n * footing before comparing them.\n *\n * The three transforms exist because of three MEASURED false-positive\n * mechanisms, not from caution:\n * - **entity decode** — the baked side carries `Repairs &amp; Carpentry`\n * while `document.title` yields `Repairs & Carpentry`. Identical strings.\n * - **whitespace collapse** — `document.title` is specified to strip and\n * collapse ASCII whitespace; the emitted `<title>` keeps the author's.\n * - **Unicode NFC** — a composed `é` and a decomposed `é` render identically\n * and mean the same thing; only the byte sequence differs.\n *\n * Deliberately NOT normalised: case, punctuation, and typographic look-alikes\n * (`-` vs `—`, `'` vs `’`). Those are real content differences and a rail that\n * hides them is the vacuous-green shape this whole class of work exists to kill.\n */\nexport function normalizeHeadText(value: string | null | undefined): string | null {\n if (value === null || value === undefined) return null\n const decoded = decodeHtmlEntities(value)\n const collapsed = decoded.replace(/\\s+/g, ' ').trim()\n return collapsed.normalize('NFC')\n}\n\n// -----------------------------------------------------------------------------\n// Baked-side extraction\n// -----------------------------------------------------------------------------\n\n/** The baked `<title>` / description of an emitted document. */\nexport interface BakedHead {\n title: string | null\n description: string | null\n}\n\n/**\n * Read one attribute out of a tag, honouring the QUOTE DELIMITER.\n *\n * The naive form `/\\battr\\s*=\\s*[\"']([\\s\\S]*?)[\"']/` terminates on the first\n * quote OR apostrophe, so `content=\"Rochester Hills' favorite venue\"` reads back\n * as `Rochester Hills`. Measured 2026-07-27 on the candidate matrix: that bug\n * alone manufactured P1 \"divergences\" on every route whose approved description\n * contains an apostrophe (boogie-babies /, mi-handyman /, all 9 iron-oak blog\n * posts) — a rail that reds a build over its own parser is worse than no rail.\n */\nexport function readTagAttr(tag: string, attr: string): string | null {\n const re = new RegExp(`\\\\b${attr}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s\"'=<>\\`]+))`, 'i')\n const m = re.exec(tag)\n if (!m) return null\n return m[1] ?? m[2] ?? m[3] ?? ''\n}\n\n/**\n * Read the baked `<title>` and `meta[name=description]` out of an emitted file.\n *\n * Regex-based on purpose — it mirrors `spliceHeadHtml`, runs over the emitter's\n * own deterministic output, and keeps a heavy HTML parser out of the build.\n */\nexport function extractBakedHead(html: string): BakedHead {\n const titleMatch = /<title[^>]*>([\\s\\S]*?)<\\/title>/i.exec(html)\n const title = titleMatch ? titleMatch[1] : null\n\n let description: string | null = null\n const metaRe = /<meta\\b[^>]*>/gi\n let m: RegExpExecArray | null\n while ((m = metaRe.exec(html)) !== null) {\n const tag = m[0]\n const name = readTagAttr(tag, 'name')\n if (name?.toLowerCase() !== 'description') continue\n description = readTagAttr(tag, 'content') ?? ''\n }\n\n return { title, description }\n}\n\n// -----------------------------------------------------------------------------\n// Comparison\n// -----------------------------------------------------------------------------\n\n/**\n * Compare every observation and return the divergences.\n *\n * A `null` on either side is meaningful and reported: a runtime that DELETES the\n * baked description is the same class of silent undoing as one that rewrites it.\n * The one exception is a route where BOTH sides are absent — nothing was claimed,\n * so nothing was undone.\n */\nexport function findHeadHonestyViolations(\n observations: HeadObservation[],\n options: HeadHonestyOptions = {}\n): HeadHonestyViolation[] {\n const { checkDescription = true, allow = [] } = options\n const allowed = new Set(allow)\n const violations: HeadHonestyViolation[] = []\n\n for (const obs of observations) {\n if (allowed.has(obs.route)) continue\n\n const bakedTitle = normalizeHeadText(obs.bakedTitle)\n const runtimeTitle = normalizeHeadText(obs.runtimeTitle)\n if (!(bakedTitle === null && runtimeTitle === null) && bakedTitle !== runtimeTitle) {\n violations.push({ route: obs.route, field: 'title', baked: bakedTitle, runtime: runtimeTitle })\n }\n\n if (!checkDescription) continue\n const bakedDescription = normalizeHeadText(obs.bakedDescription)\n const runtimeDescription = normalizeHeadText(obs.runtimeDescription)\n if (\n !(bakedDescription === null && runtimeDescription === null) &&\n bakedDescription !== runtimeDescription\n ) {\n violations.push({\n route: obs.route,\n field: 'description',\n baked: bakedDescription,\n runtime: runtimeDescription,\n })\n }\n }\n\n return violations\n}\n\n/** `null` renders as an explicit marker so an absent tag never looks like `''`. */\nfunction display(value: string | null): string {\n return value === null ? '(absent)' : JSON.stringify(value)\n}\n\n/**\n * Render the full report. Every violation names the ROUTE and BOTH values, so\n * the operator never has to reproduce the divergence to act on it — which is the\n * difference between a rail people fix and a rail people mute.\n */\nexport function formatHeadHonestyReport(violations: HeadHonestyViolation[]): string {\n const routes = new Set(violations.map((v) => v.route))\n const lines: string[] = [\n `[dcs-seo] head honesty: ${violations.length} divergence(s) across ${routes.size} route(s) — ` +\n `the BAKED <head> (what a non-JS AI crawler receives) does not match the RENDERED <head> ` +\n `(what Google and every human sees).`,\n ]\n for (const v of violations) {\n lines.push(` ${v.route} [${v.field}]`)\n lines.push(` baked : ${display(v.baked)}`)\n lines.push(` rendered: ${display(v.runtime)}`)\n }\n lines.push(\n ` Fix: delete the hardcoded title/description from the route's ` +\n `applyHead({…}) call so .dcs/seo.yaml is the only writer. ` +\n `(applyHead's \\`title\\` override is used VERBATIM — it does NOT go through ` +\n `global.titleTemplate, which is why an override that \"looks right\" still diverges.)`\n )\n return lines.join('\\n')\n}\n\n/** Thrown in `error` mode so the build goes red on a divergence. */\nexport class HeadHonestyError extends Error {\n readonly violations: HeadHonestyViolation[]\n constructor(violations: HeadHonestyViolation[]) {\n super(formatHeadHonestyReport(violations))\n this.name = 'HeadHonestyError'\n this.violations = violations\n }\n}\n\n// -----------------------------------------------------------------------------\n// Mode resolution\n// -----------------------------------------------------------------------------\n\n/** Inputs that can set the rail's severity, in increasing precedence. */\nexport interface HonestyModeInput {\n /** Compiled-in default. */\n fallback?: HonestyMode\n /** The plugin option in `vite.config.ts`. */\n option?: HonestyMode | boolean\n /** The per-site escape hatch in `.dcs/seo.yaml`. */\n seoYaml?: HonestyMode | boolean\n /** The env override (e.g. `DCS_SEO_HEAD_HONESTY=warn`) — highest precedence. */\n env?: string | undefined\n}\n\nfunction coerceMode(value: HonestyMode | boolean | string | undefined): HonestyMode | undefined {\n if (value === undefined || value === null) return undefined\n if (value === true) return 'error'\n if (value === false) return 'off'\n const normalized = String(value).trim().toLowerCase()\n if (normalized === 'error' || normalized === 'fail' || normalized === 'strict') return 'error'\n if (normalized === 'warn' || normalized === 'warning' || normalized === 'report') return 'warn'\n if (normalized === 'off' || normalized === 'false' || normalized === 'none') return 'off'\n return undefined\n}\n\n/**\n * Resolve the effective severity.\n *\n * Precedence is env > seo.yaml > plugin option > default, i.e. the MOST\n * operator-visible signal wins. An unrecognised value is ignored rather than\n * treated as \"off\" — a typo'd escape hatch must never silently disable a gate.\n */\nexport function resolveHonestyMode(input: HonestyModeInput): HonestyMode {\n return (\n coerceMode(input.env) ??\n coerceMode(input.seoYaml) ??\n coerceMode(input.option) ??\n input.fallback ??\n 'error'\n )\n}\n\n// -----------------------------------------------------------------------------\n// Execution assertion (C-338) — \"did this rail actually run?\"\n// -----------------------------------------------------------------------------\n\n/**\n * What the render pass was able to observe, so the rail can prove it RAN.\n *\n * ## The defect this exists for\n *\n * C-334 shipped P1 with an environment-dependent silent skip: when\n * `createPlaywrightRenderer` found no playwright/browser it warned and returned\n * no observations, and the caller then *skipped the assert entirely* — so an\n * `error`-severity rail let the build pass having checked nothing. That is the\n * same shape as C-322 (the snapshot rail was vacuous for months because `gh` was\n * missing on the runner) and the exact class this campaign exists to close.\n *\n * A rail is only a gate if \"it did not run\" is itself a failure. In `error`\n * mode the three non-execution shapes below are hard failures; in `warn` mode\n * they are warnings that name exactly what was not checked.\n */\nexport interface HeadHonestyExecution {\n /** Did the build have a headless renderer at all? */\n rendererAvailable: boolean\n /** Routes that reached the renderer (the denominator). */\n eligibleRoutes: string[]\n /** Routes that produced a baked-vs-rendered observation (the numerator). */\n observedRoutes: string[]\n /** Routes that never reached the renderer, with why. */\n filteredRoutes: Array<{ route: string; reason: string }>\n /** Every route in the manifest, for the \"0 of N\" arithmetic. */\n totalRoutes: number\n}\n\n/** Why the rail did not run (empty ⇒ it ran). */\nexport function findHeadHonestyExecutionFaults(ex: HeadHonestyExecution): string[] {\n const faults: string[] = []\n if (!ex.rendererAvailable) {\n faults.push(\n 'no headless renderer was available (playwright missing, or Chromium failed to launch), ' +\n 'so NOT ONE route\\'s rendered <head> was observed'\n )\n }\n if (ex.eligibleRoutes.length === 0) {\n const byReason = new Map<string, string[]>()\n for (const f of ex.filteredRoutes) {\n byReason.set(f.reason, [...(byReason.get(f.reason) ?? []), f.route])\n }\n const detail =\n [...byReason.entries()].map(([reason, routes]) => `${reason}: ${routes.join(', ')}`).join('; ') ||\n 'the route manifest was empty'\n faults.push(\n `0 of ${ex.totalRoutes} route(s) were eligible for observation (${detail}), ` +\n 'so the baked-vs-rendered comparison had nothing to compare'\n )\n } else {\n const observed = new Set(ex.observedRoutes)\n const unobserved = ex.eligibleRoutes.filter((r) => !observed.has(r))\n if (unobserved.length > 0) {\n faults.push(\n `${ex.observedRoutes.length} of ${ex.eligibleRoutes.length} eligible route(s) produced an ` +\n `observation; these were NOT checked: ${unobserved.join(', ')}`\n )\n }\n }\n return faults\n}\n\n/** Render the \"this rail did not run\" report — it names what went unchecked. */\nexport function formatHeadHonestyExecutionReport(\n ex: HeadHonestyExecution,\n faults: string[]\n): string {\n const lines: string[] = [\n '[dcs-seo] head honesty DID NOT RUN — the baked/rendered <head> divergence class was ' +\n 'NOT checked for this build:',\n ]\n for (const f of faults) lines.push(` - ${f}`)\n lines.push(\n ` observed ${ex.observedRoutes.length} / eligible ${ex.eligibleRoutes.length} / ` +\n `manifest ${ex.totalRoutes} route(s); renderer ${ex.rendererAvailable ? 'available' : 'ABSENT'}`\n )\n lines.push(\n ' Fix: install the renderer (`pnpm add -D playwright && npx playwright install chromium`) ' +\n 'BEFORE the build step, or lower this rail deliberately and visibly ' +\n '(DCS_SEO_HEAD_HONESTY=warn / .dcs/seo.yaml headHonesty.mode). A rail that silently ' +\n 'does not run is the exact class this work exists to close.'\n )\n return lines.join('\\n')\n}\n\n/** Thrown in `error` mode when the P1 rail could not run. */\nexport class HeadHonestyNotRunError extends Error {\n readonly execution: HeadHonestyExecution\n readonly faults: string[]\n constructor(execution: HeadHonestyExecution, faults: string[]) {\n super(formatHeadHonestyExecutionReport(execution, faults))\n this.name = 'HeadHonestyNotRunError'\n this.execution = execution\n this.faults = faults\n }\n}\n\n/**\n * Assert the rail RAN. Throws {@link HeadHonestyNotRunError} in `error` mode,\n * warns in `warn` mode. Returns `true` when the comparison is worth running\n * (i.e. there is at least one observation to compare).\n */\nexport function assertHeadHonestyExecuted(\n execution: HeadHonestyExecution,\n options: { mode: HonestyMode }\n): boolean {\n if (options.mode === 'off') return false\n const faults = findHeadHonestyExecutionFaults(execution)\n if (faults.length === 0) return true\n\n const report = formatHeadHonestyExecutionReport(execution, faults)\n if (options.mode === 'warn') {\n console.warn(report)\n console.warn(\n '[dcs-seo] head honesty is in WARN mode; this build is NOT gated on the above ' +\n '(in error mode it would be RED).'\n )\n // Partial observations are still worth comparing — a warn-mode build should\n // report the divergences it CAN see, having already said what it cannot.\n return execution.observedRoutes.length > 0\n }\n throw new HeadHonestyNotRunError(execution, faults)\n}\n\n/**\n * Apply the rail: throw in `error` mode, log in `warn` mode, do nothing in\n * `off`. Returns the violations so callers can report counts.\n */\nexport function assertHeadHonesty(\n observations: HeadObservation[],\n options: HeadHonestyOptions & { mode: HonestyMode; debug?: boolean }\n): HeadHonestyViolation[] {\n const { mode, allow = [], debug = false } = options\n if (mode === 'off') {\n console.warn(\n '[dcs-seo] head honesty is OFF for this build — the baked/rendered <title> ' +\n 'divergence class is NOT being checked.'\n )\n return []\n }\n\n if (allow.length > 0) {\n console.warn(\n `[dcs-seo] head honesty: ${allow.length} route(s) EXEMPTED from the baked==rendered ` +\n `assert: ${allow.join(', ')}`\n )\n }\n\n const violations = findHeadHonestyViolations(observations, options)\n\n if (violations.length === 0) {\n console.log(\n `[dcs-seo] head honesty: ${observations.length} route(s) verified — baked <head> == rendered <head>`\n )\n return violations\n }\n\n if (mode === 'warn') {\n console.warn(formatHeadHonestyReport(violations))\n console.warn(\n '[dcs-seo] head honesty is in WARN mode; this build is NOT gated on the divergence above.'\n )\n return violations\n }\n\n if (debug) console.error(formatHeadHonestyReport(violations))\n throw new HeadHonestyError(violations)\n}\n","/**\n * Build-time BODY prerender for the Vue-SPA static-HTML emitter.\n *\n * The companion to `emitStaticSeoHtml` (which bakes per-route `<head>` meta +\n * JSON-LD). Today every SPA route's emitted `<body>` is literally\n * `<div id=\"app\"></div>` — allow-listed AI crawlers see the `@graph` + llms.txt\n * but ZERO body prose (blog copy, service descriptions, GEO-citable content).\n * This module fills the mount container with the REAL rendered DOM so a non-JS\n * crawler sees the page's body content.\n *\n * ── Why a render pass, not a content.yaml dump ────────────────────────────────\n * `.dcs/content.yaml` stores only visual-editor TEXT OVERRIDES — \"Vue component\n * `defaults` objects are the source of truth for baseline text\" (its own header).\n * Blog-post prose lives entirely in the site's `src/data/*.ts` and renders via\n * `v-html`; it is NOT in content.yaml at all. So the only faithful, generic way\n * to obtain a route's body is to RENDER the built app. This module is therefore\n * renderer-agnostic: it takes a {@link RouteRenderer} (the production default is\n * a headless-Playwright renderer of the just-built `dist/`; tests inject a fake)\n * and is otherwise pure — deterministic, `fs`-only, and unit-testable.\n *\n * ── Fabrication safety (the A-34 scar) ────────────────────────────────────────\n * A prior cms SSR path baked fabricated review defaults into crawler-visible\n * HTML. The default renderer drives the PRODUCTION client bundle, where Vite has\n * statically replaced `import.meta.env.DEV` with `false`, so the dev-only\n * `defaults` honesty gate (`useReviewContent.ts`) resolves to the honest empty\n * state — fabrication is structurally impossible, not merely avoided. Any route\n * that instead CRASHES (uncaught exception, or the app never mounts) surfaces as\n * a {@link BodyPrerenderError} and FAILS THE BUILD LOUD rather than shipping a\n * broken/empty body.\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { PageRouteEntry } from './pagesManifest'\nimport { matchesExcludedGlob } from './sitemap'\nimport { extractBakedHead, type HeadObservation } from './headHonesty'\n\n/**\n * Normalise a route path to the on-disk output file the head emitter wrote:\n * '/' -> '<outDir>/index.html'\n * '/services' -> '<outDir>/services/index.html'\n * '/blog/my-post' -> '<outDir>/blog/my-post/index.html'\n *\n * Lives here (the lower-level module) so both this pass and `dcsSeoPlugin`'s\n * head emitter import it without a plugin↔module cycle.\n */\nexport function routeToOutputFile(outDir: string, routePath: string): string {\n const trimmed = routePath.replace(/^\\/+/, '').replace(/\\/+$/, '')\n if (trimmed === '') return path.join(outDir, 'index.html')\n return path.join(outDir, ...trimmed.split('/'), 'index.html')\n}\n\n/**\n * The `<head>` values the app left behind after it mounted — i.e. what a\n * JS-rendering crawler (and every human) actually sees. Captured in the SAME\n * render pass that produces the body, so the P1 head-honesty rail costs no extra\n * page loads. See `headHonesty.ts` for what is done with it.\n */\nexport interface RenderedHead {\n /** `document.title` after mount. */\n title: string | null\n /** The EFFECTIVE `meta[name=description]` (the last one in the DOM). */\n description: string | null\n /** How many `meta[name=description]` tags exist — >1 is itself a smell. */\n descriptionCount: number\n}\n\n/** The rendered body of one route plus any fatal render errors observed. */\nexport interface RenderedRoute {\n /** The `innerHTML` of the app mount container (`#app`) after the app rendered. */\n bodyHtml: string\n /**\n * The post-mount `<head>` observations, when the renderer captures them. The\n * fake renderers in unit tests may omit it; the production Playwright renderer\n * always provides it.\n */\n head?: RenderedHead\n /**\n * FATAL render errors (uncaught exceptions / failed mount) that mean the body\n * is untrustworthy. A non-empty list makes `prerenderBodies` throw a\n * {@link BodyPrerenderError} so the build fails loud instead of shipping wrong\n * content. Benign offline noise (blocked external asset, expected API 401) is\n * NOT reported here by the default renderer.\n */\n errors: string[]\n}\n\n/**\n * A build-time renderer that returns the rendered body for a route path. The\n * production implementation ({@link createPlaywrightRenderer}) drives the built\n * SPA in a headless browser; unit tests pass a deterministic fake.\n */\nexport interface RouteRenderer {\n renderRoute(routePath: string): Promise<RenderedRoute>\n}\n\n/**\n * Thrown when a route's render produced FATAL errors (a crash or a mount\n * failure). Deliberately escapes the plugin's never-break-the-build try/catch so\n * a component that would crash or emit wrong content FAILS THE BUILD (hard\n * requirement: never ship fabricated/broken body content).\n */\nexport class BodyPrerenderError extends Error {\n readonly route: string\n readonly errors: string[]\n constructor(route: string, errors: string[]) {\n super(\n `[dcs-seo] body prerender FAILED for route ${route}: ${errors.join(' | ')}. ` +\n `Refusing to ship a broken/empty body — fix the route or disable body ` +\n `prerender for this site (seo.yaml prerenderBody: false).`\n )\n this.name = 'BodyPrerenderError'\n this.route = route\n this.errors = errors\n }\n}\n\n/**\n * Replace the inner content of the FIRST `<div id=\"app\">…</div>` mount container\n * with `bodyInnerHtml`, leaving `<head>`, the app `<script>`, and everything\n * else untouched. Free of a DOM dependency (mirrors `spliceHeadHtml`), and\n * robust to nested `<div>`s via depth counting so it also works when re-run over\n * an already-filled container (idempotent given a deterministic renderer).\n *\n * Returns the HTML unchanged when there is no `#app` container (defensive — the\n * caller logs + skips), so a non-SPA shell can never be corrupted.\n */\nexport function spliceBodyHtml(html: string, bodyInnerHtml: string): string {\n const openRe = /<div\\s+[^>]*\\bid=[\"']app[\"'][^>]*>/i\n const openMatch = openRe.exec(html)\n if (!openMatch) return html\n\n const openEnd = openMatch.index + openMatch[0].length\n\n // Find the matching </div> for the opening #app tag via depth counting over\n // every subsequent <div…> / </div>. Void/self-closing divs don't exist, so a\n // simple open/close depth walk finds the true container close even with nested\n // markup (the rendered app tree).\n const tagRe = /<(\\/?)div\\b[^>]*>/gi\n tagRe.lastIndex = openEnd\n let depth = 1\n let closeStart = -1\n let m: RegExpExecArray | null\n while ((m = tagRe.exec(html)) !== null) {\n if (m[1] === '/') {\n depth -= 1\n if (depth === 0) {\n closeStart = m.index\n break\n }\n } else {\n depth += 1\n }\n }\n // Malformed (no matching close) — leave the document alone rather than risk a\n // truncated write.\n if (closeStart === -1) return html\n\n return html.slice(0, openEnd) + bodyInnerHtml + html.slice(closeStart)\n}\n\n/** Inputs for {@link prerenderBodies}. */\nexport interface PrerenderBodiesParams {\n /** Absolute path to the built SPA output directory (`dist`). */\n outDir: string\n /** The SAME route set the head emitter used (from `.dcs/pages.yaml`). */\n routes: PageRouteEntry[]\n /** The renderer that returns each route's rendered body. */\n renderer: RouteRenderer\n /** Routes to skip entirely — matched against route `path` OR `slug`. */\n exclude?: string[]\n /**\n * Routes that received `robots: noindex,nofollow` (auth-gated / private). These\n * are NOT body-prerendered — a noindex/auth route must never leak rendered body\n * content into `dist/` (hard requirement). Matched against `path` OR `slug`.\n */\n noindex?: string[]\n /** `pages.yaml` top-level `excluded:` globs — matching routes are skipped. */\n excludedGlobs?: string[]\n /**\n * Write the rendered body back into the per-route file (default `true`).\n *\n * Set `false` to run the pass purely as an OBSERVER: routes are still rendered\n * and their `<head>` still captured for the P1 honesty rail, but `dist/` is not\n * touched. That is what makes head honesty checkable on a site that has opted\n * out of body prerender (`seo.yaml prerenderBody: false`) without giving it a\n * second, duplicate browser pass.\n */\n spliceBody?: boolean\n debug?: boolean\n}\n\n/** Why a route never reached the renderer. */\nexport type PrerenderFilterReason =\n | 'excluded'\n | 'excluded-glob'\n | 'noindex'\n | 'no-head-file'\n\n/** One route the pass never rendered, and why. */\nexport interface FilteredRoute {\n route: string\n reason: PrerenderFilterReason\n}\n\n/** Outcome of a body-prerender pass. */\nexport interface PrerenderBodiesResult {\n /** Number of per-route files whose body was filled. */\n prerendered: number\n /** Number of routes skipped (excluded / noindex / missing head file / empty). */\n skipped: number\n /** The route paths that were prerendered, in order (for logging/tests). */\n routes: string[]\n /**\n * Per-route baked-vs-rendered `<head>` observations, collected for every route\n * this pass rendered (including ones whose body was skipped as empty). Consumed\n * by the P1 rail; empty when the renderer does not report head values.\n */\n headObservations: HeadObservation[]\n /**\n * Every route that actually reached `renderer.renderRoute` — i.e. the set the\n * P1 head-honesty rail COULD have observed. This is the denominator that makes\n * \"the rail ran\" checkable: `headObservations.length < eligibleRoutes.length`\n * means some eligible route produced no observation, and an empty\n * `eligibleRoutes` means the pass checked nothing at all (C-338).\n */\n eligibleRoutes: string[]\n /**\n * Routes that never reached the renderer, each with the reason. Lets the\n * caller name exactly what was NOT checked instead of reporting a bare zero.\n */\n filteredRoutes: FilteredRoute[]\n}\n\n/**\n * For each emitted route: render its body and splice it into the per-route\n * `dist/<path>/index.html` the head emitter already wrote. Reuses the exact same\n * exclude/excluded-glob filters as `emitStaticSeoHtml`, and ADDITIONALLY skips\n * every `noindex` route (auth-gated/private pages get a head-only file, never a\n * prerendered body).\n *\n * FAILS LOUD (throws {@link BodyPrerenderError}) when a route's render reports\n * fatal errors — better a red build than fabricated/broken crawler content.\n * Pure aside from `fs` reads/writes; the browser lives behind `renderer`.\n */\nexport async function prerenderBodies(\n params: PrerenderBodiesParams\n): Promise<PrerenderBodiesResult> {\n const {\n outDir,\n routes,\n renderer,\n exclude = [],\n noindex = [],\n excludedGlobs = [],\n spliceBody = true,\n debug = false,\n } = params\n\n const excludeSet = new Set(exclude)\n const noindexSet = new Set(noindex)\n\n let prerendered = 0\n let skipped = 0\n const done: string[] = []\n const headObservations: HeadObservation[] = []\n const eligibleRoutes: string[] = []\n const filteredRoutes: FilteredRoute[] = []\n\n for (const route of routes) {\n // (1) Same exclusions as the head emitter.\n if (excludeSet.has(route.path) || (route.slug && excludeSet.has(route.slug))) {\n skipped += 1\n filteredRoutes.push({ route: route.path, reason: 'excluded' })\n if (debug) console.log(`[dcs-seo] body-prerender skip (excluded): ${route.path}`)\n continue\n }\n if (matchesExcludedGlob(route.path, excludedGlobs)) {\n skipped += 1\n filteredRoutes.push({ route: route.path, reason: 'excluded-glob' })\n if (debug) console.log(`[dcs-seo] body-prerender skip (excluded glob): ${route.path}`)\n continue\n }\n // (2) HARD REQUIREMENT: never prerender a noindex / auth-gated route's body.\n if (noindexSet.has(route.path) || (route.slug && noindexSet.has(route.slug))) {\n skipped += 1\n filteredRoutes.push({ route: route.path, reason: 'noindex' })\n if (debug) console.log(`[dcs-seo] body-prerender skip (noindex/auth): ${route.path}`)\n continue\n }\n\n const outFile = routeToOutputFile(outDir, route.path)\n if (!fs.existsSync(outFile)) {\n // The head emitter should have written this; if not, there's nothing to\n // fill. Skip rather than fabricate a file.\n skipped += 1\n filteredRoutes.push({ route: route.path, reason: 'no-head-file' })\n if (debug) console.log(`[dcs-seo] body-prerender skip (no head file): ${route.path}`)\n continue\n }\n\n // From here the route reaches the renderer, so P1 is entitled to an\n // observation for it. Recorded BEFORE the render so a route that throws is\n // still counted as eligible-but-unobserved rather than vanishing.\n eligibleRoutes.push(route.path)\n const rendered = await renderer.renderRoute(route.path)\n\n // (3) FAIL LOUD on a crash / mount failure — never ship broken body content.\n if (rendered.errors.length > 0) {\n throw new BodyPrerenderError(route.path, rendered.errors)\n }\n\n // The file on disk still carries ONLY the baked <head> at this point (the\n // splice below touches the body), so this read is the honest baked side of\n // the P1 comparison regardless of where it happens in the loop.\n const html = fs.readFileSync(outFile, 'utf8')\n\n // (4) P1: record what the app left in the <head> vs what was baked. Captured\n // for every route we rendered — including ones whose body turns out empty,\n // because a route with no body can still overwrite its title.\n if (rendered.head) {\n const baked = extractBakedHead(html)\n headObservations.push({\n route: route.path,\n bakedTitle: baked.title,\n runtimeTitle: rendered.head.title,\n bakedDescription: baked.description,\n runtimeDescription: rendered.head.description,\n runtimeDescriptionCount: rendered.head.descriptionCount,\n })\n }\n\n const body = (rendered.bodyHtml ?? '').trim()\n if (body === '') {\n // A clean render that produced nothing (e.g. a genuinely empty route):\n // leave the shell as-is rather than emptying/altering it.\n skipped += 1\n if (debug) console.log(`[dcs-seo] body-prerender skip (empty render): ${route.path}`)\n continue\n }\n\n // Observer mode: the render happened (so head honesty is checkable) but the\n // site opted out of body prerender, so dist/ is left exactly as emitted.\n if (!spliceBody) {\n skipped += 1\n if (debug) console.log(`[dcs-seo] body-prerender skip (observe-only): ${route.path}`)\n continue\n }\n\n const spliced = spliceBodyHtml(html, body)\n if (spliced === html) {\n // No #app container found in this file — cannot splice safely.\n skipped += 1\n if (debug) console.log(`[dcs-seo] body-prerender skip (no #app container): ${route.path}`)\n continue\n }\n\n fs.writeFileSync(outFile, spliced, 'utf8')\n prerendered += 1\n done.push(route.path)\n if (debug) {\n console.log(\n `[dcs-seo] body-prerender wrote ${path.relative(outDir, outFile)} (${body.length} bytes)`\n )\n }\n }\n\n return { prerendered, skipped, routes: done, headObservations, eligibleRoutes, filteredRoutes }\n}\n\n/** Inputs for {@link isBodyPrerenderEnabled}. */\nexport interface BodyPrerenderEnableInput {\n /** Whether the per-route static-HTML emitter is on (body prerender rides it). */\n emitStaticHtml: boolean\n /** The plugin option `prerenderBody` (undefined ⇒ default ON when emitting). */\n prerenderBodyOption?: boolean\n /** Preview/staging build — body prerender is skipped (kept minimal + fast). */\n preview?: boolean\n /**\n * The per-site escape hatch read from `.dcs/seo.yaml` (`prerenderBody: false`).\n * Lets an operator disable body prerender for one site with NO code change and\n * NO cms republish — the portal owns seo.yaml. `undefined`/`true` ⇒ enabled.\n */\n seoPrerenderBody?: boolean\n}\n\n/**\n * Resolve whether body prerender runs for this build. ON by default whenever the\n * static-HTML emitter is on; turned OFF by the plugin option `prerenderBody:\n * false`, by the seo.yaml `prerenderBody: false` escape hatch, in preview mode,\n * or when the emitter itself is off. Either OFF signal wins.\n */\nexport function isBodyPrerenderEnabled(input: BodyPrerenderEnableInput): boolean {\n if (!input.emitStaticHtml) return false\n if (input.preview) return false\n if (input.prerenderBodyOption === false) return false\n if (input.seoPrerenderBody === false) return false\n return true\n}\n","/**\n * P2 — emitted-URL resolution rail.\n *\n * ## The defect this exists for\n *\n * The SEO factory publishes URLs on the site's behalf: the `logo` and `image`\n * IRIs in the `@graph`, `og:image` / `twitter:image`, favicon and touch-icon\n * links, `<loc>`s in `sitemap.xml`, and the page links in `llms.txt`. Nothing\n * has ever asserted that any of them resolve.\n *\n * Measured on live production (2026-07-27):\n *\n * ```\n * GET /logos/iron-oak-mark-light.svg 200 text/html 67,532 bytes\n * GET / 200 text/html 67,532 bytes\n * sha256(both) = e83f660fc39e3ec2… ← byte-identical\n * ```\n *\n * `Organization.logo` and `HomeAndConstructionBusiness.logo` on **every route**\n * of the fleet's flagship point at a file that does not exist. There is no\n * `site/public/logos/` directory in the repo at all. The miss is invisible\n * because Azure Static Web Apps' `navigationFallback` answers any unmatched path\n * with `index.html` and a 200 — so a knowledge-graph entity's logo is a web\n * page, no 404 is ever logged, and no console error is ever raised. `dcs-seo`\n * calls this shape \"worse than a 404\".\n *\n * ## The two checks, and why they are split\n *\n * **Same-origin assets are checked against `dist/` — no network at all.** If the\n * factory emitted `https://site.com/logos/x.svg` and `dist/logos/x.svg` does not\n * exist, then the host has nothing to serve and its SPA fallback WILL answer\n * HTML. That inference is exact, deterministic, offline, and instant, so it is a\n * hard build failure. This is the check that catches the Iron Oak defect.\n *\n * **Cross-origin assets need the network, so they are designed not to flake.**\n * A CDN URL can only be verified by asking. The policy is deliberately\n * asymmetric:\n * - a *definitive* answer that is wrong (HTML where an image was promised, or\n * a 4xx/5xx) is a HARD FAILURE — that is the defect, and the answer is not\n * in doubt;\n * - a *non-answer* (DNS failure, timeout, connection reset, exhausted budget)\n * is a WARNING and never fails the build.\n *\n * That asymmetry is the whole point. A gate that reds when someone's wifi\n * hiccups gets disabled within a week and then protects nothing — the exact\n * vacuous-green failure mode this campaign keeps finding. Results are cached\n * with a TTL and the whole pass runs under a total time budget, so the marginal\n * cost on a warm CI run is ~zero.\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { readTagAttr } from './headHonesty'\n\n/** What kind of resource a URL is expected to return. */\nexport type UrlExpectation =\n /** Must NOT be HTML — an image, icon, font, or text/xml artifact. */\n | 'asset'\n /** An HTML page (canonical, sitemap entry, llms.txt page link). */\n | 'page'\n /** An identifier or a third-party profile — never fetched. */\n | 'skip'\n\n/** One URL the factory emitted, with where it came from. */\nexport interface EmittedUrl {\n /** The URL exactly as emitted. */\n url: string\n /** Human-readable provenance, e.g. `JSON-LD Organization.logo`. */\n source: string\n /** The emitted file this came from (`/services`, `sitemap.xml`, …). */\n from: string\n expectation: UrlExpectation\n}\n\n/** A URL that resolved to the wrong thing. */\nexport interface EmittedUrlViolation {\n url: string\n source: string\n from: string\n reason: string\n}\n\n/** A URL we could not get a definitive answer about. */\nexport interface EmittedUrlWarning {\n url: string\n source: string\n from: string\n reason: string\n}\n\n// -----------------------------------------------------------------------------\n// Collection\n// -----------------------------------------------------------------------------\n\n/** Extensions that are definitionally not HTML documents. */\nconst NON_HTML_EXTENSIONS = new Set([\n '.svg', '.png', '.jpg', '.jpeg', '.webp', '.avif', '.gif', '.ico', '.bmp',\n '.woff', '.woff2', '.ttf', '.otf', '.eot',\n '.txt', '.xml', '.json', '.pdf', '.mp4', '.webm', '.mp3', '.wav',\n '.css', '.js', '.mjs', '.map',\n])\n\n/** JSON-LD keys whose values are asset IRIs (must not be HTML). */\nconst JSONLD_ASSET_KEYS = new Set([\n 'logo', 'image', 'contentUrl', 'thumbnailUrl', 'photo', 'primaryImageOfPage',\n])\n\n/** JSON-LD keys whose values are page IRIs. */\nconst JSONLD_PAGE_KEYS = new Set(['url', 'item', 'mainEntityOfPage'])\n\n/** `<link rel>` values that point at an asset. */\nconst ASSET_LINK_RELS = new Set([\n 'icon', 'shortcut icon', 'apple-touch-icon', 'apple-touch-icon-precomposed',\n 'mask-icon', 'manifest', 'preload',\n])\n\n/** `<link rel>` values that point at a page. */\nconst PAGE_LINK_RELS = new Set(['canonical', 'alternate'])\n\nfunction extensionOf(url: string): string {\n try {\n const u = url.startsWith('http') ? new URL(url) : new URL(url, 'https://placeholder.invalid')\n return path.extname(u.pathname).toLowerCase()\n } catch {\n return ''\n }\n}\n\n/** Recursively pull asset/page IRIs out of a parsed JSON-LD value. */\nfunction walkJsonLd(\n node: unknown,\n from: string,\n trail: string,\n out: EmittedUrl[]\n): void {\n if (Array.isArray(node)) {\n for (const item of node) walkJsonLd(item, from, trail, out)\n return\n }\n if (node === null || typeof node !== 'object') return\n\n // Name the node by its @type so a violation reads `JSON-LD Organization.logo`\n // rather than an anonymous path — the operator needs to know WHICH entity in\n // the @graph published the bad URL.\n const nodeType = (node as Record<string, unknown>)['@type']\n const scope = typeof nodeType === 'string' ? `${trail}${nodeType}.` : trail\n\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n // `@id` is an IDENTIFIER, not a locator — schema.org node IRIs carry\n // `#organization` fragments and are not required to resolve. Fetching them\n // would manufacture noise. `sameAs` points at third-party profiles\n // (Facebook, Yelp) that bot-block and rate-limit; asserting on them would\n // make this rail flaky for reasons that are not the site's fault.\n if (key === '@id' || key === 'sameAs' || key === '@context' || key === '@type') continue\n\n if (typeof value === 'string' && /^(https?:)?\\/\\//.test(value)) {\n if (JSONLD_ASSET_KEYS.has(key)) {\n out.push({ url: value, source: `JSON-LD ${scope}${key}`, from, expectation: 'asset' })\n continue\n }\n if (JSONLD_PAGE_KEYS.has(key)) {\n out.push({ url: value, source: `JSON-LD ${scope}${key}`, from, expectation: 'page' })\n continue\n }\n continue\n }\n if (Array.isArray(value) && JSONLD_ASSET_KEYS.has(key)) {\n value.forEach((v, i) => {\n if (typeof v === 'string') {\n out.push({ url: v, source: `JSON-LD ${scope}${key}[${i}]`, from, expectation: 'asset' })\n } else {\n walkJsonLd(v, from, `${scope}${key}[${i}].`, out)\n }\n })\n continue\n }\n if (value !== null && typeof value === 'object') {\n // Arrays keep the current scope; a nested object names itself from its own\n // @type when it descends (handled at the top of this function).\n walkJsonLd(value, from, Array.isArray(value) ? scope : `${scope}${key}.`, out)\n }\n }\n}\n\n/**\n * Undo the `escapeJsonLd` transform before parsing. The emitter writes `<`, `>`\n * and `&` as `<`/`>`/`&` inside the script body, which `JSON.parse`\n * already handles — so this only strips the HTML-attribute escaping applied to\n * plain meta/link values.\n */\nfunction unescapeAttr(value: string): string {\n return value\n .replace(/&quot;/g, '\"')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&amp;/g, '&')\n}\n\n/**\n * Collect every URL an emitted HTML document publishes: OG/Twitter images,\n * asset and page `<link>`s, and every asset/page IRI inside its JSON-LD blocks.\n */\nexport function collectUrlsFromHtml(html: string, from: string): EmittedUrl[] {\n const out: EmittedUrl[] = []\n\n const metaRe = /<meta\\b[^>]*>/gi\n let m: RegExpExecArray | null\n while ((m = metaRe.exec(html)) !== null) {\n const tag = m[0]\n // Delimiter-aware: a `[\"']…[\"']` pair terminates on the first quote OR\n // apostrophe, which silently truncates any value containing one. See\n // `readTagAttr` in headHonesty.ts for the measured consequence.\n const property = readTagAttr(tag, 'property')?.toLowerCase()\n const name = readTagAttr(tag, 'name')?.toLowerCase()\n const content = readTagAttr(tag, 'content')\n if (!content) continue\n const key = property ?? name\n if (!key) continue\n if (key === 'og:image' || key === 'twitter:image' || key === 'og:image:secure_url') {\n out.push({ url: unescapeAttr(content), source: key, from, expectation: 'asset' })\n } else if (key === 'og:url') {\n out.push({ url: unescapeAttr(content), source: key, from, expectation: 'page' })\n }\n }\n\n const linkRe = /<link\\b[^>]*>/gi\n while ((m = linkRe.exec(html)) !== null) {\n const tag = m[0]\n const rel = readTagAttr(tag, 'rel')?.toLowerCase()\n const href = readTagAttr(tag, 'href')\n if (!rel || !href) continue\n if (ASSET_LINK_RELS.has(rel)) {\n out.push({ url: unescapeAttr(href), source: `link rel=\"${rel}\"`, from, expectation: 'asset' })\n } else if (PAGE_LINK_RELS.has(rel)) {\n out.push({ url: unescapeAttr(href), source: `link rel=\"${rel}\"`, from, expectation: 'page' })\n }\n }\n\n const scriptRe = /<script[^>]*type=[\"']application\\/ld\\+json[\"'][^>]*>([\\s\\S]*?)<\\/script>/gi\n while ((m = scriptRe.exec(html)) !== null) {\n try {\n walkJsonLd(JSON.parse(m[1]), from, '', out)\n } catch {\n // A JSON-LD block that does not parse is its own defect, already covered by\n // the seo-guardrail; this rail only reports on URLs it can actually read.\n }\n }\n\n return out\n}\n\n/** Collect `<loc>` entries from an emitted sitemap. */\nexport function collectUrlsFromSitemap(xml: string, from = 'sitemap.xml'): EmittedUrl[] {\n const out: EmittedUrl[] = []\n const re = /<loc>\\s*([\\s\\S]*?)\\s*<\\/loc>/gi\n let m: RegExpExecArray | null\n while ((m = re.exec(xml)) !== null) {\n out.push({ url: unescapeAttr(m[1].trim()), source: '<loc>', from, expectation: 'page' })\n }\n return out\n}\n\n/** Collect the markdown link targets from an emitted `llms.txt`. */\nexport function collectUrlsFromLlmsTxt(txt: string, from = 'llms.txt'): EmittedUrl[] {\n const out: EmittedUrl[] = []\n const re = /\\[[^\\]]*\\]\\((https?:\\/\\/[^)\\s]+)\\)/g\n let m: RegExpExecArray | null\n while ((m = re.exec(txt)) !== null) {\n const url = m[1]\n out.push({\n url,\n source: 'llms.txt link',\n from,\n // An llms.txt entry may point at a page OR at a text artifact\n // (`llms-full.txt`); classify by extension so a `.txt` reference is held to\n // the non-HTML bar — that is exactly the shape an answer engine probes for.\n expectation: NON_HTML_EXTENSIONS.has(extensionOf(url)) ? 'asset' : 'page',\n })\n }\n return out\n}\n\n/**\n * Read the emitted artifacts off disk and collect every URL they publish.\n *\n * Takes an explicit file list (built by the caller from the same route manifest\n * the emitter used) rather than walking `dist/`, so the rail only ever inspects\n * files the factory actually wrote — never a stray asset or a stale artifact\n * from a previous build.\n */\nexport function collectEmittedUrlsFromFiles(\n files: Array<{ path: string; from: string; kind: 'html' | 'sitemap' | 'llms' }>\n): EmittedUrl[] {\n const out: EmittedUrl[] = []\n for (const entry of files) {\n let text: string\n try {\n text = fs.readFileSync(entry.path, 'utf8')\n } catch {\n continue // not emitted for this build (e.g. preview skips sitemap/llms)\n }\n if (entry.kind === 'html') out.push(...collectUrlsFromHtml(text, entry.from))\n else if (entry.kind === 'sitemap') out.push(...collectUrlsFromSitemap(text, entry.from))\n else out.push(...collectUrlsFromLlmsTxt(text, entry.from))\n }\n return out\n}\n\n/** De-duplicate by URL+expectation, keeping the first provenance seen. */\nexport function dedupeUrls(urls: EmittedUrl[]): EmittedUrl[] {\n const seen = new Map<string, EmittedUrl>()\n for (const u of urls) {\n const key = `${u.expectation}\u0000${u.url}`\n if (!seen.has(key)) seen.set(key, u)\n }\n return [...seen.values()]\n}\n\n// -----------------------------------------------------------------------------\n// Classification\n// -----------------------------------------------------------------------------\n\n/** How a URL relates to the site being built. */\nexport type UrlLocality = 'same-origin' | 'remote' | 'unfetchable'\n\n/** The parsed form of an emitted URL. */\nexport interface ClassifiedUrl extends EmittedUrl {\n locality: UrlLocality\n /** Path portion, for same-origin resolution against `dist/`. */\n pathname: string\n}\n\n/**\n * Decide whether a URL points back at the site we just built (so it can be\n * proven against `dist/` offline) or somewhere else (so it needs the network).\n *\n * Protocol-relative and root-relative URLs are treated as same-origin, matching\n * how a browser resolves them from the emitted document.\n */\nexport function classifyUrl(entry: EmittedUrl, siteUrl: string | undefined): ClassifiedUrl {\n const url = entry.url.trim()\n if (/^(data|mailto|tel|javascript|blob):/i.test(url)) {\n return { ...entry, locality: 'unfetchable', pathname: '' }\n }\n\n let siteOrigin: string | undefined\n try {\n if (siteUrl) siteOrigin = new URL(siteUrl).origin\n } catch {\n siteOrigin = undefined\n }\n\n if (url.startsWith('/') && !url.startsWith('//')) {\n return { ...entry, locality: 'same-origin', pathname: url.split(/[?#]/)[0] }\n }\n\n try {\n const parsed = new URL(url.startsWith('//') ? `https:${url}` : url)\n if (siteOrigin && parsed.origin === siteOrigin) {\n return { ...entry, locality: 'same-origin', pathname: parsed.pathname }\n }\n return { ...entry, locality: 'remote', pathname: parsed.pathname }\n } catch {\n return { ...entry, locality: 'unfetchable', pathname: '' }\n }\n}\n\n// -----------------------------------------------------------------------------\n// Same-origin check (fs only — deterministic, offline, hard-fail)\n// -----------------------------------------------------------------------------\n\n/**\n * Resolve a same-origin pathname to the file that would serve it from `dist/`.\n * Honours a non-root Vite `base` (subpath preview deploys) by trying the\n * base-stripped path as well.\n */\nfunction resolveDistCandidates(outDir: string, pathname: string, base: string): string[] {\n const clean = decodeURIComponent(pathname).replace(/^\\/+/, '')\n const candidates = [path.join(outDir, ...clean.split('/'))]\n const normalizedBase = base.replace(/^\\/+|\\/+$/g, '')\n if (normalizedBase && clean.startsWith(normalizedBase + '/')) {\n candidates.push(path.join(outDir, ...clean.slice(normalizedBase.length + 1).split('/')))\n }\n return candidates\n}\n\n/**\n * Assert that every same-origin ASSET URL the factory emitted has a real file in\n * `dist/`.\n *\n * This is the check that catches the Iron Oak logo IRI, and it does so with no\n * network, no browser, and no ambiguity: an emitted `https://site/logos/x.svg`\n * with no `dist/logos/x.svg` behind it means the host's SPA fallback answers the\n * crawler with `index.html`.\n */\nexport function checkSameOriginAssets(params: {\n urls: ClassifiedUrl[]\n outDir: string\n base?: string\n allow?: string[]\n}): EmittedUrlViolation[] {\n return auditSameOriginAssets(params).violations\n}\n\n/** What the same-origin half actually did — the numbers behind its claim. */\nexport interface SameOriginAuditResult {\n violations: EmittedUrlViolation[]\n /** Same-origin ASSET URLs actually resolved against `dist/`. */\n checked: number\n /** Same-origin asset URLs skipped because they are on the allow-list. */\n exempted: number\n}\n\n/**\n * The counting form of {@link checkSameOriginAssets}.\n *\n * C-338: the caller used to report `classified.length` (\"N published URL(s)\n * verified\") while this function only ever looks at `expectation === 'asset'`.\n * Page URLs — every sitemap `<loc>`, every canonical, every llms.txt page link —\n * were counted as verified without being touched. The claim must never be\n * broader than the check, so the check now returns its own denominator.\n */\nexport function auditSameOriginAssets(params: {\n urls: ClassifiedUrl[]\n outDir: string\n base?: string\n allow?: string[]\n}): SameOriginAuditResult {\n const { urls, outDir, base = '/', allow = [] } = params\n const allowed = new Set(allow)\n const violations: EmittedUrlViolation[] = []\n let checked = 0\n let exempted = 0\n\n // De-duplicate here as well as at the call site: the same logo IRI appears on\n // every route and inside several @graph nodes, and one missing file must be\n // reported ONCE — a wall of identical lines is how a real finding gets skimmed.\n for (const entry of dedupeUrls(urls) as ClassifiedUrl[]) {\n if (entry.locality !== 'same-origin' || entry.expectation !== 'asset') continue\n if (allowed.has(entry.url) || allowed.has(entry.pathname)) {\n exempted += 1\n continue\n }\n\n checked += 1\n const candidates = resolveDistCandidates(outDir, entry.pathname, base)\n const found = candidates.some((c) => {\n try {\n return fs.statSync(c).isFile()\n } catch {\n return false\n }\n })\n if (found) continue\n\n violations.push({\n url: entry.url,\n source: entry.source,\n from: entry.from,\n reason:\n `no file at ${candidates.map((c) => path.relative(outDir, c)).join(' or ')} in the build output — ` +\n `the host's SPA fallback will answer this with index.html (200 text/html), so every ` +\n `crawler resolving it gets a web page where a non-HTML asset was promised`,\n })\n }\n\n return { violations, checked, exempted }\n}\n\n// -----------------------------------------------------------------------------\n// Remote check (bounded, cached, WARN-on-nonanswer)\n// -----------------------------------------------------------------------------\n\n/** One cached probe result. */\ninterface CachedProbe {\n /** `ok` | `html` | `status` — a definitive verdict worth remembering. */\n verdict: 'ok' | 'html' | 'status'\n status?: number\n contentType?: string\n /** ms epoch. */\n at: number\n}\n\ninterface ProbeCacheFile {\n version: 1\n entries: Record<string, CachedProbe>\n}\n\nconst CACHE_VERSION = 1\n\nfunction readCache(cacheFile: string): ProbeCacheFile {\n try {\n const parsed = JSON.parse(fs.readFileSync(cacheFile, 'utf8')) as ProbeCacheFile\n if (parsed?.version === CACHE_VERSION && parsed.entries) return parsed\n } catch {\n /* cold cache */\n }\n return { version: CACHE_VERSION, entries: {} }\n}\n\nfunction writeCache(cacheFile: string, cache: ProbeCacheFile): void {\n try {\n fs.mkdirSync(path.dirname(cacheFile), { recursive: true })\n fs.writeFileSync(cacheFile, JSON.stringify(cache), 'utf8')\n } catch {\n // A cache we cannot persist just means the next build re-probes. Never fatal.\n }\n}\n\n/** Options for the network half of the rail. */\nexport interface RemoteCheckOptions {\n /** Per-request timeout. Default 5000ms. */\n timeoutMs?: number\n /** Total wall-clock budget for the whole pass. Default 20000ms. */\n budgetMs?: number\n /** Parallel requests. Default 6. */\n concurrency?: number\n /** Cache file path. Default `<projectRoot>/node_modules/.cache/dcs-seo/url-probes.json`. */\n cacheFile?: string\n /** Cache TTL. Default 24h. */\n cacheTtlMs?: number\n /** URLs exempted from the check (still logged). */\n allow?: string[]\n /** Injectable for tests — defaults to global `fetch`. */\n fetchImpl?: typeof fetch\n debug?: boolean\n}\n\n/** Outcome of the remote pass. */\nexport interface RemoteCheckResult {\n violations: EmittedUrlViolation[]\n warnings: EmittedUrlWarning[]\n /** How many probes were served from cache (0 network cost). */\n cached: number\n /** How many probes hit the network. */\n probed: number\n /**\n * Remote ASSET URLs this pass was asked to check (`cached + probed +\n * unanswered`). The honest denominator for the build log — see\n * {@link auditSameOriginAssets} for why the count must come from the checker.\n */\n checked?: number\n /** Remote asset URLs skipped because they are on the allow-list. */\n exempted?: number\n}\n\nfunction isHtmlContentType(contentType: string | null | undefined): boolean {\n if (!contentType) return false\n return /^\\s*(text\\/html|application\\/xhtml\\+xml)/i.test(contentType)\n}\n\n/**\n * Probe every REMOTE asset URL and report only what the network told us\n * definitively.\n *\n * The verdict table is the contract:\n * | observation | outcome | why |\n * |--------------------------------------------|----------|-----|\n * | 2xx, non-HTML content-type | pass | the promise held |\n * | 2xx, `text/html` | **FAIL** | the exact silent-degradation shape |\n * | 4xx / 5xx | **FAIL** | a definitive \"this does not exist\" |\n * | DNS/timeout/reset/abort, budget exhausted | warn | we learned nothing; never red on that |\n */\nexport async function checkRemoteAssets(\n urls: ClassifiedUrl[],\n options: RemoteCheckOptions & { projectRoot: string }\n): Promise<RemoteCheckResult> {\n const {\n projectRoot,\n timeoutMs = 5000,\n budgetMs = 20000,\n concurrency = 6,\n cacheTtlMs = 24 * 60 * 60 * 1000,\n allow = [],\n fetchImpl = globalThis.fetch,\n debug = false,\n } = options\n const cacheFile =\n options.cacheFile ?? path.join(projectRoot, 'node_modules', '.cache', 'dcs-seo', 'url-probes.json')\n\n const allowed = new Set(allow)\n const remoteAssets = dedupeUrls(\n urls.filter((u) => u.locality === 'remote' && u.expectation === 'asset')\n ) as ClassifiedUrl[]\n const targets = remoteAssets.filter((u) => !allowed.has(u.url))\n const exempted = remoteAssets.length - targets.length\n\n const violations: EmittedUrlViolation[] = []\n const warnings: EmittedUrlWarning[] = []\n let cachedCount = 0\n let probedCount = 0\n /** Targets that got a DEFINITIVE answer — the only ones that may be claimed. */\n let answeredCount = 0\n\n if (targets.length === 0) {\n return { violations, warnings, cached: 0, probed: 0, checked: 0, exempted }\n }\n if (typeof fetchImpl !== 'function') {\n warnings.push({\n url: '(all remote)',\n source: 'remote probe',\n from: '-',\n reason: 'no global fetch available in this Node runtime; remote URL checks skipped',\n })\n return { violations, warnings, cached: 0, probed: 0, checked: 0, exempted }\n }\n\n const cache = readCache(cacheFile)\n const now = Date.now()\n const deadline = now + budgetMs\n\n const record = (entry: ClassifiedUrl, probe: CachedProbe): void => {\n if (probe.verdict === 'ok') return\n if (probe.verdict === 'html') {\n violations.push({\n url: entry.url,\n source: entry.source,\n from: entry.from,\n reason:\n `resolved 200 with content-type \"${probe.contentType}\" — an HTML page where a ` +\n `non-HTML asset was promised. This is the \"worse than a 404\" shape: nothing errors, ` +\n `nothing logs, and every crawler resolving it silently gets a web page`,\n })\n return\n }\n violations.push({\n url: entry.url,\n source: entry.source,\n from: entry.from,\n reason: `resolved HTTP ${probe.status} — the URL the factory published does not exist`,\n })\n }\n\n const queue = [...targets]\n const worker = async (): Promise<void> => {\n for (;;) {\n const entry = queue.shift()\n if (!entry) return\n\n const cached = cache.entries[entry.url]\n if (cached && now - cached.at < cacheTtlMs) {\n cachedCount += 1\n answeredCount += 1\n record(entry, cached)\n continue\n }\n\n if (Date.now() >= deadline) {\n warnings.push({\n url: entry.url,\n source: entry.source,\n from: entry.from,\n reason: `remote-probe time budget (${budgetMs}ms) exhausted before this URL was checked`,\n })\n continue\n }\n\n probedCount += 1\n let res: Response | undefined\n try {\n res = await fetchImpl(entry.url, {\n method: 'HEAD',\n redirect: 'follow',\n signal: AbortSignal.timeout(timeoutMs),\n })\n // Some CDNs and SWA reject HEAD; fall back to a ranged GET so we still\n // learn the content type without downloading the whole asset.\n if (res.status === 405 || res.status === 501) {\n res = await fetchImpl(entry.url, {\n method: 'GET',\n redirect: 'follow',\n headers: { Range: 'bytes=0-2047' },\n signal: AbortSignal.timeout(timeoutMs),\n })\n }\n } catch (err) {\n warnings.push({\n url: entry.url,\n source: entry.source,\n from: entry.from,\n reason: `network probe did not complete (${\n err instanceof Error ? err.message : String(err)\n }) — treated as UNKNOWN, not as a failure`,\n })\n continue\n }\n\n const contentType = res.headers.get('content-type') ?? undefined\n let probe: CachedProbe\n if (res.status >= 400) {\n probe = { verdict: 'status', status: res.status, contentType, at: Date.now() }\n } else if (isHtmlContentType(contentType)) {\n probe = { verdict: 'html', status: res.status, contentType, at: Date.now() }\n } else {\n probe = { verdict: 'ok', status: res.status, contentType, at: Date.now() }\n }\n cache.entries[entry.url] = probe\n answeredCount += 1\n if (debug) {\n console.log(`[dcs-seo] url probe ${entry.url} -> ${probe.verdict} (${res.status} ${contentType ?? '?'})`)\n }\n record(entry, probe)\n }\n }\n\n await Promise.all(Array.from({ length: Math.min(concurrency, targets.length) }, worker))\n writeCache(cacheFile, cache)\n\n // `checked` counts only URLs that got a definitive answer — a budget-exhausted\n // or DNS-failed probe is an UNKNOWN and must never be counted as verified.\n return {\n violations,\n warnings,\n cached: cachedCount,\n probed: probedCount,\n checked: answeredCount,\n exempted,\n }\n}\n\n// -----------------------------------------------------------------------------\n// Reporting\n// -----------------------------------------------------------------------------\n\n/** Render the failure report — every violation names the URL, where it was\n * emitted, and what actually came back. */\nexport function formatEmittedUrlReport(violations: EmittedUrlViolation[]): string {\n const lines: string[] = [\n `[dcs-seo] emitted-URL honesty: ${violations.length} URL(s) the SEO factory publishes do not ` +\n `resolve to what they promise.`,\n ]\n for (const v of violations) {\n lines.push(` ${v.url}`)\n lines.push(` emitted by: ${v.source} (in ${v.from})`)\n lines.push(` problem : ${v.reason}`)\n }\n lines.push(\n ` Fix: point the URL at something that exists (via .dcs/seo.yaml — brand marks usually ` +\n `live on the per-site CDN), or ship the file into the site's public/ directory.`\n )\n return lines.join('\\n')\n}\n\n// -----------------------------------------------------------------------------\n// Execution + claim honesty (C-338)\n// -----------------------------------------------------------------------------\n\n/**\n * Exactly what the P2 pass touched — the numbers the build log is allowed to\n * claim.\n *\n * ## The defect this exists for\n *\n * C-334's success line printed `${classified.length} published URL(s) verified`\n * while BOTH checkers filter `expectation !== 'asset'`. Sitemap `<loc>`s,\n * canonicals and llms.txt page links were counted as verified and never probed —\n * the claim was broader than the check, which is the same vacuous-green shape\n * the rail was built to close. The log now reports these fields verbatim:\n * assets proven / assets probed / pages COLLECTED-BUT-NOT-VERIFIED / unknown.\n */\nexport interface EmittedUrlAudit {\n /** Every distinct URL collected from the emitted artifacts. */\n collected: number\n /** Same-origin asset URLs resolved against `dist/` (offline, exact). */\n sameOriginAssetsChecked: number\n /** Remote asset URLs that got a definitive answer. */\n remoteAssetsChecked: number\n /** Remote asset probes that hit the network / came from cache. */\n remoteProbed: number\n remoteCached: number\n /**\n * Remote asset URLs the pass could NOT get a definitive answer about (DNS,\n * timeout, budget) plus every remote asset when probing is disabled. Never\n * counted as verified.\n */\n unknown: number\n /**\n * Page URLs collected and classified but NOT verified by this rail. This is\n * the honest name for what used to be counted as \"verified\".\n */\n pagesUnverified: number\n /** URLs that are not fetchable by construction (`data:`, `mailto:`, `tel:`). */\n unfetchable: number\n /** URLs skipped by an explicit allow-list entry (still logged). */\n exempted: number\n /** Violations found (the failures). */\n failed: number\n}\n\n/** Total URLs this pass actually verified. */\nexport function auditCheckedTotal(audit: EmittedUrlAudit): number {\n return audit.sameOriginAssetsChecked + audit.remoteAssetsChecked\n}\n\n/**\n * The success/summary line. It states the denominator of every claim and names\n * what was NOT checked, so no reader can infer more coverage than exists.\n */\nexport function formatEmittedUrlAudit(audit: EmittedUrlAudit, routeCount: number): string {\n const checked = auditCheckedTotal(audit)\n const parts = [\n `[dcs-seo] emitted-URL honesty: ${checked} asset URL(s) VERIFIED ` +\n `(${audit.sameOriginAssetsChecked} proven against dist/, ${audit.remoteAssetsChecked} probed remotely` +\n `${audit.remoteCached ? `, ${audit.remoteCached} from cache` : ''})`,\n `${audit.pagesUnverified} page URL(s) collected but NOT verified by this rail`,\n ]\n if (audit.unknown > 0) parts.push(`${audit.unknown} UNKNOWN (no definitive answer)`)\n if (audit.exempted > 0) parts.push(`${audit.exempted} exempted`)\n if (audit.unfetchable > 0) parts.push(`${audit.unfetchable} unfetchable by construction`)\n return (\n parts.join('; ') +\n `. ${audit.collected} URL(s) collected across ${routeCount} route(s) + sitemap + llms.txt.`\n )\n}\n\n/** Thrown in `error` mode when the P2 rail collected nothing to check. */\nexport class EmittedUrlNotRunError extends Error {\n readonly audit: EmittedUrlAudit\n constructor(audit: EmittedUrlAudit, detail: string) {\n super(\n '[dcs-seo] emitted-URL honesty DID NOT RUN — ' +\n `${detail}. Nothing was verified, so this build proves nothing about the URLs it ` +\n 'publishes. (Emitted artifacts unreadable or empty? outDir wrong?) A rail that ' +\n 'silently does not run is the exact class this work exists to close.'\n )\n this.name = 'EmittedUrlNotRunError'\n this.audit = audit\n }\n}\n\n/** Thrown in `error` mode so the build goes red. */\nexport class EmittedUrlError extends Error {\n readonly violations: EmittedUrlViolation[]\n constructor(violations: EmittedUrlViolation[]) {\n super(formatEmittedUrlReport(violations))\n this.name = 'EmittedUrlError'\n this.violations = violations\n }\n}\n","/**\n * P12 — `<meta charset>` head-budget rail.\n *\n * ## The defect this exists for\n *\n * The HTML spec requires a document's character encoding declaration to appear\n * **within the first 1024 bytes** of the resource; a parser that has not found\n * one by then stops looking and falls back to its locale default. The 2026-07-26\n * Iron Oak review measured the shipped fleet HTML and found:\n *\n * ```\n * <!doctype html>\n * <html lang=\"en\">\n * <head>\n * <style data-dcs-motion-tokens=\"\">:root { … 858 bytes of tokens … }</style>\n * <meta charset=\"UTF-8\" /> ← byte 910 of a 1024-byte window\n * ```\n *\n * The cms motion-token `<style>` was injected `head-prepend`, i.e. ABOVE the\n * site's own `<meta charset>`, leaving 90 bytes of headroom past the end of the\n * declaration. The SWA response also sends `Content-Type: text/html` with **no**\n * `charset` parameter, so there is no header-level backstop. One more head\n * injection — or a handful of new motion tokens — silently mojibakes every cms\n * SPA in the fleet, on every route, with nothing failing.\n *\n * That is the exact \"a capability silently undone downstream, with nothing\n * noticing\" class this module closes.\n *\n * ## The fix + the rail\n *\n * - **C-414 fixed the source.** `dcsMotionTokensStyleTag()` now injects `head`\n * (append) rather than `head-prepend`, so the site's own declaration stays\n * first in `<head>` and the block still lands above the bundled site CSS.\n * This is the only fix that reaches VitePress sites, which never run the\n * `emitStaticHtml` path below.\n * - {@link hoistCharsetMeta} moves the encoding declaration to the very top of\n * `<head>` (inserting `<meta charset=\"utf-8\" />` when the document has none).\n * Pure, idempotent, and cascade-safe: only the charset meta moves, so the\n * motion-token `<style>` keeps its position relative to the site's own CSS.\n * Since C-414 this is a no-op on a stock cms shell — it stays as the backstop\n * for hand-rolled shells and any future head injector.\n * - {@link checkCharsetBudget} measures the real BYTE offset of the declaration\n * in the emitted document and reports headroom, so a regression is caught even\n * if the hoist is ever bypassed.\n *\n * Everything here is framework-free and `fs`-free so both the Vite plugin path\n * and the unit tests share one implementation.\n */\n\n/**\n * The spec's encoding-sniffing window. Per HTML Standard §13.2.3.2 (\"determining\n * the character encoding\") a prescan examines at most the first 1024 bytes.\n */\nexport const CHARSET_BUDGET_BYTES = 1024\n\n/**\n * Recommended minimum slack between the declaration and the end of the window.\n * Below this the document is *technically* conformant but one head injection\n * away from breaking — the Iron Oak measurement (114 bytes) is precisely this\n * shape, which is why the rail reports headroom and not just pass/fail.\n */\nexport const CHARSET_HEADROOM_WARN_BYTES = 256\n\n/** Matches `<meta charset=\"…\">` (the modern form). */\nconst META_CHARSET_RE = /<meta\\s+[^>]*\\bcharset\\s*=\\s*[\"']?[^\"'>\\s]+[\"']?[^>]*>/i\n\n/**\n * Matches the legacy `<meta http-equiv=\"Content-Type\" content=\"…charset=…\">`\n * form, which is an equally valid declaration for the prescan.\n */\nconst META_HTTP_EQUIV_CHARSET_RE =\n /<meta\\s+[^>]*http-equiv\\s*=\\s*[\"']?content-type[\"']?[^>]*content\\s*=\\s*[\"'][^\"']*charset=[^\"']*[\"'][^>]*>/i\n\n/** The canonical declaration this module inserts when a document has none. */\nexport const CANONICAL_CHARSET_META = '<meta charset=\"utf-8\" />'\n\n/** The result of measuring a document against the encoding-sniffing window. */\nexport interface CharsetBudgetResult {\n /** `true` when a declaration exists AND its last byte is inside the window. */\n ok: boolean\n /** Byte offset of the START of the declaration, or `-1` when absent. */\n offset: number\n /** Byte offset of the END of the declaration, or `-1` when absent. */\n endOffset: number\n /** `limitBytes - endOffset`; negative when the declaration is out of budget. */\n headroom: number\n /** The window used for the measurement (default {@link CHARSET_BUDGET_BYTES}). */\n limitBytes: number\n /** The matched declaration text, for reporting. */\n declaration: string | null\n}\n\n/**\n * Byte offset (UTF-8) of the character encoding declaration, or `-1`.\n *\n * Deliberately measured in BYTES, not characters: the spec's window is a byte\n * window, and a head containing non-ASCII (a `©` in a `<title>`, an em dash in a\n * meta description) makes the character index an under-estimate — the exact way\n * a naive check would report \"fine\" on a document that is not.\n */\nexport function findCharsetByteOffset(html: string): number {\n const match = META_CHARSET_RE.exec(html) ?? META_HTTP_EQUIV_CHARSET_RE.exec(html)\n if (!match) return -1\n return Buffer.byteLength(html.slice(0, match.index), 'utf8')\n}\n\n/**\n * Measure a document against the encoding-sniffing window.\n *\n * The declaration must END inside the window to count — a `<meta charset>` that\n * straddles byte 1024 is not seen by the prescan.\n */\nexport function checkCharsetBudget(\n html: string,\n options: { limitBytes?: number } = {}\n): CharsetBudgetResult {\n const limitBytes = options.limitBytes ?? CHARSET_BUDGET_BYTES\n const match = META_CHARSET_RE.exec(html) ?? META_HTTP_EQUIV_CHARSET_RE.exec(html)\n if (!match) {\n return { ok: false, offset: -1, endOffset: -1, headroom: -limitBytes, limitBytes, declaration: null }\n }\n const offset = Buffer.byteLength(html.slice(0, match.index), 'utf8')\n const endOffset = offset + Buffer.byteLength(match[0], 'utf8')\n return {\n ok: endOffset <= limitBytes,\n offset,\n endOffset,\n headroom: limitBytes - endOffset,\n limitBytes,\n declaration: match[0],\n }\n}\n\n/**\n * Move the character encoding declaration to the first position inside `<head>`\n * — or insert the canonical one when the document has none.\n *\n * Guarantees, in order of importance:\n * 1. **Idempotent.** Re-running on already-hoisted HTML is a no-op (the\n * declaration is already the first child of `<head>`), so it is safe to\n * apply in `transformIndexHtml` AND again over the emitted per-route files.\n * 2. **Minimal.** Only the charset meta moves. The motion-token `<style>`, the\n * site's CSS links, and the app script keep their relative order, so no\n * cascade or execution-order behaviour changes.\n * 3. **Total.** A document with no `<head>` is returned unchanged rather than\n * rewritten — the caller logs and no-ops (never corrupt a non-SPA shell).\n *\n * @param html - a full HTML document\n * @param options.insertIfMissing - insert {@link CANONICAL_CHARSET_META} when\n * the document declares no encoding at all (default `true`). A cms SPA shell\n * always has one; a hand-rolled template might not, and shipping *no*\n * declaration is strictly worse than shipping a late one.\n */\nexport function hoistCharsetMeta(\n html: string,\n options: { insertIfMissing?: boolean } = {}\n): string {\n const insertIfMissing = options.insertIfMissing ?? true\n\n const headOpen = /<head(\\s[^>]*)?>/i.exec(html)\n if (!headOpen) return html\n const insertAt = headOpen.index + headOpen[0].length\n\n const match = META_CHARSET_RE.exec(html) ?? META_HTTP_EQUIV_CHARSET_RE.exec(html)\n\n if (!match) {\n if (!insertIfMissing) return html\n return html.slice(0, insertAt) + '\\n ' + CANONICAL_CHARSET_META + html.slice(insertAt)\n }\n\n // Already the first thing in <head> (allowing only whitespace between)? No-op,\n // which is what makes repeated application safe.\n const between = html.slice(insertAt, match.index)\n if (between.trim() === '') return html\n\n // A declaration outside <head> entirely (malformed input) is left alone —\n // moving it could change what the document means.\n const headClose = /<\\/head\\s*>/i.exec(html)\n if (headClose && match.index > headClose.index) return html\n\n const declaration = match[0]\n // Remove it from its current position, along with the whitespace-only line it\n // sat on, so the hoist does not leave a ragged blank line behind.\n const before = html.slice(0, match.index).replace(/[ \\t]*$/, '')\n const after = html.slice(match.index + declaration.length).replace(/^[ \\t]*\\r?\\n/, '')\n const withoutDeclaration = before + after\n\n // insertAt is unchanged: the declaration always sat AFTER <head> (guarded\n // above), so removing it cannot shift the opening tag's end offset.\n return (\n withoutDeclaration.slice(0, insertAt) +\n '\\n ' +\n declaration +\n withoutDeclaration.slice(insertAt)\n )\n}\n\n/** Thrown when an emitted document's encoding declaration is out of budget. */\nexport class CharsetBudgetError extends Error {\n readonly file: string\n readonly result: CharsetBudgetResult\n constructor(file: string, result: CharsetBudgetResult) {\n super(\n result.declaration === null\n ? `[dcs-seo] charset budget FAILED for ${file}: the document declares NO character ` +\n `encoding. Browsers fall back to a locale default and every non-ASCII byte can ` +\n `mojibake. Emit <meta charset=\"utf-8\"> as the first child of <head>.`\n : `[dcs-seo] charset budget FAILED for ${file}: <meta charset> ends at byte ` +\n `${result.endOffset} of the ${result.limitBytes}-byte encoding-sniffing window ` +\n `(${result.headroom} bytes of headroom). The HTML parser stops looking after ` +\n `byte ${result.limitBytes}, so this document's encoding is not guaranteed to be ` +\n `detected. Move the declaration to the top of <head>.`\n )\n this.name = 'CharsetBudgetError'\n this.file = file\n this.result = result\n }\n}\n\n/**\n * Human-readable one-liner for the low-headroom (still-passing) case, so a\n * document creeping toward the cliff is visible in the build log before it falls\n * off it. Returns `null` when there is nothing worth saying.\n */\nexport function formatCharsetHeadroomWarning(\n file: string,\n result: CharsetBudgetResult,\n warnBelow = CHARSET_HEADROOM_WARN_BYTES\n): string | null {\n if (!result.ok) return null\n if (result.headroom >= warnBelow) return null\n return (\n `[dcs-seo] charset budget: ${file} declares its encoding at byte ${result.offset} — ` +\n `only ${result.headroom} bytes of the ${result.limitBytes}-byte window remain. ` +\n `One more <head> injection breaks encoding detection on this site.`\n )\n}\n","/**\n * Build-time SEO for VitePress static-site generation.\n *\n * VitePress 1.6 does **not** use `unhead`, so the runtime `useSEO`/`applyHead`\n * composable is a no-op against the SSG HTML. The SSG-correct sink is the\n * `transformPageData(pageData)` build hook: writing `<meta>`/`<link>`/JSON-LD\n * into `pageData.frontmatter.head` (VitePress bakes those into the rendered\n * `<head>`) and overwriting `pageData.title` / `pageData.description` (VitePress\n * renders the `<title>` — via `titleTemplate` — and the `description` meta from\n * those two fields).\n *\n * This factory generalises the bespoke `buildSeoHead`/`transformPageData` that\n * shipped inline in a site's `.vitepress/config.ts`. It reuses the shared,\n * framework-agnostic resolver (`resolvePageSeo`, `generateOpenGraphMeta`,\n * `generateTwitterMeta`, `generateJsonLd`) for global + page meta / OG / Twitter\n * / canonical and the global JSON-LD knowledge graph, and delegates **page-type\n * JSON-LD** (e.g. Article / Place / CollectionPage / Service / FAQPage +\n * BreadcrumbList) to a *pluggable* rule set the site supplies. None of the\n * real-estate (or any other vertical's) schema logic lives in this package — it\n * is all site CONFIG.\n *\n * It is the VitePress counterpart to the Vue-SPA per-route emitter in\n * `dcsSeoPlugin({ emitStaticHtml: true })`; both produce identical global\n * meta/OG/Twitter/JSON-LD from the same `seo.yaml` via the shared resolver.\n *\n * @example\n * ```ts\n * // docs/.vitepress/config.ts\n * import { createSeoTransformPageData } from '@duffcloudservices/cms/plugins'\n * import seoConfig from '../../.dcs/seo.yaml'\n *\n * export default defineConfig({\n * transformPageData: createSeoTransformPageData({\n * seoConfig,\n * pageTypeRules: [\n * { match: (ctx) => ctx.route.startsWith('/blogs/'), build: (ctx) => [ ... ] },\n * // ...Place / CollectionPage / Service / FAQPage rules\n * ],\n * }),\n * })\n * ```\n */\n\nimport type {\n SeoConfiguration,\n GlobalSeoConfig,\n SeoOpenGraphConfig,\n} from '../types/seo'\nimport {\n resolvePageSeo,\n generateOpenGraphMeta,\n generateTwitterMeta,\n generateJsonLd,\n} from './headTags'\nimport {\n buildGlobalGraph,\n buildBreadcrumbList,\n buildBlogPosting,\n buildFaqPage,\n breadcrumbTrailFromRoute,\n graphAbsorbs,\n slugToTitle,\n type ReviewSource,\n type FaqSource,\n type BlogMeta,\n} from './schemaGraph'\nimport { escapeJsonLd } from './spliceHeadHtml'\nimport type { SeoSchemaConfig } from '../types/seo'\n\n/**\n * A VitePress `head` entry. Mirrors VitePress's `HeadConfig` without taking a\n * dependency on the `vitepress` package (which is not a dependency of this\n * library). The tuple forms are:\n * ['meta', { name|property, content }]\n * ['link', { rel, href, ... }]\n * ['script', { type: 'application/ld+json' }, '<serialised json>']\n */\nexport type VitePressHeadConfig =\n | [string, Record<string, string>]\n | [string, Record<string, string>, string]\n\n/**\n * The minimal slice of VitePress's `PageData` this factory reads and mutates.\n * Typed structurally so callers can pass VitePress's real `PageData` without a\n * cast and without this package importing `vitepress`.\n */\nexport interface VitePressPageData {\n /** Source-relative path, e.g. `index.md`, `blogs/my-post.md`. */\n relativePath: string\n /** Dynamic-route params (e.g. `{ topic: 'home-buying' }`). */\n params?: Record<string, unknown>\n /**\n * Page frontmatter; `head` is appended to here.\n *\n * Deliberately `any`: site `seo.config.ts` rules dot-walk arbitrary frontmatter,\n * so `unknown` would be a breaking API change for every consuming site.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n frontmatter: Record<string, any>\n /** VitePress page title (drives `<title>` via `titleTemplate`). */\n title?: string\n /** VitePress page description (drives the `description` meta). */\n description?: string\n [key: string]: unknown\n}\n\n/**\n * Context handed to the site's page-type rules and resolver hooks. Everything a\n * site needs to derive its title/description/og/schemas for one page, computed\n * once per page by the factory.\n */\nexport interface SeoPageContext {\n /** Route path, e.g. `/`, `/blogs/my-post`, `/locations/birmingham`. */\n route: string\n /** Slug: `'home'` for `/`, otherwise the route without its leading slash. */\n slug: string\n /** Absolute canonical URL for this route. */\n canonical: string\n /** Normalised site base URL (no trailing slash), e.g. `https://example.com`. */\n siteUrl: string\n /**\n * The page's frontmatter (read-only convenience; same object as pageData).\n *\n * Deliberately `any` — mirrors `VitePressPageData.frontmatter` above.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n frontmatter: Record<string, any>\n /** The resolved global SEO config block. */\n global: GlobalSeoConfig\n /** The full VitePress page data (for rules that need more than the above). */\n pageData: VitePressPageData\n}\n\n/**\n * The resolved per-page title / description / OG type a site may override.\n * Returned by the optional `resolvePage` hook so a site can apply its own\n * per-page-type title precedence (e.g. \"{City} Luxury Real Estate\") and decide\n * whether that title should win over VitePress's `titleTemplate`.\n */\nexport interface ResolvedPageOverrides {\n /**\n * The page title. When `setPageTitle` is true this is written to\n * `pageData.title` (VitePress then applies `titleTemplate`).\n */\n title?: string\n /**\n * When true, `title` is written back to `pageData.title`. Leave false for\n * pages whose frontmatter title should remain authoritative (e.g. blog posts\n * that already carry a good `<h1>`/title).\n */\n setPageTitle?: boolean\n /** The meta description. Written to `pageData.description` when truthy. */\n description?: string\n /** Open Graph type override (e.g. `'article'`, `'profile'`). */\n ogType?: SeoOpenGraphConfig['type']\n /** Open Graph image URL override (e.g. a post's header image). */\n ogImage?: string\n /**\n * Extra Open Graph fields to merge into the OG config (e.g. `publishedTime`,\n * `modifiedTime`, `section`, `tags`). The shared OG generator emits the\n * matching `article:*` tags when `ogType === 'article'`. This is how a site\n * supplies article metadata (date/category) without that logic living in the\n * package. Lower precedence than `ogType`/`ogImage`/`ogTitle`/`ogDescription`.\n */\n og?: Partial<SeoOpenGraphConfig>\n /** Keywords override (comma-separated) for the `keywords` meta. */\n keywords?: string\n /**\n * Open Graph title override. Defaults to `title` so og:title tracks <title>.\n */\n ogTitle?: string\n /**\n * Open Graph description override. Defaults to `description`.\n */\n ogDescription?: string\n}\n\n/** A single pluggable page-type rule: when `match` is true, emit `build`. */\nexport interface SeoPageTypeRule {\n /** Return true when this rule applies to the page (by route/slug/etc.). */\n match: (ctx: SeoPageContext) => boolean\n /** Build the page-type JSON-LD objects to emit (already plain objects). */\n build: (ctx: SeoPageContext) => Array<Record<string, unknown>>\n}\n\nexport interface CreateSeoTransformPageDataOptions {\n /** The parsed `.dcs/seo.yaml` (global graph + per-page meta). */\n seoConfig: SeoConfiguration | undefined\n /**\n * Pluggable page-type rules. Evaluated in order; **every** matching rule's\n * `build` output is emitted (so a route can contribute both a primary schema\n * and a BreadcrumbList from one rule, or be matched by several). The\n * real-estate BlogPosting / Place / CollectionPage / Service / FAQPage logic\n * is supplied here by the site — never hardcoded in this package.\n */\n pageTypeRules?: SeoPageTypeRule[]\n /**\n * Optional hook to override per-page title / description / OG before tags are\n * built — the site's title precedence and per-type description fallbacks.\n * Receives the same context as the rules. Anything it omits falls back to the\n * resolver / frontmatter defaults.\n */\n resolvePage?: (ctx: SeoPageContext) => ResolvedPageOverrides | undefined\n /**\n * Map a `relativePath` (+ params) to a route. Defaults to a VitePress-correct\n * implementation: `index` becomes `/`, a trailing `/index` is dropped, `.md`\n * is stripped, and dynamic `[name]` segments are substituted from\n * `pageData.params`. Override only for unusual routing.\n */\n relativePathToRoute?: (relativePath: string, params?: Record<string, unknown>) => string\n /**\n * Emit a `<meta name=\"keywords\">` from the resolved/overridden keywords.\n * Default true (parity with the bespoke KDH emitter, which emitted keywords).\n */\n includeKeywords?: boolean\n\n // ── Schema-completeness (shared with the SPA path via ./schemaGraph.ts) ────\n\n /**\n * Emit the cross-linked global `@graph` spine (Organization + WebSite + the\n * promoted LocalBusiness node) in place of the flat per-schema global JSON-LD.\n * When ON, the LocalBusiness subtype in `global.schemas` is ABSORBED into the\n * graph (not emitted twice); other global schemas (Person, etc.) are still\n * emitted standalone. Default `false`, so existing sites (which supply their\n * own graph via `global.schemas`) are unchanged until they opt in.\n */\n emitGraph?: boolean\n /**\n * Emit an automatic `BreadcrumbList` derived from the route depth on every\n * non-home page. Default `false` (sites that already emit breadcrumbs via\n * `pageTypeRules` should leave this off to avoid duplicates). Intermediate /\n * leaf crumb titles come from `breadcrumbTitles(ctx)` when provided, else a\n * slug-derived Title Case.\n */\n emitBreadcrumbs?: boolean\n /**\n * Map a context to a `{ route → title }` map for breadcrumb hop labels (e.g.\n * `{ '/': 'Home', '/blogs': 'Blog' }`). Missing entries fall back to a\n * slug-derived title. Only consulted when `emitBreadcrumbs` is true.\n */\n breadcrumbTitles?: (ctx: SeoPageContext) => Record<string, string> | undefined\n /**\n * Emit an automatic `BlogPosting` (author/publisher as `@id` refs) for blog\n * routes, derived from frontmatter (`title`/`date`/`image`/`description`).\n * `blogMatch` decides which routes are posts; default off. Skipped when a\n * `pageTypeRule` already emitted a `BlogPosting` for the page.\n */\n emitBlogPosting?: boolean\n /** Predicate selecting blog-post routes for `emitBlogPosting`. */\n blogMatch?: (ctx: SeoPageContext) => boolean\n /**\n * Emit an automatic, honesty-gated `FAQPage` from `frontmatter.faq` (an array\n * of `{ q, a }` / `{ question, answer }`). Emits nothing when the frontmatter\n * carries no structured Q&A. Default `false`.\n */\n emitFaq?: boolean\n /**\n * Provide REAL review items for the LocalBusiness node in the `@graph` (only\n * used when `emitGraph` is true). Honesty-gated downstream — items lacking a\n * rating/text/authorName are dropped, and an empty result emits no Review or\n * aggregateRating. Default: none.\n */\n resolveReviews?: (ctx: SeoPageContext) => ReviewSource[] | undefined\n /**\n * Provide the free-text trade/occupational license (the `business.license`\n * content.yaml key) for the LocalBusiness node in the `@graph` (only used when\n * `emitGraph` is true). When non-empty a `hasCredential`\n * (`EducationalOccupationalCredential`, `credentialCategory: \"license\"`) is\n * added to the business node; empty/absent ⇒ nothing. Default: none.\n */\n resolveLicense?: (ctx: SeoPageContext) => string | undefined\n\n /** Enable debug logging of the emitted head per page. */\n debug?: boolean\n}\n\n/** Default VitePress route derivation (matches the bespoke KDH helper). */\nexport function defaultRelativePathToRoute(\n relativePath: string,\n params?: Record<string, unknown>\n): string {\n let stem = (relativePath ?? '').replace(/\\.md$/i, '')\n // Substitute dynamic [param] segments (e.g. topics/[topic] → topics/home-buying).\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value == null) continue\n stem = stem.replace(`[${key}]`, String(value))\n }\n }\n if (stem === 'index') return '/'\n if (stem.endsWith('/index')) stem = stem.slice(0, -'/index'.length)\n return `/${stem}`\n}\n\nfunction normaliseSiteUrl(siteUrl: string | undefined): string {\n return (siteUrl ?? '').replace(/\\/$/, '')\n}\n\n/**\n * A `<script type=\"application/ld+json\">` VitePress head tuple.\n *\n * VitePress's `renderHead` writes this third tuple element into the script's\n * innerHTML WITHOUT escaping it for the HTML script context. Untrusted CMS free\n * text (review bodies, FAQ Q&A, blog descriptions) carrying `</script>` would\n * therefore break out and inject markup (stored XSS). We escape the serialized\n * JSON-LD via the SAME shared guard the SPA emit path uses (`escapeJsonLd`),\n * which `\\u`-escapes `<`/`>`/`&` while keeping the payload valid JSON.\n */\nfunction ldScript(obj: Record<string, unknown>): VitePressHeadConfig {\n return ['script', { type: 'application/ld+json' }, escapeJsonLd(JSON.stringify(obj))]\n}\n\n/**\n * Build just the SEO head tuples for a page (no `pageData` mutation). Exposed\n * separately so it is unit-testable without a VitePress `pageData` round-trip\n * and reusable by callers that manage the `head`/`title` sinks themselves.\n *\n * @returns `{ head, title, description }` — the head tuples to append, and the\n * final title/description (already overridden) the caller should write to\n * `pageData` when `applyTitle`/`applyDescription` are appropriate.\n */\nexport function buildVitePressSeoHead(\n pageData: VitePressPageData,\n options: CreateSeoTransformPageDataOptions\n): { head: VitePressHeadConfig[]; title?: string; description?: string; setPageTitle: boolean } {\n const {\n seoConfig,\n pageTypeRules = [],\n resolvePage,\n relativePathToRoute = defaultRelativePathToRoute,\n includeKeywords = true,\n emitGraph = false,\n emitBreadcrumbs = false,\n breadcrumbTitles,\n emitBlogPosting = false,\n blogMatch,\n emitFaq = false,\n resolveReviews,\n resolveLicense,\n } = options\n\n const global = seoConfig?.global ?? {}\n const siteUrl = normaliseSiteUrl(global.siteUrl)\n\n const fm = pageData.frontmatter ?? {}\n const route = relativePathToRoute(pageData.relativePath, pageData.params)\n const slug = route === '/' ? 'home' : route.slice(1)\n const canonical = route === '/' ? `${siteUrl}/` : `${siteUrl}${route}`\n\n const ctx: SeoPageContext = {\n route,\n slug,\n canonical,\n siteUrl,\n frontmatter: fm,\n global,\n pageData,\n }\n\n // Resolve global + per-page SEO from the shared resolver. We pass the page's\n // frontmatter title as the fallback so un-configured pages still get a\n // sensible (templated) title; seo.yaml `pages.<slug>.title` still wins.\n const resolved = resolvePageSeo(slug, route, seoConfig, fm.title)\n\n // Site-provided per-page overrides (title precedence + per-type description).\n const overrides = resolvePage?.(ctx) ?? {}\n\n // ── Title handling ────────────────────────────────────────────────────────\n // Two titles matter for VitePress:\n // • `rawTitle` → written to `pageData.title` (VitePress applies its OWN\n // `titleTemplate`, so the <title> element is templated once).\n // • `templated` → used for og:title / twitter:title so they match the final\n // <title> element (the shared resolver's og:title fix).\n // A site override (`overrides.title`) is the RAW per-type title; we template\n // it ourselves for OG/Twitter using the same precedence as the resolver.\n const rawTitle =\n overrides.title ?? pageSpecificSeoTitle(slug, seoConfig) ?? fm.title ?? resolved.title\n const templated = applyTitleTemplate(rawTitle, global, resolved)\n\n // Description precedence mirrors the bespoke emitter: a site per-type override\n // → the explicit seo.yaml `page.description` → the page frontmatter → the\n // global default. (The shared resolver collapses frontmatter and the global\n // default, so we read the explicit page description directly to keep\n // frontmatter ahead of the global fallback.)\n const pageSeoDescription = seoConfig?.pages?.[slug]?.description\n const description =\n overrides.description ||\n pageSeoDescription ||\n fm.description ||\n global.defaultDescription ||\n ''\n const keywords =\n overrides.keywords ||\n seoConfig?.pages?.[slug]?.keywords ||\n (Array.isArray(fm.tags) ? fm.tags.join(', ') : '')\n\n // Open Graph type/image: site override → seo.yaml → frontmatter → default.\n const ogType =\n overrides.ogType || resolved.openGraph.type\n const ogImage =\n overrides.ogImage || resolved.openGraph.image || fm.headerImage || fm.image || global.images?.ogDefault\n\n const head: VitePressHeadConfig[] = []\n\n // Keywords (parity: the bespoke emitter pushed keywords when present).\n if (includeKeywords && keywords) {\n head.push(['meta', { name: 'keywords', content: keywords }])\n }\n\n // Robots.\n head.push(['meta', { name: 'robots', content: resolved.robots }])\n\n // Canonical.\n head.push(['link', { rel: 'canonical', href: canonical }])\n\n // Verification.\n if (global.verification?.google) {\n head.push(['meta', { name: 'google-site-verification', content: global.verification.google }])\n }\n if (global.verification?.bing) {\n head.push(['meta', { name: 'msvalidate.01', content: global.verification.bing }])\n }\n\n // Open Graph — reuse the shared generator, with our resolved title/desc/og\n // overrides applied via a synthetic OG config so the output matches the SPA\n // emitter byte-for-byte (article:* fields included when type === 'article').\n // og:title tracks the TEMPLATED title (the resolver's og:title fix), so it\n // matches the final <title> element rather than the raw per-type title.\n //\n // We DON'T spread `resolved.openGraph` wholesale: for an un-configured page\n // its `description` is the global default, which would wrongly win over this\n // page's actual `description`. We instead keep only the page's *explicit* OG\n // fields (from seo.yaml) and let `description`/`title` fall through correctly.\n const pageOg = seoConfig?.pages?.[slug]?.openGraph\n const ogConfig: SeoOpenGraphConfig = {\n ...pageOg,\n // Site-supplied extra OG fields (publishedTime/section/tags/…) — lower\n // precedence than the explicit type/title/desc/image resolved below.\n ...overrides.og,\n type: ogType,\n title: overrides.ogTitle || templated,\n description: overrides.ogDescription || pageOg?.description || description,\n ...(ogImage ? { image: ogImage } : {}),\n url: canonical,\n }\n for (const t of generateOpenGraphMeta(ogConfig, global, templated, description, canonical)) {\n head.push(['meta', { property: t.property, content: t.content }])\n }\n\n // Twitter — shared generator (twitter:title also tracks the templated title).\n for (const t of generateTwitterMeta(resolved.twitter, global, templated, description)) {\n head.push(['meta', { name: t.name, content: t.content }])\n }\n\n // JSON-LD: the global knowledge graph (RealEstateAgent / Person / WebSite /\n // Organization / …) on every page, via the shared generator.\n //\n // When `emitGraph` is on, the shared spine (Organization + WebSite + the\n // promoted LocalBusiness node) is emitted as a single `@graph` block and the\n // LocalBusiness subtype is removed from the flat per-schema emission so it is\n // not duplicated. Other global schemas (Person, etc.) still emit standalone.\n let globalSchemas: SeoSchemaConfig[] = resolved.schemas\n if (emitGraph) {\n const reviews = resolveReviews?.(ctx)\n const license = resolveLicense?.(ctx)\n const graph = buildGlobalGraph(global, { reviews, license })\n if (graph.length > 0) {\n for (const obj of graph) head.push(ldScript(obj))\n // Drop the schemas the @graph now provides canonically (LocalBusiness +\n // any Organization/WebSite) so they are not emitted twice.\n globalSchemas = resolved.schemas.filter((s) => !graphAbsorbs(s, global))\n }\n }\n for (const obj of generateJsonLd(globalSchemas, global)) {\n head.push(ldScript(obj as Record<string, unknown>))\n }\n\n // ── Shared schema-completeness builders (./schemaGraph.ts) ────────────────\n // Run BEFORE the site's pageTypeRules so vertical-specific extras (Place /\n // CollectionPage) layer on top of the shared Breadcrumb/Blog/FAQ base.\n //\n // BreadcrumbList from route depth (home ⇒ none).\n if (emitBreadcrumbs) {\n const titles = breadcrumbTitles?.(ctx) ?? {}\n const trail = breadcrumbTrailFromRoute(route, siteUrl, titles)\n for (const obj of buildBreadcrumbList(trail)) head.push(ldScript(obj))\n }\n\n // BlogPosting from frontmatter — only when the site marks this a blog route\n // and no pageTypeRule will also emit one (it would be a dup). We detect a\n // rule-emitted BlogPosting by checking the head we are about to add below, so\n // here we simply defer to the site: if it wired a BlogPosting rule, it should\n // leave `emitBlogPosting` off. (See README/option docs.)\n if (emitBlogPosting && (blogMatch ? blogMatch(ctx) : false)) {\n const blogMeta: BlogMeta = {\n headline: typeof fm.title === 'string' ? fm.title : slugToTitle(slug),\n datePublished: typeof fm.date === 'string' ? fm.date : undefined,\n dateModified: typeof fm.lastUpdated === 'string' ? fm.lastUpdated : undefined,\n url: canonical,\n image: typeof fm.headerImage === 'string' ? fm.headerImage : (typeof fm.image === 'string' ? fm.image : undefined),\n description: typeof fm.description === 'string' ? fm.description : undefined,\n }\n for (const obj of buildBlogPosting(blogMeta, siteUrl, global)) head.push(ldScript(obj))\n }\n\n // FAQPage from frontmatter.faq (honesty-gated; component-only FAQ ⇒ nothing).\n if (emitFaq) {\n const faq = Array.isArray(fm.faq) ? (fm.faq as FaqSource[]) : undefined\n for (const obj of buildFaqPage(faq)) head.push(ldScript(obj))\n }\n\n // Page-type JSON-LD: every matching site rule contributes its objects.\n for (const rule of pageTypeRules) {\n let matched = false\n try {\n matched = rule.match(ctx)\n } catch (err) {\n console.warn('[dcs-seo] pageTypeRule.match threw; skipping rule:', err)\n continue\n }\n if (!matched) continue\n let objects: Array<Record<string, unknown>> = []\n try {\n objects = rule.build(ctx) ?? []\n } catch (err) {\n console.warn('[dcs-seo] pageTypeRule.build threw; skipping rule output:', err)\n continue\n }\n for (const obj of objects) {\n if (obj) head.push(ldScript(obj))\n }\n }\n\n return {\n // Hand back the RAW title for `pageData.title`; VitePress applies its own\n // `titleTemplate` to produce the templated <title> element.\n title: rawTitle,\n head,\n description: description || undefined,\n setPageTitle: overrides.setPageTitle ?? false,\n }\n}\n\n/**\n * Compute the page-specific (un-templated) `seo.yaml` title for a slug, if any.\n * This is the title VitePress should template — it must NOT come pre-templated.\n */\nfunction pageSpecificSeoTitle(\n slug: string,\n seoConfig: SeoConfiguration | undefined\n): string | undefined {\n return seoConfig?.pages?.[slug]?.title || undefined\n}\n\n/**\n * Apply the global `titleTemplate` to a raw page title, matching the shared\n * resolver's precedence: a page that opts out (`noTitleTemplate`) or a\n * brand-complete global default is used verbatim; otherwise `%s` is filled.\n *\n * When `rawTitle` is the resolver's already-final title (e.g. the global\n * `defaultTitle`, used when there is no page-specific title), we return it\n * unchanged to avoid double-templating.\n */\nfunction applyTitleTemplate(\n rawTitle: string,\n global: GlobalSeoConfig,\n resolved: ResolvedPageSeoLike\n): string {\n // If rawTitle is exactly the resolver's final title, it is either already\n // templated (page-specific) or a brand-complete default — use as-is.\n if (rawTitle === resolved.title) return rawTitle\n if (!global.titleTemplate) return rawTitle\n return global.titleTemplate.replace('%s', rawTitle)\n}\n\n/** Structural subset of `ResolvedPageSeo` used by the title helper. */\ninterface ResolvedPageSeoLike {\n title: string\n}\n\n/**\n * Create a VitePress `transformPageData(pageData)` function that bakes DCS SEO\n * (global meta/OG/Twitter/canonical + global JSON-LD graph + pluggable\n * page-type JSON-LD) into the SSG `<head>`.\n *\n * Mutations performed on `pageData`:\n * - **`frontmatter.head`** — the resolved tags are *appended* to any existing\n * `head` (so site-level `head` config is preserved).\n * - **`description`** — set to the resolved/overridden description so\n * VitePress emits exactly one `description` meta (no duplicate; we do not\n * push our own description meta).\n * - **`title`** — set only when the site's `resolvePage` hook returns\n * `setPageTitle: true` for this page, mirroring the bespoke behaviour where\n * seo.yaml/per-type titles are authoritative but a post's frontmatter title\n * is left intact.\n *\n * Defensive: never throws (a failure logs a warning and leaves `pageData`\n * untouched), so SEO can never break a production VitePress build.\n */\nexport function createSeoTransformPageData(\n options: CreateSeoTransformPageDataOptions\n): (pageData: VitePressPageData) => void {\n return function transformPageData(pageData: VitePressPageData): void {\n try {\n const { head, title, description, setPageTitle } = buildVitePressSeoHead(pageData, options)\n\n pageData.frontmatter = pageData.frontmatter ?? {}\n const existing = (pageData.frontmatter.head as VitePressHeadConfig[] | undefined) ?? []\n pageData.frontmatter.head = [...existing, ...head]\n\n // Drive VitePress's own description meta from our resolved value so it is\n // not duplicated by the site-level default. (VitePress reads\n // pageData.description BEFORE transformPageData and renders the meta from\n // it, so overwriting the field here is the correct sink.)\n if (description) {\n pageData.description = description\n }\n\n // Make seo.yaml / per-type titles authoritative for <title> when the site\n // asks for it; VitePress then appends its titleTemplate.\n if (setPageTitle && title) {\n pageData.title = title\n }\n\n if (options.debug) {\n console.log(\n `[dcs-seo] transformPageData ${pageData.relativePath} → +${head.length} head tag(s)` +\n (setPageTitle && title ? ` (title=\"${title}\")` : '')\n )\n }\n } catch (err) {\n console.warn(\n `[dcs-seo] createSeoTransformPageData failed for ${pageData?.relativePath}; head left unchanged:`,\n err\n )\n }\n }\n}\n"]}