@bettercms-ai/ui 0.20.1 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/blocks/Heading.tsx","../src/blocks/portableText.tsx","../src/renderer.tsx","../src/blockStyle.ts","../../richtext/src/types.ts","../../richtext/src/styled.ts","../../richtext/src/href.ts","../../richtext/src/key.ts","../../richtext/src/table.ts","../../richtext/src/key-takeaways.ts","../../richtext/src/component-copy.ts","../../richtext/src/plain.ts","../../richtext/src/read.ts","../src/blocks/Text.tsx","../src/lib/utils.ts","../src/blocks/Image.tsx","../src/blocks/Button.tsx","../src/blocks/Spacer.tsx","../src/blocks/Columns.tsx","../src/blocks/Video.tsx","../src/blocks/Section.tsx","../src/blocks/Navbar.tsx","../src/blocks/Footer.tsx","../src/blocks/Slider.tsx","../src/blocks/Tabs.tsx","../src/blocks/Placeholders.tsx","../src/registry.ts"],"sourcesContent":["import type { HeadingProps } from \"@bettercms-ai/types\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface HeadingBlockProps extends HeadingProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Heading({\n text,\n level = 2,\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: HeadingBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const classNames = `font-heading font-bold tracking-tight text-foreground ${alignClass} ${className}`;\n\n switch (level) {\n case 1:\n return <h1 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h1>;\n case 2:\n return <h2 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h2>;\n case 3:\n return <h3 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h3>;\n default:\n return <h2 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h2>;\n }\n}\n","import React from \"react\";\nimport { PortableText as PT, type PortableTextComponents } from \"@portabletext/react\";\nimport type { ContentBlock } from \"@bettercms-ai/types\";\nimport { BlocksRenderer } from \"../renderer\";\nimport {\n PT_TYPES,\n pullQuoteHref,\n keyTakeawaysView,\n safeHref,\n isStyledTag,\n styledAttrEntries,\n normalizePortableText,\n tableGroupLayout,\n tableSections,\n highlightedColumns,\n sortableColumns,\n tableColumns,\n isTypedTable,\n typedCellView,\n jsonLdOf,\n designOf,\n footnotesOf,\n footnoteNumbers,\n portableTextToPlainText,\n detachedComponentCopy,\n type PortableText as PtValue,\n type PortableTextTable,\n type PortableTextTableRow,\n} from \"@bettercms-ai/richtext\";\n\n/**\n * Portable Text → React, on the official renderer.\n *\n * REPLACES A 66-LINE HAND-ROLLED WALKER that could not express a hyperlink. It had no notion\n * of `markDefs`, so a link had nowhere to store its href; it also DROPPED unknown marks and\n * unknown styles silently, meaning content written by a newer editor disappeared here with no\n * error. `@portabletext/react` handles both: annotations resolve through `markDefs`, and\n * `unknownMark`/`unknownType` render through instead of swallowing.\n *\n * ── Why v6 and not v8 ────────────────────────────────────────────────────────────────────\n * `@portabletext/react@6.2.0` peers `react ^18.2 || ^19`. v7 and v8 require `react ^19`, and\n * this package peers `react >=18` — adopting them would force a breaking peer bump on every\n * customer of `@bettercms-ai/ui` and `@bettercms-ai/next` for no rendering benefit.\n *\n * ── The binding, which is the point of the whole migration ───────────────────────────────\n * Every top-level node carries `data-bcms-field=\"<blockId>__<prop>#<_key>\"`. That suffix is\n * what makes ONE PARAGRAPH inside a rich-text field addressable, instead of the field being a\n * single opaque string with one binding on the outside. `_key` and not an index, because a\n * list is edited continuously and an index-addressed binding silently retargets a different\n * paragraph the moment someone inserts one above it.\n */\n\nconst TOKEN = /^[a-z0-9-]+$/i;\n\n/**\n * `style=\"color:red\"` → `{ color: \"red\" }`, because React refuses the string form outright\n * (\"The `style` prop expects a mapping from style properties to values, not a string\") — it\n * THROWS rather than degrading, so a styled run carrying a style attribute would blank the\n * page it is on. The HTML serialiser writes the same declaration back out verbatim; this is\n * only the shape React demands to write it.\n *\n * Custom properties keep their `--` spelling (React sets those through `setProperty`);\n * everything else is camelised, which is the only name React recognises.\n */\nfunction styleObject(css: string): React.CSSProperties {\n const out: Record<string, string> = {};\n for (const declaration of css.split(\";\")) {\n const at = declaration.indexOf(\":\");\n if (at < 0) continue;\n const name = declaration.slice(0, at).trim();\n const value = declaration.slice(at + 1).trim();\n if (!name || !value) continue;\n out[name.startsWith(\"--\") ? name : name.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())] = value;\n }\n return out as React.CSSProperties;\n}\n\nexport interface PortableTextProps {\n value: unknown;\n /** `<blockId>__<prop>`. Present ⇒ stamp a per-block address. Absent ⇒ published output. */\n bindingBase?: string;\n /**\n * Renders an embedded block this renderer cannot resolve (a component — it has no project).\n * Anything but `undefined` replaces the marker; `null` renders nothing.\n */\n renderBlock?: (value: Record<string, unknown>) => React.ReactNode;\n}\n\n/** Per-block wrapper: the element the editor selects when someone clicks this paragraph. */\nfunction Bound({\n base,\n nodeKey,\n children,\n}: {\n base: string | undefined;\n nodeKey: string | undefined;\n children: React.ReactNode;\n}) {\n if (!base || !nodeKey) return <>{children}</>;\n return (\n <div data-bcms-field={`${base}#${nodeKey}`} data-bcms-kind=\"richtext\">\n {children}\n </div>\n );\n}\n\n/**\n * A table, drawn from the value rather than from a marker.\n *\n * A cell holds BLOCKS, so it renders through this same component set — a link or a bolded word\n * inside a cell is content, and a cell that rendered only text would drop it. `thead` is the\n * LEADING RUN of `head` rows and `th` follows the cell's own `header`, matching\n * `portableTextToHtml` exactly: two renderers of one value that disagree is the whole bug class\n * this package exists to close.\n */\nfunction Table({ value }: { value: PortableTextTable }) {\n const components = React.useMemo(buildComponents, []);\n /**\n * A LONE paragraph in a cell is unwrapped, and only a lone one.\n *\n * `<td>Pro</td>` is what a cell of one line looks like in every table ever authored, and\n * `portableTextToHtml` unwraps it for exactly that reason — the html it emits is compared\n * against stored markup. Two renderers of one value that disagree about a `<p>` is the drift\n * this package exists to end, so the rule is spelled the same way here. A cell holding two\n * blocks keeps both paragraphs, in both renderers.\n */\n const inline = React.useMemo(\n () => ({ ...components, block: { normal: ({ children }: { children?: React.ReactNode }) => <>{children}</> } }),\n [components],\n );\n const rows = Array.isArray(value?.rows) ? value.rows : [];\n if (rows.length === 0) return null;\n\n // \"Highlight this column\": the columns a first-row cell marks, as `portableTextToHtml` stamps them.\n const marked = highlightedColumns(value);\n // \"Visitors can sort columns\": `<col data-sortable=\"true\">` per sortable column, as `portableTextToHtml` writes it.\n const sortable = sortableColumns(value);\n // \"On phones\" (stack / cards): V2's explicit ARIA roles (withTableRoles) and each td's column label, as\n // `portableTextToHtml` writes them; \"Keep column names in view\" is the figure's data-sticky.\n const design = designOf(value.design);\n const stacked = design?.responsive === \"stack\" || design?.responsive === \"cards\";\n const role = (r: string) => (stacked ? { role: r } : {});\n const labels: string[] = [];\n if (stacked) {\n const headRows = tableSections(value).head as PortableTextTableRow[];\n let at = 0;\n for (const c of Array.isArray(headRows[headRows.length - 1]?.cells) ? headRows[headRows.length - 1].cells : []) {\n const text = portableTextToPlainText(Array.isArray(c?.content) ? c.content : []).replace(/\\s+/g, \" \").trim();\n const w = typeof c?.span === \"number\" && c.span > 1 ? c.span : 1;\n for (let i = 0; i < w; i += 1) labels[at + i] = text;\n at += w;\n }\n }\n // The V2 table's typed columns (a first-row cell's `column`): the same markup `portableTextToHtml` writes (typedCellView).\n const columns = tableColumns(value);\n const typed = isTypedTable(value);\n const headSet = new Set<unknown>(tableSections(value).head);\n const props = (list: [string, string][]) => Object.fromEntries(list);\n // Footnotes: a cell's data-note and numbered marker, and V2's notes block under the table, as `portableTextToHtml` writes them\n const fnNumber = footnoteNumbers(value);\n const footnotes = footnotesOf(value.footnotes) ?? [];\n const tableId = typeof value._key === \"string\" && value._key ? value._key : \"tbl\";\n const cells = (row: PortableTextTableRow) => {\n let col = 0;\n return (Array.isArray(row?.cells) ? row.cells : []).map((cell) => {\n const Tag = cell?.header === true ? \"th\" : \"td\";\n const span = typeof cell?.span === \"number\" && cell.span > 1 ? cell.span : undefined;\n const at = col;\n col += span ?? 1;\n const rowSpan = typeof cell?.rowSpan === \"number\" && cell.rowSpan > 1 ? cell.rowSpan : undefined;\n const n = typeof cell?.note === \"string\" ? fnNumber.get(cell.note) : undefined;\n const content = Array.isArray(cell?.content) ? cell.content : [];\n const lone =\n content.length === 1\n && (content[0] as { _type?: string; style?: string; listItem?: string })._type === \"block\"\n && (content[0] as { style?: string }).style === \"normal\"\n && !(content[0] as { listItem?: string }).listItem;\n const view = typed ? typedCellView(columns[at], row, (cell ?? {}) as unknown as Record<string, unknown>, headSet.has(row)) : undefined;\n // a footnote: data-note after the value and before data-align (V2 cellHtml), the marker after the cell's words\n const noteAttr: [string, string][] = n ? [[\"data-note\", cell.note as string]] : [];\n const tail = [...(view?.tail ?? []).filter(([k]) => k !== \"data-align\"), ...noteAttr, ...(view?.tail ?? []).filter(([k]) => k === \"data-align\")];\n const supHtml = n ? `<sup><a href=\"#${tableId}-fn-${n}\" aria-label=\"Note ${n}\">${n}</a></sup>` : \"\";\n return (\n <Tag key={cell?._key} {...(Tag === \"td\" ? role(\"cell\") : {})} {...(span ? { colSpan: span } : {})} {...(rowSpan ? { rowSpan } : {})} {...props(view?.lead ?? [])} {...(stacked && Tag === \"td\" ? { \"data-label\": labels[at] || `Column ${at + 1}` } : {})} {...(marked.has(at) ? { \"data-highlight\": \"true\" } : {})} {...props(tail)} {...(view?.inner !== undefined ? { dangerouslySetInnerHTML: { __html: view.inner + supHtml } } : {})}>\n {view?.inner !== undefined ? undefined : (\n <>\n <PT value={content as never} components={lone ? inline : components} />\n {n ? <sup><a href={`#${tableId}-fn-${n}`} aria-label={`Note ${n}`}>{n}</a></sup> : null}\n </>\n )}\n </Tag>\n );\n });\n };\n // \"Highlight this row\" is `data-highlight=\"true\"`, as `portableTextToHtml` and the V2 table write it.\n const rowEl = (row: PortableTextTableRow) => (\n <tr key={row?._key} {...role(\"row\")} {...(typeof row?.type === \"string\" ? { \"data-type\": row.type } : {})} {...(row?.highlight === true ? { \"data-highlight\": \"true\" } : {})}>{cells(row)}</tr>\n );\n // The same three sections `portableTextToHtml` draws: leading head run, body, trailing footer run.\n const sections = tableSections(value);\n const head = sections.head.length;\n const foot = sections.foot as PortableTextTableRow[];\n\n /**\n * Caption, column groups, row groups and the note: the same runs `portableTextToHtml` draws,\n * from the same `tableGroupLayout`, so the two renderers cannot disagree about where a group\n * starts. A table without them draws exactly what it drew before they existed.\n */\n const layout = tableGroupLayout(value);\n const caption = typeof value.caption === \"string\" ? value.caption.trim() : \"\";\n const notes = typeof value.notes === \"string\" ? value.notes.trim() : \"\";\n const notesId = (notes || footnotes.length) && typeof value._key === \"string\" && value._key ? `${value._key}-notes` : undefined;\n const span = (n: number) => (n > 1 ? n : undefined);\n const groupRow = layout.columns.length ? (\n <tr {...role(\"row\")}>\n {layout.columns.flatMap((run, i) =>\n run.label === undefined\n ? Array.from({ length: run.width }, (_, k) => <td key={`${i}-${k}`} {...role(\"cell\")} />)\n : [<th key={i} {...role(\"columnheader\")} scope=\"colgroup\" colSpan={span(run.width)} data-group={run.key}>{run.label}</th>],\n )}\n </tr>\n ) : null;\n const bodies = layout.rows.length\n ? layout.rows.map((run, i) => (\n <tbody key={i} {...role(\"rowgroup\")} data-key={run.key}>\n {run.label !== undefined && (\n <tr {...role(\"row\")}><th {...role(\"rowheader\")} scope=\"rowgroup\" colSpan={span(layout.width)}>{run.label}</th></tr>\n )}\n {(run.rows as PortableTextTableRow[]).map(rowEl)}\n </tbody>\n ))\n : sections.body.length > 0 && <tbody {...role(\"rowgroup\")}>{(sections.body as PortableTextTableRow[]).map(rowEl)}</tbody>;\n\n // A typed column's `<col>`: V2 renderTableHtml's attributes in its order, as `portableTextToHtml` writes them.\n const colProps = (i: number): Record<string, string> => {\n const c = columns[i];\n return {\n ...(c ? { \"data-type\": c.type ?? \"text\" } : {}),\n ...(c?.unit ? { \"data-unit\": c.unit } : {}),\n ...(c?.currency ? { \"data-currency\": c.currency } : {}),\n ...(c?.decimals != null ? { \"data-decimals\": String(c.decimals) } : {}),\n ...(c?.align ? { \"data-align\": c.align } : {}),\n ...(sortable.has(i) ? { \"data-sortable\": \"true\" } : {}),\n ...(c?.trueLabel ? { \"data-true\": c.trueLabel } : {}),\n ...(c?.falseLabel ? { \"data-false\": c.falseLabel } : {}),\n ...(c?.max != null ? { \"data-max\": String(c.max) } : {}),\n ...(c?.description ? { \"data-description\": c.description } : {}),\n };\n };\n // `bcms-table` on migrated values only, exactly as `portableTextToHtml` writes it — see\n // `PortableTextTable[\"legacy\"]`. The two renderers agree about the class or they agree about\n // nothing.\n const table = (\n <table {...role(\"table\")} {...(value.legacy === true ? { className: \"bcms-table\" } : {})} {...(notesId ? { \"aria-describedby\": notesId } : {})}>\n {caption && <caption>{caption}</caption>}\n {sortable.size || columns.some(Boolean)\n ? (layout.columns.length ? layout.columns.map((run) => run.width) : [layout.width]).map((w, i, all) => {\n const from = all.slice(0, i).reduce((n, x) => n + x, 0);\n return (\n <colgroup key={i}>\n {Array.from({ length: w }, (_, k) => <col key={k} {...colProps(from + k)} />)}\n </colgroup>\n );\n })\n : layout.columns.map((run, i) => <colgroup key={i} span={span(run.width)} />)}\n {(groupRow || head > 0) && <thead {...role(\"rowgroup\")}>{groupRow}{rows.slice(0, head).map(rowEl)}</thead>}\n {bodies}\n {foot.length > 0 && <tfoot {...role(\"rowgroup\")}>{foot.map(rowEl)}</tfoot>}\n </table>\n );\n // \"Visitors can search rows\" is the figure's data-filter, as `portableTextToHtml` and the V2 table write it.\n const filter = value.filterable === true;\n // \"Help search engines read this as\": the figure's data-jsonld (+ axis, price row), as `portableTextToHtml` writes it\n const ld = jsonLdOf(value.jsonLd);\n const ldAttrs = ld\n ? { \"data-jsonld\": ld.type, ...(ld.axis ? { \"data-jsonld-axis\": ld.axis } : {}), ...(ld.priceRow ? { \"data-jsonld-price\": ld.priceRow } : {}) }\n : {};\n const designAttrs = design ? { ...(design.responsive ? { \"data-responsive\": design.responsive } : {}), ...(design.sticky ? { \"data-sticky\": \"true\" } : {}) } : {};\n return notes || filter || ld || design || footnotes.length ? (\n <figure data-rt=\"table\" {...designAttrs} {...(filter ? { \"data-filter\": \"true\" } : {})} {...ldAttrs}>\n {table}\n {footnotes.length ? (\n <div className=\"bt-notes\" id={`${tableId}-notes`}>\n {notes ? <p data-notes=\"\">{notes}</p> : null}\n <ol data-footnotes=\"\">\n {footnotes.map((f, i) => <li key={f._key} id={`${tableId}-fn-${i + 1}`} data-key={f._key}>{f.text}</li>)}\n </ol>\n </div>\n ) : notes ? <p data-notes=\"\" id={notesId}>{notes}</p> : null}\n </figure>\n ) : table;\n}\n\nfunction buildComponents(renderBlock?: PortableTextProps[\"renderBlock\"]): PortableTextComponents {\n return {\n types: {\n [PT_TYPES.divider]: () => <hr />,\n [PT_TYPES.table]: ({ value }) => <Table value={value as PortableTextTable} />,\n [PT_TYPES.code]: ({ value }) => {\n const v = value as { code?: unknown; language?: unknown };\n const lang = typeof v.language === \"string\" && TOKEN.test(v.language) ? v.language : undefined;\n return (\n <pre>\n <code className={lang ? `language-${lang}` : undefined}>\n {typeof v.code === \"string\" ? v.code : \"\"}\n </code>\n </pre>\n );\n },\n /**\n * A generic embedded block renders as a marker, not as content: its payload references\n * something (a component, a media id) that has to be RESOLVED against the project, and\n * this renderer has no project. A marker keeps the node visible and addressable rather\n * than rendering nothing at all, which is how embedded content used to vanish. A caller\n * that CAN resolve it passes `renderBlock`.\n */\n [PT_TYPES.block]: ({ value }) => {\n const rendered = renderBlock?.(value as Record<string, unknown>);\n if (rendered !== undefined) return <>{rendered}</>;\n // A callout and a toggle carry their own text: the same DOM as portableTextToHtml's (callout-toggle.test.tsx).\n const builtIn = calloutOrToggle(value);\n if (builtIn) return builtIn;\n // A separate copy of a component carries its own frozen blocks: nothing to resolve.\n const copy = detachedComponentCopy(value);\n if (copy) {\n return (\n <div className=\"bcms-block bcms-block--component\" data-bcms-component-copy={copy.componentId} data-bcms-instance={copy.id}>\n <BlocksRenderer blocks={copy.blocks as ContentBlock[]} />\n </div>\n );\n }\n const v = value as { schemaKey?: unknown };\n const key = typeof v.schemaKey === \"string\" ? v.schemaKey : \"block\";\n return <div className=\"bcms-block\" data-bcms-schema-key={key} />;\n },\n /**\n * A pull quote — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's\n * `<figure data-rt=\"pullquote\">`, the name linked (and the quote cited) only through\n * `safeHref`. pull-quote.test.tsx compares the two.\n */\n [PT_TYPES.pullQuote]: ({ value }) => {\n const v = value as { quote?: unknown; name?: unknown; role?: unknown; link?: unknown };\n const text = (x: unknown) => (typeof x === \"string\" ? x.trim() : \"\");\n const quote = typeof v.quote === \"string\" ? v.quote : \"\";\n const name = text(v.name);\n const role = text(v.role);\n const href = safeHref(pullQuoteHref(v.link)) ?? undefined;\n return (\n <figure data-rt=\"pullquote\">\n <blockquote cite={href}>\n <p>{quote}</p>\n </blockquote>\n {name || role ? (\n <figcaption>\n {name ? (href ? <a href={href} data-name=\"\">{name}</a> : <span data-name=\"\">{name}</span>) : null}\n {name && role ? \", \" : null}\n {role ? <span data-role=\"\">{role}</span> : null}\n </figcaption>\n ) : null}\n </figure>\n );\n },\n /**\n * Key takeaways — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's\n * `<aside data-rt=\"takeaways\">`, read through the one `keyTakeawaysView` both renderers share.\n * portable-text-key-takeaways.test.tsx compares the two.\n */\n [PT_TYPES.keyTakeaways]: ({ value }) => {\n const { title, points } = keyTakeawaysView(value);\n return (\n <aside data-rt=\"takeaways\">\n {title ? <p data-title=\"\">{title}</p> : null}\n <ul>\n {points.map((p, i) => (\n <li key={i}>{p}</li>\n ))}\n </ul>\n </aside>\n );\n },\n [PT_TYPES.mention]: ({ value }) => {\n const v = value as { label?: unknown };\n return <span className=\"bcms-mention\">{typeof v.label === \"string\" ? v.label : \"\"}</span>;\n },\n },\n marks: {\n /**\n * The only attribute this renderer writes from author data. An unsafe href renders the\n * LABEL rather than deleting it — blocking a link must not lose the words.\n */\n [PT_TYPES.link]: ({ value, children }) => {\n const v = (value ?? {}) as { href?: unknown; target?: unknown };\n const href = typeof v.href === \"string\" ? safeHref(v.href) : null;\n if (!href) return <>{children}</>;\n const blank = v.target === \"_blank\";\n return (\n <a href={href} {...(blank ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {})}>\n {children}\n </a>\n );\n },\n /**\n * The site's own inline markup, rendered as itself — the React half of\n * `portableTextToHtml`'s `styled` mark, and it has to stay the same shape: Astro and the\n * editor canvas render through that serialiser while a Next site renders through this\n * one, and two renderers of one value that disagree is the whole bug class.\n *\n * The tag is re-checked against `STYLED_TAGS` and the attribute bag re-filtered, because\n * a markDef is DATA and arrives from imports, the Management API and AI writes. React\n * escapes the values it writes; the allowlist is what keeps a `href` or an `onClick` out\n * of the props entirely. An unrecognised tag renders its CHILDREN — the words are the\n * author's, the wrapper is only the design.\n */\n [PT_TYPES.styled]: ({ value, children }) => {\n const v = (value ?? {}) as { tag?: unknown; className?: unknown; attrs?: unknown };\n const tag = typeof v.tag === \"string\" ? v.tag.toLowerCase() : \"\";\n if (!isStyledTag(tag)) return <>{children}</>;\n const Tag = tag as \"span\";\n const className = typeof v.className === \"string\" && v.className ? v.className : undefined;\n const attrs = styledAttrEntries(v.attrs);\n const style = attrs.find(([name]) => name === \"style\")?.[1];\n return (\n <Tag\n className={className}\n {...Object.fromEntries(attrs.filter(([name]) => name !== \"style\"))}\n {...(style ? { style: styleObject(style) } : {})}\n >\n {children}\n </Tag>\n );\n },\n highlight: ({ children }) => <mark>{children}</mark>,\n \"strike-through\": ({ children }) => <s>{children}</s>,\n /**\n * A colour mark carries a TOKEN name (`color:brand-accent`), never a baked hex, so the\n * consuming site styles it and a rebrand does not orphan the content.\n */\n color: ({ children, markType }) => {\n const token = markType.startsWith(\"color:\") ? markType.slice(6) : \"\";\n if (!TOKEN.test(token)) return <>{children}</>;\n return <span className={`bcms-color-${token}`}>{children}</span>;\n },\n },\n /** Unknown marks and types render their children. Never swallow content. */\n unknownMark: ({ children }) => <>{children}</>,\n unknownType: ({ children }) => <>{children ?? null}</>,\n unknownBlockStyle: ({ children }) => <p>{children}</p>,\n };\n}\n\nconst isListItem = (node: { _type?: string; listItem?: unknown }): boolean =>\n node._type === \"block\" && typeof node.listItem === \"string\" && !!node.listItem;\n\n/**\n * Consecutive list items as ONE run, everything else alone — the same grouping\n * `portableTextToHtml({ bindingBase })` does, so both renderers draw one `<ol>` per list.\n */\nfunction runsOf<T extends { _type?: string; listItem?: unknown }>(blocks: readonly T[]): T[][] {\n const runs: T[][] = [];\n for (const node of blocks) {\n const last = runs.at(-1);\n if (last && isListItem(node) && isListItem(last[0])) last.push(node);\n else runs.push([node]);\n }\n return runs;\n}\n\n/** Plain multi-line text as paragraphs, as the serialiser splits it: blank line = new paragraph, newline = `<br>`. */\nfunction paragraphs(text: string): React.ReactNode[] {\n return text.split(/\\n\\s*\\n/).map((p) => p.trim()).filter(Boolean).map((p, i) => (\n <p key={i}>{p.split(\"\\n\").map((line, j) => (j === 0 ? line : [<br key={j} />, line]))}</p>\n ));\n}\n\nconst CALLOUT_TONES = new Set([\"info\", \"tip\", \"warning\"]);\n\n/** `builtin:callout` / `builtin:toggle`, drawn from their own fields; null for any other block. */\nfunction calloutOrToggle(value: unknown): React.ReactElement | null {\n const v = (value ?? {}) as { schemaKey?: unknown; fields?: Record<string, unknown> };\n const f = v.fields && typeof v.fields === \"object\" ? v.fields : {};\n const text = (x: unknown) => (typeof x === \"string\" ? x.trim() : \"\");\n if (v.schemaKey === \"builtin:callout\") {\n const tone = CALLOUT_TONES.has(text(f.tone)) ? text(f.tone) : \"info\";\n const title = text(f.title);\n const body = paragraphs(typeof f.message === \"string\" ? f.message : \"\");\n if (!title && !body.length) return <></>;\n return (\n <div className={`bcms-callout bcms-callout--${tone}`} data-rt=\"callout\" data-callout={tone} role=\"note\">\n {title ? <p className=\"bcms-callout__title\" data-title=\"\">{title}</p> : null}\n <div className=\"bcms-callout__body\" data-body=\"\">{body}</div>\n </div>\n );\n }\n if (v.schemaKey === \"builtin:toggle\") {\n const title = text(f.title);\n const body = paragraphs(typeof f.body === \"string\" ? f.body : \"\");\n if (!title && !body.length) return <></>;\n return (\n <details className=\"bcms-toggle\" data-rt=\"toggle\">\n <summary className=\"bcms-toggle__title\">{title}</summary>\n <div className=\"bcms-toggle__body\" data-body=\"\">{body}</div>\n </details>\n );\n }\n return null;\n}\n\nexport function PortableText({ value, bindingBase, renderBlock }: PortableTextProps) {\n const blocks = React.useMemo(() => normalizePortableText(readValue(value)), [value]);\n const components = React.useMemo(() => buildComponents(renderBlock), [renderBlock]);\n // A list item carries its address on its own `<li>`; only keys the value really has count,\n // because the renderer invents one for an item that has none.\n const listComponents = React.useMemo((): PortableTextComponents | null => {\n if (!bindingBase) return null;\n const keys = new Set<string | undefined>(blocks.map((b) => b._key));\n return {\n ...components,\n listItem: ({ value: item, children }) =>\n keys.has(item._key) ? (\n <li data-bcms-field={`${bindingBase}#${item._key}`} data-bcms-kind=\"richtext\">\n {children}\n </li>\n ) : (\n <li>{children}</li>\n ),\n };\n }, [bindingBase, blocks, components]);\n if (blocks.length === 0) return null;\n\n // Binding wraps at the TOP LEVEL rather than inside `components.block`, because a block\n // component only sees text blocks — a divider or an embedded image would get no address,\n // and those are exactly the nodes an editor most wants to click. A run of list items is the\n // exception: one wrapper per item would split the list into one-item lists.\n if (!bindingBase || !listComponents) return <PT value={blocks as never} components={components} />;\n\n return (\n <>\n {runsOf(blocks).map((run) =>\n isListItem(run[0]) ? (\n <PT key={run[0]._key} value={run as never} components={listComponents} />\n ) : (\n <Bound key={run[0]._key} base={bindingBase} nodeKey={run[0]._key}>\n <PT value={run as never} components={components} />\n </Bound>\n ),\n )}\n </>\n );\n}\n\n/** Accept the array, the `{format,value}` envelope, and the legacy `{version,content}` tree. */\nfunction readValue(value: unknown): PtValue {\n if (Array.isArray(value)) return value as PtValue;\n if (value && typeof value === \"object\") {\n const o = value as Record<string, unknown>;\n if (Array.isArray(o.value)) return o.value as PtValue;\n if (Array.isArray(o.content)) return o.content as PtValue;\n }\n return [];\n}\n","import type { ContentBlock } from \"@bettercms-ai/types\";\nimport { RESPONSIVE_CSS, hiddenEverywhere, hideOnAttr, splitButtonStyle, usesHideOn, visibleBlocks, windowState } from \"@bettercms-ai/types\";\nimport { BLOCK_REGISTRY, type BlockRegistryEntry } from \"./registry\";\nimport { blockStyleToReactStyle } from \"./blockStyle\";\nimport type { BcmsBindingProps } from \"./binding\";\nimport React from \"react\";\n\n// Container blocks hold nested blocks and need the `renderBlock` callback threaded\n// so their children render recursively.\nconst CONTAINER_TYPES = new Set([\"columns\", \"section\", \"slider\", \"tabs\"]);\n\n/**\n * The wrapper's style and, for a button, the paint for its `<a>`: a button's wrapper keeps only\n * its placement keys, so a fill or theme never paints a box behind the anchor (as on every\n * delivery renderer — `splitButtonStyle` in @bettercms-ai/types).\n */\nfunction styleOf(block: ContentBlock): { wrapper: React.CSSProperties | undefined; paint: { style?: React.CSSProperties } } {\n if (block.type !== \"button\") return { wrapper: blockStyleToReactStyle(block.style), paint: {} };\n const { placement, paint } = splitButtonStyle(block.style);\n return { wrapper: blockStyleToReactStyle(placement), paint: paint ? { style: paint as React.CSSProperties } : {} };\n}\n\n/**\n * A block that hides somewhere gets a wrapper carrying `data-bcms-hide` (`RESPONSIVE_CSS` keys on it).\n * `stampAt` (the editor lane) also marks a block outside its window `data-bcms-window=\"pending|expired\"`,\n * and one hidden on every breakpoint `data-bcms-hidden=\"all\"`, on the same wrapper: there the block\n * is drawn so its author can find it.\n */\nfunction withHide(block: ContentBlock, wrapperStyle: React.CSSProperties | undefined, el: React.ReactElement, stampAt?: Date) {\n const hide = hideOnAttr(block.style);\n const state = stampAt ? windowState(block, stampAt) : \"live\";\n const hidden = !!stampAt && hiddenEverywhere(block);\n if (!wrapperStyle && !hide && state === \"live\" && !hidden) return el;\n return (\n <div style={wrapperStyle} {...(hide ? { \"data-bcms-hide\": hide } : {})} {...(state === \"live\" ? {} : { \"data-bcms-window\": state })} {...(hidden ? { \"data-bcms-hidden\": \"all\" } : {})}>\n {el}\n </div>\n );\n}\n\ninterface BlockRendererProps {\n block: ContentBlock;\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n /** Emit visual-editor field bindings. Default-off keeps published consumers clean. */\n annotate?: boolean;\n /** The instant block windows are read against. Internal: `BlocksRenderer` passes it when annotating. */\n stampAt?: Date;\n}\n\n/**\n * Direct scalar props of the four prose block types, and nothing else. `component`, `form` and\n * `collection` render as placeholders here (see blocks/Placeholders.tsx): this lane has no\n * definition lookup and no entry fetch, so it never emits the `<instanceId>__overrides.<key>`\n * addresses the delivery renderers do — resolution is theirs, through @bettercms-ai/ui/resolve.\n */\nfunction bindingProps(block: ContentBlock, annotate: boolean): BcmsBindingProps {\n if (!annotate || !block.id) return {};\n switch (block.type) {\n case \"heading\":\n case \"button\":\n return { \"data-bcms-field\": `${block.id}__text`, \"data-bcms-kind\": \"text\" };\n case \"text\":\n case \"richtext\":\n return { \"data-bcms-field\": `${block.id}__html`, \"data-bcms-kind\": \"richtext\" };\n case \"image\":\n return { \"data-bcms-field\": `${block.id}__src`, \"data-bcms-kind\": \"image\" };\n default:\n return {};\n }\n}\n\nfunction resolveBlock(block: ContentBlock, className?: string, annotate = false, stampAt?: Date): React.ReactNode {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? \"\"}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n const Component = entry.component;\n const { wrapper: blockStyle, paint } = styleOf(block);\n const el = <Component {...block.props} {...paint} className={className} {...bindingProps(block, annotate)} />;\n return withHide(block, blockStyle, el, stampAt);\n}\n\nexport function BlockRenderer({\n block,\n className = \"\",\n renderBlock,\n annotate = false,\n stampAt,\n}: BlockRendererProps) {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n // Container blocks (columns/section/slider/tabs) hold nested blocks, so thread the\n // renderBlock callback. Standalone usage (no BlocksRenderer above) falls back to\n // resolveBlock so one level of nesting still renders.\n const Component = entry.component;\n const containerProps = CONTAINER_TYPES.has(block.type)\n ? { renderBlock: renderBlock ?? ((child: ContentBlock) => resolveBlock(child, undefined, annotate, stampAt)) }\n : {};\n const { wrapper: blockStyle, paint } = styleOf(block);\n const el = (\n <Component\n {...block.props}\n {...paint}\n className={className}\n {...containerProps}\n {...bindingProps(block, annotate)}\n />\n );\n return withHide(block, blockStyle, el, stampAt);\n}\n\ninterface BlocksRendererProps {\n blocks: ContentBlock[];\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n annotate?: boolean;\n /**\n * The instant block windows (`block.window`) are read against. Defaults to the render time.\n * A block outside its window, or hidden on every breakpoint, is not rendered, at any depth; with\n * `annotate` it is rendered and marked `data-bcms-window=\"pending|expired\"` / `data-bcms-hidden=\"all\"`.\n */\n now?: Date;\n}\n\nexport function BlocksRenderer({\n blocks: all,\n className = \"\",\n renderBlock,\n annotate = false,\n now,\n}: BlocksRendererProps) {\n const at = now ?? new Date();\n const blocks = annotate ? all : visibleBlocks(all, at);\n if (blocks.length === 0) return null;\n\n return (\n <div className={className}>\n {usesHideOn(blocks) && <style dangerouslySetInnerHTML={{ __html: RESPONSIVE_CSS }} />}\n {blocks.map((block) => (\n <BlockRenderer\n key={block.id}\n block={block}\n renderBlock={renderBlock}\n annotate={annotate}\n stampAt={annotate ? at : undefined}\n />\n ))}\n </div>\n );\n}\n","import type { CSSProperties } from \"react\";\nimport { blockStyleToCss, type BlockStyle } from \"@bettercms-ai/types\";\n\n/**\n * FLO-264 — React-typed wrapper over the shared, framework-agnostic\n * `blockStyleToCss` in @bettercms-ai/types. One mapping, three consumers (this UI\n * renderer, the @bettercms-ai/next adapter, and — in string form — the hosted\n * render-page.ts).\n */\nexport function blockStyleToReactStyle(style: BlockStyle | undefined): CSSProperties | undefined {\n return blockStyleToCss(style) as CSSProperties | undefined;\n}\n","/**\n * Portable Text, the real spec — https://portabletext.org\n *\n * BetterCMS shipped four hand-rolled \"Portable Text\" trees before this file, and all four\n * diverged from the spec in the same expensive way: no `markDefs`. Without markDefs a link\n * has nowhere to live, so every one of them could render bold text and none of them could\n * render a hyperlink. Two also dropped unknown marks silently. This file is the spec, so the\n * official `@portabletext/*` packages can read and write our content without a shim.\n *\n * THE SHAPE, and why each part earns its place:\n *\n * block ─ one paragraph/heading/quote. `style` says which. `listItem` + `level` make\n * it a list item, because Portable Text keeps lists FLAT and reconstructs the\n * nesting at render time; there is no `<ul>` node to lose.\n * span ─ a run of text inside a block. `marks` are strings, and a mark is either a\n * DECORATOR (a bare name like \"strong\") or an ANNOTATION (the `_key` of an\n * entry in the block's `markDefs`). That one indirection is the whole reason\n * links, and any other data-carrying mark, are expressible.\n * markDefs ─ the data behind annotation marks. A link's `href` lives here, not on the span.\n * blockObject─ anything that is not text: an image, an embed, a table, a placed component.\n *\n * `_key` is not decoration. It is the stable identity of a block, and it is what lets the\n * Visual Editor address ONE paragraph inside a rich-text field instead of treating the whole\n * field as a single opaque string. Everything the editor gained rests on it, so `_key` is\n * minted on ingest and never trusted from foreign content.\n */\n\n/** Every Portable Text node carries a type and a stable key. */\nexport interface PortableTextNode {\n _type: string;\n _key: string;\n}\n\n/**\n * A run of text. `marks` mixes decorator NAMES and markDef `_key`s; a consumer resolves the\n * difference by looking each mark up in the parent block's `markDefs`. A mark that resolves to\n * nothing is a decorator.\n */\nexport interface PortableTextSpan extends PortableTextNode {\n _type: \"span\";\n text: string;\n marks: string[];\n}\n\n/** The data behind an annotation mark. `_key` is what a span's `marks` entry points at. */\nexport interface PortableTextMarkDef extends PortableTextNode {\n [field: string]: unknown;\n}\n\n/** A text block. Lists are FLAT: `listItem` + `level`, never a nested container. */\nexport interface PortableTextBlock extends PortableTextNode {\n _type: \"block\";\n style: string;\n markDefs: PortableTextMarkDef[];\n children: (PortableTextSpan | PortableTextNode)[];\n listItem?: string;\n level?: number;\n /** Only meaningful when `listItem` is \"check\". A checklist item's state is content, not style. */\n checked?: boolean;\n}\n\n/** Anything that is not text — an image, an embed, a table, a placed component. */\nexport interface PortableTextObject extends PortableTextNode {\n [field: string]: unknown;\n}\n\n/** The value of a rich-text field: a flat array of blocks and block objects. */\nexport type PortableText = (PortableTextBlock | PortableTextObject)[];\n\n/**\n * ── THE TABLE ────────────────────────────────────────────────────────────────────────────\n *\n * A cell holds BLOCKS, not a string. The predecessor stored a string matrix, and the price was\n * paid on every edge of it: a link, a bolded word or a second paragraph inside a cell was\n * flattened to text on the way in and could never be authored on the way out. A cell is a small\n * rich-text field, so it is spelled as one, and every mark, annotation and list the rest of the\n * document can express works inside it for free.\n *\n * `header` is per CELL because a row-header table is a COLUMN of `<th>`; `head` is per ROW\n * because `<thead>` is a section of the table and a browser does not invent one. Both exist so\n * the four spellings of a header (`thead`+`th`, `thead`+`td`, bare `th`, none) survive a round\n * trip instead of being normalised into whichever one we happened to prefer — the html cache is\n * compared against the stored markup, so an invented `<thead>` reads as content drift.\n */\nexport interface PortableTextTableCell {\n _key: string;\n /** The cell's own blocks. Usually one paragraph; a cell may hold several, or none. */\n content: PortableText;\n /** `<th>` rather than `<td>`. */\n header?: boolean;\n /**\n * The COLUMN span (`colspan`), RECORDED but not honoured: the grid keeps exactly one cell per\n * `<td>`, and this is what the renderer writes back out. Honouring it would mean a ragged\n * model every consumer has to re-square, for a layout the editor cannot author anyway.\n */\n span?: number;\n /**\n * The ROW span (`rowspan`), recorded on the same terms.\n *\n * It is here because DROPPING it is lossy in a way the eye catches immediately: a spanning\n * cell that comes back spanning one row leaves a hole in the grid, and the round-trip audit\n * would refuse the whole table over an attribute we simply forgot to carry.\n */\n rowSpan?: number;\n /**\n * The COLUMN GROUP this cell's column belongs to: a `_key` of the table's `columnGroups`.\n *\n * Read from the FIRST row only (the V2 table puts `group` on the column; this grid has no\n * column object, so the column's first cell carries it). Living on a cell is what keeps it\n * right when the editor inserts, deletes or moves a column: the cell moves with its column.\n */\n group?: string;\n /**\n * \"Highlight this column\" (the V2 table's `TableColumn.highlight`): named, like `group`, on the\n * column's cell in the FIRST row only. Every cell of that column renders `data-highlight=\"true\"`.\n */\n highlight?: boolean;\n /**\n * \"Visitors can sort columns\" (the V2 table's `TableColumn.sortable`): named on the column's FIRST-row cell, like\n * `highlight`. Renders as the column's `<col data-sortable=\"true\">`, as the V2 table writes it.\n */\n sortable?: boolean;\n /**\n * The column's type and its settings (the V2 table's `TableColumn` type, unit, currency, decimals, align, trueLabel,\n * falseLabel, max, description): named, like `group`, on the column's FIRST-row cell. Nested rather than spread on the\n * cell because the cell's own `type` is the per-cell type. Renders as the column's `<col data-type …>`, as V2 writes it.\n */\n column?: PortableTextTableColumn;\n /** The per-cell type (the V2 table's `TableCellObject.type`): wins over the row's and the column's. */\n type?: PortableTextTableColumnType;\n /**\n * The typed value of a Number, Price, Yes / No or Rating cell (the V2 table's `TableCellValue`): a number, or true /\n * false. `content` still holds the words the cell says (V2's `cellText`), so every reader that reads blocks reads it.\n */\n value?: number | boolean;\n /**\n * A footnote on this cell (the V2 table's `TableCellObject.note`): a `_key` of the table's `footnotes`. The cell renders\n * `data-note` and the numbered marker V2 writes, `<sup><a href=\"#<table key>-fn-<n>\" aria-label=\"Note <n>\"><n></a></sup>`.\n */\n note?: string;\n}\n\n/**\n * The column types a rich-text table offers: the V2 table's `TableColumnType`s its island offers (table-editor.tsx:80-86\n * TYPE_UI without the two `dev` ones; V2's rich-text TableBlock passes no `developer`).\n */\nexport const PT_TABLE_COLUMN_TYPES = [\"text\", \"number\", \"money\", \"boolean\", \"rating\", \"link\"] as const;\nexport type PortableTextTableColumnType = (typeof PT_TABLE_COLUMN_TYPES)[number];\n\n/** A column's type and settings, the V2 table's `TableColumn` keys (schema/table.ts:44-72), spelled the same way. */\nexport interface PortableTextTableColumn {\n /** Absent reads as `text`. */\n type?: PortableTextTableColumnType;\n /** Explains the column: the header's `title`. */\n description?: string;\n /** number: written after every value (\"kg\", \"%\", \"min\"). */\n unit?: string;\n /** money: ISO 4217 code. Default USD. */\n currency?: string;\n /** number / money: fixed decimals. */\n decimals?: number;\n /** Default: numbers, money and ratings align end, everything else start. */\n align?: \"start\" | \"center\" | \"end\";\n /** boolean: the words. Defaults \"Included\" / \"Not included\". */\n trueLabel?: string;\n falseLabel?: string;\n /** rating: the scale. Default 5. */\n max?: number;\n}\n\nexport interface PortableTextTableRow {\n _key: string;\n cells: PortableTextTableCell[];\n /** The row sat in `<thead>`. Absent means `<tbody>`, which is what a parser assumes. */\n head?: boolean;\n /** The ROW GROUP this body row belongs to: a `_key` of the table's `rowGroups`. */\n group?: string;\n /**\n * A totals / summary row: the TRAILING run of these renders as `<tfoot>` (the V2 table's\n * \"Totals row\", `TableRow.footer` in `@bettercms-ai/types` table-value.ts).\n */\n footer?: boolean;\n /** \"Highlight this row\": `<tr data-highlight=\"true\">`, as the V2 table renders it. */\n highlight?: boolean;\n /** The per-row type (the V2 table's `TableRow.type`): a pricing matrix mixes Yes / No rows with limit rows. `<tr data-type>`. */\n type?: PortableTextTableColumnType;\n}\n\n/**\n * A row or column group, the V2 table's `TableGroup` (`@bettercms-ai/types` table-value.ts), so\n * both tables speak one language: a stable `_key` and the label a reader sees.\n */\nexport interface PortableTextTableGroup {\n _key: string;\n label: string;\n}\n\n/** A table. The ONE table BetterCMS has — see `PT_TYPES.table`. */\nexport interface PortableTextTable extends PortableTextObject {\n _type: \"table\";\n rows: PortableTextTableRow[];\n /**\n * Render with the platform's own `class=\"bcms-table\"`, and with every row squared off.\n *\n * 🔴 THIS IS A COMPATIBILITY FLAG, and it is the whole of what a pre-P1c table's rendition\n * needs. The renderer this block object replaces emitted that class on every table and padded\n * short rows to the widest one, so a value migrated from `builtin:table` MUST keep both or it\n * renders differently than the `html` sitting beside it in the same row — which is drift, and\n * the parity gate is right to refuse it.\n *\n * Set on every migrated value, and recognised on ingest (`class=\"bcms-table\"` and nothing\n * else), because that class only ever appears in markup that renderer produced. A table\n * authored or pasted since P1c carries neither the flag nor the class.\n */\n legacy?: boolean;\n /**\n * The four keys below are the V2 table's (`TableValue`), spelled the same way. All optional,\n * and a table that has none of them renders byte for byte as it did before they existed.\n */\n /** What the table compares, written as `<caption>`. At most 300 characters. */\n caption?: string;\n /** A note under the table (`<p data-notes>`), plain text. At most 2,000 characters. */\n notes?: string;\n /** Body rows name one with `row.group`; each run of rows is its own `<tbody>` under a label row. */\n rowGroups?: PortableTextTableGroup[];\n /** First-row cells name one with `cell.group`; a run of columns gets one `scope=\"colgroup\"` header. */\n columnGroups?: PortableTextTableGroup[];\n /**\n * \"Visitors can search rows\" (the V2 table's `TableValue.filterable`): the table renders inside\n * `<figure data-rt=\"table\" data-filter=\"true\">`, as the V2 table writes it.\n */\n filterable?: boolean;\n /**\n * \"Help search engines read this as\" (the V2 table's `TableValue.jsonLd`, the field-lane table's `jsonLd`): a\n * comparison, a data report or a price list. Written on the figure as `data-jsonld=\"<type>\"` (plus\n * `data-jsonld-axis` / `data-jsonld-price`), as the V2 table writes it. `\"none\"` is not stored.\n */\n jsonLd?: PortableTextTableJsonLd;\n /**\n * \"On phones\" and \"Keep column names in view\" (the V2 table's `TableValue.design.responsive` / `.sticky`, the field-lane\n * table's `design`). `responsive` is `stack` or `cards` (`scroll`, the default, is not stored, as V2 reads it); written\n * on the figure as `data-responsive` / `data-sticky=\"true\"`, as the V2 table writes them.\n */\n design?: PortableTextTableDesign;\n /**\n * Footnotes (the V2 table's `TableValue.footnotes`, the field-lane table's `footnotes`): numbered in order, named by a\n * cell's `note`, written under the table as V2's `<ol data-footnotes>` inside `<div class=\"bt-notes\">`.\n */\n footnotes?: PortableTextTableFootnote[];\n}\n\n/** One table footnote: a stable `_key` (what a cell's `note` names) and its text. */\nexport interface PortableTextTableFootnote {\n _key: string;\n text: string;\n}\n\n/** The V2 table's `design` keys the rich-text table offers. */\nexport interface PortableTextTableDesign {\n responsive?: \"stack\" | \"cards\";\n sticky?: boolean;\n}\n\n/** The V2 table's JSON-LD choice: `priceRow` is a row's `_key` (Offers), `axis` what an ItemList lists. */\nexport interface PortableTextTableJsonLd {\n type: \"ItemList\" | \"Dataset\" | \"Offers\";\n axis?: \"rows\" | \"columns\";\n priceRow?: string;\n}\n\n/**\n * A video block. `src` is an uploaded asset's path or a provider URL; `provider` and `poster`\n * are what an author set, never something this package infers — the renderer asks\n * `resolveVideoSource` about `src` and draws what it answers.\n *\n * 🔴 THE SHAPE IS A CONTRACT WITH THE EDITOR. The dashboard writes these fields (`ptNodes.tsx`,\n * `formNodes.tsx`) and this package renders them; spelling it once here is what stops the two\n * sides disagreeing about a key and rendering an empty box.\n */\nexport interface PortableTextVideo extends PortableTextObject {\n _type: \"video\";\n src: string;\n poster?: string;\n provider?: string;\n title?: string;\n}\n\n/** A downloadable file. `name` is the link's text — a file with no name has nothing to click. */\nexport interface PortableTextFile extends PortableTextObject {\n _type: \"file\";\n src: string;\n name: string;\n size?: number;\n mime?: string;\n}\n\n/**\n * A pull quote: a quotation lifted out of the body, with who said it.\n *\n * 🔴 THE SHAPE IS THE V2 KIT'S, 1:1 — `PullQuoteModel` in the dashboard's\n * `kit/controls/rich-text/article.ts`, which renders `<figure data-rt=\"pullquote\">`. The kit\n * writes these four fields and every renderer here draws them, so the names are spelled once.\n *\n * `link` is where the NAME links (and the blockquote's `cite`): a URL string, as the kit stores\n * it, or a link object carrying `href` (the shape a `link` field value takes). Anything\n * `safeHref` refuses renders the name unlinked rather than dropping it.\n */\nexport interface PortableTextPullQuote extends PortableTextObject {\n _type: \"pullQuote\";\n quote: string;\n name: string;\n role?: string;\n link?: string | { href: string; [k: string]: unknown };\n}\n\n/** The href a pull quote's `link` carries — a string, or a link object's `href`. \"\" for none. */\nexport function pullQuoteHref(link: unknown): string {\n if (typeof link === \"string\") return link.trim();\n if (link && typeof link === \"object\" && typeof (link as { href?: unknown }).href === \"string\") {\n return ((link as { href: string }).href).trim();\n }\n return \"\";\n}\n\n/**\n * Key takeaways: the story in a few points, set apart near the top of a body.\n *\n * 🔴 THE SHAPE IS THE V2 KIT'S, 1:1 — `TakeawaysModel` and `ARTICLE_SCHEMAS.takeaways` (`name:\n * \"keyTakeaways\"`, fields `title` and `points`) in the kit's `controls/rich-text/article.ts`,\n * which renders `<aside data-rt=\"takeaways\">`. `title` is the label over the list (\"Key\n * takeaways\" by default), ABSENT rather than empty when an author cleared it. `points` are plain\n * strings, one per bullet; V2 holds at most 8 and a title of at most 40 characters\n * (`PT_KEY_TAKEAWAYS_LIMITS`, key-takeaways.ts).\n */\nexport interface PortableTextKeyTakeaways extends PortableTextObject {\n _type: \"keyTakeaways\";\n title?: string;\n points: string[];\n}\n\nexport const isPortableTextBlock = (n: unknown): n is PortableTextBlock =>\n !!n && typeof n === \"object\" && (n as PortableTextNode)._type === \"block\";\n\nexport const isPortableTextSpan = (n: unknown): n is PortableTextSpan =>\n !!n && typeof n === \"object\" && (n as PortableTextNode)._type === \"span\";\n\n/**\n * The stored envelope.\n *\n * `value` is the source of truth; `html` is a DERIVED CACHE and is never trusted from a client\n * when `value` is present. The envelope survives rather than storing a bare array because\n * customer sites that shipped months ago read `.html` directly and cannot be recompiled —\n * `packages/sdk/src/richtext.ts` `isRichText()` even returns false for an array, so a bare\n * array would make every deployed `rich()` call return \"\" and blank the page at 200 OK.\n */\nexport const PORTABLE_TEXT_FORMAT = \"portable-text-1\" as const;\n\nexport interface RichTextEnvelope {\n format: typeof PORTABLE_TEXT_FORMAT;\n value: PortableText;\n html: string;\n}\n\n/**\n * Our block-object and annotation type names, spelled once.\n *\n * Two repos serialise these and four renderers read them. A typo in any one of them is a node\n * that renders as nothing, with no error — the failure mode this whole migration exists to\n * stop — so the strings live here and nowhere else.\n */\nexport const PT_TYPES = {\n /** A generic embedded block: `{schemaKey, schemaLabel, fields}`, the successor to Lexical's BlockNode. */\n block: \"bcmsBlock\",\n /** An inline mention. A PT inline object is natively void, which is what Lexical's `token` mode was faking. */\n mention: \"bcmsMention\",\n divider: \"bcmsDivider\",\n code: \"bcmsCode\",\n /**\n * A video — an uploaded file or a provider URL. DEDICATED rather than another `schemaKey` on\n * the generic block, because a video is the one media node whose rendition depends on its own\n * payload: the same field is a `<video>` for an mp4 and a provider `<iframe>` for a YouTube\n * link (@see resolveVideoSource). The generic block carries a reference this package cannot\n * resolve; a video carries a URL it can.\n */\n video: \"video\",\n /** A downloadable document — a PDF, a spec sheet. `<a href download>`, nothing more. */\n file: \"file\",\n /** A pull quote — see `PortableTextPullQuote`. The V2 kit's `<figure data-rt=\"pullquote\">`. */\n pullQuote: \"pullQuote\",\n /** Key takeaways — see `PortableTextKeyTakeaways`. The V2 kit's `<aside data-rt=\"takeaways\">`. */\n keyTakeaways: \"keyTakeaways\",\n /**\n * A table. Spelled `table`, unprefixed, because that is what every other Portable Text\n * producer calls one — the Sanity-shaped `{_type:\"table\", rows}` this platform used to refuse\n * as foreign content now lands on OUR type and renders, rather than being data nothing draws.\n */\n table: \"table\",\n /** The annotation carrying a hyperlink's `href`. Spelled `link` because that is the spec's convention. */\n link: \"link\",\n /**\n * The annotation carrying a site's OWN inline markup — `<span class>`, `<small>`, `<time\n * datetime>`. Unprefixed, like `table`, because it describes HTML rather than a BetterCMS\n * concept. See `styled.ts` for what it may hold and why the list is that short.\n */\n styled: \"styled\",\n} as const;\n\n/**\n * The `schemaKey` a table stored under BEFORE it had a type of its own.\n *\n * Every table written up to P1c is a generic `bcmsBlock` carrying a string matrix under this\n * key. Those rows are still in the database and nothing rewrites them, so this stays READABLE\n * forever: `migrateTable` (table.ts) turns one into a `table` on the way out, which is why the\n * reader, the renderer and the plain-text pass all still recognise it.\n *\n * Nothing WRITES it any more.\n */\nexport const BUILTIN_TABLE_KEY = \"builtin:table\" as const;\n\n/**\n * Every `_type` that may appear at the TOP LEVEL of a stored value.\n *\n * Closed on purpose, and short: a text block, the table, the two standalone block objects, and\n * the generic `bcmsBlock` envelope that carries an image, an embed or columns behind its\n * `schemaKey` (see `ptSchemaFor`). Anything else is content from another system and has no\n * renderer here.\n *\n * `table` joined the set in P1c, and it is the one member tested by SHAPE as well as by name —\n * see `isStorableTable`. The Sanity-shaped `{_type:\"table\", rows:[{cells}]}` that prompted this\n * list is the reason: refusing it was right while nothing could draw a table, and became wrong\n * the moment something could. `migrateTable` adopts that shape on read, so the write it used to\n * refuse now renders every cell.\n *\n * `bcmsMention` is included although it is an INLINE object: a mention that has somehow been\n * hoisted to the top level is ours, harmless, and refusing an author's save over it would be\n * the cure being worse than the disease.\n */\nexport const STORABLE_BLOCK_TYPES: ReadonlySet<string> = new Set<string>([\n \"block\",\n PT_TYPES.block,\n PT_TYPES.table,\n PT_TYPES.code,\n PT_TYPES.divider,\n PT_TYPES.mention,\n PT_TYPES.video,\n PT_TYPES.file,\n PT_TYPES.pullQuote,\n PT_TYPES.keyTakeaways,\n]);\n\n/**\n * The `schemaKey`s the GENERIC block object carries for the two media kinds that keep it.\n *\n * Image and embed stay on `bcmsBlock` because thousands of stored values already spell them\n * that way and a migration buys nothing: unlike a video, their rendition is a straight read of\n * their own fields. Named here because this package now RENDERS them — it used to emit a marker\n * — and a renderer keyed on a string literal typed at the call site is the drift this file\n * exists to prevent.\n *\n * The embed's key is historically `builtin:video`: it is the \"Video embed\" block, and it\n * predates the dedicated `video` type by two years of content.\n */\nexport const BUILTIN_IMAGE_KEY = \"builtin:image\" as const;\nexport const BUILTIN_EMBED_KEY = \"builtin:video\" as const;\n/** A callout (`{ tone: info|tip|warning, title, message }`) and a toggle (`{ title, body }`): plain text, read straight. */\nexport const BUILTIN_CALLOUT_KEY = \"builtin:callout\" as const;\nexport const BUILTIN_TOGGLE_KEY = \"builtin:toggle\" as const;\n\n/**\n * The `schemaKey` of a component embedded in rich text. A wire value shared with the dashboard\n * (`COMPONENT_SCHEMA_KEY`) and the backend (`RICHTEXT_COMPONENT_KEY`).\n */\nexport const EMBEDDED_COMPONENT_KEY = \"component\" as const;\n\n/**\n * Top-level nodes whose `_type` this platform cannot store or render, in document order.\n *\n * WHY THIS IS A WRITE-TIME CHECK AND NOT A RENDER-TIME SHRUG. An unrecognised block survives\n * the JSON round trip perfectly — it is data, and every reader here hands it back untouched —\n * but `portableTextToHtml` has no component for it, so it renders as NOTHING. The `html` on\n * the envelope is a cache that every delivery surface reads, so the node silently disappears\n * from the live site while the stored value still looks complete. That is the \"renders as\n * nothing at 200 OK\" failure this codebase keeps paying for, and the only place to catch it\n * cheaply is the write, where the author is still present to be told.\n *\n * Returns the offenders rather than a boolean so the refusal can NAME them — `_type` and\n * `_key` are what let an author find the block in a body of eighty.\n */\nexport function foreignBlocks(value: unknown): { _type: string; _key: string }[] {\n if (!Array.isArray(value)) return [];\n const out: { _type: string; _key: string }[] = [];\n for (const node of value) {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) continue;\n const type = (node as PortableTextNode)._type;\n if (typeof type !== \"string\") continue;\n // A table, a pull quote and key takeaways are admitted by SHAPE, never by `_type` alone —\n // see `isStorableTable`.\n const storable = type === PT_TYPES.table\n ? isStorableTable(node)\n : type === PT_TYPES.pullQuote\n ? isStorablePullQuote(node)\n : type === PT_TYPES.keyTakeaways\n ? isStorableKeyTakeaways(node)\n : STORABLE_BLOCK_TYPES.has(type);\n if (storable) continue;\n const key = (node as PortableTextNode)._key;\n out.push({ _type: type, _key: typeof key === \"string\" ? key : \"\" });\n }\n return out;\n}\n\n/**\n * Is this `_type:\"table\"` a table we can actually RENDER?\n *\n * 🔴 THE TYPE NAME IS NOT A CONTRACT. `table` is what every Portable Text producer calls a\n * table, and they do not agree on what is inside one. Admitting the name alone would let any\n * foreign `rows` through the write guard, and the reader — permissive by design, because a\n * reader that throws is a field that opens blank — would then coerce whatever it did not\n * recognise into EMPTY cells. That is the silent-emptying failure the guard exists to stop,\n * wearing our own type name.\n *\n * So exactly two shapes pass, tested cell by cell:\n * · ours — `cells: [{ content: PortableText }]`\n * · Sanity's — `cells: [\"a\", \"b\"]`, a row of strings\n *\n * Everything else is refused at the write, where the author is present to be told, and stays\n * readable through `migrateTable` for anything already stored.\n */\nexport function isStorableTable(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n const rows = (node as { rows?: unknown }).rows;\n if (!Array.isArray(rows)) return false;\n return rows.every((row) => {\n if (!row || typeof row !== \"object\" || Array.isArray(row)) return false;\n const cells = (row as { cells?: unknown }).cells;\n if (!Array.isArray(cells)) return false;\n return cells.every((cell) => {\n if (typeof cell === \"string\") return true;\n if (!cell || typeof cell !== \"object\" || Array.isArray(cell)) return false;\n const content = (cell as { content?: unknown }).content;\n return Array.isArray(content)\n && content.every((n) => !!n && typeof n === \"object\" && typeof (n as PortableTextNode)._type === \"string\");\n });\n });\n}\n\n/**\n * Is this `_type:\"pullQuote\"` one we can render?\n *\n * Same reason as the table: `pullQuote` is a name other producers use too, and a renderer that\n * met `{_type:\"pullQuote\", text}` would draw an empty figure. `quote` and `name` are strings\n * (either may be empty while an author is still typing); `role`, when present, is a string; and\n * `link`, when present, is a string or an object carrying a string `href`.\n */\nexport function isStorablePullQuote(node: unknown): boolean {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return false;\n const v = node as Record<string, unknown>;\n if (typeof v.quote !== \"string\" || typeof v.name !== \"string\") return false;\n if (v.role !== undefined && typeof v.role !== \"string\") return false;\n if (v.link === undefined || typeof v.link === \"string\") return true;\n return !!v.link && typeof v.link === \"object\" && typeof (v.link as { href?: unknown }).href === \"string\";\n}\n\n/**\n * Is this `_type:\"keyTakeaways\"` one we can render?\n *\n * Same reason as the table: the name alone is not a contract, and a renderer that met another\n * producer's `{_type:\"keyTakeaways\", items}` would draw an empty aside. Ours carries `points`, an\n * array. What is IN it, the title and the limits are judged by `keyTakeawaysProblems`\n * (key-takeaways.ts), which names each problem with a code; this only decides whether the node is\n * ours at all.\n */\nexport function isStorableKeyTakeaways(node: unknown): boolean {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return false;\n return Array.isArray((node as { points?: unknown }).points);\n}\n\n/**\n * @deprecated The marker a table-accepting field used to carry INSTEAD of a table.\n *\n * It existed because a table stored as a generic block object, so `ptSchemaFor` had nothing\n * table-shaped to gate the HTML ingest on. `PT_TYPES.table` is that thing now, and it is a real\n * block object rather than a marker, so the ingest asks for it by name and nothing emits this.\n *\n * Kept exported for one release: the dashboard filters it out of the schema it hands\n * `defineSchema` (`src/lib/richtext/ptSchema.ts`), and removing the export would fail that\n * import rather than make the filter a harmless no-op. Delete it once that filter is gone.\n */\nexport const TABLE_CAPABILITY = \"bcmsTableCapability\" as const;\n","/**\n * The `styled` annotation — a site's own inline markup, carried through Portable Text.\n *\n * WHY IT EXISTS. An imported site writes `<h1>Notes from a <span class=\"serif italic\">web\n * studio</span></h1>`. Portable Text models `strong`/`em`/`underline`/`strike`/`code`/\n * `highlight` and `link`, and NOTHING ELSE inline — so that span had no node to become,\n * `unknownMark` rendered its children, and the class was gone the first time an author saved.\n * That loss is why `derive-schema` deliberately refused to bind any heading containing a\n * `<span class>`: a field that destroys the design on save is worse than no field at all.\n *\n * So this is NOT an authoring mark. There is no toolbar button and no editing UI: it is the\n * site's design, recorded so the round trip is lossless, and it rides along with whatever the\n * author actually types inside it.\n *\n * ── The attribute allowlist IS the threat model ──────────────────────────────────────────\n * A markDef is data, and data reaches this platform from an import, the Management API, MCP\n * and AI writes. Rendering an arbitrary attribute bag would be an XSS primitive wearing a\n * design system's clothes, so exactly four names plus `data-*` are admitted, and the render\n * escapes every value. `id` is refused because it is an addressing surface (a duplicate id\n * breaks anchors and label association), `data-bcms-*` because those attributes ARE the Visual\n * Editor's binding addresses and content must not be able to mint one. `style` is admitted but\n * NOT passed through — its value is a language of its own, bounded by `safeStyle` below.\n */\n\n/**\n * Inline tags a `styled` annotation may wear. Nothing outside this list is ever rendered.\n *\n * The decorator tags (`strong` … `code`) are here for the one case their decorator cannot\n * hold: the site put a CLASS on them (`<strong class=\"text-yellow-500\">`). A decorator is a\n * bit with nowhere to keep a class, so without this the design died on ingest — which is what\n * made agents rewrite a site's markup to dodge the loss. A bare `<strong>` still becomes the\n * decorator (see `BARE_IS_DESIGN`).\n */\nexport const STYLED_TAGS = [\n \"span\", \"small\", \"sup\", \"sub\", \"kbd\", \"abbr\", \"time\",\n \"strong\", \"b\", \"em\", \"i\", \"u\", \"s\", \"mark\", \"code\", \"q\", \"cite\", \"dfn\", \"var\", \"samp\", \"ins\", \"del\",\n] as const;\n\n/**\n * Tags whose bare form IS the design, so they are annotated even with no class: every styled tag\n * that is neither a `<span>` nor a tag a decorator already holds. A keycap, a citation or a\n * deleted price dropped to bare words is the page restyled by the author's first save.\n */\nexport const BARE_IS_DESIGN: ReadonlySet<string> = new Set([\n \"small\", \"sup\", \"sub\", \"kbd\", \"abbr\", \"time\", \"q\", \"cite\", \"dfn\", \"var\", \"samp\", \"ins\", \"del\",\n]);\n\nexport type StyledTag = (typeof STYLED_TAGS)[number];\n\nconst TAG_SET: ReadonlySet<string> = new Set<string>(STYLED_TAGS);\n\nexport const isStyledTag = (tag: unknown): tag is StyledTag =>\n typeof tag === \"string\" && TAG_SET.has(tag);\n\n/**\n * Attribute names a `styled` annotation may carry.\n *\n * Presentational or descriptive only. Never `href`/`src` (a navigation or fetch surface),\n * never `id` (an addressing surface), never `on*` (script), and never `data-bcms-*` — those\n * are binding addresses the editor reads, and content that could write one could point an\n * author's edit at somebody else's field.\n */\nconst ATTR_NAMES: ReadonlySet<string> = new Set([\"title\", \"datetime\", \"style\", \"aria-label\"]);\n\nconst DATA_ATTR = /^data-[a-z][a-z0-9-]*$/;\n\nexport const isStyledAttr = (name: string): boolean =>\n ATTR_NAMES.has(name) || (DATA_ATTR.test(name) && !name.startsWith(\"data-bcms-\"));\n\n/**\n * CSS an inline run may carry: how the words LOOK inside their own box, and nothing else.\n *\n * `style` is the one admitted attribute whose value is itself a language, so the tag allowlist\n * buys nothing here — a bare passthrough lets any markDef write\n * `position:fixed;inset:0;z-index:9999`, which is a full-viewport overlay over the published\n * page (clickjacking, defacement) minted by anyone who can write content, and\n * `background:url(https://…)` which fetches a third-party URL on every page view. Neither is a\n * design an import needs, so neither is on the list. Properties that only paint glyphs are.\n *\n * Values are checked as well as names, because `url()`, a CSS escape (`\\75 rl(`), a comment and\n * `expression()` are all ways to smuggle a fetch past a name check.\n */\nconst STYLE_PROPS: ReadonlySet<string> = new Set([\n \"color\", \"background-color\", \"opacity\",\n \"font\", \"font-family\", \"font-size\", \"font-style\", \"font-weight\", \"font-variant\",\n \"font-feature-settings\", \"font-variation-settings\",\n \"letter-spacing\", \"line-height\", \"word-spacing\", \"white-space\", \"word-break\",\n \"text-align\", \"text-decoration\", \"text-decoration-color\", \"text-decoration-line\",\n \"text-decoration-style\", \"text-transform\", \"text-underline-offset\", \"vertical-align\",\n]);\n\nconst STYLE_VALUE_BAN = /url\\(|expression\\(|@import|\\\\|\\/\\*|[<>]/i;\n\n/**\n * The declarations of a `style` attribute that survive, rejoined — or undefined when none do.\n * Custom properties (`--brand`) are kept: they paint nothing on their own, and a site's own CSS\n * is what decides whether they mean anything.\n */\nexport function safeStyle(css: string): string | undefined {\n const kept: string[] = [];\n for (const declaration of css.split(\";\")) {\n const at = declaration.indexOf(\":\");\n if (at < 0) continue;\n const name = declaration.slice(0, at).trim().toLowerCase();\n const value = declaration.slice(at + 1).trim();\n if (!name || !value) continue;\n if (!name.startsWith(\"--\") && !STYLE_PROPS.has(name)) continue;\n if (STYLE_VALUE_BAN.test(value)) continue;\n kept.push(`${name}:${value}`);\n }\n return kept.length ? kept.join(\";\") : undefined;\n}\n\n/** The annotation's payload, as stored. `tag` is validated again at render time. */\nexport interface StyledMarkDef {\n _type: \"styled\";\n _key: string;\n tag: StyledTag;\n className?: string;\n attrs?: Record<string, string>;\n}\n\n/**\n * The admitted attributes of an element, in source order, or undefined when there are none.\n *\n * `class` is deliberately absent — it lives on the markDef as `className`, because it is the\n * one attribute every consumer of this annotation needs to reach without parsing a bag.\n */\nexport function styledAttrsOf(el: Element): Record<string, string> | undefined {\n const out: Record<string, string> = {};\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n if (name === \"class\" || !isStyledAttr(name)) continue;\n const value = name === \"style\" ? safeStyle(attr.value) : attr.value;\n if (value === undefined) continue;\n out[name] = value;\n }\n return Object.keys(out).length ? out : undefined;\n}\n\n/**\n * The same filter applied to a STORED bag, which may have arrived from anywhere.\n *\n * Both ends, and this is the end that matters: a markDef reaches storage from an import, the\n * Management API, MCP and AI writes, so the ingest filter is a courtesy and this one is the\n * control. Every renderer of the annotation goes through here.\n */\nexport function styledAttrEntries(attrs: unknown): [string, string][] {\n if (!attrs || typeof attrs !== \"object\" || Array.isArray(attrs)) return [];\n const out: [string, string][] = [];\n for (const [name, raw] of Object.entries(attrs as Record<string, unknown>)) {\n if (!isStyledAttr(name) || typeof raw !== \"string\") continue;\n const value = name === \"style\" ? safeStyle(raw) : raw;\n if (value === undefined) continue;\n out.push([name, value]);\n }\n return out;\n}\n","/**\n * The URL allowlist, lifted verbatim from `src/lib/richtext/notion-doc.ts:232-243`.\n *\n * It moves here rather than being re-derived because Portable Text turns every hyperlink into\n * a `link` markDef, and markDefs are written by four ingest paths (paste, the Management API,\n * MCP, markdown). One shared check is the difference between \"javascript: is blocked\" and\n * \"javascript: is blocked on the path we remembered to guard\".\n */\nconst SAFE_HREF_SCHEMES = [\"http:\", \"https:\", \"mailto:\", \"tel:\"];\n\n/** The href if it is safe to emit, otherwise null. Callers render the label as plain text. */\nexport function safeHref(raw: string): string | null {\n const trimmed = raw.trim();\n // An in-page FRAGMENT (`#where-it-comes-from`) can only move within the page, so it is as safe\n // as a site-relative path. Refusing it cost every imported Contents list its links: the\n // importer's Lexical anchors were dropped on conversion, on ingest and on render alike\n // (astro-sumi, 2026-09-14). No whitespace or markup characters, so it cannot break out of the\n // attribute it is written into.\n if (trimmed.startsWith(\"#\")) return /^#[^\\s<>\"'`]*$/.test(trimmed) ? trimmed : null;\n // A site-relative path has no scheme to check and cannot navigate off-origin.\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) return trimmed;\n try {\n return SAFE_HREF_SCHEMES.includes(new URL(trimmed).protocol) ? trimmed : null;\n } catch {\n return null;\n }\n}\n","/**\n * `_key` minting.\n *\n * THE CHARSET IS LOAD-BEARING, not cosmetic. A `_key` ends up inside a Visual Editor address\n * (`<blockId>__<prop>#<key>`), and that address is parsed by splitting on `__`, `.`, `[`, `]`\n * and `#`. A key containing any of those characters would silently retarget an edit to the\n * wrong node, so we mint from `[A-Za-z0-9-]` only — no underscore, no dot, no separator.\n *\n * Foreign keys are RE-MINTED, never trusted: imported content and pasted HTML can carry\n * anything, and the address grammar is not a place to find out.\n *\n * Determinism matters at migration time. Converting a field twice — once for `data`, once for\n * `published_data` — with random keys makes two semantically identical copies compare unequal,\n * which lights up the change feed and the parity check on every entry in the project. The\n * backfill converts once and writes the same object to both columns; `deterministicKeys()`\n * exists for the cases that cannot.\n */\nconst ALPHABET = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\nexport const KEY_PATTERN = /^[A-Za-z0-9-]{1,32}$/;\n\nexport const isValidKey = (k: unknown): k is string =>\n typeof k === \"string\" && KEY_PATTERN.test(k);\n\n/** A fresh random key. 12 chars of this alphabet is ~71 bits; collisions are not a concern. */\nexport function randomKey(length = 12): string {\n let out = \"\";\n for (let i = 0; i < length; i += 1) {\n out += ALPHABET[Math.floor(Math.random() * ALPHABET.length)];\n }\n return out;\n}\n\n/**\n * A counter-based generator, for conversions that must be byte-reproducible.\n *\n * Same input, same keys, every run — so a value converted for the draft column and the\n * published column produces identical output and `jsonbEqual` still says they match.\n */\nexport function deterministicKeys(prefix = \"k\"): () => string {\n let n = 0;\n return () => {\n n += 1;\n return `${prefix}${n}`;\n };\n}\n\nexport type KeyGenerator = () => string;\n","import {\n BUILTIN_TABLE_KEY,\n PT_TABLE_COLUMN_TYPES,\n PT_TYPES,\n type PortableText,\n type PortableTextTable,\n type PortableTextTableCell,\n type PortableTextTableColumn,\n type PortableTextTableColumnType,\n type PortableTextTableGroup,\n type PortableTextTableDesign,\n type PortableTextTableFootnote,\n type PortableTextTableJsonLd,\n type PortableTextTableRow,\n} from \"./types\";\nimport { deterministicKeys, isValidKey, randomKey, type KeyGenerator } from \"./key\";\n\n/**\n * The one table, and every older spelling of it.\n *\n * THREE SHAPES REACH THIS FILE, and only one of them is written any more:\n *\n * `{_type:\"table\", rows:[{cells:[{content}]}]}` ours, since P1c\n * `bcmsBlock` + `schemaKey:\"builtin:table\"` every table stored BEFORE P1c: a string\n * matrix under `fields.rows`, plus `hasHeader`\n * `{_type:\"table\", rows:[{cells:[\"a\",\"b\"]}]}` content from another system (Sanity's is the\n * one found in this corpus): string cells\n *\n * They converge HERE rather than at each of the four readers, because the alternative is what\n * this package was created to end — four places deciding independently what a stored value means,\n * and a table rendering on one surface and vanishing on another. Nothing rewrites the database:\n * a legacy row is migrated on the way OUT, every time it is read.\n *\n * A string cell becomes one paragraph holding one span. That is lossless in the direction that\n * matters — the matrix never held marks to lose — and it means the rest of the pipeline has\n * exactly one cell shape to render, not two.\n *\n * ── 🔴 KEYS ARE DERIVED, NEVER MINTED AT RANDOM ──────────────────────────────────────────\n * This runs on EVERY READ of a value nothing rewrites, so a random `_key` here is a different\n * key each time the same row is read. Three things break at once: React remounts every cell on\n * every render because its `key` changed; a Visual Editor address minted against one read points\n * at nothing on the next; and the migration backfill converting `data` and `published_data`\n * produces two values that compare unequal, lighting up the change feed for every entry in the\n * project — the exact trap `deterministicKeys` was added for.\n *\n * So a migrated table's keys come from the table's OWN `_key` plus the row and cell INDEX, and\n * any key the value already carries is kept and reserved so a derived one cannot collide with it.\n * Same value in, same keys out, on every read, in every process.\n */\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\nexport const isPortableTextTable = (n: unknown): n is PortableTextTable =>\n isRecord(n) && n._type === PT_TYPES.table && Array.isArray(n.rows);\n\n/** A table as it was stored before it had a type: the generic block object under its schemaKey. */\nexport const isBuiltInTableBlock = (n: unknown): boolean =>\n isRecord(n) && n._type === PT_TYPES.block && n.schemaKey === BUILTIN_TABLE_KEY;\n\n/**\n * How a cell's `content` is read back.\n *\n * Injected rather than imported so this file stays free of `read.ts` — the reader already\n * depends on the table, and a cycle between the two is the kind of thing that resolves at type\n * level and explodes at runtime. The reader passes `normalizePortableText`; a caller with no\n * reader (the renderer, the plain-text pass) gets the identity, which is all a legacy string\n * matrix ever needs.\n *\n * It takes the cell's OWN key generator, so a block inside a cell that arrives without a `_key`\n * is keyed from the cell rather than from the document's random minter.\n */\nexport type CellContentReader = (value: unknown, key: KeyGenerator) => PortableText;\n\nconst asIs: CellContentReader = (v) => (Array.isArray(v) ? (v as PortableText) : []);\n\n/**\n * `<table key>-r0c1` — deterministic, unique among siblings, and inside `KEY_PATTERN`.\n *\n * The base is TRUNCATED because a `_key` is capped at 32 characters and an address is parsed by\n * splitting on the separators; 16 leaves room for `-r99c99-9`, which is longer than any table a\n * person writes. A collision with a key the value already carries is resolved by suffixing,\n * never by falling back to randomness.\n */\nfunction keyMinter(base: string, reserved: Set<string>) {\n const prefix = (base.match(/[A-Za-z0-9-]/g) ?? []).join(\"\").slice(0, 16) || \"t\";\n return (...parts: (string | number)[]): string => {\n const candidate = `${prefix}-${parts.join(\"\")}`.slice(0, 30);\n let key = candidate;\n for (let n = 1; reserved.has(key); n += 1) key = `${candidate}-${n}`;\n reserved.add(key);\n return key;\n };\n}\n\n/** Every `_key` the stored value already carries, so a derived key cannot land on one. */\nfunction reservedKeys(rows: unknown[]): Set<string> {\n const out = new Set<string>();\n for (const row of rows) {\n if (!isRecord(row)) continue;\n if (isValidKey(row._key)) out.add(row._key);\n for (const cell of Array.isArray(row.cells) ? row.cells : []) {\n if (isRecord(cell) && isValidKey(cell._key)) out.add(cell._key);\n }\n }\n return out;\n}\n\n/** One line of text as the single block a cell holds. Empty text holds NO block, not an empty one. */\nconst textContent = (text: string, key: KeyGenerator): PortableText =>\n text === \"\"\n ? []\n : [{\n _type: \"block\",\n _key: key(),\n style: \"normal\",\n markDefs: [],\n children: [{ _type: \"span\", _key: key(), text, marks: [] }],\n }];\n\n/** A positive integer span, or nothing. A `colspan=\"0\"` or `\"two\"` is not a span. */\nconst spanOf = (value: unknown): number | undefined => {\n const n = typeof value === \"number\" ? value : Number(value);\n return Number.isInteger(n) && n > 1 ? n : undefined;\n};\n\ntype Mint = (...parts: (string | number)[]) => string;\n\nfunction normalizeCell(raw: unknown, at: [number, number], mint: Mint, read: CellContentReader): PortableTextTableCell {\n const [row, col] = at;\n const key = isRecord(raw) && isValidKey(raw._key) ? raw._key : mint(\"r\", row, \"c\", col);\n // The cell's own generator: a block inside it that has no key is keyed from the CELL, so the\n // whole subtree is reproducible from the stored value alone.\n const inner = deterministicKeys(`${key}-`);\n\n if (typeof raw === \"string\" || typeof raw === \"number\" || typeof raw === \"boolean\") {\n return { _key: key, content: textContent(String(raw), inner) };\n }\n if (!isRecord(raw)) return { _key: key, content: [] };\n\n const span = spanOf(raw.span ?? raw.colSpan ?? raw.colspan);\n const rowSpan = spanOf(raw.rowSpan ?? raw.rowspan);\n return {\n _key: key,\n // `text` is the spelling a string matrix uses once its cells have been given objects; it is\n // read here so a half-migrated value cannot lose its words.\n content: Array.isArray(raw.content)\n ? read(raw.content, inner)\n : textContent(typeof raw.text === \"string\" ? raw.text : \"\", inner),\n ...(raw.header === true ? { header: true } : {}),\n ...(span === undefined ? {} : { span }),\n ...(rowSpan === undefined ? {} : { rowSpan }),\n ...(typeof raw.group === \"string\" ? { group: raw.group } : {}),\n ...(raw.highlight === true ? { highlight: true } : {}),\n ...(raw.sortable === true ? { sortable: true } : {}),\n // The V2 table's typed cells: the column (first-row cell), the per-cell type, the typed value.\n ...(columnOf(raw.column) ? { column: columnOf(raw.column) } : {}),\n ...(isColumnType(raw.type) ? { type: raw.type } : {}),\n ...(typeof raw.value === \"boolean\" || (typeof raw.value === \"number\" && Number.isFinite(raw.value)) ? { value: raw.value } : {}),\n ...(typeof raw.note === \"string\" && raw.note ? { note: raw.note } : {}),\n };\n}\n\nfunction normalizeRow(raw: unknown, at: number, mint: Mint, read: CellContentReader): PortableTextTableRow {\n // A bare array IS a row: that is how the legacy matrix spells one, and how a hand-written\n // value most often arrives.\n const cells = Array.isArray(raw) ? raw : isRecord(raw) && Array.isArray(raw.cells) ? raw.cells : [];\n return {\n _key: isRecord(raw) && isValidKey(raw._key) ? raw._key : mint(\"r\", at),\n cells: cells.map((c, col) => normalizeCell(c, [at, col], mint, read)),\n ...(isRecord(raw) && raw.head === true ? { head: true } : {}),\n ...(isRecord(raw) && typeof raw.group === \"string\" ? { group: raw.group } : {}),\n // The V2 table's two row flags (\"Totals row\", \"Highlight this row\"); see `PortableTextTableRow`.\n ...(isRecord(raw) && raw.footer === true ? { footer: true } : {}),\n ...(isRecord(raw) && raw.highlight === true ? { highlight: true } : {}),\n ...(isRecord(raw) && isColumnType(raw.type) ? { type: raw.type } : {}),\n };\n}\n\n/**\n * A pre-P1c `builtin:table` block → the table.\n *\n * `hasHeader` defaults TRUE when absent, exactly as the renderer it replaces did, so a value\n * written before that flag existed keeps the header row it has always displayed. It is a WHOLE\n * ROW of headers, which is the only header the string matrix could express.\n */\nexport function builtInTableToTable(node: unknown, key: KeyGenerator = randomKey): PortableTextTable | null {\n if (!isBuiltInTableBlock(node)) return null;\n const fields = (node as { fields?: unknown }).fields;\n const raw = isRecord(fields) && Array.isArray(fields.rows) ? fields.rows : [];\n const hasHeader = isRecord(fields) ? fields.hasHeader !== false : true;\n const own = (node as { _key?: unknown })._key;\n const tableKey = isValidKey(own) ? own : key();\n const mint = keyMinter(tableKey, reservedKeys(raw));\n\n const rows = raw.map((row, i) => {\n const normalized = normalizeRow(row, i, mint, asIs);\n if (!(hasHeader && i === 0)) return normalized;\n return {\n ...normalized,\n head: true,\n cells: normalized.cells.map((c) => ({ ...c, header: true })),\n };\n });\n\n // 🔴 SQUARE THE GRID. The renderer this replaces padded every row to the widest one, so a\n // ragged matrix has always DISPLAYED as a full rectangle. Migrating without the padding drops\n // those cells from the page — a value that rendered five columns rendering four.\n const width = rows.reduce((max, r) => Math.max(max, r.cells.length), 0);\n for (const [i, row] of rows.entries()) {\n while (row.cells.length < width) {\n row.cells.push({\n _key: mint(\"r\", i, \"c\", row.cells.length),\n content: [],\n ...(row.head === true ? { header: true } : {}),\n });\n }\n }\n return { _type: PT_TYPES.table, _key: tableKey, rows, legacy: true };\n}\n\n/**\n * Any stored table shape → the one table, or `null` when the node is not a table at all.\n *\n * Returning `null` rather than throwing or coercing is what lets the reader call this on EVERY\n * node cheaply: a non-table answers no, and the caller carries on.\n *\n * PERMISSIVE ON READ, by design: a shape it does not recognise reads as far as it can rather\n * than throwing, because a reader that throws is a field that opens blank. The strict test lives\n * on the WRITE, where the author is present to be told — see `isStorableTable` in `types.ts`.\n */\nexport function migrateTable(\n node: unknown,\n key: KeyGenerator = randomKey,\n read: CellContentReader = asIs,\n): PortableTextTable | null {\n const builtIn = builtInTableToTable(node, key);\n if (builtIn) return builtIn;\n if (!isPortableTextTable(node)) return null;\n const rows = node.rows as unknown[];\n const tableKey = isValidKey(node._key) ? node._key : key();\n const mint = keyMinter(tableKey, reservedKeys(rows));\n return {\n _type: PT_TYPES.table,\n _key: tableKey,\n rows: rows.map((r, i) => normalizeRow(r, i, mint, read)),\n // Carried through: a migrated table that an author saves back is still the same table, and\n // dropping the flag on that save would change the page the next time it renders.\n ...(node.legacy === true ? { legacy: true } : {}),\n ...tableExtras(node),\n };\n}\n\n/**\n * Caption, notes and the two group lists, carried through the reader. Each is written only when\n * the stored value has it, so a table without them reads back byte for byte as it did before\n * they existed. Permissive like the rest of the read: a group entry that is not an object with a\n * string `_key` is skipped (nothing could name it), and a missing label reads as \"\". The write\n * refuses all of it by name — see `tableProblems`.\n */\nfunction tableExtras(node: Record<string, unknown>): Partial<PortableTextTable> {\n const groups = (list: unknown): PortableTextTableGroup[] | undefined =>\n Array.isArray(list)\n ? list\n .filter((g): g is Record<string, unknown> => isRecord(g) && typeof g._key === \"string\" && g._key !== \"\")\n .map((g) => ({ _key: g._key as string, label: typeof g.label === \"string\" ? g.label : \"\" }))\n : undefined;\n const rowGroups = groups(node.rowGroups);\n const columnGroups = groups(node.columnGroups);\n return {\n ...(typeof node.caption === \"string\" ? { caption: node.caption } : {}),\n ...(typeof node.notes === \"string\" ? { notes: node.notes } : {}),\n ...(rowGroups ? { rowGroups } : {}),\n ...(columnGroups ? { columnGroups } : {}),\n ...(node.filterable === true ? { filterable: true } : {}),\n ...(jsonLdOf(node.jsonLd) ? { jsonLd: jsonLdOf(node.jsonLd) } : {}),\n ...(designOf(node.design) ? { design: designOf(node.design) } : {}),\n ...(footnotesOf(node.footnotes) ? { footnotes: footnotesOf(node.footnotes) } : {}),\n };\n}\n\n/** Stored footnotes read permissively: entries with a string `_key`, their text (\"\" when missing); none is nothing. */\nexport function footnotesOf(v: unknown): PortableTextTableFootnote[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out = v\n .filter((f): f is Record<string, unknown> => isRecord(f) && typeof f._key === \"string\" && f._key !== \"\")\n .map((f) => ({ _key: f._key as string, text: typeof f.text === \"string\" ? f.text : \"\" }));\n return out.length ? out : undefined;\n}\n\n/** footnote `_key` → its number (1-based, in order), as V2's footnoteIndex. */\nexport function footnoteNumbers(table: PortableTextTable): Map<string, number> {\n return new Map((footnotesOf(table.footnotes) ?? []).map((f, i) => [f._key, i + 1]));\n}\n\nconst DESIGN_KEYS = new Set([\"responsive\", \"sticky\"]);\n\n/**\n * A stored `design` read permissively, as V2's parseTableElement reads the figure: `responsive` only when `stack` or `cards`\n * (`scroll` is the default and is not kept), `sticky` only when true. Nothing left is nothing.\n */\nexport function designOf(v: unknown): PortableTextTableDesign | undefined {\n if (!isRecord(v)) return undefined;\n const d: PortableTextTableDesign = {\n ...(v.responsive === \"stack\" || v.responsive === \"cards\" ? { responsive: v.responsive } : {}),\n ...(v.sticky === true ? { sticky: true } : {}),\n };\n return Object.keys(d).length ? d : undefined;\n}\n\nconst JSONLD_TYPES = [\"ItemList\", \"Dataset\", \"Offers\"] as const;\nconst JSONLD_KEYS = new Set([\"type\", \"axis\", \"priceRow\"]);\n\n/** A stored `jsonLd` read permissively: a type this table offers, its axis and price row when well formed, else nothing. */\nexport function jsonLdOf(v: unknown): PortableTextTableJsonLd | undefined {\n if (!isRecord(v) || !(JSONLD_TYPES as readonly unknown[]).includes(v.type)) return undefined;\n return {\n type: v.type as PortableTextTableJsonLd[\"type\"],\n ...(v.axis === \"rows\" || v.axis === \"columns\" ? { axis: v.axis } : {}),\n ...(typeof v.priceRow === \"string\" && v.priceRow ? { priceRow: v.priceRow } : {}),\n };\n}\n\n/**\n * The limits a table's caption, notes and group labels are held to — the V2 table's\n * (`TABLE_LIMITS` in `@bettercms-ai/types` table-value.ts), restated so this package keeps no\n * dependency on that one.\n */\nexport const PT_TABLE_LIMITS = { captionChars: 300, notesChars: 2000, labelChars: 300, keyChars: 64 } as const;\n\n/** One reason a stored table is refused, at its own path inside the table. The V2 table's shape. */\nexport type PortableTextTableProblem = { path: Array<string | number>; code: string; message: string };\n\nconst GROUP_KEYS = new Set([\"_key\", \"label\"]);\n\n/**\n * Every reason the caption, notes and groups of `node` cannot be stored, each named with the V2\n * table's codes (`table_value_invalid`, `table_limit_exceeded`) and its path inside the table.\n *\n * 🔴 ONLY THE NEW KEYS ARE JUDGED. A table that carries none of them answers `[]` whatever else it\n * holds, so every table stored before they existed keeps saving exactly as it did. None of these\n * checks reads field config, so there is nothing to grandfather.\n */\nexport function tableProblems(node: unknown): PortableTextTableProblem[] {\n const out: PortableTextTableProblem[] = [];\n if (!isRecord(node)) return out;\n const bad = (path: Array<string | number>, message: string, code = \"table_value_invalid\") =>\n out.push({ path, code, message: `${code}: ${message}` });\n const text = (v: unknown, at: Array<string | number>, what: string, max: number, required = false) => {\n if (v === undefined && !required) return;\n if (typeof v !== \"string\") bad(at, `${what} must be a string`);\n else if (v.length > max) bad(at, `${what} is ${v.length} characters; at most ${max}`, \"table_limit_exceeded\");\n };\n text(node.caption, [\"caption\"], \"caption\", PT_TABLE_LIMITS.captionChars);\n text(node.notes, [\"notes\"], \"notes\", PT_TABLE_LIMITS.notesChars);\n if (node.filterable !== undefined && typeof node.filterable !== \"boolean\") bad([\"filterable\"], \"filterable must be true or false\");\n if (node.design !== undefined) {\n const d = node.design;\n if (!isRecord(d)) bad([\"design\"], \"design must be an object { responsive?, sticky? }\");\n else {\n for (const k of Object.keys(d)) if (!DESIGN_KEYS.has(k)) bad([\"design\", k], `unknown key \"${k}\" (responsive, sticky)`);\n if (d.responsive !== undefined && ![\"scroll\", \"stack\", \"cards\"].includes(d.responsive as string)) bad([\"design\", \"responsive\"], 'responsive must be \"scroll\", \"stack\" or \"cards\"');\n if (d.sticky !== undefined && typeof d.sticky !== \"boolean\") bad([\"design\", \"sticky\"], \"sticky must be true or false\");\n }\n }\n if (node.jsonLd !== undefined) {\n const j = node.jsonLd;\n if (!isRecord(j)) bad([\"jsonLd\"], \"jsonLd must be an object { type, axis?, priceRow? }\");\n else {\n for (const k of Object.keys(j)) if (!JSONLD_KEYS.has(k)) bad([\"jsonLd\", k], `unknown key \"${k}\" (type, axis, priceRow)`);\n if (!(JSONLD_TYPES as readonly unknown[]).includes(j.type)) bad([\"jsonLd\", \"type\"], `type must be one of ${JSONLD_TYPES.join(\", \")} (leave jsonLd out for none)`);\n if (j.axis !== undefined && j.axis !== \"rows\" && j.axis !== \"columns\") bad([\"jsonLd\", \"axis\"], 'axis must be \"rows\" or \"columns\"');\n if (j.priceRow !== undefined) {\n const keys = (Array.isArray(node.rows) ? node.rows : []).map((r) => (isRecord(r) ? r._key : undefined));\n if (typeof j.priceRow !== \"string\" || !keys.includes(j.priceRow)) bad([\"jsonLd\", \"priceRow\"], \"priceRow must be the _key of one of the table's rows\");\n }\n }\n }\n\n const keyed = (list: unknown, at: \"rowGroups\" | \"columnGroups\"): Set<string> => {\n const seen = new Set<string>();\n if (list === undefined) return seen;\n if (!Array.isArray(list)) {\n bad([at], `${at} must be an array of { _key, label }`);\n return seen;\n }\n list.forEach((g, i) => {\n if (!isRecord(g)) return bad([at, i], `${at}[${i}] must be an object { _key, label }`);\n for (const k of Object.keys(g)) if (!GROUP_KEYS.has(k)) bad([at, i, k], `unknown key \"${k}\" (_key, label)`);\n if (typeof g._key !== \"string\" || g._key === \"\" || g._key.length > PT_TABLE_LIMITS.keyChars) {\n bad([at, i, \"_key\"], `needs a _key string of 1 to ${PT_TABLE_LIMITS.keyChars} characters`);\n } else if (seen.has(g._key)) bad([at, i, \"_key\"], `_key \"${g._key}\" is used twice`);\n else seen.add(g._key);\n text(g.label, [at, i, \"label\"], \"label\", PT_TABLE_LIMITS.labelChars, true);\n });\n return seen;\n };\n const rowGroups = keyed(node.rowGroups, \"rowGroups\");\n const columnGroups = keyed(node.columnGroups, \"columnGroups\");\n // Footnotes: [{ _key, text }], the field-lane table's (`text` held to the note's limit)\n const footnoteKeys = new Set<string>();\n if (node.footnotes !== undefined) {\n if (!Array.isArray(node.footnotes)) bad([\"footnotes\"], \"footnotes must be an array of { _key, text }\");\n else\n node.footnotes.forEach((f, i) => {\n if (!isRecord(f)) return bad([\"footnotes\", i], `footnotes[${i}] must be an object { _key, text }`);\n for (const k of Object.keys(f)) if (k !== \"_key\" && k !== \"text\") bad([\"footnotes\", i, k], `unknown key \"${k}\" (_key, text)`);\n if (typeof f._key !== \"string\" || f._key === \"\" || f._key.length > PT_TABLE_LIMITS.keyChars) bad([\"footnotes\", i, \"_key\"], `needs a _key string of 1 to ${PT_TABLE_LIMITS.keyChars} characters`);\n else if (footnoteKeys.has(f._key)) bad([\"footnotes\", i, \"_key\"], `_key \"${f._key}\" is used twice`);\n else footnoteKeys.add(f._key);\n text(f.text, [\"footnotes\", i, \"text\"], \"text\", PT_TABLE_LIMITS.notesChars, true);\n });\n }\n\n const rows = Array.isArray(node.rows) ? node.rows : [];\n // The columns' types, by cell index, as the first row names them (a typed value is judged against its column).\n const firstCells = isRecord(rows[0]) && Array.isArray(rows[0].cells) ? (rows[0].cells as unknown[]) : [];\n const columns = firstCells.map((c) => (isRecord(c) ? columnOf(c.column) : undefined));\n rows.forEach((row, r) => {\n if (!isRecord(row)) return;\n for (const flag of [\"footer\", \"highlight\"] as const) {\n if (row[flag] !== undefined && typeof row[flag] !== \"boolean\") bad([\"rows\", r, flag], `${flag} must be true or false`);\n }\n if (row.head === true && row.footer === true) bad([\"rows\", r, \"footer\"], \"a head row cannot also be a footer (totals) row\");\n if (row.type !== undefined && !isColumnType(row.type)) bad([\"rows\", r, \"type\"], `type must be one of ${PT_TABLE_COLUMN_TYPES.join(\", \")}`);\n if (row.group !== undefined) {\n if (row.head === true) bad([\"rows\", r, \"group\"], \"a head row takes no group (row groups hold body rows)\");\n else if (typeof row.group !== \"string\" || !rowGroups.has(row.group)) {\n bad([\"rows\", r, \"group\"], `group \"${String(row.group)}\" is not a key of rowGroups`);\n }\n }\n (Array.isArray(row.cells) ? row.cells : []).forEach((cell, c) => {\n if (isRecord(cell)) for (const p of typedCellProblems(cell, row, r, c, columns[c])) bad(p.path, p.message);\n if (isRecord(cell) && cell.note !== undefined && (typeof cell.note !== \"string\" || !footnoteKeys.has(cell.note))) {\n bad([\"rows\", r, \"cells\", c, \"note\"], `note \"${String(cell.note)}\" is not a key of footnotes`);\n }\n for (const flag of [\"highlight\", \"sortable\"] as const) {\n if (!isRecord(cell) || cell[flag] === undefined) continue;\n const at = [\"rows\", r, \"cells\", c, flag];\n if (typeof cell[flag] !== \"boolean\") bad(at, `${flag} must be true or false`);\n else if (r !== 0 && cell[flag]) bad(at, `a column's ${flag} is named on its cell in the first row only`);\n }\n if (!isRecord(cell) || cell.group === undefined) return;\n const at = [\"rows\", r, \"cells\", c, \"group\"];\n if (r !== 0) bad(at, \"a column's group is named on its cell in the first row only\");\n else if (typeof cell.group !== \"string\" || !columnGroups.has(cell.group)) {\n bad(at, `group \"${String(cell.group)}\" is not a key of columnGroups`);\n }\n });\n });\n return out;\n}\n\n/**\n * `tableProblems` for every table in a rich-text value — top level, and inside cells — with the\n * table's `_key` so a refusal names the table an author has to find.\n */\nexport function portableTextTableProblems(value: unknown): Array<PortableTextTableProblem & { table: string }> {\n const out: Array<PortableTextTableProblem & { table: string }> = [];\n const walk = (blocks: unknown) => {\n if (!Array.isArray(blocks)) return;\n for (const node of blocks) {\n if (!isPortableTextTable(node)) continue;\n const table = typeof node._key === \"string\" ? node._key : \"\";\n for (const p of tableProblems(node)) out.push({ ...p, table });\n for (const row of node.rows as unknown[]) {\n if (!isRecord(row) || !Array.isArray(row.cells)) continue;\n for (const cell of row.cells) if (isRecord(cell)) walk(cell.content);\n }\n }\n };\n walk(value);\n return out;\n}\n\n/**\n * A table's rows split into its three sections: the LEADING run of `head` rows (`<thead>`), the\n * TRAILING run of `footer` rows (`<tfoot>`, the V2 table's \"Totals row\"), and the body between.\n *\n * Runs, not every flagged row, for the reason `<thead>` already is one: a section cannot be\n * lifted out of document order without moving content the author placed. A `footer` row with a\n * body row after it stays in the body (permissive read). Shared by every renderer so they agree.\n */\nexport function tableSections(table: PortableTextTable): { head: unknown[]; body: unknown[]; foot: unknown[] } {\n const rows: unknown[] = Array.isArray(table.rows) ? table.rows : [];\n const headCount = rows.findIndex((r) => !isRecord(r) || r.head !== true);\n const head = rows.slice(0, headCount === -1 ? rows.length : headCount);\n let footFrom = rows.length;\n while (footFrom > head.length && isRecord(rows[footFrom - 1]) && (rows[footFrom - 1] as Record<string, unknown>).footer === true) footFrom -= 1;\n return { head, body: rows.slice(head.length, footFrom), foot: rows.slice(footFrom) };\n}\n\n/**\n * The columns \"Highlight this column\" marks: the start column of each FIRST-row cell carrying\n * `highlight` (spans counted). A renderer stamps `data-highlight` on every cell starting there.\n */\nexport function highlightedColumns(table: PortableTextTable): Set<number> {\n return markedColumns(table, \"highlight\");\n}\n\n/** The columns \"Visitors can sort columns\" marks (a FIRST-row cell's `sortable`), as `highlightedColumns`. */\nexport function sortableColumns(table: PortableTextTable): Set<number> {\n return markedColumns(table, \"sortable\");\n}\n\nfunction markedColumns(table: PortableTextTable, flag: \"highlight\" | \"sortable\"): Set<number> {\n const out = new Set<number>();\n const first = Array.isArray(table.rows) ? table.rows[0] : undefined;\n let at = 0;\n for (const cell of isRecord(first) && Array.isArray(first.cells) ? first.cells : []) {\n if (isRecord(cell) && cell[flag] === true) out.add(at);\n at += isRecord(cell) && typeof cell.span === \"number\" && Number.isInteger(cell.span) && cell.span > 1 ? cell.span : 1;\n }\n return out;\n}\n\n/** A run of columns, or of body rows, that share one group; `label` is absent for an ungrouped run. */\nexport type TableColumnRun = { width: number; key?: string; label?: string };\nexport type TableRowRun = { rows: unknown[]; key?: string; label?: string };\n\n/**\n * How a table's groups lay out, or two empty lists when it has none.\n *\n * Shared by both renderers (`@bettercms-ai/ui` draws the same runs), so they cannot disagree\n * about where a group starts. A group key that names no group is ignored here rather than\n * thrown on: the read is permissive, and the write refuses it by name (`tableProblems`).\n *\n * `columns` is empty unless some first-row cell names a known column group; `rows` is empty\n * unless some body row names a known row group. `width` is the widest row, spans counted.\n */\nexport function tableGroupLayout(table: PortableTextTable): { columns: TableColumnRun[]; rows: TableRowRun[]; width: number } {\n const rows: unknown[] = Array.isArray(table.rows) ? table.rows : [];\n const labels = (list: unknown): Map<string, string> =>\n new Map(\n (Array.isArray(list) ? list : [])\n .filter((g): g is Record<string, unknown> => isRecord(g) && typeof g._key === \"string\")\n .map((g) => [g._key as string, typeof g.label === \"string\" ? g.label : \"\"]),\n );\n const cellsOf = (row: unknown): unknown[] => (isRecord(row) && Array.isArray(row.cells) ? row.cells : []);\n const spanOf = (cell: unknown): number =>\n isRecord(cell) && typeof cell.span === \"number\" && Number.isInteger(cell.span) && cell.span > 1 ? cell.span : 1;\n const width = rows.reduce<number>((max, row) => Math.max(max, cellsOf(row).reduce<number>((n, c) => n + spanOf(c), 0)), 0);\n\n const colLabels = labels(table.columnGroups);\n const columns: TableColumnRun[] = [];\n for (const cell of cellsOf(rows[0])) {\n const key = isRecord(cell) && typeof cell.group === \"string\" && colLabels.has(cell.group) ? cell.group : undefined;\n const last = columns[columns.length - 1];\n if (last && last.key === key) last.width += spanOf(cell);\n else columns.push({ width: spanOf(cell), ...(key === undefined ? {} : { key, label: colLabels.get(key) }) });\n }\n\n const rowLabels = labels(table.rowGroups);\n // The body only: a footer (totals) run sits in `<tfoot>`, outside every row group.\n const { body } = tableSections(table);\n const runs: TableRowRun[] = [];\n for (const row of body) {\n const key = isRecord(row) && typeof row.group === \"string\" && rowLabels.has(row.group) ? row.group : undefined;\n const last = runs[runs.length - 1];\n if (last && last.key === key) last.rows.push(row);\n else runs.push({ rows: [row], ...(key === undefined ? {} : { key, label: rowLabels.get(key) }) });\n }\n\n return {\n columns: columns.some((r) => r.key !== undefined) ? columns : [],\n rows: runs.some((r) => r.key !== undefined) ? runs : [],\n width,\n };\n}\n\n/* ── Typed cells (the V2 table's column types, src/schema/table.ts) ─────────────────────── */\n\nconst COLUMN_KEYS = new Set([\"type\", \"description\", \"unit\", \"currency\", \"decimals\", \"align\", \"trueLabel\", \"falseLabel\", \"max\"]);\nconst NUMERIC_TYPES = new Set<string>([\"number\", \"money\", \"rating\"]);\n\nexport const isColumnType = (v: unknown): v is PortableTextTableColumnType => (PT_TABLE_COLUMN_TYPES as readonly unknown[]).includes(v);\n\n/** A stored `column` read permissively: each V2 key kept when well formed, a type this table offers; nothing left is nothing. */\nexport function columnOf(v: unknown): PortableTextTableColumn | undefined {\n if (!isRecord(v)) return undefined;\n const str = (x: unknown) => (typeof x === \"string\" && x !== \"\" ? x : undefined);\n const int = (x: unknown, lo: number, hi: number) => (Number.isInteger(x) && (x as number) >= lo && (x as number) <= hi ? (x as number) : undefined);\n const c: PortableTextTableColumn = {\n ...(isColumnType(v.type) ? { type: v.type } : {}),\n ...(str(v.description) ? { description: str(v.description) } : {}),\n ...(str(v.unit) ? { unit: str(v.unit) } : {}),\n ...(typeof v.currency === \"string\" && /^[A-Z]{3}$/.test(v.currency) ? { currency: v.currency } : {}),\n ...(int(v.decimals, 0, 10) !== undefined ? { decimals: int(v.decimals, 0, 10) } : {}),\n ...(v.align === \"start\" || v.align === \"center\" || v.align === \"end\" ? { align: v.align } : {}),\n ...(str(v.trueLabel) ? { trueLabel: str(v.trueLabel) } : {}),\n ...(str(v.falseLabel) ? { falseLabel: str(v.falseLabel) } : {}),\n ...(int(v.max, 1, 10) !== undefined ? { max: int(v.max, 1, 10) } : {}),\n };\n return Object.keys(c).length ? c : undefined;\n}\n\n/** Each column's `column`, by column index (spans counted), from the FIRST-row cells. */\nexport function tableColumns(table: PortableTextTable): (PortableTextTableColumn | undefined)[] {\n const out: (PortableTextTableColumn | undefined)[] = [];\n const first = Array.isArray(table.rows) ? table.rows[0] : undefined;\n for (const cell of isRecord(first) && Array.isArray(first.cells) ? first.cells : []) {\n const w = isRecord(cell) && typeof cell.span === \"number\" && Number.isInteger(cell.span) && cell.span > 1 ? cell.span : 1;\n const col = isRecord(cell) ? columnOf(cell.column) : undefined;\n for (let i = 0; i < w; i += 1) out.push(col);\n }\n return out;\n}\n\n/**\n * A table that draws V2's typed markup: its first row names a `column`, or a row or cell carries a type or a value (a \"Write\n * words instead\" cell in a link column is typed even when no column is).\n */\nexport function isTypedTable(table: PortableTextTable): boolean {\n if (tableColumns(table).some(Boolean)) return true;\n return (Array.isArray(table.rows) ? (table.rows as unknown[]) : []).some(\n (r) => isRecord(r) && (r.type !== undefined || (Array.isArray(r.cells) && r.cells.some((c: unknown) => isRecord(c) && (c.type !== undefined || c.value !== undefined)))),\n );\n}\n\n/** V2 `cellType`: the cell's own type, else the row's, else the column's (text when none says). */\nexport const cellTypeOf = (col: PortableTextTableColumn | undefined, row: unknown, cell: unknown): PortableTextTableColumnType =>\n (isRecord(cell) && isColumnType(cell.type) ? cell.type : undefined) ?? (isRecord(row) && isColumnType(row.type) ? row.type : undefined) ?? col?.type ?? \"text\";\n\n/** V2 `alignOf` (schema/table.ts:365): numbers, money and ratings align end unless the column says otherwise. */\nexport const columnAlign = (col: PortableTextTableColumn | undefined, type: PortableTextTableColumnType): \"start\" | \"center\" | \"end\" =>\n col?.align ?? (NUMERIC_TYPES.has(type) ? \"end\" : \"start\");\n\n/** The types whose cell is a value rather than words: V2 renders them from the value (cellHtml). */\nexport const isValueType = (t: PortableTextTableColumnType): boolean => t === \"boolean\" || NUMERIC_TYPES.has(t);\n\nconst TRUE_WORDS = new Set([\"✓\", \"✔\", \"✔️\", \"☑\", \"✅\", \"yes\", \"y\", \"true\", \"included\", \"available\", \"supported\", \"x ✓\", \"check\", \"●\"]);\nconst FALSE_WORDS = new Set([\"✗\", \"✕\", \"✖\", \"❌\", \"×\", \"x\", \"no\", \"n\", \"false\", \"not included\", \"unavailable\", \"not available\", \"unsupported\", \"—\", \"–\", \"-\", \"○\"]);\n/** V2 `toBool` (schema/table.ts:315). */\nexport function toBool(v: unknown): boolean | null {\n if (typeof v === \"boolean\") return v;\n if (typeof v !== \"string\") return null;\n const s = v.trim().toLowerCase();\n return TRUE_WORDS.has(s) ? true : FALSE_WORDS.has(s) ? false : null;\n}\n/** V2 `toNumber` (schema/table.ts:273). */\nexport function toNumber(v: unknown): number | null {\n if (typeof v === \"number\") return Number.isFinite(v) ? v : null;\n if (typeof v !== \"string\") return null;\n const s = v.replace(/[,\\s]/g, \"\").replace(/[^\\d.+-]/g, \"\");\n if (!s || !/^[+-]?\\d*\\.?\\d+$/.test(s)) return null;\n return Number(s);\n}\n/** V2 `formatNumber` (schema/table.ts:262). */\nexport const formatNumber = (n: number, decimals?: number): string =>\n new Intl.NumberFormat(\"en-US\", decimals != null ? { minimumFractionDigits: decimals, maximumFractionDigits: decimals } : { maximumFractionDigits: 6 }).format(n);\n/** V2 `formatMoney` (schema/table.ts:265). */\nexport function formatMoney(n: number, currency = \"USD\", decimals?: number): string {\n try {\n const whole = Number.isInteger(n) && decimals == null;\n return new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency, minimumFractionDigits: whole ? 0 : decimals ?? 2, maximumFractionDigits: whole ? 0 : decimals ?? 2 }).format(n);\n } catch {\n return `${formatNumber(n, decimals)} ${currency}`;\n }\n}\n/** V2 `boolLabels` (schema/table.ts:252). */\nexport const boolLabels = (col: PortableTextTableColumn | undefined) => ({ yes: col?.trueLabel || \"Included\", no: col?.falseLabel || \"Not included\" });\n\n/**\n * V2 `cellText` (schema/table.ts:282) for a value type: the words a Number, Price, Yes / No or Rating cell says, which is what a\n * typed cell's `content` holds. `v` is the typed value, or the cell's words when it has none.\n */\nexport function typedCellText(col: PortableTextTableColumn | undefined, type: PortableTextTableColumnType, v: unknown): string {\n if (v == null || v === \"\") return \"\";\n const words = String(v).replace(/\\s+/g, \" \").trim();\n switch (type) {\n case \"boolean\": {\n const b = toBool(v);\n return b == null ? words : b ? boolLabels(col).yes : boolLabels(col).no;\n }\n case \"number\": {\n const n = toNumber(v);\n return n == null ? words : `${formatNumber(n, col?.decimals)}${col?.unit ? (/^[%°]/.test(col.unit) ? col.unit : ` ${col.unit}`) : \"\"}`;\n }\n case \"money\": {\n const n = toNumber(v);\n return n == null ? words : formatMoney(n, col?.currency || \"USD\", col?.decimals);\n }\n case \"rating\": {\n const n = toNumber(v);\n return n == null ? words : `${formatNumber(n, 1).replace(/\\.0$/, \"\")} out of ${col?.max ?? 5}`;\n }\n default:\n return words;\n }\n}\n\nconst escHtml = (s: string): string => s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n\n/**\n * What a cell of a TYPED table (one whose first row names a `column`) draws, shared by both renderers so they cannot disagree:\n * a head-row cell → `lead` data-align (V2: not when start), `tail` the column's description as `title`\n * (V2 renderTableHtml's `<th scope=\"col\" data-align data-highlight title>`, schema/table.ts:564)\n * any other cell → `tail` data-type (its own type), data-value / data-empty, data-align, and `inner`, V2 cellHtml's markup\n * (schema/table.ts:369-437) when the cell is empty or of a value type; text and link cells draw their blocks.\n * Attribute values are raw (the caller escapes them); `inner` is markup, its text already escaped.\n */\nexport function typedCellView(\n column: PortableTextTableColumn | undefined,\n row: unknown,\n cell: Record<string, unknown>,\n inHead: boolean,\n): { lead: [string, string][]; tail: [string, string][]; inner?: string } {\n if (inHead) {\n const align = columnAlign(column, column?.type ?? \"text\");\n return { lead: align !== \"start\" ? [[\"data-align\", align]] : [], tail: column?.description ? [[\"title\", column.description]] : [] };\n }\n const type = cellTypeOf(column, row, cell);\n const words = portableTextWords(cell.content);\n const v = typeof cell.value === \"number\" || typeof cell.value === \"boolean\" ? cell.value : words;\n const tail: [string, string][] = isColumnType(cell.type) ? [[\"data-type\", cell.type]] : [];\n let inner: string | undefined;\n if (v === \"\") {\n inner = cell.header === true ? \"\" : '<span aria-hidden=\"true\" class=\"bt-dash\">—</span><span class=\"bt-sr\">No value</span>';\n tail.push([\"data-empty\", \"\"]);\n } else if (type === \"boolean\") {\n const b = toBool(v);\n if (b == null) inner = escHtml(words);\n else {\n const { yes, no } = boolLabels(column);\n inner = `<span aria-hidden=\"true\" class=\"bt-bool\" data-bool=\"${b}\">${b ? \"✓\" : \"–\"}</span><span class=\"bt-sr\">${escHtml(b ? yes : no)}</span>`;\n tail.push([\"data-value\", String(b)]);\n }\n } else if (type === \"rating\") {\n const n = toNumber(v);\n if (n == null) inner = escHtml(words);\n else {\n inner = `<span aria-hidden=\"true\" class=\"bt-star\">★</span> ${escHtml(formatNumber(n, 1).replace(/\\.0$/, \"\"))}<span class=\"bt-dim\"> / ${column?.max ?? 5}</span>`;\n tail.push([\"data-value\", String(n)]);\n }\n } else if (type === \"number\" || type === \"money\") {\n const n = toNumber(v);\n inner = escHtml(typedCellText(column, type, v));\n if (n != null) tail.push([\"data-value\", String(n)]);\n }\n const align = columnAlign(column, type);\n if (align !== \"start\") tail.push([\"data-align\", align]);\n return { lead: [], tail, ...(inner === undefined ? {} : { inner }) };\n}\n\n/** A cell's words: the text of its blocks, whitespace collapsed (as `portableTextToPlainText` reads a paragraph). */\nfunction portableTextWords(content: unknown): string {\n return (Array.isArray(content) ? content : [])\n .map((b) => (isRecord(b) && Array.isArray(b.children) ? b.children.map((c) => (isRecord(c) && typeof c.text === \"string\" ? c.text : \"\")).join(\"\") : \"\"))\n .join(\" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n/** Why a cell's typed keys cannot be stored (`column` only on the first row, a known type, a value its type holds). */\nfunction typedCellProblems(cell: Record<string, unknown>, row: Record<string, unknown>, r: number, c: number, col: PortableTextTableColumn | undefined): PortableTextTableProblem[] {\n const out: PortableTextTableProblem[] = [];\n const bad = (k: string | string[], message: string, code = \"table_value_invalid\") =>\n out.push({ path: [\"rows\", r, \"cells\", c, ...(Array.isArray(k) ? k : [k])], code, message: `${code}: ${message}` });\n if (cell.column !== undefined) {\n const v = cell.column;\n if (r !== 0) bad(\"column\", \"a column's type is named on its cell in the first row only\");\n else if (!isRecord(v)) bad(\"column\", \"column must be an object { type, unit?, currency?, decimals?, align?, trueLabel?, falseLabel?, max?, description? }\");\n else {\n for (const k of Object.keys(v)) if (!COLUMN_KEYS.has(k)) bad([\"column\", k], `unknown key \"${k}\" (${[...COLUMN_KEYS].join(\", \")})`);\n if (v.type !== undefined && !isColumnType(v.type)) bad([\"column\", \"type\"], `type must be one of ${PT_TABLE_COLUMN_TYPES.join(\", \")}`);\n const text = (k: string, max: number) => {\n if (v[k] === undefined) return;\n if (typeof v[k] !== \"string\") bad([\"column\", k], `${k} must be a string`);\n else if ((v[k] as string).length > max) bad([\"column\", k], `${k} is ${(v[k] as string).length} characters; at most ${max}`, \"table_limit_exceeded\");\n };\n text(\"description\", PT_TABLE_LIMITS.notesChars);\n text(\"unit\", 16);\n text(\"trueLabel\", PT_TABLE_LIMITS.labelChars);\n text(\"falseLabel\", PT_TABLE_LIMITS.labelChars);\n if (v.currency !== undefined && (typeof v.currency !== \"string\" || !/^[A-Z]{3}$/.test(v.currency))) bad([\"column\", \"currency\"], \"currency must be an ISO 4217 code such as USD\");\n if (v.decimals !== undefined && (!Number.isInteger(v.decimals) || (v.decimals as number) < 0 || (v.decimals as number) > 10)) bad([\"column\", \"decimals\"], \"decimals must be a whole number from 0 to 10\");\n if (v.max !== undefined && (!Number.isInteger(v.max) || (v.max as number) < 1 || (v.max as number) > 10)) bad([\"column\", \"max\"], \"max must be a whole number from 1 to 10\");\n if (v.align !== undefined && v.align !== \"start\" && v.align !== \"center\" && v.align !== \"end\") bad([\"column\", \"align\"], 'align must be \"start\", \"center\" or \"end\"');\n }\n }\n if (cell.type !== undefined && !isColumnType(cell.type)) bad(\"type\", `type must be one of ${PT_TABLE_COLUMN_TYPES.join(\", \")}`);\n if (cell.value !== undefined) {\n const type = cellTypeOf(col, row, cell);\n if (type === \"boolean\" ? typeof cell.value !== \"boolean\" : NUMERIC_TYPES.has(type) ? typeof cell.value !== \"number\" || !Number.isFinite(cell.value) : true) {\n bad(\"value\", `a ${type} cell's value must be ${type === \"boolean\" ? \"true or false\" : NUMERIC_TYPES.has(type) ? \"a number\" : \"absent (its words are its content)\"}`);\n }\n }\n return out;\n}\n","import { PT_TYPES } from \"./types\";\nimport { isPortableTextTable } from \"./table\";\n\n/**\n * Key takeaways (`PortableTextKeyTakeaways`, types.ts): the limits, the write check, and the\n * read every renderer shares.\n *\n * The limits are the V2 kit's (`ARTICLE_SCHEMAS.takeaways` in `controls/rich-text/article.ts`):\n * a title of at most 40 characters and at most 8 points. V2's `min: 1` on points is the\n * editor's, not the write's: its editor always holds a line to type in, and the value it renders\n * while that line is still empty has no point in it. Refusing that would refuse an autosave\n * mid-typing, so an empty list stores, and renders as V2 renders it.\n */\nexport const PT_KEY_TAKEAWAYS_LIMITS = { titleChars: 40, points: 8 } as const;\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\n/** One reason a stored key-takeaways block is refused, at its own path inside the block. */\nexport type PortableTextKeyTakeawaysProblem = { path: Array<string | number>; code: string; message: string };\n\nconst KEYS = new Set([\"_type\", \"_key\", \"title\", \"points\"]);\n\n/**\n * Every reason `node` cannot be stored, each named with a code: `key_takeaways_invalid` (a\n * shape problem) or `key_takeaways_limit_exceeded` (a V2 limit).\n *\n * Only a `keyTakeaways` block is judged. The type is new, so no value stored before it existed\n * can hold one: there is nothing to grandfather.\n */\nexport function keyTakeawaysProblems(node: unknown): PortableTextKeyTakeawaysProblem[] {\n const out: PortableTextKeyTakeawaysProblem[] = [];\n if (!isRecord(node) || node._type !== PT_TYPES.keyTakeaways) return out;\n const bad = (path: Array<string | number>, message: string, code = \"key_takeaways_invalid\") =>\n out.push({ path, code, message: `${code}: ${message}` });\n for (const k of Object.keys(node)) if (!KEYS.has(k)) bad([k], `unknown key \"${k}\" (title, points)`);\n if (node.title !== undefined) {\n if (typeof node.title !== \"string\") bad([\"title\"], \"title must be a string\");\n else if (node.title.length > PT_KEY_TAKEAWAYS_LIMITS.titleChars) {\n bad([\"title\"], `title is ${node.title.length} characters; at most ${PT_KEY_TAKEAWAYS_LIMITS.titleChars}`, \"key_takeaways_limit_exceeded\");\n }\n }\n if (!Array.isArray(node.points)) {\n bad([\"points\"], \"points must be an array of strings\");\n return out;\n }\n node.points.forEach((p, i) => {\n if (typeof p !== \"string\") bad([\"points\", i], `points[${i}] must be a string`);\n });\n if (node.points.length > PT_KEY_TAKEAWAYS_LIMITS.points) {\n bad([\"points\"], `${node.points.length} points; at most ${PT_KEY_TAKEAWAYS_LIMITS.points}`, \"key_takeaways_limit_exceeded\");\n }\n return out;\n}\n\n/**\n * `keyTakeawaysProblems` for every key-takeaways block in a rich-text value — top level, and\n * inside table cells — with the block's `_key` so a refusal names the block an author has to find.\n */\nexport function portableTextKeyTakeawaysProblems(value: unknown): Array<PortableTextKeyTakeawaysProblem & { block: string }> {\n const out: Array<PortableTextKeyTakeawaysProblem & { block: string }> = [];\n const walk = (blocks: unknown) => {\n if (!Array.isArray(blocks)) return;\n for (const node of blocks) {\n if (isPortableTextTable(node)) {\n for (const row of node.rows as unknown[]) {\n if (!isRecord(row) || !Array.isArray(row.cells)) continue;\n for (const cell of row.cells) if (isRecord(cell)) walk(cell.content);\n }\n continue;\n }\n if (!isRecord(node) || node._type !== PT_TYPES.keyTakeaways) continue;\n const block = typeof node._key === \"string\" ? node._key : \"\";\n for (const p of keyTakeawaysProblems(node)) out.push({ ...p, block });\n }\n };\n walk(value);\n return out;\n}\n\n/**\n * What a reader sees of a key-takeaways block: the title, trimmed (\"\" for none), and the points,\n * each trimmed, the empty ones left out (V2 `renderArticle(\"takeaways\")`). Read defensively: a\n * value that never passed the write check renders what it can.\n */\nexport function keyTakeawaysView(value: unknown): { title: string; points: string[] } {\n const v = isRecord(value) ? value : {};\n const title = typeof v.title === \"string\" ? v.title.trim() : \"\";\n const points = (Array.isArray(v.points) ? v.points : [])\n .map((p) => (typeof p === \"string\" ? p.trim() : \"\"))\n .filter(Boolean);\n return { title, points };\n}\n","import { EMBEDDED_COMPONENT_KEY } from \"./types\";\n\n/**\n * A component embedded in rich text, kept as a SEPARATE COPY (V2 \"Keep as a separate copy\" /\n * \"Make a separate copy\", bettercms-v2-demo features/blocks/instances.ts `instanceOps.detach`).\n *\n * Linked (every value stored before this existed):\n * { _type: \"bcmsBlock\", schemaKey: \"component\", fields: { componentId, overrides?, variant? } }\n * → renders the shared component with this place's overrides; edits to the component reach it.\n *\n * Detached:\n * { _type: \"bcmsBlock\", schemaKey: \"component\", fields: {\n * componentId, the component it was copied from (kept for \"Use shared version again\")\n * detached: true,\n * props, the values readers saw when it was copied (the component's ⊕ this place's)\n * blocks, the block tree readers see, resolved and frozen: no `component` block inside\n * sourcePublishedAt, the source's `lastPublishedAt` when it was copied (0454; older copies hold its first `publishedAt`): the version it was taken from\n * variant?\n * } }\n * → renders `blocks` and never looks the component up again.\n *\n * V2 keeps only `props` because its renderer draws a catalog type from props. Here a component's\n * markup is its own block tree, so a copy that kept only props would still follow the shared\n * component's structure; `blocks` is what makes the copy separate.\n */\nexport interface DetachedComponentCopy {\n /** The node's `_key`. */\n id: string;\n componentId: string;\n props: Record<string, unknown>;\n blocks: unknown[];\n sourcePublishedAt?: string;\n variant?: string;\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> => !!v && typeof v === \"object\" && !Array.isArray(v);\n\nconst fieldsOf = (value: unknown): Record<string, unknown> | null =>\n isRecord(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord(value.fields) ? value.fields : null;\n\n/** True when the node is an embedded component stored as a separate copy (whatever else it holds). */\nexport function isDetachedComponent(value: unknown): boolean {\n return fieldsOf(value)?.detached === true;\n}\n\n/** The separate copy a node stands for, or null (a linked embed, any other node, a malformed copy). */\nexport function detachedComponentCopy(value: unknown): DetachedComponentCopy | null {\n const f = fieldsOf(value);\n if (!f || f.detached !== true || !Array.isArray(f.blocks)) return null;\n return {\n id: isRecord(value) && typeof value._key === \"string\" ? value._key : \"\",\n componentId: typeof f.componentId === \"string\" ? f.componentId : \"\",\n props: isRecord(f.props) ? f.props : {},\n blocks: f.blocks,\n ...(typeof f.sourcePublishedAt === \"string\" ? { sourcePublishedAt: f.sourcePublishedAt } : {}),\n ...(typeof f.variant === \"string\" ? { variant: f.variant } : {}),\n };\n}\n\n/** One reason a stored copy is refused, at its path inside the node's `fields`. */\nexport type ComponentCopyProblem = { path: Array<string | number>; code: string; message: string };\n\n/**\n * Every reason a separate copy cannot be stored, named `component_copy_invalid` (the shape) or\n * `component_copy_not_frozen` (its blocks still reference a component, so it would keep\n * following one).\n *\n * 🔴 ONLY A NODE THAT CARRIES `detached` IS JUDGED. A linked embed answers `[]`, so every value\n * stored before copies existed saves exactly as it did.\n */\nexport function componentCopyProblems(node: unknown): ComponentCopyProblem[] {\n const f = fieldsOf(node);\n if (!f || f.detached === undefined) return [];\n const out: ComponentCopyProblem[] = [];\n const bad = (path: Array<string | number>, message: string, code = \"component_copy_invalid\") =>\n out.push({ path, code, message: `${code}: ${message}` });\n if (f.detached !== true) bad([\"detached\"], \"detached must be true (leave it out for a linked component)\");\n if (typeof f.componentId !== \"string\" || !f.componentId) bad([\"componentId\"], \"needs the componentId it was copied from\");\n if (!isRecord(f.props)) bad([\"props\"], \"props must be an object of the values readers see\");\n if (!Array.isArray(f.blocks)) {\n bad([\"blocks\"], \"needs blocks, the copy readers see; get_component { detachedCopy } returns them\");\n } else {\n const live = componentRefs(f.blocks);\n if (live.length) {\n bad([\"blocks\"], `still holds ${live.length} component block(s) (${live.slice(0, 3).join(\", \")}), so it would keep following the shared one`, \"component_copy_not_frozen\");\n }\n }\n if (f.sourcePublishedAt !== undefined && (typeof f.sourcePublishedAt !== \"string\" || Number.isNaN(Date.parse(f.sourcePublishedAt)))) {\n bad([\"sourcePublishedAt\"], \"sourcePublishedAt must be an ISO date\");\n }\n if (isRecord(f.overrides) && Object.keys(f.overrides).length) {\n bad([\"overrides\"], \"a separate copy keeps its values in props; overrides belong to a linked component\");\n }\n return out;\n}\n\n/** `componentCopyProblems` for every embedded component at the top of a rich-text value, with its `_key`. */\nexport function portableTextComponentCopyProblems(value: unknown): Array<ComponentCopyProblem & { block: string }> {\n if (!Array.isArray(value)) return [];\n return value.flatMap((node) => {\n const block = isRecord(node) && typeof node._key === \"string\" ? node._key : \"\";\n return componentCopyProblems(node).map((p) => ({ ...p, block }));\n });\n}\n\n/** The component ids a block tree still references, at any depth. */\nfunction componentRefs(blocks: unknown, out: string[] = [], depth = 0): string[] {\n if (depth > 32) return out;\n if (Array.isArray(blocks)) {\n for (const b of blocks) componentRefs(b, out, depth + 1);\n return out;\n }\n if (!isRecord(blocks)) return out;\n if (blocks.type === \"component\") {\n const p = isRecord(blocks.props) ? blocks.props : {};\n out.push(typeof p.componentId === \"string\" && p.componentId ? p.componentId : \"(no id)\");\n }\n for (const v of Object.values(blocks)) if (v && typeof v === \"object\") componentRefs(v, out, depth + 1);\n return out;\n}\n\n/**\n * What a separate copy says, one line per value, for search and plain text: its own copy is\n * content this page owns, unlike a linked embed, whose words live in the component.\n * Strings (markup stripped), numbers, and a link's or button's label; nothing else.\n */\nexport function componentCopyLines(copy: Pick<DetachedComponentCopy, \"props\">): string[] {\n const line = (v: unknown): string | null => {\n if (typeof v === \"number\" && Number.isFinite(v)) return String(v);\n if (typeof v === \"string\") {\n const t = v.replace(/<[^>]+>/g, \" \").replace(/&nbsp;/g, \" \").replace(/\\s+/g, \" \").trim();\n return t || null;\n }\n if (isRecord(v)) return line(v.label ?? v.text ?? v.title);\n return null;\n };\n return Object.values(copy.props).map(line).filter((s): s is string => !!s);\n}\n","import { isPortableTextBlock, isPortableTextSpan, PT_TYPES, type PortableText, type PortableTextTable } from \"./types\";\nimport { migrateTable, tableGroupLayout } from \"./table\";\nimport { keyTakeawaysView } from \"./key-takeaways\";\nimport { componentCopyLines, detachedComponentCopy } from \"./component-copy\";\n\n/**\n * Portable Text → plain text.\n *\n * Replaces `lexicalJsonToText`, and is the one place the repo's oldest formatting trap gets\n * settled: joining spans with `\"\"` versus `\" \"`. Spans are runs WITHIN one paragraph, so they\n * join with nothing — inserting spaces would put a gap in the middle of a bolded word. Blocks\n * are separate paragraphs, so they join with a newline.\n */\nexport function portableTextToPlainText(value: PortableText | null | undefined): string {\n if (!Array.isArray(value)) return \"\";\n const lines: string[] = [];\n for (const node of value) {\n if (isPortableTextBlock(node)) {\n lines.push(node.children.map((c) => (isPortableTextSpan(c) ? c.text : \"\")).join(\"\"));\n continue;\n }\n if (node._type === PT_TYPES.code && typeof (node as Record<string, unknown>).code === \"string\") {\n lines.push((node as Record<string, unknown>).code as string);\n }\n /**\n * A TABLE is the exception, and it has to be: a reader sees every cell. Left out, the\n * corpus audit compared a table's stored text against a conversion that reported none and\n * called 104 sound values a text loss. Cells join with a space and rows with a newline, the\n * same shape `portableTextToHtml` gives them.\n *\n * Both spellings arrive here — the `table` and the pre-P1c `bcmsBlock` — because this runs\n * over values that never passed through the reader, the corpus audit's above all.\n * `migrateTable` answers null for everything else, so the cost of asking is one comparison.\n *\n * Every other embedded block — an image, an embed, a LINKED component — resolves against a\n * project this function does not have, so it still contributes nothing.\n */\n /**\n * A pull quote is words a reader sees — the quote, then who said it — so search and the\n * audit count them. The attribution line is the one the Markdown export writes.\n */\n if (node._type === PT_TYPES.pullQuote) {\n lines.push(...pullQuoteLines(node as Record<string, unknown>));\n continue;\n }\n /** Key takeaways are words a reader sees too: the title, then one line per point. */\n if (node._type === PT_TYPES.keyTakeaways) {\n const { title, points } = keyTakeawaysView(node);\n lines.push(...(title ? [title] : []), ...points);\n continue;\n }\n const table = migrateTable(node);\n if (table) {\n lines.push(...tableLines(table));\n continue;\n }\n // A separate copy of a component holds its own words (a linked one's live in the component).\n const copy = detachedComponentCopy(node);\n if (copy) {\n lines.push(...componentCopyLines(copy));\n continue;\n }\n // Dividers and other embedded blocks contribute no text, which is what a reader wants.\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * One line per row, cells joined with a space.\n *\n * A cell's own blocks go back through this function — a cell is a rich-text field, so a link or\n * a second paragraph inside one is text a reader sees — and its newlines flatten to spaces,\n * because a row is a LINE and a cell that broke it would make the audit's before/after\n * comparison depend on where a paragraph happened to fall.\n */\nconst tableLines = (table: PortableTextTable): string[] => {\n const rows = Array.isArray(table.rows) ? table.rows : [];\n const rowLine = (row: (typeof rows)[number]): string =>\n (Array.isArray(row?.cells) ? row.cells : [])\n .map((cell) => portableTextToPlainText(cell?.content).replace(/\\s+/g, \" \").trim())\n .join(\" \")\n .trim();\n /**\n * Caption, column-group labels, then the rows with each row group's label before its run, then\n * the note: the order the html draws them in. A table without them is one line per row, as\n * before they existed.\n */\n const layout = tableGroupLayout(table);\n const one = (s: unknown): string => (typeof s === \"string\" ? s.replace(/\\s+/g, \" \").trim() : \"\");\n const head = layout.rows.length ? rows.slice(0, rows.length - layout.rows.reduce((n, r) => n + r.rows.length, 0)) : rows;\n const lines = [\n one(table.caption),\n layout.columns.map((r) => one(r.label)).filter(Boolean).join(\" \"),\n ...head.map(rowLine),\n ...layout.rows.flatMap((run) => [...(run.label === undefined ? [] : [one(run.label)]), ...(run.rows as typeof rows).map(rowLine)]),\n one(table.notes),\n // footnotes, numbered as the html numbers them (V2 tableSearchText reads them too)\n ...(Array.isArray(table.footnotes) ? table.footnotes : []).map((f, i) => one(`[${i + 1}] ${typeof f?.text === \"string\" ? f.text : \"\"}`)),\n ];\n // Only the NEW lines are dropped when empty: an empty row has always contributed its \"\" line.\n const fnCount = Array.isArray(table.footnotes) ? table.footnotes.length : 0;\n const extras = new Set([0, 1, lines.length - 1 - fnCount]);\n return lines.filter((l, i) => l !== \"\" || !extras.has(i));\n};\n\n/** The quote, then `— name, role` when either is set. */\nconst pullQuoteLines = (v: Record<string, unknown>): string[] => {\n const text = (x: unknown): string => (typeof x === \"string\" ? x.trim() : \"\");\n const by = [text(v.name), text(v.role)].filter(Boolean).join(\", \");\n return [text(v.quote), ...(by ? [`— ${by}`] : [])].filter(Boolean);\n};\n","import { EMBEDDED_COMPONENT_KEY, PORTABLE_TEXT_FORMAT, PT_TYPES, type PortableText } from \"./types\";\nimport { lexicalToPortableText } from \"./from-lexical\";\nimport { migrateTable } from \"./table\";\nimport { detachedComponentCopy, type DetachedComponentCopy } from \"./component-copy\";\nimport { randomKey, type KeyGenerator } from \"./key\";\n\n/**\n * THE reader. One function, every stored shape, and it never throws.\n *\n * ── What this replaces ───────────────────────────────────────────────────────────────────\n * Five readers, each guessing at the same six shapes, each maintained by hand, each with its\n * own idea of what an unrecognised value meant:\n *\n * `RichTextField.toEditorContent` (dashboard, the dock)\n * `pageDocAdapter.richtextToEditorString`(dashboard, the canvas)\n * the `LexicalEditor` import path (dashboard, parse-then-fallback)\n * `lexicalJsonToText` (dashboard, plain text)\n * `renderRichtextValue` (dashboard, reflection)\n *\n * A comment in the codebase says they \"must agree on every shape or a field loads on one\n * surface and not the other\". They did not agree, five separate times, and the symptom each\n * time was a field that opened blank and then autosaved the blank over real content. Keeping\n * five copies in sync by hand was the bug; there is now one copy, and it lives in a package\n * both repos import.\n *\n * ── Never throws, by contract ────────────────────────────────────────────────────────────\n * This runs on every read during the dual-read window. A throw here is a blank field, and a\n * blank field is one autosave away from being permanent. Unrecognised input returns `[]` and\n * is reported through `onUnreadable`, never raised.\n */\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\nconst looksLikeHtml = (s: string): boolean => /<[a-z!/][\\s\\S]*>/i.test(s);\n\nexport interface ReadOptions {\n keyGenerator?: KeyGenerator;\n /** HTML ingest, injected: this module must stay free of any DOM dependency. */\n fromHtml?: (html: string) => PortableText;\n /** Called instead of throwing. The migration turns this into a quarantine report. */\n onUnreadable?: (reason: string, value: unknown) => void;\n}\n\n/**\n * Make a Portable Text array OURS: a `_key` on every node, `markDefs` on every block.\n *\n * Values reach this from two places that predate the spec — a bare array persisted before\n * `_key` was minted, and the hand-rolled `{version:1, content}` tree the `ui` package's old\n * renderer emitted. Both are structurally Portable Text and neither carries markDefs, so\n * `@portabletext/react` would treat a block without them as malformed. Filling them in is\n * cheaper than a migration and makes every historical value renderable today.\n *\n * A TABLE is migrated here too, and that is the whole of the P1c read path: every table stored\n * before P1c is a generic `bcmsBlock` holding a string matrix, and every one of them comes back\n * out as a `table`. Nothing rewrites the database — the shape a reader sees is settled on the\n * way out, so a value converted years ago and a value pasted this morning are the same thing to\n * everything downstream.\n */\nexport function normalizePortableText(value: unknown, key: KeyGenerator = randomKey): PortableText {\n if (!Array.isArray(value)) return [];\n return value.flatMap((raw) => {\n if (!isRecord(raw) || typeof raw._type !== \"string\") return [];\n // Recursing through this same function is what gives a cell everything a block has: keys,\n // markDefs, and a nested table in the rare cell that holds one.\n const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));\n if (table) return [table as never];\n const node: Record<string, unknown> = { ...raw, _key: isRecord(raw) && typeof raw._key === \"string\" && raw._key ? raw._key : key() };\n if (node._type === \"block\") {\n if (!Array.isArray(node.markDefs)) node.markDefs = [];\n if (typeof node.style !== \"string\") node.style = \"normal\";\n node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {\n if (!isRecord(c)) return c;\n const child: Record<string, unknown> = { ...c, _key: typeof c._key === \"string\" && c._key ? c._key : key() };\n if (child._type === \"span\" && !Array.isArray(child.marks)) child.marks = [];\n return child;\n });\n }\n return [node as never];\n });\n}\n\nfunction plainBlocks(text: string, key: KeyGenerator): PortableText {\n if (text === \"\") return [];\n return text.split(\"\\n\").map((line) => ({\n _type: \"block\" as const,\n _key: key(),\n style: \"normal\",\n markDefs: [],\n children: [{ _type: \"span\" as const, _key: key(), text: line, marks: [] }],\n }));\n}\n\nfunction fromString(raw: string, key: KeyGenerator, opts: ReadOptions): PortableText {\n const trimmed = raw.trim();\n if (trimmed === \"\") return [];\n\n // A serialized Lexical state arrives as a JSON STRING, which is why this is tried before\n // the HTML sniff — `{\"root\":…}` contains no angle brackets but is not plain text either.\n if (trimmed.startsWith(\"{\")) {\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (isRecord(parsed) && isRecord(parsed.root)) {\n return lexicalToPortableText(parsed, { keyGenerator: key }).value;\n }\n } catch {\n // Not JSON after all. Fall through: it is text that happens to start with a brace.\n }\n }\n\n if (looksLikeHtml(trimmed) && opts.fromHtml) return opts.fromHtml(raw);\n\n // A bare string is LITERAL. It is NOT run through a markdown parser, because that is how\n // a line beginning \"#\" silently became a heading in copy the author never marked up.\n return plainBlocks(raw, key);\n}\n\n/**\n * Read any stored rich-text value as Portable Text.\n *\n * Shapes handled, in the order they are tested:\n * 1. `null` / `\"\"` → []\n * 2. `PortableText[]` → as-is\n * 3. `{format:\"portable-text-1\", value}` → `.value`\n * 4. `{format:\"lexical-…\", value:{root}}` → converted\n * 5. `{root:…}` bare Lexical state → converted\n * 6. `{format:\"bettercms-notion-doc\", blocks}` → walked, recursing per block\n * 7. `{html:\"…\"}` pre-envelope seed → ingested\n * 8. `\"{\\\"root\\\":…}\"` Lexical JSON string → parsed, converted\n * 9. `\"<p>…</p>\"` bare HTML → ingested\n * 10. `\"plain text\"` → one block per line, literal\n */\nexport function toPortableText(value: unknown, opts: ReadOptions = {}): PortableText {\n const key = opts.keyGenerator ?? randomKey;\n\n if (value == null || value === \"\") return [];\n if (Array.isArray(value)) return normalizePortableText(value, key);\n if (typeof value === \"string\") return fromString(value, key, opts);\n\n if (!isRecord(value)) {\n opts.onUnreadable?.(`unsupported ${typeof value}`, value);\n return [];\n }\n\n if (value.format === PORTABLE_TEXT_FORMAT && Array.isArray(value.value)) {\n return normalizePortableText(value.value, key);\n }\n\n // The hand-rolled `{version:1, content}` tree the old `ui` renderer emitted. Structurally\n // Portable Text, missing only `_key` and `markDefs`, so normalising is the whole conversion.\n if (value.version === 1 && Array.isArray(value.content)) {\n return normalizePortableText(value.content, key);\n }\n\n if (Array.isArray(value.blocks)) return fromNotionDoc(value.blocks, key, opts);\n\n // A Lexical envelope carries the state under `value`; a bare state IS the state.\n if (isRecord(value.value) && isRecord((value.value as Record<string, unknown>).root)) {\n return lexicalToPortableText(value.value, { keyGenerator: key }).value;\n }\n if (isRecord(value.root)) return lexicalToPortableText(value, { keyGenerator: key }).value;\n\n // The pre-envelope seed. Last, so a real envelope is never mistaken for one.\n if (typeof value.html === \"string\") return fromString(value.html, key, opts);\n\n opts.onUnreadable?.(\"unrecognised object shape\", value);\n return [];\n}\n\n/**\n * The `document` / Body format — `{format:\"bettercms-notion-doc\", version:1, blocks}`.\n *\n * Its blocks are heterogeneous and one of them (`richtext`) nests a whole rich-text value of\n * its own, so this recurses through `toPortableText` rather than duplicating the ladder. That\n * recursion is the point: a Body containing a rich-text block containing a link now produces\n * real markDefs, which the old renderer could not express at any depth.\n */\nfunction fromNotionDoc(blocks: unknown[], key: KeyGenerator, opts: ReadOptions): PortableText {\n const out: PortableText = [];\n for (const raw of blocks) {\n if (!isRecord(raw)) continue;\n const type = typeof raw.type === \"string\" ? raw.type : \"\";\n\n if (type === \"paragraph\") {\n out.push(...plainBlocks(typeof raw.text === \"string\" ? raw.text : \"\", key));\n continue;\n }\n if (type === \"richtext\") {\n out.push(...toPortableText(raw.value, { ...opts, keyGenerator: key }));\n continue;\n }\n // `field`, `component` and `reference` blocks reference something that has to be RESOLVED\n // against the project before it renders. They keep their whole payload in a declared\n // block object so the resolution can happen at publish time, where the project is known.\n out.push({\n _type: PT_TYPES.block,\n _key: key(),\n schemaKey: type || \"block\",\n schemaLabel: typeof raw.label === \"string\" ? raw.label : \"\",\n fields: {\n ...(raw.value !== undefined ? { value: raw.value } : {}),\n ...(typeof raw.fieldType === \"string\" ? { fieldType: raw.fieldType } : {}),\n },\n });\n }\n return out;\n}\n\n/**\n * The structured value on a block's props, or null when there is none.\n *\n * Every delivery lane asks this same question — \"is there structured content, or do I fall\n * back to the html cache?\" — and each one answering it locally is how the lanes drifted in the\n * first place: the hosted renderer preferred `content`, the shared React renderer preferred\n * `content`, and Next and Astro did not look for it at all. One implementation, four callers.\n *\n * Accepts every spelling in the wild: a bare array, `{value}` (the new envelope), and\n * `{content}` (the legacy hand-rolled tree). EMPTY is treated as absent on purpose — an empty\n * array must fall back to `html` rather than render a blank region.\n */\nexport function readStructuredContent(props: unknown): PortableText | null {\n if (!isRecord(props)) return null;\n const candidate = props.content ?? props.value;\n const arr = Array.isArray(candidate)\n ? candidate\n : isRecord(candidate)\n ? Array.isArray(candidate.content)\n ? candidate.content\n : Array.isArray(candidate.value)\n ? candidate.value\n : null\n : null;\n return arr && arr.length > 0 ? (arr as PortableText) : null;\n}\n\n/** The `component` block an embed in rich text stands for. */\nexport interface EmbeddedComponentBlock {\n type: \"component\";\n /** The node's `_key` — unique within its rich-text field only. */\n id: string;\n props: { componentId: string; overrides: Record<string, unknown> };\n}\n\n/**\n * A component embedded in rich text, as the ordinary `component` block every renderer already\n * draws — so an embed gets the same resolver, cycle guard and identity attributes as a placed\n * instance. Null for any other node, and for a separate copy (`fields.detached`): a copy is not\n * a reference, and resolving its `componentId` would draw the shared component it left.\n * `embeddedComponentRender` answers for both.\n */\nexport function embeddedComponentBlock(value: unknown): EmbeddedComponentBlock | null {\n if (!isRecord(value) || value.schemaKey !== EMBEDDED_COMPONENT_KEY || !isRecord(value.fields)) return null;\n if (value.fields.detached !== undefined) return null;\n const { componentId, overrides } = value.fields;\n return {\n type: \"component\",\n id: typeof value._key === \"string\" ? value._key : \"\",\n props: { componentId: String(componentId ?? \"\"), overrides: isRecord(overrides) ? overrides : {} },\n };\n}\n\n/**\n * What an embedded component renders as: a linked one, its `component` block (resolved by the\n * host); a separate copy, its own frozen blocks, which need no lookup. Null for any other node,\n * and for a copy whose blocks are missing (the write refuses one; a reader draws nothing for it).\n */\nexport function embeddedComponentRender(\n value: unknown,\n): { kind: \"linked\"; block: EmbeddedComponentBlock } | { kind: \"copy\"; copy: DetachedComponentCopy } | null {\n const block = embeddedComponentBlock(value);\n if (block) return { kind: \"linked\", block };\n const copy = detachedComponentCopy(value);\n return copy ? { kind: \"copy\", copy } : null;\n}\n","import type { PortableTextDocument, TextProps } from \"@bettercms-ai/types\";\nimport { PortableText } from \"./portableText\";\nimport { readStructuredContent } from \"@bettercms-ai/richtext\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface TextBlockProps extends TextProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n /** Portable Text source. WINS over `html`, which is the derived cache. */\n content?: PortableTextDocument | unknown[];\n}\n\nexport function Text({\n html,\n content,\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: TextBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const cls = `text-muted-foreground leading-relaxed ${alignClass} ${className}`;\n\n /**\n * 🔴 THE BINDING USED TO BE DROPPED HERE, and that made Portable Text uneditable.\n *\n * This was two mutually exclusive `return`s: the structured branch rendered `<PortableText>`\n * in a plain `<div>`, and `data-bcms-field` / `data-bcms-kind` were spread ONLY on the HTML\n * fallback below it. So the moment a block carried structured content it lost every editable\n * address — and `block-bindings.ts` suppressed `html` in the same case, leaving the block\n * with none at all. Structured content, the thing that exists to make rich text MORE\n * editable, made it completely uneditable.\n *\n * One container now, always carrying the binding; only the CHILDREN switch. And when\n * structured content is present the binding gets richer rather than poorer: `PortableText`\n * stamps a per-block address off each `_key`, so a click lands on one paragraph instead of\n * the whole field.\n */\n const structured = readStructuredContent({ content });\n\n return (\n <div\n className={cls}\n {...(structured ? {} : { \"data-bcms-field\": field, \"data-bcms-kind\": kind })}\n >\n {structured ? (\n <PortableText value={structured} bindingBase={field} />\n ) : (\n // eslint-disable-next-line react/no-danger\n <span dangerouslySetInnerHTML={{ __html: html }} />\n )}\n </div>\n );\n}\n\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import type { ImageProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface ImageBlockProps extends ImageProps, BcmsBindingProps {\n className?: string;\n sizes?: string;\n}\n\nexport function Image({\n src,\n alt,\n width,\n height,\n caption,\n className = \"\",\n sizes,\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: ImageBlockProps) {\n // A set width is the author's size for this image and must win over `w-full`; it stays a\n // ceiling (max-width) so a narrow column still shrinks it, and height follows the aspect ratio.\n const sized = typeof width === \"number\" && width > 0;\n return (\n <figure className={cn(sized ? \"max-w-full\" : \"w-full\", className)}>\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n data-bcms-field={field}\n data-bcms-kind={kind}\n style={sized ? { width: `${width}px`, maxWidth: \"100%\", height: \"auto\" } : undefined}\n className={cn(!sized && \"w-full\", \"h-auto rounded-md object-cover\")}\n loading=\"lazy\"\n />\n {caption && (\n <figcaption className=\"mt-2 text-sm text-muted-foreground text-center italic\">\n {caption}\n </figcaption>\n )}\n </figure>\n );\n}\n","import type { CSSProperties } from \"react\";\nimport type { ButtonProps, ButtonVariant } from \"@bettercms-ai/types\";\nimport { LINK_RELS, buttonIconOf, buttonIconSvg, buttonVariantOf, isScriptHref } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface ButtonBlockProps extends ButtonProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n /** The block style's paint (`splitButtonStyle(style).paint`), on the anchor itself. */\n style?: CSSProperties;\n}\n\nexport function Button({\n text,\n href,\n variant = \"primary\",\n size = \"md\",\n fullWidth = false,\n arrow = false,\n icon: iconProp,\n newTab = false,\n rel,\n action,\n align = \"left\",\n className = \"\",\n style,\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: ButtonBlockProps) {\n const baseStyles =\n \"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\";\n\n const variantStyles: Record<ButtonVariant, string> = {\n primary:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n tertiary: \"bg-transparent text-primary hover:bg-primary/10\",\n brand: \"bg-accent text-accent-foreground shadow hover:bg-accent/90\",\n dark: \"bg-foreground text-background shadow hover:bg-foreground/90\",\n light: \"border bg-background text-foreground shadow-sm hover:bg-muted\",\n outline: \"border border-current bg-transparent text-primary hover:bg-primary/10\",\n link: \"h-auto bg-transparent p-0 text-primary underline underline-offset-4\",\n };\n const icon = buttonIconOf(iconProp);\n\n const alignClass = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n }[align];\n\n // The hosted renderer's S/M/L (6px 14px / 10px 20px / 14px 28px) in this package's Tailwind\n // scale — approximate by design: M stays the historical `px-4 py-2`. Props arrive spread\n // from stored block JSON, so an unknown size falls back to M, as it does on every renderer.\n const sizeStyles = {\n sm: \"px-3 py-1.5 text-xs\",\n md: \"px-4 py-2\",\n lg: \"px-6 py-3 text-base\",\n };\n const sizeClass = size === \"sm\" || size === \"lg\" ? sizeStyles[size] : sizeStyles.md;\n // Full width is a block-level box across the column, label centred (`bcms-btn-full`).\n const fullClass = fullWidth === true ? \"flex w-full justify-center\" : \"\";\n\n // The hosted renderer's anchorTargetRel, as attributes: a new tab keeps `noopener noreferrer`,\n // a link relation (nofollow, sponsored, ugc) joins it in the same rel.\n const relTokens = [...(newTab === true ? [\"noopener\", \"noreferrer\"] : []), ...(LINK_RELS.includes(rel as (typeof LINK_RELS)[number]) ? [rel as string] : [])];\n\n // The site's script owns the click: a `<button type=\"button\">`, never an anchor with an invented href.\n if (action === \"code\") {\n return (\n <button type=\"button\" data-bcms-field={field} data-bcms-kind={kind} style={style} className={cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className)}>\n {icon && <span className=\"mr-2 inline-flex\" dangerouslySetInnerHTML={{ __html: buttonIconSvg(icon, \"bcms-btn-icon\") }} />}\n {text}\n {arrow === true && <span className=\"ml-2 inline-flex\" dangerouslySetInnerHTML={{ __html: buttonIconSvg(\"arrow-right\", \"bcms-btn-arrow\") }} />}\n </button>\n );\n }\n return (\n <a\n // Block props arrive from stored JSON and instance overrides: a script href renders as \"#\".\n href={typeof href === \"string\" && isScriptHref(href) ? \"#\" : href}\n {...(newTab === true ? { target: \"_blank\" } : {})}\n {...(relTokens.length ? { rel: relTokens.join(\" \") } : {})}\n data-bcms-field={field}\n data-bcms-kind={kind}\n style={style}\n className={cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className)}\n >\n {icon && <span className=\"mr-2 inline-flex\" dangerouslySetInnerHTML={{ __html: buttonIconSvg(icon, \"bcms-btn-icon\") }} />}\n {text}\n {arrow === true && <span className=\"ml-2 inline-flex\" dangerouslySetInnerHTML={{ __html: buttonIconSvg(\"arrow-right\", \"bcms-btn-arrow\") }} />}\n </a>\n );\n}\n","import type { SpacerProps } from \"@bettercms-ai/types\";\n\ninterface SpacerBlockProps extends SpacerProps {\n className?: string;\n}\n\nexport function Spacer({ height = 32, className = \"\" }: SpacerBlockProps) {\n return (\n <div\n className={`w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`}\n style={{ height: `${height}px` }}\n aria-hidden=\"true\"\n >\n <span className=\"text-xs text-muted-foreground/40 select-none\">\n {height}px\n </span>\n </div>\n );\n}\n","import type { ColumnsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface ColumnsBlockProps extends ColumnsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Columns({\n columns,\n gap = 16,\n className = \"\",\n renderBlock,\n}: ColumnsBlockProps) {\n return (\n <div\n className={cn(\"grid w-full\", className)}\n style={{\n gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,\n gap: `${gap}px`,\n }}\n >\n {columns.map((columnBlocks, colIndex) => (\n <div key={colIndex} className=\"flex flex-col gap-4 min-w-0\">\n {columnBlocks.map((block) => (\n <span key={block.id}>\n {renderBlock ? renderBlock(block) : null}\n </span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { VideoProps } from \"@bettercms-ai/types\";\nimport { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\ninterface VideoBlockProps extends VideoProps {\n className?: string;\n}\n\nfunction getEmbedUrl(url: string): { embedUrl: string; provider: \"youtube\" | \"vimeo\" | \"unknown\" } {\n // YouTube\n const ytMatch = url.match(\n /(?:youtube\\.com\\/(?:watch\\?v=|embed\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/\n );\n if (ytMatch) {\n return {\n embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,\n provider: \"youtube\",\n };\n }\n\n // Vimeo\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/);\n if (vimeoMatch) {\n return {\n embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}`,\n provider: \"vimeo\",\n };\n }\n\n return { embedUrl: url, provider: \"unknown\" };\n}\n\nexport function Video({\n url,\n poster,\n autoplay = false,\n loop = false,\n className = \"\",\n}: VideoBlockProps) {\n const [isLoaded, setIsLoaded] = useState(false);\n const { embedUrl, provider } = getEmbedUrl(url);\n\n // If not a recognized embed, render a native video tag\n if (provider === \"unknown\") {\n return (\n <video\n src={url}\n poster={poster}\n autoPlay={autoplay}\n loop={loop}\n controls\n className={className}\n playsInline\n />\n );\n }\n\n return (\n <div className={cn(\"w-full overflow-hidden rounded-md bg-black aspect-video\", className)}>\n {!isLoaded && poster && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={poster}\n alt=\"Video thumbnail\"\n className=\"w-full h-full object-cover absolute inset-0\"\n />\n )}\n <iframe\n src={`${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n loading=\"lazy\"\n onLoad={() => setIsLoaded(true)}\n className=\"w-full h-full\"\n />\n </div>\n );\n}\n","import type { SectionProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface SectionBlockProps extends SectionProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Section({\n children,\n background,\n paddingY,\n maxWidth,\n className = \"\",\n renderBlock,\n}: SectionBlockProps) {\n const inner = (children ?? []).map((block) => (\n <span key={block.id}>{renderBlock ? renderBlock(block) : null}</span>\n ));\n return (\n <section\n className={cn(\"w-full\", className)}\n style={{ background, paddingTop: paddingY, paddingBottom: paddingY }}\n >\n {maxWidth ? <div style={{ maxWidth, marginInline: \"auto\" }}>{inner}</div> : inner}\n </section>\n );\n}\n","import type { NavbarProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface NavbarBlockProps extends NavbarProps {\n className?: string;\n}\n\nexport function Navbar({ logo, links, cta, className = \"\" }: NavbarBlockProps) {\n return (\n <nav className={cn(\"flex items-center gap-6 py-4\", className)}>\n {logo?.src ? (\n <a href={logo.href ?? \"/\"} className=\"shrink-0\">\n <img src={logo.src} alt={logo.alt} className=\"h-8 w-auto\" />\n </a>\n ) : null}\n <div className=\"flex flex-1 gap-5\">\n {(links ?? []).map((l, i) => (\n <a key={i} href={l.href} className=\"text-inherit no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n {cta ? (\n <a\n href={cta.href}\n className=\"rounded-lg bg-black px-4 py-2 font-semibold text-white no-underline\"\n >\n {cta.text}\n </a>\n ) : null}\n </nav>\n );\n}\n","import type { FooterProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface FooterBlockProps extends FooterProps {\n className?: string;\n}\n\nexport function Footer({ columns, copyright, className = \"\" }: FooterBlockProps) {\n return (\n <footer className={cn(\"mt-12 border-t pt-8\", className)}>\n <div className=\"flex flex-wrap gap-12\">\n {(columns ?? []).map((col, i) => (\n <div key={i} className=\"flex flex-col gap-1.5\">\n {col.heading ? <h4 className=\"mb-2 font-semibold\">{col.heading}</h4> : null}\n {(col.links ?? []).map((l, j) => (\n <a key={j} href={l.href} className=\"text-neutral-500 no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n ))}\n </div>\n {copyright ? <p className=\"mt-6 text-sm text-neutral-500\">{copyright}</p> : null}\n </footer>\n );\n}\n","\"use client\";\n\nimport type { SliderProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface SliderBlockProps extends SliderProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Slider({ slides, className = \"\", renderBlock }: SliderBlockProps) {\n const [i, setI] = useState(0);\n const list = slides ?? [];\n const go = (n: number) => setI((n + list.length) % (list.length || 1));\n const btn =\n \"absolute top-1/2 -translate-y-1/2 flex h-9 w-9 items-center justify-center rounded-full border-0 bg-black/50 text-white cursor-pointer\";\n return (\n <div className={cn(\"relative w-full\", className)}>\n {list.map((s, idx) => (\n <div key={s.id} hidden={idx !== i} className=\"flex flex-col gap-4\">\n {(s.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n <button type=\"button\" aria-label=\"Previous\" onClick={() => go(i - 1)} className={cn(btn, \"left-2\")}>\n &#8249;\n </button>\n <button type=\"button\" aria-label=\"Next\" onClick={() => go(i + 1)} className={cn(btn, \"right-2\")}>\n &#8250;\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport type { TabsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface TabsBlockProps extends TabsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Tabs({ tabs, className = \"\", renderBlock }: TabsBlockProps) {\n const [active, setActive] = useState(0);\n const list = tabs ?? [];\n return (\n <div className={cn(\"w-full\", className)}>\n <div role=\"tablist\" className=\"mb-4 flex gap-1 border-b\">\n {list.map((t, idx) => (\n <button\n key={t.id}\n role=\"tab\"\n type=\"button\"\n aria-selected={idx === active}\n onClick={() => setActive(idx)}\n className={cn(\n \"cursor-pointer border-b-2 border-transparent bg-transparent px-4 py-2\",\n idx === active && \"border-black font-semibold\",\n )}\n >\n {t.label}\n </button>\n ))}\n </div>\n {list.map((t, idx) => (\n <div key={t.id} role=\"tabpanel\" hidden={idx !== active} className=\"flex flex-col gap-4\">\n {(t.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { ComponentProps as BcmsComponentProps, FormProps, CollectionProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\nconst box =\n \"rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500\";\n\n/**\n * Builder-preview placeholder for a `component` instance. Live instance resolution\n * (definition lookup + override merge) happens server-side in the delivery renderer\n * and in the page-builder, which pass a resolver — the standalone library preview\n * just shows what's referenced.\n */\nexport function ComponentInstance({\n componentId,\n className = \"\",\n}: BcmsComponentProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Component instance: <code>{componentId}</code>\n </div>\n );\n}\n\n/** Builder-preview placeholder for a `form` block (rendered live by @bettercms-ai/next). */\nexport function FormPlaceholder({ formId, className = \"\" }: FormProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Form: <code>{formId}</code>\n </div>\n );\n}\n\n/**\n * Builder-preview placeholder for a `collection` block.\n *\n * The builder canvas has no entries to list — the list is fetched per request by the site\n * renderer, deliberately, so that a published snapshot can never bake in a stale set of\n * posts. A placeholder is therefore the honest render here, not a limitation.\n */\nexport function CollectionPlaceholder({\n cardComponentId,\n className = \"\",\n}: CollectionProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Collection: entries of this page\n {cardComponentId ? <> as <code>{cardComponentId}</code></> : <> (default card)</>}\n </div>\n );\n}\n","import type { ContentBlock, BlockType } from \"@bettercms-ai/types\";\nimport { Heading } from \"./blocks/Heading\";\nimport { Text } from \"./blocks/Text\";\nimport { Image } from \"./blocks/Image\";\nimport { Button } from \"./blocks/Button\";\nimport { Spacer } from \"./blocks/Spacer\";\nimport { Columns } from \"./blocks/Columns\";\nimport { Video } from \"./blocks/Video\";\nimport { Section } from \"./blocks/Section\";\nimport { Navbar } from \"./blocks/Navbar\";\nimport { Footer } from \"./blocks/Footer\";\nimport { Slider } from \"./blocks/Slider\";\nimport { Tabs } from \"./blocks/Tabs\";\nimport { ComponentInstance, FormPlaceholder, CollectionPlaceholder } from \"./blocks/Placeholders\";\n\n/**\n * Block registry entry — maps a block type to its React component\n * and metadata for the block library UI.\n */\nexport interface BlockRegistryEntry {\n component: React.ComponentType<Record<string, unknown>>;\n label: string;\n icon: string;\n}\n\nexport const BLOCK_REGISTRY: Record<BlockType, BlockRegistryEntry> = {\n // The entries of the page this block sits on. No preview here by design: the list is\n // fetched per request by the site renderer, so the builder has nothing to show.\n collection: {\n component: CollectionPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Collection\",\n icon: \"list\",\n },\n heading: {\n component: Heading as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Heading\",\n icon: \"type\",\n },\n text: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Text\",\n icon: \"align-left\",\n },\n // Same payload, same renderer — deliberately. `richtext` differs from `text` only in how\n // an author EDITS it (a formatting surface vs a plain field); at delivery both are an HTML\n // string, so duplicating the component would be two things to keep in sync for no gain.\n richtext: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Rich text\",\n icon: \"pilcrow\",\n },\n image: {\n component: Image as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Image\",\n icon: \"image\",\n },\n button: {\n component: Button as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Button\",\n icon: \"square\",\n },\n spacer: {\n component: Spacer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Spacer\",\n icon: \"minus\",\n },\n columns: {\n component: Columns as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Columns\",\n icon: \"columns\",\n },\n video: {\n component: Video as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Video\",\n icon: \"play\",\n },\n section: {\n component: Section as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Section\",\n icon: \"layout\",\n },\n navbar: {\n component: Navbar as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Navbar\",\n icon: \"menu\",\n },\n footer: {\n component: Footer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Footer\",\n icon: \"panel-bottom\",\n },\n slider: {\n component: Slider as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Slider\",\n icon: \"gallery-horizontal\",\n },\n tabs: {\n component: Tabs as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Tabs\",\n icon: \"folder\",\n },\n component: {\n component: ComponentInstance as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Component\",\n icon: \"component\",\n },\n form: {\n component: FormPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Form\",\n icon: \"clipboard-list\",\n },\n} as const;\n\nexport const BLOCK_TYPES = Object.keys(BLOCK_REGISTRY) as BlockType[];\n\nexport type { ContentBlock, BlockType };\n"],"mappings":"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","names":["React","jsx","spanOf","align","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","jsx","jsxs","React","span","jsx","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsxs","useState","jsx","jsxs","useState","jsx","jsxs","Fragment","jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/blocks/Heading.tsx","../src/blocks/portableText.tsx","../src/renderer.tsx","../src/blockStyle.ts","../../richtext/src/types.ts","../../richtext/src/styled.ts","../../richtext/src/href.ts","../../richtext/src/key.ts","../../richtext/src/table.ts","../../richtext/src/key-takeaways.ts","../../richtext/src/image-layout.ts","../../richtext/src/gallery.ts","../../richtext/src/columns.ts","../../richtext/src/component-copy.ts","../../richtext/src/plain.ts","../../richtext/src/toc.ts","../../richtext/src/footnote.ts","../../richtext/src/references.ts","../../richtext/src/read.ts","../../richtext/src/video.ts","../../richtext/src/embed.ts","../src/blocks/Text.tsx","../src/lib/utils.ts","../src/blocks/Image.tsx","../src/blocks/Button.tsx","../src/blocks/Spacer.tsx","../src/blocks/Columns.tsx","../src/blocks/Video.tsx","../src/blocks/Section.tsx","../src/blocks/Navbar.tsx","../src/blocks/Footer.tsx","../src/blocks/Slider.tsx","../src/blocks/Tabs.tsx","../src/blocks/Placeholders.tsx","../src/registry.ts"],"sourcesContent":["import type { HeadingProps } from \"@bettercms-ai/types\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface HeadingBlockProps extends HeadingProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Heading({\n text,\n level = 2,\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: HeadingBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const classNames = `font-heading font-bold tracking-tight text-foreground ${alignClass} ${className}`;\n\n switch (level) {\n case 1:\n return <h1 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h1>;\n case 2:\n return <h2 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h2>;\n case 3:\n return <h3 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h3>;\n default:\n return <h2 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h2>;\n }\n}\n","import React from \"react\";\nimport { PortableText as PT, type PortableTextComponents } from \"@portabletext/react\";\nimport type { ContentBlock, RichTextResolved } from \"@bettercms-ai/types\";\nimport { BlocksRenderer } from \"../renderer\";\nimport {\n PT_TYPES,\n pullQuoteHref,\n keyTakeawaysView,\n safeHref,\n isStyledTag,\n styledAttrEntries,\n normalizePortableText,\n tableGroupLayout,\n tableSections,\n highlightedColumns,\n sortableColumns,\n tableColumns,\n isTypedTable,\n typedCellView,\n jsonLdOf,\n designOf,\n footnotesOf,\n footnoteNumbers,\n portableTextToPlainText,\n detachedComponentCopy,\n resolveEmbed,\n isEmbedAspect,\n galleryView,\n GALLERY_STYLES,\n outlineOf,\n footnoteOrder,\n columnsView,\n COLUMNS_STYLES,\n relatedPostsView,\n authorBioView,\n tocTree,\n tocView,\n type OutlineItem,\n type EmbedCard,\n type EmbedRender,\n type PortableText as PtValue,\n type PortableTextTable,\n type PortableTextTableRow,\n} from \"@bettercms-ai/richtext\";\n\n/**\n * Portable Text → React, on the official renderer.\n *\n * REPLACES A 66-LINE HAND-ROLLED WALKER that could not express a hyperlink. It had no notion\n * of `markDefs`, so a link had nowhere to store its href; it also DROPPED unknown marks and\n * unknown styles silently, meaning content written by a newer editor disappeared here with no\n * error. `@portabletext/react` handles both: annotations resolve through `markDefs`, and\n * `unknownMark`/`unknownType` render through instead of swallowing.\n *\n * ── Why v6 and not v8 ────────────────────────────────────────────────────────────────────\n * `@portabletext/react@6.2.0` peers `react ^18.2 || ^19`. v7 and v8 require `react ^19`, and\n * this package peers `react >=18` — adopting them would force a breaking peer bump on every\n * customer of `@bettercms-ai/ui` and `@bettercms-ai/next` for no rendering benefit.\n *\n * ── The binding, which is the point of the whole migration ───────────────────────────────\n * Every top-level node carries `data-bcms-field=\"<blockId>__<prop>#<_key>\"`. That suffix is\n * what makes ONE PARAGRAPH inside a rich-text field addressable, instead of the field being a\n * single opaque string with one binding on the outside. `_key` and not an index, because a\n * list is edited continuously and an index-addressed binding silently retargets a different\n * paragraph the moment someone inserts one above it.\n */\n\nconst TOKEN = /^[a-z0-9-]+$/i;\n\n/**\n * `style=\"color:red\"` → `{ color: \"red\" }`, because React refuses the string form outright\n * (\"The `style` prop expects a mapping from style properties to values, not a string\") — it\n * THROWS rather than degrading, so a styled run carrying a style attribute would blank the\n * page it is on. The HTML serialiser writes the same declaration back out verbatim; this is\n * only the shape React demands to write it.\n *\n * Custom properties keep their `--` spelling (React sets those through `setProperty`);\n * everything else is camelised, which is the only name React recognises.\n */\nfunction styleObject(css: string): React.CSSProperties {\n const out: Record<string, string> = {};\n for (const declaration of css.split(\";\")) {\n const at = declaration.indexOf(\":\");\n if (at < 0) continue;\n const name = declaration.slice(0, at).trim();\n const value = declaration.slice(at + 1).trim();\n if (!name || !value) continue;\n out[name.startsWith(\"--\") ? name : name.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())] = value;\n }\n return out as React.CSSProperties;\n}\n\nexport interface PortableTextProps {\n value: unknown;\n /** `<blockId>__<prop>`. Present ⇒ stamp a per-block address. Absent ⇒ published output. */\n bindingBase?: string;\n /**\n * Renders an embedded block this renderer cannot resolve (a component — it has no project).\n * Anything but `undefined` replaces the marker; `null` renders nothing.\n */\n renderBlock?: (value: Record<string, unknown>) => React.ReactNode;\n /**\n * RT3: the X cards fetched for this render (by canonical url), as `portableTextToHtml`'s\n * `embeds`. Absent: every X post is a link card.\n */\n embeds?: EmbedRender;\n /**\n * RT8: the entries this value's author bios and related posts point at, resolved by the caller\n * when the page is produced (never stored), as `portableTextToHtml`'s `resolved`. Absent: each\n * block is the placeholder aside the stored html holds.\n */\n resolved?: RichTextResolved;\n}\n\n/** Per-block wrapper: the element the editor selects when someone clicks this paragraph. */\nfunction Bound({\n base,\n nodeKey,\n children,\n}: {\n base: string | undefined;\n nodeKey: string | undefined;\n children: React.ReactNode;\n}) {\n if (!base || !nodeKey) return <>{children}</>;\n return (\n <div data-bcms-field={`${base}#${nodeKey}`} data-bcms-kind=\"richtext\">\n {children}\n </div>\n );\n}\n\n/**\n * A table, drawn from the value rather than from a marker.\n *\n * A cell holds BLOCKS, so it renders through this same component set — a link or a bolded word\n * inside a cell is content, and a cell that rendered only text would drop it. `thead` is the\n * LEADING RUN of `head` rows and `th` follows the cell's own `header`, matching\n * `portableTextToHtml` exactly: two renderers of one value that disagree is the whole bug class\n * this package exists to close.\n */\nfunction Table({ value }: { value: PortableTextTable }) {\n const components = React.useMemo(buildComponents, []);\n /**\n * A LONE paragraph in a cell is unwrapped, and only a lone one.\n *\n * `<td>Pro</td>` is what a cell of one line looks like in every table ever authored, and\n * `portableTextToHtml` unwraps it for exactly that reason — the html it emits is compared\n * against stored markup. Two renderers of one value that disagree about a `<p>` is the drift\n * this package exists to end, so the rule is spelled the same way here. A cell holding two\n * blocks keeps both paragraphs, in both renderers.\n */\n const inline = React.useMemo(\n () => ({ ...components, block: { normal: ({ children }: { children?: React.ReactNode }) => <>{children}</> } }),\n [components],\n );\n const rows = Array.isArray(value?.rows) ? value.rows : [];\n if (rows.length === 0) return null;\n\n // \"Highlight this column\": the columns a first-row cell marks, as `portableTextToHtml` stamps them.\n const marked = highlightedColumns(value);\n // \"Visitors can sort columns\": `<col data-sortable=\"true\">` per sortable column, as `portableTextToHtml` writes it.\n const sortable = sortableColumns(value);\n // \"On phones\" (stack / cards): V2's explicit ARIA roles (withTableRoles) and each td's column label, as\n // `portableTextToHtml` writes them; \"Keep column names in view\" is the figure's data-sticky.\n const design = designOf(value.design);\n const stacked = design?.responsive === \"stack\" || design?.responsive === \"cards\";\n const role = (r: string) => (stacked ? { role: r } : {});\n const labels: string[] = [];\n if (stacked) {\n const headRows = tableSections(value).head as PortableTextTableRow[];\n let at = 0;\n for (const c of Array.isArray(headRows[headRows.length - 1]?.cells) ? headRows[headRows.length - 1].cells : []) {\n const text = portableTextToPlainText(Array.isArray(c?.content) ? c.content : []).replace(/\\s+/g, \" \").trim();\n const w = typeof c?.span === \"number\" && c.span > 1 ? c.span : 1;\n for (let i = 0; i < w; i += 1) labels[at + i] = text;\n at += w;\n }\n }\n // The V2 table's typed columns (a first-row cell's `column`): the same markup `portableTextToHtml` writes (typedCellView).\n const columns = tableColumns(value);\n const typed = isTypedTable(value);\n const headSet = new Set<unknown>(tableSections(value).head);\n const props = (list: [string, string][]) => Object.fromEntries(list);\n // Footnotes: a cell's data-note and numbered marker, and V2's notes block under the table, as `portableTextToHtml` writes them\n const fnNumber = footnoteNumbers(value);\n const footnotes = footnotesOf(value.footnotes) ?? [];\n const tableId = typeof value._key === \"string\" && value._key ? value._key : \"tbl\";\n const cells = (row: PortableTextTableRow) => {\n let col = 0;\n return (Array.isArray(row?.cells) ? row.cells : []).map((cell) => {\n const Tag = cell?.header === true ? \"th\" : \"td\";\n const span = typeof cell?.span === \"number\" && cell.span > 1 ? cell.span : undefined;\n const at = col;\n col += span ?? 1;\n const rowSpan = typeof cell?.rowSpan === \"number\" && cell.rowSpan > 1 ? cell.rowSpan : undefined;\n const n = typeof cell?.note === \"string\" ? fnNumber.get(cell.note) : undefined;\n const content = Array.isArray(cell?.content) ? cell.content : [];\n const lone =\n content.length === 1\n && (content[0] as { _type?: string; style?: string; listItem?: string })._type === \"block\"\n && (content[0] as { style?: string }).style === \"normal\"\n && !(content[0] as { listItem?: string }).listItem;\n const view = typed ? typedCellView(columns[at], row, (cell ?? {}) as unknown as Record<string, unknown>, headSet.has(row)) : undefined;\n // a footnote: data-note after the value and before data-align (V2 cellHtml), the marker after the cell's words\n const noteAttr: [string, string][] = n ? [[\"data-note\", cell.note as string]] : [];\n const tail = [...(view?.tail ?? []).filter(([k]) => k !== \"data-align\"), ...noteAttr, ...(view?.tail ?? []).filter(([k]) => k === \"data-align\")];\n const supHtml = n ? `<sup><a href=\"#${tableId}-fn-${n}\" aria-label=\"Note ${n}\">${n}</a></sup>` : \"\";\n return (\n <Tag key={cell?._key} {...(Tag === \"td\" ? role(\"cell\") : {})} {...(span ? { colSpan: span } : {})} {...(rowSpan ? { rowSpan } : {})} {...props(view?.lead ?? [])} {...(stacked && Tag === \"td\" ? { \"data-label\": labels[at] || `Column ${at + 1}` } : {})} {...(marked.has(at) ? { \"data-highlight\": \"true\" } : {})} {...props(tail)} {...(view?.inner !== undefined ? { dangerouslySetInnerHTML: { __html: view.inner + supHtml } } : {})}>\n {view?.inner !== undefined ? undefined : (\n <>\n <PT value={content as never} components={lone ? inline : components} />\n {n ? <sup><a href={`#${tableId}-fn-${n}`} aria-label={`Note ${n}`}>{n}</a></sup> : null}\n </>\n )}\n </Tag>\n );\n });\n };\n // \"Highlight this row\" is `data-highlight=\"true\"`, as `portableTextToHtml` and the V2 table write it.\n const rowEl = (row: PortableTextTableRow) => (\n <tr key={row?._key} {...role(\"row\")} {...(typeof row?.type === \"string\" ? { \"data-type\": row.type } : {})} {...(row?.highlight === true ? { \"data-highlight\": \"true\" } : {})}>{cells(row)}</tr>\n );\n // The same three sections `portableTextToHtml` draws: leading head run, body, trailing footer run.\n const sections = tableSections(value);\n const head = sections.head.length;\n const foot = sections.foot as PortableTextTableRow[];\n\n /**\n * Caption, column groups, row groups and the note: the same runs `portableTextToHtml` draws,\n * from the same `tableGroupLayout`, so the two renderers cannot disagree about where a group\n * starts. A table without them draws exactly what it drew before they existed.\n */\n const layout = tableGroupLayout(value);\n const caption = typeof value.caption === \"string\" ? value.caption.trim() : \"\";\n const notes = typeof value.notes === \"string\" ? value.notes.trim() : \"\";\n const notesId = (notes || footnotes.length) && typeof value._key === \"string\" && value._key ? `${value._key}-notes` : undefined;\n const span = (n: number) => (n > 1 ? n : undefined);\n const groupRow = layout.columns.length ? (\n <tr {...role(\"row\")}>\n {layout.columns.flatMap((run, i) =>\n run.label === undefined\n ? Array.from({ length: run.width }, (_, k) => <td key={`${i}-${k}`} {...role(\"cell\")} />)\n : [<th key={i} {...role(\"columnheader\")} scope=\"colgroup\" colSpan={span(run.width)} data-group={run.key}>{run.label}</th>],\n )}\n </tr>\n ) : null;\n const bodies = layout.rows.length\n ? layout.rows.map((run, i) => (\n <tbody key={i} {...role(\"rowgroup\")} data-key={run.key}>\n {run.label !== undefined && (\n <tr {...role(\"row\")}><th {...role(\"rowheader\")} scope=\"rowgroup\" colSpan={span(layout.width)}>{run.label}</th></tr>\n )}\n {(run.rows as PortableTextTableRow[]).map(rowEl)}\n </tbody>\n ))\n : sections.body.length > 0 && <tbody {...role(\"rowgroup\")}>{(sections.body as PortableTextTableRow[]).map(rowEl)}</tbody>;\n\n // A typed column's `<col>`: V2 renderTableHtml's attributes in its order, as `portableTextToHtml` writes them.\n const colProps = (i: number): Record<string, string> => {\n const c = columns[i];\n return {\n ...(c ? { \"data-type\": c.type ?? \"text\" } : {}),\n ...(c?.unit ? { \"data-unit\": c.unit } : {}),\n ...(c?.currency ? { \"data-currency\": c.currency } : {}),\n ...(c?.decimals != null ? { \"data-decimals\": String(c.decimals) } : {}),\n ...(c?.align ? { \"data-align\": c.align } : {}),\n ...(sortable.has(i) ? { \"data-sortable\": \"true\" } : {}),\n ...(c?.trueLabel ? { \"data-true\": c.trueLabel } : {}),\n ...(c?.falseLabel ? { \"data-false\": c.falseLabel } : {}),\n ...(c?.max != null ? { \"data-max\": String(c.max) } : {}),\n ...(c?.description ? { \"data-description\": c.description } : {}),\n };\n };\n // `bcms-table` on migrated values only, exactly as `portableTextToHtml` writes it — see\n // `PortableTextTable[\"legacy\"]`. The two renderers agree about the class or they agree about\n // nothing.\n const table = (\n <table {...role(\"table\")} {...(value.legacy === true ? { className: \"bcms-table\" } : {})} {...(notesId ? { \"aria-describedby\": notesId } : {})}>\n {caption && <caption>{caption}</caption>}\n {sortable.size || columns.some(Boolean)\n ? (layout.columns.length ? layout.columns.map((run) => run.width) : [layout.width]).map((w, i, all) => {\n const from = all.slice(0, i).reduce((n, x) => n + x, 0);\n return (\n <colgroup key={i}>\n {Array.from({ length: w }, (_, k) => <col key={k} {...colProps(from + k)} />)}\n </colgroup>\n );\n })\n : layout.columns.map((run, i) => <colgroup key={i} span={span(run.width)} />)}\n {(groupRow || head > 0) && <thead {...role(\"rowgroup\")}>{groupRow}{rows.slice(0, head).map(rowEl)}</thead>}\n {bodies}\n {foot.length > 0 && <tfoot {...role(\"rowgroup\")}>{foot.map(rowEl)}</tfoot>}\n </table>\n );\n // \"Visitors can search rows\" is the figure's data-filter, as `portableTextToHtml` and the V2 table write it.\n const filter = value.filterable === true;\n // \"Help search engines read this as\": the figure's data-jsonld (+ axis, price row), as `portableTextToHtml` writes it\n const ld = jsonLdOf(value.jsonLd);\n const ldAttrs = ld\n ? { \"data-jsonld\": ld.type, ...(ld.axis ? { \"data-jsonld-axis\": ld.axis } : {}), ...(ld.priceRow ? { \"data-jsonld-price\": ld.priceRow } : {}) }\n : {};\n const designAttrs = design ? { ...(design.responsive ? { \"data-responsive\": design.responsive } : {}), ...(design.sticky ? { \"data-sticky\": \"true\" } : {}) } : {};\n return notes || filter || ld || design || footnotes.length ? (\n <figure data-rt=\"table\" {...designAttrs} {...(filter ? { \"data-filter\": \"true\" } : {})} {...ldAttrs}>\n {table}\n {footnotes.length ? (\n <div className=\"bt-notes\" id={`${tableId}-notes`}>\n {notes ? <p data-notes=\"\">{notes}</p> : null}\n <ol data-footnotes=\"\">\n {footnotes.map((f, i) => <li key={f._key} id={`${tableId}-fn-${i + 1}`} data-key={f._key}>{f.text}</li>)}\n </ol>\n </div>\n ) : notes ? <p data-notes=\"\" id={notesId}>{notes}</p> : null}\n </figure>\n ) : table;\n}\n\n/**\n * A checklist item: V2's `<li data-checked=\"true|false\">` with a disabled checkbox in front of the\n * words, the markup `portableTextToHtml` writes, so a page without the V2 stylesheet still shows\n * the box and its state. `address` is the item's binding when the value is bound.\n */\nfunction CheckItem({ checked, address, children }: { checked: boolean; address?: Record<string, string>; children?: React.ReactNode }) {\n return (\n <li {...address} data-checked={String(checked)}>\n <input type=\"checkbox\" disabled defaultChecked={checked} />\n {children}\n </li>\n );\n}\n\n/** An X post's card, the React half of html.ts `xCardHtml`: the blockquote X's widget upgrades, no script. */\nfunction XCard({ url, card }: { url: string; card: EmbedCard }) {\n return (\n <blockquote className=\"twitter-tweet bcms-embed-card\" cite={url}>\n <p>{card.text}</p>{\"— \"}{card.authorName}{\" \"}<a href={url}>{card.date || \"View on X\"}</a>\n </blockquote>\n );\n}\n\ntype Outline = { items: OutlineItem[]; nodes: WeakMap<object, string> };\ntype Footnotes = { notes: string[]; nodes: WeakMap<object, number> };\n\n/** RT8: related posts, the React half of html.ts `relatedPostsHtml`: placeholder, nothing, or filled. */\nfunction RelatedPosts({ value, resolved }: { value: unknown; resolved: RichTextResolved | undefined }) {\n const { key, title, mode, count, picked, items } = relatedPostsView(value, resolved);\n if (!resolved) {\n return <aside data-rt=\"related\" data-key={key} data-mode={mode} data-count={count} data-posts={picked.join(\",\")} data-title={title || undefined} />;\n }\n if (!items.length) return null;\n return (\n <aside data-rt=\"related\" data-mode={mode} data-count={count} data-posts={picked.join(\",\")}>\n {title ? <p data-title=\"\">{title}</p> : null}\n {items.length ? (\n <ul>\n {items.map((p) => (\n <li key={p.id}><a href={p.url}>{p.title}</a></li>\n ))}\n </ul>\n ) : null}\n </aside>\n );\n}\n\n/** RT8: an author bio, the React half of html.ts `authorBioHtml`: placeholder, nothing, or filled. */\nfunction AuthorBio({ value, resolved }: { value: unknown; resolved: RichTextResolved | undefined }) {\n const { key, id, author } = authorBioView(value, resolved);\n if (!resolved) return <aside data-rt=\"author\" data-key={key} data-author={id} />;\n if (!author) return null;\n return (\n <aside data-rt=\"author\" data-author={id}>\n {author?.avatar ? <img src={author.avatar} alt=\"\" /> : null}\n {author ? <p data-name=\"\">{author.name}</p> : null}\n {author?.bio ? <p data-bio=\"\">{author.bio}</p> : null}\n </aside>\n );\n}\n\n/** RT7: columns, the React half of html.ts `columnsHtml`: each column's text drawn by the body's own components. */\nfunction Columns({ value, components }: { value: unknown; components: PortableTextComponents }) {\n const view = columnsView(value);\n if (!view) return null;\n const s = COLUMNS_STYLES[view.gap];\n return (\n <div data-rt=\"columns\" data-gap={view.gap} data-cols={view.columns.length} style={styleObject(s.row)}>\n {view.columns.map((c) => (\n <div key={c.key} data-col=\"true\" style={styleObject(s.col)}>\n {c.content.length ? <PT value={c.content as never} components={components} /> : null}\n </div>\n ))}\n </div>\n );\n}\n\n/** RT6: a footnote's marker, the React half of html.ts `footnoteMarkHtml`. */\nfunction FootnoteMark({ value, footnotes }: { value: object; footnotes: Footnotes }) {\n const note = typeof (value as { note?: unknown }).note === \"string\" ? (value as { note: string }).note : \"\";\n const n = footnotes.nodes.get(value);\n if (!n) return <sup data-rt=\"fn\" data-note={note} />;\n return (\n <sup data-rt=\"fn\" id={`fnref-${n}`} data-note={note}>\n <a href={`#fn-${n}`} aria-label={`Footnote ${n}`}>{n}</a>\n </sup>\n );\n}\n\n/** RT6: the end list, the React half of html.ts `footnoteListHtml` (V2 `renderFootnotes`). */\nfunction FootnoteList({ notes }: { notes: readonly string[] }) {\n if (!notes.length) return null;\n return (\n <ol className=\"cs-footnotes\" data-rt=\"footnotes\" aria-label=\"Footnotes\">\n {notes.map((note, i) => (\n <li key={i} id={`fn-${i + 1}`}>\n {note}{\" \"}<a href={`#fnref-${i + 1}`} aria-label={`Back to reference ${i + 1}`}>↩</a>\n </li>\n ))}\n </ol>\n );\n}\n\n/** RT5: a table of contents, the React half of html.ts `tocHtml`: the same nav over the same outline. */\nfunction Toc({ value, outline }: { value: unknown; outline: Outline }) {\n const { title, label, depth } = tocView(value);\n const link = (o: OutlineItem) => <a href={`#${o.id}`}>{o.text}</a>;\n return (\n <nav data-rt=\"toc\" data-depth={depth} aria-label={label}>\n {title ? <p data-title=\"\">{title}</p> : null}\n <ol>\n {tocTree(outline.items, depth).map((e) => (\n <li key={e.id}>\n {link(e)}\n {e.children.length ? <ol>{e.children.map((c) => <li key={c.id}>{link(c)}</li>)}</ol> : null}\n </li>\n ))}\n </ol>\n </nav>\n );\n}\n\n/** RT4: a gallery, the React half of html.ts `galleryHtml`: the same figure, list and inline layout. */\nfunction Gallery({ value }: { value: unknown }) {\n const { layout, images } = galleryView(value);\n const s = GALLERY_STYLES[layout];\n return (\n <figure data-rt=\"gallery\" data-layout={layout}>\n <ul style={styleObject(s.ul)} tabIndex={layout === \"carousel\" ? 0 : undefined}>\n {images.map((x) => (\n <li key={x.key} style={styleObject(s.li)}>\n <figure style={styleObject(s.figure)}>\n <img\n alt={x.alt}\n style={styleObject(x.position ? `${s.img};object-position:${x.position}` : s.img)}\n loading=\"lazy\"\n {...Object.fromEntries(x.data)}\n src={x.src}\n />\n {x.caption ? <figcaption>{x.caption}</figcaption> : null}\n </figure>\n </li>\n ))}\n </ul>\n </figure>\n );\n}\n\n/** RT3: an embed, the React half of html.ts `embedFigureHtml`: the same figure around the same derived body. */\nfunction Embed({ value, embeds }: { value: unknown; embeds: EmbedRender }) {\n const v = (value ?? {}) as { url?: unknown; title?: unknown; aspect?: unknown };\n const url = typeof v.url === \"string\" ? v.url.trim() : \"\";\n const aspect = isEmbedAspect(v.aspect) ? v.aspect : \"16:9\";\n const title = typeof v.title === \"string\" ? v.title.trim() : \"\";\n const r = resolveEmbed(url, aspect);\n const label = title || r.label;\n // Keyed by the canonical url (`r.url`), as html.ts reads it.\n const card = r.kind === \"card\" ? embeds.cards?.[r.url] : undefined;\n let body: React.ReactNode = null;\n if (r.kind === \"frame\") {\n body = <iframe src={r.src} title={label} width={r.width} height={r.height} loading=\"lazy\" referrerPolicy=\"strict-origin-when-cross-origin\" allowFullScreen />;\n } else if (r.kind === \"audio\") {\n body = <audio controls preload=\"none\" src={r.src} title={label} />;\n } else if (r.kind === \"card\" && card) {\n body = <XCard url={r.url} card={card} />;\n } else if (r.url) {\n body = <a href={r.url}>{label}</a>;\n }\n return (\n <figure data-rt=\"embed\" data-url={url} data-provider={r.provider} data-aspect={aspect} aria-label={title}>\n {body}\n </figure>\n );\n}\n\nfunction buildComponents(\n renderBlock?: PortableTextProps[\"renderBlock\"],\n embeds: EmbedRender = {},\n outline: Outline = { items: [], nodes: new WeakMap() },\n footnotes: Footnotes = { notes: [], nodes: new WeakMap() },\n resolved?: RichTextResolved,\n): PortableTextComponents {\n const components: PortableTextComponents = {\n // RT5: every top-level h2 and h3 carries its outline id, as html.ts writes it.\n block: {\n h2: ({ value, children }) => <h2 id={outline.nodes.get(value)}>{children}</h2>,\n h3: ({ value, children }) => <h3 id={outline.nodes.get(value)}>{children}</h3>,\n },\n list: { check: ({ children }) => <ul data-task=\"true\">{children}</ul> },\n listItem: { check: ({ value, children }) => <CheckItem checked={(value as { checked?: unknown }).checked === true}>{children}</CheckItem> },\n types: {\n [PT_TYPES.divider]: () => <hr />,\n [PT_TYPES.table]: ({ value }) => <Table value={value as PortableTextTable} />,\n [PT_TYPES.code]: ({ value }) => {\n const v = value as { code?: unknown; language?: unknown };\n const lang = typeof v.language === \"string\" && TOKEN.test(v.language) ? v.language : undefined;\n return (\n <pre>\n <code className={lang ? `language-${lang}` : undefined}>\n {typeof v.code === \"string\" ? v.code : \"\"}\n </code>\n </pre>\n );\n },\n /**\n * A generic embedded block renders as a marker, not as content: its payload references\n * something (a component, a media id) that has to be RESOLVED against the project, and\n * this renderer has no project. A marker keeps the node visible and addressable rather\n * than rendering nothing at all, which is how embedded content used to vanish. A caller\n * that CAN resolve it passes `renderBlock`.\n */\n [PT_TYPES.block]: ({ value }) => {\n const rendered = renderBlock?.(value as Record<string, unknown>);\n if (rendered !== undefined) return <>{rendered}</>;\n // A callout and a toggle carry their own text: the same DOM as portableTextToHtml's (callout-toggle.test.tsx).\n const builtIn = calloutOrToggle(value);\n if (builtIn) return builtIn;\n // RT7: a legacy `builtin:columns` reads as columns, as html.ts draws it.\n if (columnsView(value)) return <Columns value={value} components={components} />;\n // A separate copy of a component carries its own frozen blocks: nothing to resolve.\n const copy = detachedComponentCopy(value);\n if (copy) {\n return (\n <div className=\"bcms-block bcms-block--component\" data-bcms-component-copy={copy.componentId} data-bcms-instance={copy.id}>\n <BlocksRenderer blocks={copy.blocks as ContentBlock[]} />\n </div>\n );\n }\n const v = value as { schemaKey?: unknown };\n const key = typeof v.schemaKey === \"string\" ? v.schemaKey : \"block\";\n return <div className=\"bcms-block\" data-bcms-schema-key={key} />;\n },\n /**\n * A pull quote — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's\n * `<figure data-rt=\"pullquote\">`, the name linked (and the quote cited) only through\n * `safeHref`. pull-quote.test.tsx compares the two.\n */\n [PT_TYPES.pullQuote]: ({ value }) => {\n const v = value as { quote?: unknown; name?: unknown; role?: unknown; link?: unknown };\n const text = (x: unknown) => (typeof x === \"string\" ? x.trim() : \"\");\n const quote = typeof v.quote === \"string\" ? v.quote : \"\";\n const name = text(v.name);\n const role = text(v.role);\n const href = safeHref(pullQuoteHref(v.link)) ?? undefined;\n return (\n <figure data-rt=\"pullquote\">\n <blockquote cite={href}>\n <p>{quote}</p>\n </blockquote>\n {name || role ? (\n <figcaption>\n {name ? (href ? <a href={href} data-name=\"\">{name}</a> : <span data-name=\"\">{name}</span>) : null}\n {name && role ? \", \" : null}\n {role ? <span data-role=\"\">{role}</span> : null}\n </figcaption>\n ) : null}\n </figure>\n );\n },\n [PT_TYPES.embed]: ({ value }) => <Embed value={value} embeds={embeds} />,\n [PT_TYPES.gallery]: ({ value }) => <Gallery value={value} />,\n [PT_TYPES.tableOfContents]: ({ value }) => <Toc value={value} outline={outline} />,\n [PT_TYPES.columns]: ({ value }) => <Columns value={value} components={components} />,\n [PT_TYPES.relatedPosts]: ({ value }) => <RelatedPosts value={value} resolved={resolved} />,\n [PT_TYPES.authorBio]: ({ value }) => <AuthorBio value={value} resolved={resolved} />,\n /**\n * Key takeaways — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's\n * `<aside data-rt=\"takeaways\">`, read through the one `keyTakeawaysView` both renderers share.\n * portable-text-key-takeaways.test.tsx compares the two.\n */\n [PT_TYPES.keyTakeaways]: ({ value }) => {\n const { title, points } = keyTakeawaysView(value);\n return (\n <aside data-rt=\"takeaways\">\n {title ? <p data-title=\"\">{title}</p> : null}\n <ul>\n {points.map((p, i) => (\n <li key={i}>{p}</li>\n ))}\n </ul>\n </aside>\n );\n },\n [PT_TYPES.footnote]: ({ value }) => <FootnoteMark value={value} footnotes={footnotes} />,\n [PT_TYPES.mention]: ({ value }) => {\n const v = value as { label?: unknown };\n return <span className=\"bcms-mention\">{typeof v.label === \"string\" ? v.label : \"\"}</span>;\n },\n },\n marks: {\n /**\n * The only attribute this renderer writes from author data. An unsafe href renders the\n * LABEL rather than deleting it — blocking a link must not lose the words.\n */\n [PT_TYPES.link]: ({ value, children }) => {\n const v = (value ?? {}) as { href?: unknown; target?: unknown; button?: unknown };\n const href = typeof v.href === \"string\" ? safeHref(v.href) : null;\n if (!href) return <>{children}</>;\n const blank = v.target === \"_blank\";\n // RT2: a button is a link marked as one; `data-button` is the site's style hook, as html.ts writes it.\n return (\n <a {...(v.button === true ? { \"data-button\": \"true\" } : {})} href={href} {...(blank ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {})}>\n {children}\n </a>\n );\n },\n /**\n * The site's own inline markup, rendered as itself — the React half of\n * `portableTextToHtml`'s `styled` mark, and it has to stay the same shape: Astro and the\n * editor canvas render through that serialiser while a Next site renders through this\n * one, and two renderers of one value that disagree is the whole bug class.\n *\n * The tag is re-checked against `STYLED_TAGS` and the attribute bag re-filtered, because\n * a markDef is DATA and arrives from imports, the Management API and AI writes. React\n * escapes the values it writes; the allowlist is what keeps a `href` or an `onClick` out\n * of the props entirely. An unrecognised tag renders its CHILDREN — the words are the\n * author's, the wrapper is only the design.\n */\n [PT_TYPES.styled]: ({ value, children }) => {\n const v = (value ?? {}) as { tag?: unknown; className?: unknown; attrs?: unknown };\n const tag = typeof v.tag === \"string\" ? v.tag.toLowerCase() : \"\";\n if (!isStyledTag(tag)) return <>{children}</>;\n const Tag = tag as \"span\";\n const className = typeof v.className === \"string\" && v.className ? v.className : undefined;\n const attrs = styledAttrEntries(v.attrs);\n const style = attrs.find(([name]) => name === \"style\")?.[1];\n return (\n <Tag\n className={className}\n {...Object.fromEntries(attrs.filter(([name]) => name !== \"style\"))}\n {...(style ? { style: styleObject(style) } : {})}\n >\n {children}\n </Tag>\n );\n },\n highlight: ({ children }) => <mark>{children}</mark>,\n \"strike-through\": ({ children }) => <s>{children}</s>,\n /**\n * A colour mark carries a TOKEN name (`color:brand-accent`), never a baked hex, so the\n * consuming site styles it and a rebrand does not orphan the content.\n */\n color: ({ children, markType }) => {\n const token = markType.startsWith(\"color:\") ? markType.slice(6) : \"\";\n if (!TOKEN.test(token)) return <>{children}</>;\n return <span className={`bcms-color-${token}`}>{children}</span>;\n },\n },\n /** Unknown marks and types render their children. Never swallow content. */\n unknownMark: ({ children }) => <>{children}</>,\n unknownType: ({ children }) => <>{children ?? null}</>,\n unknownBlockStyle: ({ children }) => <p>{children}</p>,\n };\n return components;\n}\n\nconst isListItem = (node: { _type?: string; listItem?: unknown }): boolean =>\n node._type === \"block\" && typeof node.listItem === \"string\" && !!node.listItem;\n\n/**\n * Consecutive list items as ONE run, everything else alone — the same grouping\n * `portableTextToHtml({ bindingBase })` does, so both renderers draw one `<ol>` per list.\n */\nfunction runsOf<T extends { _type?: string; listItem?: unknown }>(blocks: readonly T[]): T[][] {\n const runs: T[][] = [];\n for (const node of blocks) {\n const last = runs.at(-1);\n if (last && isListItem(node) && isListItem(last[0])) last.push(node);\n else runs.push([node]);\n }\n return runs;\n}\n\n/** Plain multi-line text as paragraphs, as the serialiser splits it: blank line = new paragraph, newline = `<br>`. */\nfunction paragraphs(text: string): React.ReactNode[] {\n return text.split(/\\n\\s*\\n/).map((p) => p.trim()).filter(Boolean).map((p, i) => (\n <p key={i}>{p.split(\"\\n\").map((line, j) => (j === 0 ? line : [<br key={j} />, line]))}</p>\n ));\n}\n\nconst CALLOUT_TONES = new Set([\"info\", \"tip\", \"warning\"]);\n\n/** `builtin:callout` / `builtin:toggle`, drawn from their own fields; null for any other block. */\nfunction calloutOrToggle(value: unknown): React.ReactElement | null {\n const v = (value ?? {}) as { schemaKey?: unknown; fields?: Record<string, unknown> };\n const f = v.fields && typeof v.fields === \"object\" ? v.fields : {};\n const text = (x: unknown) => (typeof x === \"string\" ? x.trim() : \"\");\n if (v.schemaKey === \"builtin:callout\") {\n const tone = CALLOUT_TONES.has(text(f.tone)) ? text(f.tone) : \"info\";\n const title = text(f.title);\n const body = paragraphs(typeof f.message === \"string\" ? f.message : \"\");\n if (!title && !body.length) return <></>;\n return (\n <div className={`bcms-callout bcms-callout--${tone}`} data-rt=\"callout\" data-callout={tone} role=\"note\">\n {title ? <p className=\"bcms-callout__title\" data-title=\"\">{title}</p> : null}\n <div className=\"bcms-callout__body\" data-body=\"\">{body}</div>\n </div>\n );\n }\n if (v.schemaKey === \"builtin:toggle\") {\n const title = text(f.title);\n const body = paragraphs(typeof f.body === \"string\" ? f.body : \"\");\n if (!title && !body.length) return <></>;\n return (\n <details className=\"bcms-toggle\" data-rt=\"toggle\">\n <summary className=\"bcms-toggle__title\">{title}</summary>\n <div className=\"bcms-toggle__body\" data-body=\"\">{body}</div>\n </details>\n );\n }\n return null;\n}\n\nexport function PortableText({ value, bindingBase, renderBlock, embeds, resolved }: PortableTextProps) {\n const blocks = React.useMemo(() => normalizePortableText(readValue(value)), [value]);\n const outline = React.useMemo(() => outlineOf(blocks), [blocks]);\n const footnotes = React.useMemo(() => footnoteOrder(blocks), [blocks]);\n const components = React.useMemo(\n () => buildComponents(renderBlock, embeds, outline, footnotes, resolved),\n [renderBlock, embeds, outline, footnotes, resolved],\n );\n // A list item carries its address on its own `<li>`; only keys the value really has count,\n // because the renderer invents one for an item that has none.\n const listComponents = React.useMemo((): PortableTextComponents | null => {\n if (!bindingBase) return null;\n const keys = new Set<string | undefined>(blocks.map((b) => b._key));\n return {\n ...components,\n listItem: ({ value: item, children }) => {\n const address = keys.has(item._key) ? { \"data-bcms-field\": `${bindingBase}#${item._key}`, \"data-bcms-kind\": \"richtext\" } : undefined;\n if (item.listItem === \"check\") {\n return <CheckItem checked={(item as { checked?: unknown }).checked === true} address={address}>{children}</CheckItem>;\n }\n return <li {...address}>{children}</li>;\n },\n };\n }, [bindingBase, blocks, components]);\n if (blocks.length === 0) return null;\n\n // Binding wraps at the TOP LEVEL rather than inside `components.block`, because a block\n // component only sees text blocks — a divider or an embedded image would get no address,\n // and those are exactly the nodes an editor most wants to click. A run of list items is the\n // exception: one wrapper per item would split the list into one-item lists.\n // RT6: the end list follows the body, derived, so it carries no address.\n const notes = <FootnoteList notes={footnotes.notes} />;\n if (!bindingBase || !listComponents) {\n return (\n <>\n <PT value={blocks as never} components={components} />\n {notes}\n </>\n );\n }\n\n return (\n <>\n {runsOf(blocks).map((run) =>\n isListItem(run[0]) ? (\n <PT key={run[0]._key} value={run as never} components={listComponents} />\n ) : (\n <Bound key={run[0]._key} base={bindingBase} nodeKey={run[0]._key}>\n <PT value={run as never} components={components} />\n </Bound>\n ),\n )}\n {notes}\n </>\n );\n}\n\n/** Accept the array, the `{format,value}` envelope, and the legacy `{version,content}` tree. */\nfunction readValue(value: unknown): PtValue {\n if (Array.isArray(value)) return value as PtValue;\n if (value && typeof value === \"object\") {\n const o = value as Record<string, unknown>;\n if (Array.isArray(o.value)) return o.value as PtValue;\n if (Array.isArray(o.content)) return o.content as PtValue;\n }\n return [];\n}\n","import type { ContentBlock } from \"@bettercms-ai/types\";\nimport { RESPONSIVE_CSS, hiddenEverywhere, hideOnAttr, splitButtonStyle, usesHideOn, visibleBlocks, windowState } from \"@bettercms-ai/types\";\nimport { BLOCK_REGISTRY, type BlockRegistryEntry } from \"./registry\";\nimport { blockStyleToReactStyle } from \"./blockStyle\";\nimport type { BcmsBindingProps } from \"./binding\";\nimport React from \"react\";\n\n// Container blocks hold nested blocks and need the `renderBlock` callback threaded\n// so their children render recursively.\nconst CONTAINER_TYPES = new Set([\"columns\", \"section\", \"slider\", \"tabs\"]);\n\n/**\n * The wrapper's style and, for a button, the paint for its `<a>`: a button's wrapper keeps only\n * its placement keys, so a fill or theme never paints a box behind the anchor (as on every\n * delivery renderer — `splitButtonStyle` in @bettercms-ai/types).\n */\nfunction styleOf(block: ContentBlock): { wrapper: React.CSSProperties | undefined; paint: { style?: React.CSSProperties } } {\n if (block.type !== \"button\") return { wrapper: blockStyleToReactStyle(block.style), paint: {} };\n const { placement, paint } = splitButtonStyle(block.style);\n return { wrapper: blockStyleToReactStyle(placement), paint: paint ? { style: paint as React.CSSProperties } : {} };\n}\n\n/**\n * A block that hides somewhere gets a wrapper carrying `data-bcms-hide` (`RESPONSIVE_CSS` keys on it).\n * `stampAt` (the editor lane) also marks a block outside its window `data-bcms-window=\"pending|expired\"`,\n * and one hidden on every breakpoint `data-bcms-hidden=\"all\"`, on the same wrapper: there the block\n * is drawn so its author can find it.\n */\nfunction withHide(block: ContentBlock, wrapperStyle: React.CSSProperties | undefined, el: React.ReactElement, stampAt?: Date) {\n const hide = hideOnAttr(block.style);\n const state = stampAt ? windowState(block, stampAt) : \"live\";\n const hidden = !!stampAt && hiddenEverywhere(block);\n if (!wrapperStyle && !hide && state === \"live\" && !hidden) return el;\n return (\n <div style={wrapperStyle} {...(hide ? { \"data-bcms-hide\": hide } : {})} {...(state === \"live\" ? {} : { \"data-bcms-window\": state })} {...(hidden ? { \"data-bcms-hidden\": \"all\" } : {})}>\n {el}\n </div>\n );\n}\n\ninterface BlockRendererProps {\n block: ContentBlock;\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n /** Emit visual-editor field bindings. Default-off keeps published consumers clean. */\n annotate?: boolean;\n /** The instant block windows are read against. Internal: `BlocksRenderer` passes it when annotating. */\n stampAt?: Date;\n}\n\n/**\n * Direct scalar props of the four prose block types, and nothing else. `component`, `form` and\n * `collection` render as placeholders here (see blocks/Placeholders.tsx): this lane has no\n * definition lookup and no entry fetch, so it never emits the `<instanceId>__overrides.<key>`\n * addresses the delivery renderers do — resolution is theirs, through @bettercms-ai/ui/resolve.\n */\nfunction bindingProps(block: ContentBlock, annotate: boolean): BcmsBindingProps {\n if (!annotate || !block.id) return {};\n switch (block.type) {\n case \"heading\":\n case \"button\":\n return { \"data-bcms-field\": `${block.id}__text`, \"data-bcms-kind\": \"text\" };\n case \"text\":\n case \"richtext\":\n return { \"data-bcms-field\": `${block.id}__html`, \"data-bcms-kind\": \"richtext\" };\n case \"image\":\n return { \"data-bcms-field\": `${block.id}__src`, \"data-bcms-kind\": \"image\" };\n default:\n return {};\n }\n}\n\nfunction resolveBlock(block: ContentBlock, className?: string, annotate = false, stampAt?: Date): React.ReactNode {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? \"\"}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n const Component = entry.component;\n const { wrapper: blockStyle, paint } = styleOf(block);\n const el = <Component {...block.props} {...paint} className={className} {...bindingProps(block, annotate)} />;\n return withHide(block, blockStyle, el, stampAt);\n}\n\nexport function BlockRenderer({\n block,\n className = \"\",\n renderBlock,\n annotate = false,\n stampAt,\n}: BlockRendererProps) {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n // Container blocks (columns/section/slider/tabs) hold nested blocks, so thread the\n // renderBlock callback. Standalone usage (no BlocksRenderer above) falls back to\n // resolveBlock so one level of nesting still renders.\n const Component = entry.component;\n const containerProps = CONTAINER_TYPES.has(block.type)\n ? { renderBlock: renderBlock ?? ((child: ContentBlock) => resolveBlock(child, undefined, annotate, stampAt)) }\n : {};\n const { wrapper: blockStyle, paint } = styleOf(block);\n const el = (\n <Component\n {...block.props}\n {...paint}\n className={className}\n {...containerProps}\n {...bindingProps(block, annotate)}\n />\n );\n return withHide(block, blockStyle, el, stampAt);\n}\n\ninterface BlocksRendererProps {\n blocks: ContentBlock[];\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n annotate?: boolean;\n /**\n * The instant block windows (`block.window`) are read against. Defaults to the render time.\n * A block outside its window, or hidden on every breakpoint, is not rendered, at any depth; with\n * `annotate` it is rendered and marked `data-bcms-window=\"pending|expired\"` / `data-bcms-hidden=\"all\"`.\n */\n now?: Date;\n}\n\nexport function BlocksRenderer({\n blocks: all,\n className = \"\",\n renderBlock,\n annotate = false,\n now,\n}: BlocksRendererProps) {\n const at = now ?? new Date();\n const blocks = annotate ? all : visibleBlocks(all, at);\n if (blocks.length === 0) return null;\n\n return (\n <div className={className}>\n {usesHideOn(blocks) && <style dangerouslySetInnerHTML={{ __html: RESPONSIVE_CSS }} />}\n {blocks.map((block) => (\n <BlockRenderer\n key={block.id}\n block={block}\n renderBlock={renderBlock}\n annotate={annotate}\n stampAt={annotate ? at : undefined}\n />\n ))}\n </div>\n );\n}\n","import type { CSSProperties } from \"react\";\nimport { blockStyleToCss, type BlockStyle } from \"@bettercms-ai/types\";\n\n/**\n * FLO-264 — React-typed wrapper over the shared, framework-agnostic\n * `blockStyleToCss` in @bettercms-ai/types. One mapping, three consumers (this UI\n * renderer, the @bettercms-ai/next adapter, and — in string form — the hosted\n * render-page.ts).\n */\nexport function blockStyleToReactStyle(style: BlockStyle | undefined): CSSProperties | undefined {\n return blockStyleToCss(style) as CSSProperties | undefined;\n}\n","/**\n * Portable Text, the real spec — https://portabletext.org\n *\n * BetterCMS shipped four hand-rolled \"Portable Text\" trees before this file, and all four\n * diverged from the spec in the same expensive way: no `markDefs`. Without markDefs a link\n * has nowhere to live, so every one of them could render bold text and none of them could\n * render a hyperlink. Two also dropped unknown marks silently. This file is the spec, so the\n * official `@portabletext/*` packages can read and write our content without a shim.\n *\n * THE SHAPE, and why each part earns its place:\n *\n * block ─ one paragraph/heading/quote. `style` says which. `listItem` + `level` make\n * it a list item, because Portable Text keeps lists FLAT and reconstructs the\n * nesting at render time; there is no `<ul>` node to lose.\n * span ─ a run of text inside a block. `marks` are strings, and a mark is either a\n * DECORATOR (a bare name like \"strong\") or an ANNOTATION (the `_key` of an\n * entry in the block's `markDefs`). That one indirection is the whole reason\n * links, and any other data-carrying mark, are expressible.\n * markDefs ─ the data behind annotation marks. A link's `href` lives here, not on the span.\n * blockObject─ anything that is not text: an image, an embed, a table, a placed component.\n *\n * `_key` is not decoration. It is the stable identity of a block, and it is what lets the\n * Visual Editor address ONE paragraph inside a rich-text field instead of treating the whole\n * field as a single opaque string. Everything the editor gained rests on it, so `_key` is\n * minted on ingest and never trusted from foreign content.\n */\n\n/** Every Portable Text node carries a type and a stable key. */\nexport interface PortableTextNode {\n _type: string;\n _key: string;\n}\n\n/**\n * A run of text. `marks` mixes decorator NAMES and markDef `_key`s; a consumer resolves the\n * difference by looking each mark up in the parent block's `markDefs`. A mark that resolves to\n * nothing is a decorator.\n */\nexport interface PortableTextSpan extends PortableTextNode {\n _type: \"span\";\n text: string;\n marks: string[];\n}\n\n/** The data behind an annotation mark. `_key` is what a span's `marks` entry points at. */\nexport interface PortableTextMarkDef extends PortableTextNode {\n [field: string]: unknown;\n}\n\n/** A text block. Lists are FLAT: `listItem` + `level`, never a nested container. */\nexport interface PortableTextBlock extends PortableTextNode {\n _type: \"block\";\n style: string;\n markDefs: PortableTextMarkDef[];\n children: (PortableTextSpan | PortableTextNode)[];\n listItem?: string;\n level?: number;\n /** Only meaningful when `listItem` is \"check\". A checklist item's state is content, not style. */\n checked?: boolean;\n /**\n * RT9: a top-level h2/h3's own id from the page it was imported from, kept so the site's links\n * to it keep working. Only when it differs from the id its words would make (toc.ts `outlineOf`).\n */\n anchor?: string;\n}\n\n/** Anything that is not text — an image, an embed, a table, a placed component. */\nexport interface PortableTextObject extends PortableTextNode {\n [field: string]: unknown;\n}\n\n/** The value of a rich-text field: a flat array of blocks and block objects. */\nexport type PortableText = (PortableTextBlock | PortableTextObject)[];\n\n/**\n * ── THE TABLE ────────────────────────────────────────────────────────────────────────────\n *\n * A cell holds BLOCKS, not a string. The predecessor stored a string matrix, and the price was\n * paid on every edge of it: a link, a bolded word or a second paragraph inside a cell was\n * flattened to text on the way in and could never be authored on the way out. A cell is a small\n * rich-text field, so it is spelled as one, and every mark, annotation and list the rest of the\n * document can express works inside it for free.\n *\n * `header` is per CELL because a row-header table is a COLUMN of `<th>`; `head` is per ROW\n * because `<thead>` is a section of the table and a browser does not invent one. Both exist so\n * the four spellings of a header (`thead`+`th`, `thead`+`td`, bare `th`, none) survive a round\n * trip instead of being normalised into whichever one we happened to prefer — the html cache is\n * compared against the stored markup, so an invented `<thead>` reads as content drift.\n */\nexport interface PortableTextTableCell {\n _key: string;\n /** The cell's own blocks. Usually one paragraph; a cell may hold several, or none. */\n content: PortableText;\n /** `<th>` rather than `<td>`. */\n header?: boolean;\n /**\n * The COLUMN span (`colspan`), RECORDED but not honoured: the grid keeps exactly one cell per\n * `<td>`, and this is what the renderer writes back out. Honouring it would mean a ragged\n * model every consumer has to re-square, for a layout the editor cannot author anyway.\n */\n span?: number;\n /**\n * The ROW span (`rowspan`), recorded on the same terms.\n *\n * It is here because DROPPING it is lossy in a way the eye catches immediately: a spanning\n * cell that comes back spanning one row leaves a hole in the grid, and the round-trip audit\n * would refuse the whole table over an attribute we simply forgot to carry.\n */\n rowSpan?: number;\n /**\n * The COLUMN GROUP this cell's column belongs to: a `_key` of the table's `columnGroups`.\n *\n * Read from the FIRST row only (the V2 table puts `group` on the column; this grid has no\n * column object, so the column's first cell carries it). Living on a cell is what keeps it\n * right when the editor inserts, deletes or moves a column: the cell moves with its column.\n */\n group?: string;\n /**\n * \"Highlight this column\" (the V2 table's `TableColumn.highlight`): named, like `group`, on the\n * column's cell in the FIRST row only. Every cell of that column renders `data-highlight=\"true\"`.\n */\n highlight?: boolean;\n /**\n * \"Visitors can sort columns\" (the V2 table's `TableColumn.sortable`): named on the column's FIRST-row cell, like\n * `highlight`. Renders as the column's `<col data-sortable=\"true\">`, as the V2 table writes it.\n */\n sortable?: boolean;\n /**\n * The column's type and its settings (the V2 table's `TableColumn` type, unit, currency, decimals, align, trueLabel,\n * falseLabel, max, description): named, like `group`, on the column's FIRST-row cell. Nested rather than spread on the\n * cell because the cell's own `type` is the per-cell type. Renders as the column's `<col data-type …>`, as V2 writes it.\n */\n column?: PortableTextTableColumn;\n /** The per-cell type (the V2 table's `TableCellObject.type`): wins over the row's and the column's. */\n type?: PortableTextTableColumnType;\n /**\n * The typed value of a Number, Price, Yes / No or Rating cell (the V2 table's `TableCellValue`): a number, or true /\n * false. `content` still holds the words the cell says (V2's `cellText`), so every reader that reads blocks reads it.\n */\n value?: number | boolean;\n /**\n * A footnote on this cell (the V2 table's `TableCellObject.note`): a `_key` of the table's `footnotes`. The cell renders\n * `data-note` and the numbered marker V2 writes, `<sup><a href=\"#<table key>-fn-<n>\" aria-label=\"Note <n>\"><n></a></sup>`.\n */\n note?: string;\n}\n\n/**\n * The column types a rich-text table offers: the V2 table's `TableColumnType`s its island offers (table-editor.tsx:80-86\n * TYPE_UI without the two `dev` ones; V2's rich-text TableBlock passes no `developer`).\n */\nexport const PT_TABLE_COLUMN_TYPES = [\"text\", \"number\", \"money\", \"boolean\", \"rating\", \"link\"] as const;\nexport type PortableTextTableColumnType = (typeof PT_TABLE_COLUMN_TYPES)[number];\n\n/** A column's type and settings, the V2 table's `TableColumn` keys (schema/table.ts:44-72), spelled the same way. */\nexport interface PortableTextTableColumn {\n /** Absent reads as `text`. */\n type?: PortableTextTableColumnType;\n /** Explains the column: the header's `title`. */\n description?: string;\n /** number: written after every value (\"kg\", \"%\", \"min\"). */\n unit?: string;\n /** money: ISO 4217 code. Default USD. */\n currency?: string;\n /** number / money: fixed decimals. */\n decimals?: number;\n /** Default: numbers, money and ratings align end, everything else start. */\n align?: \"start\" | \"center\" | \"end\";\n /** boolean: the words. Defaults \"Included\" / \"Not included\". */\n trueLabel?: string;\n falseLabel?: string;\n /** rating: the scale. Default 5. */\n max?: number;\n}\n\nexport interface PortableTextTableRow {\n _key: string;\n cells: PortableTextTableCell[];\n /** The row sat in `<thead>`. Absent means `<tbody>`, which is what a parser assumes. */\n head?: boolean;\n /** The ROW GROUP this body row belongs to: a `_key` of the table's `rowGroups`. */\n group?: string;\n /**\n * A totals / summary row: the TRAILING run of these renders as `<tfoot>` (the V2 table's\n * \"Totals row\", `TableRow.footer` in `@bettercms-ai/types` table-value.ts).\n */\n footer?: boolean;\n /** \"Highlight this row\": `<tr data-highlight=\"true\">`, as the V2 table renders it. */\n highlight?: boolean;\n /** The per-row type (the V2 table's `TableRow.type`): a pricing matrix mixes Yes / No rows with limit rows. `<tr data-type>`. */\n type?: PortableTextTableColumnType;\n}\n\n/**\n * A row or column group, the V2 table's `TableGroup` (`@bettercms-ai/types` table-value.ts), so\n * both tables speak one language: a stable `_key` and the label a reader sees.\n */\nexport interface PortableTextTableGroup {\n _key: string;\n label: string;\n}\n\n/** A table. The ONE table BetterCMS has — see `PT_TYPES.table`. */\nexport interface PortableTextTable extends PortableTextObject {\n _type: \"table\";\n rows: PortableTextTableRow[];\n /**\n * Render with the platform's own `class=\"bcms-table\"`, and with every row squared off.\n *\n * 🔴 THIS IS A COMPATIBILITY FLAG, and it is the whole of what a pre-P1c table's rendition\n * needs. The renderer this block object replaces emitted that class on every table and padded\n * short rows to the widest one, so a value migrated from `builtin:table` MUST keep both or it\n * renders differently than the `html` sitting beside it in the same row — which is drift, and\n * the parity gate is right to refuse it.\n *\n * Set on every migrated value, and recognised on ingest (`class=\"bcms-table\"` and nothing\n * else), because that class only ever appears in markup that renderer produced. A table\n * authored or pasted since P1c carries neither the flag nor the class.\n */\n legacy?: boolean;\n /**\n * The four keys below are the V2 table's (`TableValue`), spelled the same way. All optional,\n * and a table that has none of them renders byte for byte as it did before they existed.\n */\n /** What the table compares, written as `<caption>`. At most 300 characters. */\n caption?: string;\n /** A note under the table (`<p data-notes>`), plain text. At most 2,000 characters. */\n notes?: string;\n /** Body rows name one with `row.group`; each run of rows is its own `<tbody>` under a label row. */\n rowGroups?: PortableTextTableGroup[];\n /** First-row cells name one with `cell.group`; a run of columns gets one `scope=\"colgroup\"` header. */\n columnGroups?: PortableTextTableGroup[];\n /**\n * \"Visitors can search rows\" (the V2 table's `TableValue.filterable`): the table renders inside\n * `<figure data-rt=\"table\" data-filter=\"true\">`, as the V2 table writes it.\n */\n filterable?: boolean;\n /**\n * \"Help search engines read this as\" (the V2 table's `TableValue.jsonLd`, the field-lane table's `jsonLd`): a\n * comparison, a data report or a price list. Written on the figure as `data-jsonld=\"<type>\"` (plus\n * `data-jsonld-axis` / `data-jsonld-price`), as the V2 table writes it. `\"none\"` is not stored.\n */\n jsonLd?: PortableTextTableJsonLd;\n /**\n * \"On phones\" and \"Keep column names in view\" (the V2 table's `TableValue.design.responsive` / `.sticky`, the field-lane\n * table's `design`). `responsive` is `stack` or `cards` (`scroll`, the default, is not stored, as V2 reads it); written\n * on the figure as `data-responsive` / `data-sticky=\"true\"`, as the V2 table writes them.\n */\n design?: PortableTextTableDesign;\n /**\n * Footnotes (the V2 table's `TableValue.footnotes`, the field-lane table's `footnotes`): numbered in order, named by a\n * cell's `note`, written under the table as V2's `<ol data-footnotes>` inside `<div class=\"bt-notes\">`.\n */\n footnotes?: PortableTextTableFootnote[];\n}\n\n/** One table footnote: a stable `_key` (what a cell's `note` names) and its text. */\nexport interface PortableTextTableFootnote {\n _key: string;\n text: string;\n}\n\n/** The V2 table's `design` keys the rich-text table offers. */\nexport interface PortableTextTableDesign {\n responsive?: \"stack\" | \"cards\";\n sticky?: boolean;\n}\n\n/** The V2 table's JSON-LD choice: `priceRow` is a row's `_key` (Offers), `axis` what an ItemList lists. */\nexport interface PortableTextTableJsonLd {\n type: \"ItemList\" | \"Dataset\" | \"Offers\";\n axis?: \"rows\" | \"columns\";\n priceRow?: string;\n}\n\n/**\n * A video block. `src` is an uploaded asset's path or a provider URL; `provider` and `poster`\n * are what an author set, never something this package infers — the renderer asks\n * `resolveVideoSource` about `src` and draws what it answers.\n *\n * 🔴 THE SHAPE IS A CONTRACT WITH THE EDITOR. The dashboard writes these fields (`ptNodes.tsx`,\n * `formNodes.tsx`) and this package renders them; spelling it once here is what stops the two\n * sides disagreeing about a key and rendering an empty box.\n */\nexport interface PortableTextVideo extends PortableTextObject {\n _type: \"video\";\n src: string;\n poster?: string;\n provider?: string;\n title?: string;\n}\n\n/** A downloadable file. `name` is the link's text — a file with no name has nothing to click. */\nexport interface PortableTextFile extends PortableTextObject {\n _type: \"file\";\n src: string;\n name: string;\n size?: number;\n mime?: string;\n}\n\n/**\n * A pull quote: a quotation lifted out of the body, with who said it.\n *\n * 🔴 THE SHAPE IS THE V2 KIT'S, 1:1 — `PullQuoteModel` in the dashboard's\n * `kit/controls/rich-text/article.ts`, which renders `<figure data-rt=\"pullquote\">`. The kit\n * writes these four fields and every renderer here draws them, so the names are spelled once.\n *\n * `link` is where the NAME links (and the blockquote's `cite`): a URL string, as the kit stores\n * it, or a link object carrying `href` (the shape a `link` field value takes). Anything\n * `safeHref` refuses renders the name unlinked rather than dropping it.\n */\nexport interface PortableTextPullQuote extends PortableTextObject {\n _type: \"pullQuote\";\n quote: string;\n name: string;\n role?: string;\n link?: string | { href: string; [k: string]: unknown };\n}\n\n/** The href a pull quote's `link` carries — a string, or a link object's `href`. \"\" for none. */\nexport function pullQuoteHref(link: unknown): string {\n if (typeof link === \"string\") return link.trim();\n if (link && typeof link === \"object\" && typeof (link as { href?: unknown }).href === \"string\") {\n return ((link as { href: string }).href).trim();\n }\n return \"\";\n}\n\n/**\n * Key takeaways: the story in a few points, set apart near the top of a body.\n *\n * 🔴 THE SHAPE IS THE V2 KIT'S, 1:1 — `TakeawaysModel` and `ARTICLE_SCHEMAS.takeaways` (`name:\n * \"keyTakeaways\"`, fields `title` and `points`) in the kit's `controls/rich-text/article.ts`,\n * which renders `<aside data-rt=\"takeaways\">`. `title` is the label over the list (\"Key\n * takeaways\" by default), ABSENT rather than empty when an author cleared it. `points` are plain\n * strings, one per bullet; V2 holds at most 8 and a title of at most 40 characters\n * (`PT_KEY_TAKEAWAYS_LIMITS`, key-takeaways.ts).\n */\nexport interface PortableTextKeyTakeaways extends PortableTextObject {\n _type: \"keyTakeaways\";\n title?: string;\n points: string[];\n}\n\n/**\n * RT3: an embed, as V2's EmbedModel. Only what the author sets is stored: the link, the\n * screen-reader title (≤ EMBED_TITLE_MAX) and the shape. The provider, its kind and the frame src\n * are derived from `url` at render (`resolveEmbed`), never stored.\n */\nexport interface PortableTextEmbed extends PortableTextObject {\n _type: \"embed\";\n url: string;\n title: string;\n aspect: \"16:9\" | \"4:3\" | \"1:1\" | \"9:16\";\n}\n\n/**\n * RT4: a gallery, as V2's GalleryModel. Each image is the rich-text image's own `{ src, alt }`\n * (+ an optional media id), so media rewrites, alt audits and CDN transforms read it the same way.\n * Limits and the write check: gallery.ts.\n */\nexport interface PortableTextGallery extends PortableTextObject {\n _type: \"gallery\";\n layout: \"grid\" | \"carousel\";\n images: {\n _key: string;\n /** The rich-text image's own shape, its RT9 framing (`crop`, `focal`; @bettercms-ai/image-url) included. */\n image: { src: string; alt: string; mediaId?: string; crop?: { top: number; right: number; bottom: number; left: number }; focal?: { x: number; y: number } } | null;\n caption?: string;\n }[];\n}\n\n/**\n * RT5: a table of contents, as V2's TocModel: a title (≤ 40) and which headings it lists. The\n * outline is derived from the value's headings at render (toc.ts) and never stored.\n */\nexport interface PortableTextTableOfContents extends PortableTextObject {\n _type: \"tableOfContents\";\n title?: string;\n depth: \"2\" | \"3\";\n}\n\n/**\n * RT6: a footnote, an inline object in body text (V2's `<sup data-rt=\"fn\" data-note>`). Only the\n * note is stored; its number, ids, the end list and the back links are derived (footnote.ts).\n */\nexport interface PortableTextFootnote {\n _type: \"footnote\";\n _key: string;\n note: string;\n}\n\n/**\n * RT7: columns, as V2's ColumnsModel: a gap and 2 to 4 columns, each a run of text blocks of its\n * own (depth 1). The column count is derived. Limits and the write check: columns.ts.\n */\nexport interface PortableTextColumns extends PortableTextObject {\n _type: \"columns\";\n gap: \"s\" | \"m\" | \"l\";\n columns: { _key: string; content: PortableTextBlock[] }[];\n}\n\n/** RT8: an author bio, a reference to the author's entry; name, avatar and bio are resolved at render (references.ts). */\nexport interface PortableTextAuthorBio extends PortableTextObject {\n _type: \"authorBio\";\n author?: { _ref: string };\n}\n\n/**\n * RT8: related posts: a title, how they are chosen (`topic`: shared topics; `newest`: the\n * collection's newest; `manual`: the hand-picked `posts`), and how many. Each post is resolved at render.\n */\nexport interface PortableTextRelatedPosts extends PortableTextObject {\n _type: \"relatedPosts\";\n title?: string;\n mode: \"topic\" | \"newest\" | \"manual\";\n count: number;\n /** The hand-picked posts. Absent is none, which is what an automatic (topic, newest) block holds. */\n posts?: { _key: string; _ref: string }[];\n}\n\nexport const isPortableTextBlock = (n: unknown): n is PortableTextBlock =>\n !!n && typeof n === \"object\" && (n as PortableTextNode)._type === \"block\";\n\nexport const isPortableTextSpan = (n: unknown): n is PortableTextSpan =>\n !!n && typeof n === \"object\" && (n as PortableTextNode)._type === \"span\";\n\n/**\n * The stored envelope.\n *\n * `value` is the source of truth; `html` is a DERIVED CACHE and is never trusted from a client\n * when `value` is present. The envelope survives rather than storing a bare array because\n * customer sites that shipped months ago read `.html` directly and cannot be recompiled —\n * `packages/sdk/src/richtext.ts` `isRichText()` even returns false for an array, so a bare\n * array would make every deployed `rich()` call return \"\" and blank the page at 200 OK.\n */\nexport const PORTABLE_TEXT_FORMAT = \"portable-text-1\" as const;\n\nexport interface RichTextEnvelope {\n format: typeof PORTABLE_TEXT_FORMAT;\n value: PortableText;\n html: string;\n /** RT3: what its embeds need beside `html` (the X cards), written at html-cache build. */\n embeds?: import(\"@bettercms-ai/types\").RichTextEmbeds;\n}\n\n/**\n * Our block-object and annotation type names, spelled once.\n *\n * Two repos serialise these and four renderers read them. A typo in any one of them is a node\n * that renders as nothing, with no error — the failure mode this whole migration exists to\n * stop — so the strings live here and nowhere else.\n */\nexport const PT_TYPES = {\n /** A generic embedded block: `{schemaKey, schemaLabel, fields}`, the successor to Lexical's BlockNode. */\n block: \"bcmsBlock\",\n /** An inline mention. A PT inline object is natively void, which is what Lexical's `token` mode was faking. */\n mention: \"bcmsMention\",\n divider: \"bcmsDivider\",\n code: \"bcmsCode\",\n /**\n * A video — an uploaded file or a provider URL. DEDICATED rather than another `schemaKey` on\n * the generic block, because a video is the one media node whose rendition depends on its own\n * payload: the same field is a `<video>` for an mp4 and a provider `<iframe>` for a YouTube\n * link (@see resolveVideoSource). The generic block carries a reference this package cannot\n * resolve; a video carries a URL it can.\n */\n video: \"video\",\n /** A downloadable document — a PDF, a spec sheet. `<a href download>`, nothing more. */\n file: \"file\",\n /** A pull quote — see `PortableTextPullQuote`. The V2 kit's `<figure data-rt=\"pullquote\">`. */\n pullQuote: \"pullQuote\",\n /** Key takeaways — see `PortableTextKeyTakeaways`. The V2 kit's `<aside data-rt=\"takeaways\">`. */\n keyTakeaways: \"keyTakeaways\",\n /**\n * A third-party embed (X, CodePen, Figma, Maps, Spotify, Podcasts, audio, a link card) — see\n * `PortableTextEmbed` and `embed.ts`. The V2 kit's `<figure data-rt=\"embed\">`. Gated by the\n * `embed` feature; YouTube and Vimeo links still resolve here too, through the video resolver.\n */\n embed: \"embed\",\n /** A gallery of images, a grid or a carousel — see `PortableTextGallery`. The V2 kit's `<figure data-rt=\"gallery\">`. */\n gallery: \"gallery\",\n /** A table of contents built from the headings — see `PortableTextTableOfContents`. The V2 kit's `<nav data-rt=\"toc\">`. */\n tableOfContents: \"tableOfContents\",\n /** A footnote, an INLINE object in body text — see `PortableTextFootnote`. The V2 kit's `<sup data-rt=\"fn\">`. */\n footnote: \"footnote\",\n /** Columns of text — see `PortableTextColumns`. The V2 kit's `<div data-rt=\"columns\">`; the legacy `builtin:columns` reads the same way. */\n columns: \"columns\",\n /** An author bio — see `PortableTextAuthorBio`. The V2 kit's `<aside data-rt=\"author\">`. */\n authorBio: \"authorBio\",\n /** Related posts — see `PortableTextRelatedPosts`. The V2 kit's `<aside data-rt=\"related\">`. */\n relatedPosts: \"relatedPosts\",\n /**\n * A table. Spelled `table`, unprefixed, because that is what every other Portable Text\n * producer calls one — the Sanity-shaped `{_type:\"table\", rows}` this platform used to refuse\n * as foreign content now lands on OUR type and renders, rather than being data nothing draws.\n */\n table: \"table\",\n /** The annotation carrying a hyperlink's `href`. Spelled `link` because that is the spec's convention. */\n link: \"link\",\n /**\n * The annotation carrying a site's OWN inline markup — `<span class>`, `<small>`, `<time\n * datetime>`. Unprefixed, like `table`, because it describes HTML rather than a BetterCMS\n * concept. See `styled.ts` for what it may hold and why the list is that short.\n */\n styled: \"styled\",\n} as const;\n\n/**\n * The `schemaKey` a table stored under BEFORE it had a type of its own.\n *\n * Every table written up to P1c is a generic `bcmsBlock` carrying a string matrix under this\n * key. Those rows are still in the database and nothing rewrites them, so this stays READABLE\n * forever: `migrateTable` (table.ts) turns one into a `table` on the way out, which is why the\n * reader, the renderer and the plain-text pass all still recognise it.\n *\n * Nothing WRITES it any more.\n */\nexport const BUILTIN_TABLE_KEY = \"builtin:table\" as const;\n\n/**\n * Every `_type` that may appear at the TOP LEVEL of a stored value.\n *\n * Closed on purpose, and short: a text block, the table, the two standalone block objects, and\n * the generic `bcmsBlock` envelope that carries an image, an embed or columns behind its\n * `schemaKey` (see `ptSchemaFor`). Anything else is content from another system and has no\n * renderer here.\n *\n * `table` joined the set in P1c, and it is the one member tested by SHAPE as well as by name —\n * see `isStorableTable`. The Sanity-shaped `{_type:\"table\", rows:[{cells}]}` that prompted this\n * list is the reason: refusing it was right while nothing could draw a table, and became wrong\n * the moment something could. `migrateTable` adopts that shape on read, so the write it used to\n * refuse now renders every cell.\n *\n * `bcmsMention` is included although it is an INLINE object: a mention that has somehow been\n * hoisted to the top level is ours, harmless, and refusing an author's save over it would be\n * the cure being worse than the disease.\n */\nexport const STORABLE_BLOCK_TYPES: ReadonlySet<string> = new Set<string>([\n \"block\",\n PT_TYPES.block,\n PT_TYPES.table,\n PT_TYPES.code,\n PT_TYPES.divider,\n PT_TYPES.mention,\n PT_TYPES.video,\n PT_TYPES.file,\n PT_TYPES.pullQuote,\n PT_TYPES.keyTakeaways,\n PT_TYPES.embed,\n PT_TYPES.gallery,\n PT_TYPES.tableOfContents,\n PT_TYPES.columns,\n PT_TYPES.authorBio,\n PT_TYPES.relatedPosts,\n]);\n\n/**\n * The `schemaKey`s the GENERIC block object carries for the two media kinds that keep it.\n *\n * Image and embed stay on `bcmsBlock` because thousands of stored values already spell them\n * that way and a migration buys nothing: unlike a video, their rendition is a straight read of\n * their own fields. Named here because this package now RENDERS them — it used to emit a marker\n * — and a renderer keyed on a string literal typed at the call site is the drift this file\n * exists to prevent.\n *\n * The embed's key is historically `builtin:video`: it is the \"Video embed\" block, and it\n * predates the dedicated `video` type by two years of content.\n */\nexport const BUILTIN_IMAGE_KEY = \"builtin:image\" as const;\nexport const BUILTIN_EMBED_KEY = \"builtin:video\" as const;\n/** A callout (`{ tone: info|tip|warning, title, message }`) and a toggle (`{ title, body }`): plain text, read straight. */\nexport const BUILTIN_CALLOUT_KEY = \"builtin:callout\" as const;\nexport const BUILTIN_TOGGLE_KEY = \"builtin:toggle\" as const;\n/**\n * A part of a body the SITE's own code renders: an MDX component (`<Figure>…</Figure>`, a chart, a\n * tabs widget) or an `{expression}`. Not content an author edits here: the page shows what the\n * build drew, publish never writes over it, and the editor shows it as a locked card. `fields.source`\n * keeps what the repo wrote, for the card; the delivered html carries only the empty marker.\n */\nexport const BUILTIN_SITE_KEY = \"builtin:site\" as const;\n\n/**\n * The `schemaKey` of a component embedded in rich text. A wire value shared with the dashboard\n * (`COMPONENT_SCHEMA_KEY`) and the backend (`RICHTEXT_COMPONENT_KEY`).\n */\nexport const EMBEDDED_COMPONENT_KEY = \"component\" as const;\n\n/**\n * Top-level nodes whose `_type` this platform cannot store or render, in document order.\n *\n * WHY THIS IS A WRITE-TIME CHECK AND NOT A RENDER-TIME SHRUG. An unrecognised block survives\n * the JSON round trip perfectly — it is data, and every reader here hands it back untouched —\n * but `portableTextToHtml` has no component for it, so it renders as NOTHING. The `html` on\n * the envelope is a cache that every delivery surface reads, so the node silently disappears\n * from the live site while the stored value still looks complete. That is the \"renders as\n * nothing at 200 OK\" failure this codebase keeps paying for, and the only place to catch it\n * cheaply is the write, where the author is still present to be told.\n *\n * Returns the offenders rather than a boolean so the refusal can NAME them — `_type` and\n * `_key` are what let an author find the block in a body of eighty.\n */\nexport function foreignBlocks(value: unknown): { _type: string; _key: string }[] {\n if (!Array.isArray(value)) return [];\n const out: { _type: string; _key: string }[] = [];\n for (const node of value) {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) continue;\n const type = (node as PortableTextNode)._type;\n if (typeof type !== \"string\") continue;\n // A table, a pull quote, key takeaways, a gallery and a table of contents are admitted by SHAPE, never by `_type` alone —\n // see `isStorableTable`.\n const storable = type === PT_TYPES.table\n ? isStorableTable(node)\n : type === PT_TYPES.pullQuote\n ? isStorablePullQuote(node)\n : type === PT_TYPES.keyTakeaways\n ? isStorableKeyTakeaways(node)\n : type === PT_TYPES.gallery\n ? isStorableGallery(node)\n : type === PT_TYPES.tableOfContents\n ? isStorableTableOfContents(node)\n : type === PT_TYPES.columns\n ? isStorableColumns(node)\n : type === PT_TYPES.relatedPosts\n ? // No `posts` is no picks: an automatic block (topic, newest) needs none.\n (node as { posts?: unknown }).posts === undefined || Array.isArray((node as { posts?: unknown }).posts)\n : STORABLE_BLOCK_TYPES.has(type);\n if (storable) continue;\n const key = (node as PortableTextNode)._key;\n out.push({ _type: type, _key: typeof key === \"string\" ? key : \"\" });\n }\n return out;\n}\n\n/**\n * Is this `_type:\"table\"` a table we can actually RENDER?\n *\n * 🔴 THE TYPE NAME IS NOT A CONTRACT. `table` is what every Portable Text producer calls a\n * table, and they do not agree on what is inside one. Admitting the name alone would let any\n * foreign `rows` through the write guard, and the reader — permissive by design, because a\n * reader that throws is a field that opens blank — would then coerce whatever it did not\n * recognise into EMPTY cells. That is the silent-emptying failure the guard exists to stop,\n * wearing our own type name.\n *\n * So exactly two shapes pass, tested cell by cell:\n * · ours — `cells: [{ content: PortableText }]`\n * · Sanity's — `cells: [\"a\", \"b\"]`, a row of strings\n *\n * Everything else is refused at the write, where the author is present to be told, and stays\n * readable through `migrateTable` for anything already stored.\n */\nexport function isStorableTable(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n const rows = (node as { rows?: unknown }).rows;\n if (!Array.isArray(rows)) return false;\n return rows.every((row) => {\n if (!row || typeof row !== \"object\" || Array.isArray(row)) return false;\n const cells = (row as { cells?: unknown }).cells;\n if (!Array.isArray(cells)) return false;\n return cells.every((cell) => {\n if (typeof cell === \"string\") return true;\n if (!cell || typeof cell !== \"object\" || Array.isArray(cell)) return false;\n const content = (cell as { content?: unknown }).content;\n return Array.isArray(content)\n && content.every((n) => !!n && typeof n === \"object\" && typeof (n as PortableTextNode)._type === \"string\");\n });\n });\n}\n\n/**\n * Is this `_type:\"pullQuote\"` one we can render?\n *\n * Same reason as the table: `pullQuote` is a name other producers use too, and a renderer that\n * met `{_type:\"pullQuote\", text}` would draw an empty figure. `quote` and `name` are strings\n * (either may be empty while an author is still typing); `role`, when present, is a string; and\n * `link`, when present, is a string or an object carrying a string `href`.\n */\nexport function isStorablePullQuote(node: unknown): boolean {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return false;\n const v = node as Record<string, unknown>;\n if (typeof v.quote !== \"string\" || typeof v.name !== \"string\") return false;\n if (v.role !== undefined && typeof v.role !== \"string\") return false;\n if (v.link === undefined || typeof v.link === \"string\") return true;\n return !!v.link && typeof v.link === \"object\" && typeof (v.link as { href?: unknown }).href === \"string\";\n}\n\n/**\n * Is this `_type:\"keyTakeaways\"` one we can render?\n *\n * Same reason as the table: the name alone is not a contract, and a renderer that met another\n * producer's `{_type:\"keyTakeaways\", items}` would draw an empty aside. Ours carries `points`, an\n * array. What is IN it, the title and the limits are judged by `keyTakeawaysProblems`\n * (key-takeaways.ts), which names each problem with a code; this only decides whether the node is\n * ours at all.\n */\nexport function isStorableKeyTakeaways(node: unknown): boolean {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return false;\n return Array.isArray((node as { points?: unknown }).points);\n}\n\n/** An embed holds a link, a title of at most 120 characters, and one of V2's four shapes. */\nexport function isStorableEmbed(node: unknown): boolean {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return false;\n const n = node as { url?: unknown; title?: unknown; aspect?: unknown };\n return typeof n.url === \"string\" && typeof n.title === \"string\" && n.title.length <= 120\n && (n.aspect === \"16:9\" || n.aspect === \"4:3\" || n.aspect === \"1:1\" || n.aspect === \"9:16\");\n}\n\n/**\n * Is this `_type:\"gallery\"` one we can render? An `images` array; what is in it and the limits\n * are judged by `galleryProblems` (gallery.ts), which names each problem with a code.\n */\nexport function isStorableGallery(node: unknown): boolean {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return false;\n return Array.isArray((node as { images?: unknown }).images);\n}\n\n/** Is this `_type:\"columns\"` one we can render? A `columns` array whose entries hold a `content` array; the rest is `columnsProblems`'. */\nexport function isStorableColumns(node: unknown): boolean {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return false;\n const columns = (node as { columns?: unknown }).columns;\n return Array.isArray(columns) && columns.every((c) => !!c && typeof c === \"object\" && Array.isArray((c as { content?: unknown }).content));\n}\n\n/** Is this `_type:\"tableOfContents\"` one we can render? One of V2's two depths; the title is judged by `tableOfContentsProblems`. */\nexport function isStorableTableOfContents(node: unknown): boolean {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return false;\n const depth = (node as { depth?: unknown }).depth;\n return depth === \"2\" || depth === \"3\";\n}\n\n/**\n * @deprecated The marker a table-accepting field used to carry INSTEAD of a table.\n *\n * It existed because a table stored as a generic block object, so `ptSchemaFor` had nothing\n * table-shaped to gate the HTML ingest on. `PT_TYPES.table` is that thing now, and it is a real\n * block object rather than a marker, so the ingest asks for it by name and nothing emits this.\n *\n * Kept exported for one release: the dashboard filters it out of the schema it hands\n * `defineSchema` (`src/lib/richtext/ptSchema.ts`), and removing the export would fail that\n * import rather than make the filter a harmless no-op. Delete it once that filter is gone.\n */\nexport const TABLE_CAPABILITY = \"bcmsTableCapability\" as const;\n","/**\n * The `styled` annotation — a site's own inline markup, carried through Portable Text.\n *\n * WHY IT EXISTS. An imported site writes `<h1>Notes from a <span class=\"serif italic\">web\n * studio</span></h1>`. Portable Text models `strong`/`em`/`underline`/`strike`/`code`/\n * `highlight` and `link`, and NOTHING ELSE inline — so that span had no node to become,\n * `unknownMark` rendered its children, and the class was gone the first time an author saved.\n * That loss is why `derive-schema` deliberately refused to bind any heading containing a\n * `<span class>`: a field that destroys the design on save is worse than no field at all.\n *\n * So this is NOT an authoring mark. There is no toolbar button and no editing UI: it is the\n * site's design, recorded so the round trip is lossless, and it rides along with whatever the\n * author actually types inside it.\n *\n * ── The attribute allowlist IS the threat model ──────────────────────────────────────────\n * A markDef is data, and data reaches this platform from an import, the Management API, MCP\n * and AI writes. Rendering an arbitrary attribute bag would be an XSS primitive wearing a\n * design system's clothes, so exactly four names plus `data-*` are admitted, and the render\n * escapes every value. `id` is refused because it is an addressing surface (a duplicate id\n * breaks anchors and label association), `data-bcms-*` because those attributes ARE the Visual\n * Editor's binding addresses and content must not be able to mint one. `style` is admitted but\n * NOT passed through — its value is a language of its own, bounded by `safeStyle` below.\n */\n\n/**\n * Inline tags a `styled` annotation may wear. Nothing outside this list is ever rendered.\n *\n * The decorator tags (`strong` … `code`) are here for the one case their decorator cannot\n * hold: the site put a CLASS on them (`<strong class=\"text-yellow-500\">`). A decorator is a\n * bit with nowhere to keep a class, so without this the design died on ingest — which is what\n * made agents rewrite a site's markup to dodge the loss. A bare `<strong>` still becomes the\n * decorator (see `BARE_IS_DESIGN`).\n */\nexport const STYLED_TAGS = [\n \"span\", \"small\", \"sup\", \"sub\", \"kbd\", \"abbr\", \"time\",\n \"strong\", \"b\", \"em\", \"i\", \"u\", \"s\", \"mark\", \"code\", \"q\", \"cite\", \"dfn\", \"var\", \"samp\", \"ins\", \"del\",\n] as const;\n\n/**\n * Tags whose bare form IS the design, so they are annotated even with no class: every styled tag\n * that is neither a `<span>` nor a tag a decorator already holds. A keycap, a citation or a\n * deleted price dropped to bare words is the page restyled by the author's first save.\n */\nexport const BARE_IS_DESIGN: ReadonlySet<string> = new Set([\n \"small\", \"sup\", \"sub\", \"kbd\", \"abbr\", \"time\", \"q\", \"cite\", \"dfn\", \"var\", \"samp\", \"ins\", \"del\",\n]);\n\nexport type StyledTag = (typeof STYLED_TAGS)[number];\n\nconst TAG_SET: ReadonlySet<string> = new Set<string>(STYLED_TAGS);\n\nexport const isStyledTag = (tag: unknown): tag is StyledTag =>\n typeof tag === \"string\" && TAG_SET.has(tag);\n\n/**\n * Attribute names a `styled` annotation may carry.\n *\n * Presentational or descriptive only. Never `href`/`src` (a navigation or fetch surface),\n * never `id` (an addressing surface), never `on*` (script), and never `data-bcms-*` — those\n * are binding addresses the editor reads, and content that could write one could point an\n * author's edit at somebody else's field.\n */\nconst ATTR_NAMES: ReadonlySet<string> = new Set([\"title\", \"datetime\", \"style\", \"aria-label\"]);\n\nconst DATA_ATTR = /^data-[a-z][a-z0-9-]*$/;\n\nexport const isStyledAttr = (name: string): boolean =>\n ATTR_NAMES.has(name) || (DATA_ATTR.test(name) && !name.startsWith(\"data-bcms-\"));\n\n/**\n * CSS an inline run may carry: how the words LOOK inside their own box, and nothing else.\n *\n * `style` is the one admitted attribute whose value is itself a language, so the tag allowlist\n * buys nothing here — a bare passthrough lets any markDef write\n * `position:fixed;inset:0;z-index:9999`, which is a full-viewport overlay over the published\n * page (clickjacking, defacement) minted by anyone who can write content, and\n * `background:url(https://…)` which fetches a third-party URL on every page view. Neither is a\n * design an import needs, so neither is on the list. Properties that only paint glyphs are.\n *\n * Values are checked as well as names, because `url()`, a CSS escape (`\\75 rl(`), a comment and\n * `expression()` are all ways to smuggle a fetch past a name check.\n */\nconst STYLE_PROPS: ReadonlySet<string> = new Set([\n \"color\", \"background-color\", \"opacity\",\n \"font\", \"font-family\", \"font-size\", \"font-style\", \"font-weight\", \"font-variant\",\n \"font-feature-settings\", \"font-variation-settings\",\n \"letter-spacing\", \"line-height\", \"word-spacing\", \"white-space\", \"word-break\",\n \"text-align\", \"text-decoration\", \"text-decoration-color\", \"text-decoration-line\",\n \"text-decoration-style\", \"text-transform\", \"text-underline-offset\", \"vertical-align\",\n]);\n\nconst STYLE_VALUE_BAN = /url\\(|expression\\(|@import|\\\\|\\/\\*|[<>]/i;\n\n/**\n * The declarations of a `style` attribute that survive, rejoined — or undefined when none do.\n * Custom properties (`--brand`) are kept: they paint nothing on their own, and a site's own CSS\n * is what decides whether they mean anything.\n */\nexport function safeStyle(css: string): string | undefined {\n const kept: string[] = [];\n for (const declaration of css.split(\";\")) {\n const at = declaration.indexOf(\":\");\n if (at < 0) continue;\n const name = declaration.slice(0, at).trim().toLowerCase();\n const value = declaration.slice(at + 1).trim();\n if (!name || !value) continue;\n if (!name.startsWith(\"--\") && !STYLE_PROPS.has(name)) continue;\n if (STYLE_VALUE_BAN.test(value)) continue;\n kept.push(`${name}:${value}`);\n }\n return kept.length ? kept.join(\";\") : undefined;\n}\n\n/** The annotation's payload, as stored. `tag` is validated again at render time. */\nexport interface StyledMarkDef {\n _type: \"styled\";\n _key: string;\n tag: StyledTag;\n className?: string;\n attrs?: Record<string, string>;\n}\n\n/**\n * The admitted attributes of an element, in source order, or undefined when there are none.\n *\n * `class` is deliberately absent — it lives on the markDef as `className`, because it is the\n * one attribute every consumer of this annotation needs to reach without parsing a bag.\n */\nexport function styledAttrsOf(el: Element): Record<string, string> | undefined {\n const out: Record<string, string> = {};\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n if (name === \"class\" || !isStyledAttr(name)) continue;\n const value = name === \"style\" ? safeStyle(attr.value) : attr.value;\n if (value === undefined) continue;\n out[name] = value;\n }\n return Object.keys(out).length ? out : undefined;\n}\n\n/**\n * The same filter applied to a STORED bag, which may have arrived from anywhere.\n *\n * Both ends, and this is the end that matters: a markDef reaches storage from an import, the\n * Management API, MCP and AI writes, so the ingest filter is a courtesy and this one is the\n * control. Every renderer of the annotation goes through here.\n */\nexport function styledAttrEntries(attrs: unknown): [string, string][] {\n if (!attrs || typeof attrs !== \"object\" || Array.isArray(attrs)) return [];\n const out: [string, string][] = [];\n for (const [name, raw] of Object.entries(attrs as Record<string, unknown>)) {\n if (!isStyledAttr(name) || typeof raw !== \"string\") continue;\n const value = name === \"style\" ? safeStyle(raw) : raw;\n if (value === undefined) continue;\n out.push([name, value]);\n }\n return out;\n}\n","/**\n * The URL allowlist, lifted verbatim from `src/lib/richtext/notion-doc.ts:232-243`.\n *\n * It moves here rather than being re-derived because Portable Text turns every hyperlink into\n * a `link` markDef, and markDefs are written by four ingest paths (paste, the Management API,\n * MCP, markdown). One shared check is the difference between \"javascript: is blocked\" and\n * \"javascript: is blocked on the path we remembered to guard\".\n */\nconst SAFE_HREF_SCHEMES = [\"http:\", \"https:\", \"mailto:\", \"tel:\"];\n\n/** The href if it is safe to emit, otherwise null. Callers render the label as plain text. */\nexport function safeHref(raw: string): string | null {\n const trimmed = raw.trim();\n // An in-page FRAGMENT (`#where-it-comes-from`) can only move within the page, so it is as safe\n // as a site-relative path. Refusing it cost every imported Contents list its links: the\n // importer's Lexical anchors were dropped on conversion, on ingest and on render alike\n // (astro-sumi, 2026-09-14). No whitespace or markup characters, so it cannot break out of the\n // attribute it is written into.\n if (trimmed.startsWith(\"#\")) return /^#[^\\s<>\"'`]*$/.test(trimmed) ? trimmed : null;\n // A site-relative path has no scheme to check and cannot navigate off-origin.\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) return trimmed;\n try {\n return SAFE_HREF_SCHEMES.includes(new URL(trimmed).protocol) ? trimmed : null;\n } catch {\n return null;\n }\n}\n","/**\n * `_key` minting.\n *\n * THE CHARSET IS LOAD-BEARING, not cosmetic. A `_key` ends up inside a Visual Editor address\n * (`<blockId>__<prop>#<key>`), and that address is parsed by splitting on `__`, `.`, `[`, `]`\n * and `#`. A key containing any of those characters would silently retarget an edit to the\n * wrong node, so we mint from `[A-Za-z0-9-]` only — no underscore, no dot, no separator.\n *\n * Foreign keys are RE-MINTED, never trusted: imported content and pasted HTML can carry\n * anything, and the address grammar is not a place to find out.\n *\n * Determinism matters at migration time. Converting a field twice — once for `data`, once for\n * `published_data` — with random keys makes two semantically identical copies compare unequal,\n * which lights up the change feed and the parity check on every entry in the project. The\n * backfill converts once and writes the same object to both columns; `deterministicKeys()`\n * exists for the cases that cannot.\n */\nconst ALPHABET = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\nexport const KEY_PATTERN = /^[A-Za-z0-9-]{1,32}$/;\n\nexport const isValidKey = (k: unknown): k is string =>\n typeof k === \"string\" && KEY_PATTERN.test(k);\n\n/** A fresh random key. 12 chars of this alphabet is ~71 bits; collisions are not a concern. */\nexport function randomKey(length = 12): string {\n let out = \"\";\n for (let i = 0; i < length; i += 1) {\n out += ALPHABET[Math.floor(Math.random() * ALPHABET.length)];\n }\n return out;\n}\n\n/**\n * A counter-based generator, for conversions that must be byte-reproducible.\n *\n * Same input, same keys, every run — so a value converted for the draft column and the\n * published column produces identical output and `jsonbEqual` still says they match.\n */\nexport function deterministicKeys(prefix = \"k\"): () => string {\n let n = 0;\n return () => {\n n += 1;\n return `${prefix}${n}`;\n };\n}\n\nexport type KeyGenerator = () => string;\n","import {\n BUILTIN_TABLE_KEY,\n PT_TABLE_COLUMN_TYPES,\n PT_TYPES,\n type PortableText,\n type PortableTextTable,\n type PortableTextTableCell,\n type PortableTextTableColumn,\n type PortableTextTableColumnType,\n type PortableTextTableGroup,\n type PortableTextTableDesign,\n type PortableTextTableFootnote,\n type PortableTextTableJsonLd,\n type PortableTextTableRow,\n} from \"./types\";\nimport { deterministicKeys, isValidKey, randomKey, type KeyGenerator } from \"./key\";\n\n/**\n * The one table, and every older spelling of it.\n *\n * THREE SHAPES REACH THIS FILE, and only one of them is written any more:\n *\n * `{_type:\"table\", rows:[{cells:[{content}]}]}` ours, since P1c\n * `bcmsBlock` + `schemaKey:\"builtin:table\"` every table stored BEFORE P1c: a string\n * matrix under `fields.rows`, plus `hasHeader`\n * `{_type:\"table\", rows:[{cells:[\"a\",\"b\"]}]}` content from another system (Sanity's is the\n * one found in this corpus): string cells\n *\n * They converge HERE rather than at each of the four readers, because the alternative is what\n * this package was created to end — four places deciding independently what a stored value means,\n * and a table rendering on one surface and vanishing on another. Nothing rewrites the database:\n * a legacy row is migrated on the way OUT, every time it is read.\n *\n * A string cell becomes one paragraph holding one span. That is lossless in the direction that\n * matters — the matrix never held marks to lose — and it means the rest of the pipeline has\n * exactly one cell shape to render, not two.\n *\n * ── 🔴 KEYS ARE DERIVED, NEVER MINTED AT RANDOM ──────────────────────────────────────────\n * This runs on EVERY READ of a value nothing rewrites, so a random `_key` here is a different\n * key each time the same row is read. Three things break at once: React remounts every cell on\n * every render because its `key` changed; a Visual Editor address minted against one read points\n * at nothing on the next; and the migration backfill converting `data` and `published_data`\n * produces two values that compare unequal, lighting up the change feed for every entry in the\n * project — the exact trap `deterministicKeys` was added for.\n *\n * So a migrated table's keys come from the table's OWN `_key` plus the row and cell INDEX, and\n * any key the value already carries is kept and reserved so a derived one cannot collide with it.\n * Same value in, same keys out, on every read, in every process.\n */\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\nexport const isPortableTextTable = (n: unknown): n is PortableTextTable =>\n isRecord(n) && n._type === PT_TYPES.table && Array.isArray(n.rows);\n\n/** A table as it was stored before it had a type: the generic block object under its schemaKey. */\nexport const isBuiltInTableBlock = (n: unknown): boolean =>\n isRecord(n) && n._type === PT_TYPES.block && n.schemaKey === BUILTIN_TABLE_KEY;\n\n/**\n * How a cell's `content` is read back.\n *\n * Injected rather than imported so this file stays free of `read.ts` — the reader already\n * depends on the table, and a cycle between the two is the kind of thing that resolves at type\n * level and explodes at runtime. The reader passes `normalizePortableText`; a caller with no\n * reader (the renderer, the plain-text pass) gets the identity, which is all a legacy string\n * matrix ever needs.\n *\n * It takes the cell's OWN key generator, so a block inside a cell that arrives without a `_key`\n * is keyed from the cell rather than from the document's random minter.\n */\nexport type CellContentReader = (value: unknown, key: KeyGenerator) => PortableText;\n\nconst asIs: CellContentReader = (v) => (Array.isArray(v) ? (v as PortableText) : []);\n\n/**\n * `<table key>-r0c1` — deterministic, unique among siblings, and inside `KEY_PATTERN`.\n *\n * The base is TRUNCATED because a `_key` is capped at 32 characters and an address is parsed by\n * splitting on the separators; 16 leaves room for `-r99c99-9`, which is longer than any table a\n * person writes. A collision with a key the value already carries is resolved by suffixing,\n * never by falling back to randomness.\n */\nfunction keyMinter(base: string, reserved: Set<string>) {\n const prefix = (base.match(/[A-Za-z0-9-]/g) ?? []).join(\"\").slice(0, 16) || \"t\";\n return (...parts: (string | number)[]): string => {\n const candidate = `${prefix}-${parts.join(\"\")}`.slice(0, 30);\n let key = candidate;\n for (let n = 1; reserved.has(key); n += 1) key = `${candidate}-${n}`;\n reserved.add(key);\n return key;\n };\n}\n\n/** Every `_key` the stored value already carries, so a derived key cannot land on one. */\nfunction reservedKeys(rows: unknown[]): Set<string> {\n const out = new Set<string>();\n for (const row of rows) {\n if (!isRecord(row)) continue;\n if (isValidKey(row._key)) out.add(row._key);\n for (const cell of Array.isArray(row.cells) ? row.cells : []) {\n if (isRecord(cell) && isValidKey(cell._key)) out.add(cell._key);\n }\n }\n return out;\n}\n\n/** One line of text as the single block a cell holds. Empty text holds NO block, not an empty one. */\nconst textContent = (text: string, key: KeyGenerator): PortableText =>\n text === \"\"\n ? []\n : [{\n _type: \"block\",\n _key: key(),\n style: \"normal\",\n markDefs: [],\n children: [{ _type: \"span\", _key: key(), text, marks: [] }],\n }];\n\n/** A positive integer span, or nothing. A `colspan=\"0\"` or `\"two\"` is not a span. */\nconst spanOf = (value: unknown): number | undefined => {\n const n = typeof value === \"number\" ? value : Number(value);\n return Number.isInteger(n) && n > 1 ? n : undefined;\n};\n\ntype Mint = (...parts: (string | number)[]) => string;\n\nfunction normalizeCell(raw: unknown, at: [number, number], mint: Mint, read: CellContentReader): PortableTextTableCell {\n const [row, col] = at;\n const key = isRecord(raw) && isValidKey(raw._key) ? raw._key : mint(\"r\", row, \"c\", col);\n // The cell's own generator: a block inside it that has no key is keyed from the CELL, so the\n // whole subtree is reproducible from the stored value alone.\n const inner = deterministicKeys(`${key}-`);\n\n if (typeof raw === \"string\" || typeof raw === \"number\" || typeof raw === \"boolean\") {\n return { _key: key, content: textContent(String(raw), inner) };\n }\n if (!isRecord(raw)) return { _key: key, content: [] };\n\n const span = spanOf(raw.span ?? raw.colSpan ?? raw.colspan);\n const rowSpan = spanOf(raw.rowSpan ?? raw.rowspan);\n return {\n _key: key,\n // `text` is the spelling a string matrix uses once its cells have been given objects; it is\n // read here so a half-migrated value cannot lose its words.\n content: Array.isArray(raw.content)\n ? read(raw.content, inner)\n : textContent(typeof raw.text === \"string\" ? raw.text : \"\", inner),\n ...(raw.header === true ? { header: true } : {}),\n ...(span === undefined ? {} : { span }),\n ...(rowSpan === undefined ? {} : { rowSpan }),\n ...(typeof raw.group === \"string\" ? { group: raw.group } : {}),\n ...(raw.highlight === true ? { highlight: true } : {}),\n ...(raw.sortable === true ? { sortable: true } : {}),\n // The V2 table's typed cells: the column (first-row cell), the per-cell type, the typed value.\n ...(columnOf(raw.column) ? { column: columnOf(raw.column) } : {}),\n ...(isColumnType(raw.type) ? { type: raw.type } : {}),\n ...(typeof raw.value === \"boolean\" || (typeof raw.value === \"number\" && Number.isFinite(raw.value)) ? { value: raw.value } : {}),\n ...(typeof raw.note === \"string\" && raw.note ? { note: raw.note } : {}),\n };\n}\n\nfunction normalizeRow(raw: unknown, at: number, mint: Mint, read: CellContentReader): PortableTextTableRow {\n // A bare array IS a row: that is how the legacy matrix spells one, and how a hand-written\n // value most often arrives.\n const cells = Array.isArray(raw) ? raw : isRecord(raw) && Array.isArray(raw.cells) ? raw.cells : [];\n return {\n _key: isRecord(raw) && isValidKey(raw._key) ? raw._key : mint(\"r\", at),\n cells: cells.map((c, col) => normalizeCell(c, [at, col], mint, read)),\n ...(isRecord(raw) && raw.head === true ? { head: true } : {}),\n ...(isRecord(raw) && typeof raw.group === \"string\" ? { group: raw.group } : {}),\n // The V2 table's two row flags (\"Totals row\", \"Highlight this row\"); see `PortableTextTableRow`.\n ...(isRecord(raw) && raw.footer === true ? { footer: true } : {}),\n ...(isRecord(raw) && raw.highlight === true ? { highlight: true } : {}),\n ...(isRecord(raw) && isColumnType(raw.type) ? { type: raw.type } : {}),\n };\n}\n\n/**\n * A pre-P1c `builtin:table` block → the table.\n *\n * `hasHeader` defaults TRUE when absent, exactly as the renderer it replaces did, so a value\n * written before that flag existed keeps the header row it has always displayed. It is a WHOLE\n * ROW of headers, which is the only header the string matrix could express.\n */\nexport function builtInTableToTable(node: unknown, key: KeyGenerator = randomKey): PortableTextTable | null {\n if (!isBuiltInTableBlock(node)) return null;\n const fields = (node as { fields?: unknown }).fields;\n const raw = isRecord(fields) && Array.isArray(fields.rows) ? fields.rows : [];\n const hasHeader = isRecord(fields) ? fields.hasHeader !== false : true;\n const own = (node as { _key?: unknown })._key;\n const tableKey = isValidKey(own) ? own : key();\n const mint = keyMinter(tableKey, reservedKeys(raw));\n\n const rows = raw.map((row, i) => {\n const normalized = normalizeRow(row, i, mint, asIs);\n if (!(hasHeader && i === 0)) return normalized;\n return {\n ...normalized,\n head: true,\n cells: normalized.cells.map((c) => ({ ...c, header: true })),\n };\n });\n\n // 🔴 SQUARE THE GRID. The renderer this replaces padded every row to the widest one, so a\n // ragged matrix has always DISPLAYED as a full rectangle. Migrating without the padding drops\n // those cells from the page — a value that rendered five columns rendering four.\n const width = rows.reduce((max, r) => Math.max(max, r.cells.length), 0);\n for (const [i, row] of rows.entries()) {\n while (row.cells.length < width) {\n row.cells.push({\n _key: mint(\"r\", i, \"c\", row.cells.length),\n content: [],\n ...(row.head === true ? { header: true } : {}),\n });\n }\n }\n return { _type: PT_TYPES.table, _key: tableKey, rows, legacy: true };\n}\n\n/**\n * Any stored table shape → the one table, or `null` when the node is not a table at all.\n *\n * Returning `null` rather than throwing or coercing is what lets the reader call this on EVERY\n * node cheaply: a non-table answers no, and the caller carries on.\n *\n * PERMISSIVE ON READ, by design: a shape it does not recognise reads as far as it can rather\n * than throwing, because a reader that throws is a field that opens blank. The strict test lives\n * on the WRITE, where the author is present to be told — see `isStorableTable` in `types.ts`.\n */\nexport function migrateTable(\n node: unknown,\n key: KeyGenerator = randomKey,\n read: CellContentReader = asIs,\n): PortableTextTable | null {\n const builtIn = builtInTableToTable(node, key);\n if (builtIn) return builtIn;\n if (!isPortableTextTable(node)) return null;\n const rows = node.rows as unknown[];\n const tableKey = isValidKey(node._key) ? node._key : key();\n const mint = keyMinter(tableKey, reservedKeys(rows));\n return {\n _type: PT_TYPES.table,\n _key: tableKey,\n rows: rows.map((r, i) => normalizeRow(r, i, mint, read)),\n // Carried through: a migrated table that an author saves back is still the same table, and\n // dropping the flag on that save would change the page the next time it renders.\n ...(node.legacy === true ? { legacy: true } : {}),\n ...tableExtras(node),\n };\n}\n\n/**\n * Caption, notes and the two group lists, carried through the reader. Each is written only when\n * the stored value has it, so a table without them reads back byte for byte as it did before\n * they existed. Permissive like the rest of the read: a group entry that is not an object with a\n * string `_key` is skipped (nothing could name it), and a missing label reads as \"\". The write\n * refuses all of it by name — see `tableProblems`.\n */\nfunction tableExtras(node: Record<string, unknown>): Partial<PortableTextTable> {\n const groups = (list: unknown): PortableTextTableGroup[] | undefined =>\n Array.isArray(list)\n ? list\n .filter((g): g is Record<string, unknown> => isRecord(g) && typeof g._key === \"string\" && g._key !== \"\")\n .map((g) => ({ _key: g._key as string, label: typeof g.label === \"string\" ? g.label : \"\" }))\n : undefined;\n const rowGroups = groups(node.rowGroups);\n const columnGroups = groups(node.columnGroups);\n return {\n ...(typeof node.caption === \"string\" ? { caption: node.caption } : {}),\n ...(typeof node.notes === \"string\" ? { notes: node.notes } : {}),\n ...(rowGroups ? { rowGroups } : {}),\n ...(columnGroups ? { columnGroups } : {}),\n ...(node.filterable === true ? { filterable: true } : {}),\n ...(jsonLdOf(node.jsonLd) ? { jsonLd: jsonLdOf(node.jsonLd) } : {}),\n ...(designOf(node.design) ? { design: designOf(node.design) } : {}),\n ...(footnotesOf(node.footnotes) ? { footnotes: footnotesOf(node.footnotes) } : {}),\n };\n}\n\n/** Stored footnotes read permissively: entries with a string `_key`, their text (\"\" when missing); none is nothing. */\nexport function footnotesOf(v: unknown): PortableTextTableFootnote[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out = v\n .filter((f): f is Record<string, unknown> => isRecord(f) && typeof f._key === \"string\" && f._key !== \"\")\n .map((f) => ({ _key: f._key as string, text: typeof f.text === \"string\" ? f.text : \"\" }));\n return out.length ? out : undefined;\n}\n\n/** footnote `_key` → its number (1-based, in order), as V2's footnoteIndex. */\nexport function footnoteNumbers(table: PortableTextTable): Map<string, number> {\n return new Map((footnotesOf(table.footnotes) ?? []).map((f, i) => [f._key, i + 1]));\n}\n\nconst DESIGN_KEYS = new Set([\"responsive\", \"sticky\"]);\n\n/**\n * A stored `design` read permissively, as V2's parseTableElement reads the figure: `responsive` only when `stack` or `cards`\n * (`scroll` is the default and is not kept), `sticky` only when true. Nothing left is nothing.\n */\nexport function designOf(v: unknown): PortableTextTableDesign | undefined {\n if (!isRecord(v)) return undefined;\n const d: PortableTextTableDesign = {\n ...(v.responsive === \"stack\" || v.responsive === \"cards\" ? { responsive: v.responsive } : {}),\n ...(v.sticky === true ? { sticky: true } : {}),\n };\n return Object.keys(d).length ? d : undefined;\n}\n\nconst JSONLD_TYPES = [\"ItemList\", \"Dataset\", \"Offers\"] as const;\nconst JSONLD_KEYS = new Set([\"type\", \"axis\", \"priceRow\"]);\n\n/** A stored `jsonLd` read permissively: a type this table offers, its axis and price row when well formed, else nothing. */\nexport function jsonLdOf(v: unknown): PortableTextTableJsonLd | undefined {\n if (!isRecord(v) || !(JSONLD_TYPES as readonly unknown[]).includes(v.type)) return undefined;\n return {\n type: v.type as PortableTextTableJsonLd[\"type\"],\n ...(v.axis === \"rows\" || v.axis === \"columns\" ? { axis: v.axis } : {}),\n ...(typeof v.priceRow === \"string\" && v.priceRow ? { priceRow: v.priceRow } : {}),\n };\n}\n\n/**\n * The limits a table's caption, notes and group labels are held to — the V2 table's\n * (`TABLE_LIMITS` in `@bettercms-ai/types` table-value.ts), restated so this package keeps no\n * dependency on that one.\n */\nexport const PT_TABLE_LIMITS = { captionChars: 300, notesChars: 2000, labelChars: 300, keyChars: 64 } as const;\n\n/** One reason a stored table is refused, at its own path inside the table. The V2 table's shape. */\nexport type PortableTextTableProblem = { path: Array<string | number>; code: string; message: string };\n\nconst GROUP_KEYS = new Set([\"_key\", \"label\"]);\n\n/**\n * Every reason the caption, notes and groups of `node` cannot be stored, each named with the V2\n * table's codes (`table_value_invalid`, `table_limit_exceeded`) and its path inside the table.\n *\n * 🔴 ONLY THE NEW KEYS ARE JUDGED. A table that carries none of them answers `[]` whatever else it\n * holds, so every table stored before they existed keeps saving exactly as it did. None of these\n * checks reads field config, so there is nothing to grandfather.\n */\nexport function tableProblems(node: unknown): PortableTextTableProblem[] {\n const out: PortableTextTableProblem[] = [];\n if (!isRecord(node)) return out;\n const bad = (path: Array<string | number>, message: string, code = \"table_value_invalid\") =>\n out.push({ path, code, message: `${code}: ${message}` });\n const text = (v: unknown, at: Array<string | number>, what: string, max: number, required = false) => {\n if (v === undefined && !required) return;\n if (typeof v !== \"string\") bad(at, `${what} must be a string`);\n else if (v.length > max) bad(at, `${what} is ${v.length} characters; at most ${max}`, \"table_limit_exceeded\");\n };\n text(node.caption, [\"caption\"], \"caption\", PT_TABLE_LIMITS.captionChars);\n text(node.notes, [\"notes\"], \"notes\", PT_TABLE_LIMITS.notesChars);\n if (node.filterable !== undefined && typeof node.filterable !== \"boolean\") bad([\"filterable\"], \"filterable must be true or false\");\n if (node.design !== undefined) {\n const d = node.design;\n if (!isRecord(d)) bad([\"design\"], \"design must be an object { responsive?, sticky? }\");\n else {\n for (const k of Object.keys(d)) if (!DESIGN_KEYS.has(k)) bad([\"design\", k], `unknown key \"${k}\" (responsive, sticky)`);\n if (d.responsive !== undefined && ![\"scroll\", \"stack\", \"cards\"].includes(d.responsive as string)) bad([\"design\", \"responsive\"], 'responsive must be \"scroll\", \"stack\" or \"cards\"');\n if (d.sticky !== undefined && typeof d.sticky !== \"boolean\") bad([\"design\", \"sticky\"], \"sticky must be true or false\");\n }\n }\n if (node.jsonLd !== undefined) {\n const j = node.jsonLd;\n if (!isRecord(j)) bad([\"jsonLd\"], \"jsonLd must be an object { type, axis?, priceRow? }\");\n else {\n for (const k of Object.keys(j)) if (!JSONLD_KEYS.has(k)) bad([\"jsonLd\", k], `unknown key \"${k}\" (type, axis, priceRow)`);\n if (!(JSONLD_TYPES as readonly unknown[]).includes(j.type)) bad([\"jsonLd\", \"type\"], `type must be one of ${JSONLD_TYPES.join(\", \")} (leave jsonLd out for none)`);\n if (j.axis !== undefined && j.axis !== \"rows\" && j.axis !== \"columns\") bad([\"jsonLd\", \"axis\"], 'axis must be \"rows\" or \"columns\"');\n if (j.priceRow !== undefined) {\n const keys = (Array.isArray(node.rows) ? node.rows : []).map((r) => (isRecord(r) ? r._key : undefined));\n if (typeof j.priceRow !== \"string\" || !keys.includes(j.priceRow)) bad([\"jsonLd\", \"priceRow\"], \"priceRow must be the _key of one of the table's rows\");\n }\n }\n }\n\n const keyed = (list: unknown, at: \"rowGroups\" | \"columnGroups\"): Set<string> => {\n const seen = new Set<string>();\n if (list === undefined) return seen;\n if (!Array.isArray(list)) {\n bad([at], `${at} must be an array of { _key, label }`);\n return seen;\n }\n list.forEach((g, i) => {\n if (!isRecord(g)) return bad([at, i], `${at}[${i}] must be an object { _key, label }`);\n for (const k of Object.keys(g)) if (!GROUP_KEYS.has(k)) bad([at, i, k], `unknown key \"${k}\" (_key, label)`);\n if (typeof g._key !== \"string\" || g._key === \"\" || g._key.length > PT_TABLE_LIMITS.keyChars) {\n bad([at, i, \"_key\"], `needs a _key string of 1 to ${PT_TABLE_LIMITS.keyChars} characters`);\n } else if (seen.has(g._key)) bad([at, i, \"_key\"], `_key \"${g._key}\" is used twice`);\n else seen.add(g._key);\n text(g.label, [at, i, \"label\"], \"label\", PT_TABLE_LIMITS.labelChars, true);\n });\n return seen;\n };\n const rowGroups = keyed(node.rowGroups, \"rowGroups\");\n const columnGroups = keyed(node.columnGroups, \"columnGroups\");\n // Footnotes: [{ _key, text }], the field-lane table's (`text` held to the note's limit)\n const footnoteKeys = new Set<string>();\n if (node.footnotes !== undefined) {\n if (!Array.isArray(node.footnotes)) bad([\"footnotes\"], \"footnotes must be an array of { _key, text }\");\n else\n node.footnotes.forEach((f, i) => {\n if (!isRecord(f)) return bad([\"footnotes\", i], `footnotes[${i}] must be an object { _key, text }`);\n for (const k of Object.keys(f)) if (k !== \"_key\" && k !== \"text\") bad([\"footnotes\", i, k], `unknown key \"${k}\" (_key, text)`);\n if (typeof f._key !== \"string\" || f._key === \"\" || f._key.length > PT_TABLE_LIMITS.keyChars) bad([\"footnotes\", i, \"_key\"], `needs a _key string of 1 to ${PT_TABLE_LIMITS.keyChars} characters`);\n else if (footnoteKeys.has(f._key)) bad([\"footnotes\", i, \"_key\"], `_key \"${f._key}\" is used twice`);\n else footnoteKeys.add(f._key);\n text(f.text, [\"footnotes\", i, \"text\"], \"text\", PT_TABLE_LIMITS.notesChars, true);\n });\n }\n\n const rows = Array.isArray(node.rows) ? node.rows : [];\n // The columns' types, by cell index, as the first row names them (a typed value is judged against its column).\n const firstCells = isRecord(rows[0]) && Array.isArray(rows[0].cells) ? (rows[0].cells as unknown[]) : [];\n const columns = firstCells.map((c) => (isRecord(c) ? columnOf(c.column) : undefined));\n rows.forEach((row, r) => {\n if (!isRecord(row)) return;\n for (const flag of [\"footer\", \"highlight\"] as const) {\n if (row[flag] !== undefined && typeof row[flag] !== \"boolean\") bad([\"rows\", r, flag], `${flag} must be true or false`);\n }\n if (row.head === true && row.footer === true) bad([\"rows\", r, \"footer\"], \"a head row cannot also be a footer (totals) row\");\n if (row.type !== undefined && !isColumnType(row.type)) bad([\"rows\", r, \"type\"], `type must be one of ${PT_TABLE_COLUMN_TYPES.join(\", \")}`);\n if (row.group !== undefined) {\n if (row.head === true) bad([\"rows\", r, \"group\"], \"a head row takes no group (row groups hold body rows)\");\n else if (typeof row.group !== \"string\" || !rowGroups.has(row.group)) {\n bad([\"rows\", r, \"group\"], `group \"${String(row.group)}\" is not a key of rowGroups`);\n }\n }\n (Array.isArray(row.cells) ? row.cells : []).forEach((cell, c) => {\n if (isRecord(cell)) for (const p of typedCellProblems(cell, row, r, c, columns[c])) bad(p.path, p.message);\n if (isRecord(cell) && cell.note !== undefined && (typeof cell.note !== \"string\" || !footnoteKeys.has(cell.note))) {\n bad([\"rows\", r, \"cells\", c, \"note\"], `note \"${String(cell.note)}\" is not a key of footnotes`);\n }\n for (const flag of [\"highlight\", \"sortable\"] as const) {\n if (!isRecord(cell) || cell[flag] === undefined) continue;\n const at = [\"rows\", r, \"cells\", c, flag];\n if (typeof cell[flag] !== \"boolean\") bad(at, `${flag} must be true or false`);\n else if (r !== 0 && cell[flag]) bad(at, `a column's ${flag} is named on its cell in the first row only`);\n }\n if (!isRecord(cell) || cell.group === undefined) return;\n const at = [\"rows\", r, \"cells\", c, \"group\"];\n if (r !== 0) bad(at, \"a column's group is named on its cell in the first row only\");\n else if (typeof cell.group !== \"string\" || !columnGroups.has(cell.group)) {\n bad(at, `group \"${String(cell.group)}\" is not a key of columnGroups`);\n }\n });\n });\n return out;\n}\n\n/**\n * `tableProblems` for every table in a rich-text value — top level, and inside cells — with the\n * table's `_key` so a refusal names the table an author has to find.\n */\nexport function portableTextTableProblems(value: unknown): Array<PortableTextTableProblem & { table: string }> {\n const out: Array<PortableTextTableProblem & { table: string }> = [];\n const walk = (blocks: unknown) => {\n if (!Array.isArray(blocks)) return;\n for (const node of blocks) {\n if (!isPortableTextTable(node)) continue;\n const table = typeof node._key === \"string\" ? node._key : \"\";\n for (const p of tableProblems(node)) out.push({ ...p, table });\n for (const row of node.rows as unknown[]) {\n if (!isRecord(row) || !Array.isArray(row.cells)) continue;\n for (const cell of row.cells) if (isRecord(cell)) walk(cell.content);\n }\n }\n };\n walk(value);\n return out;\n}\n\n/**\n * A table's rows split into its three sections: the LEADING run of `head` rows (`<thead>`), the\n * TRAILING run of `footer` rows (`<tfoot>`, the V2 table's \"Totals row\"), and the body between.\n *\n * Runs, not every flagged row, for the reason `<thead>` already is one: a section cannot be\n * lifted out of document order without moving content the author placed. A `footer` row with a\n * body row after it stays in the body (permissive read). Shared by every renderer so they agree.\n */\nexport function tableSections(table: PortableTextTable): { head: unknown[]; body: unknown[]; foot: unknown[] } {\n const rows: unknown[] = Array.isArray(table.rows) ? table.rows : [];\n const headCount = rows.findIndex((r) => !isRecord(r) || r.head !== true);\n const head = rows.slice(0, headCount === -1 ? rows.length : headCount);\n let footFrom = rows.length;\n while (footFrom > head.length && isRecord(rows[footFrom - 1]) && (rows[footFrom - 1] as Record<string, unknown>).footer === true) footFrom -= 1;\n return { head, body: rows.slice(head.length, footFrom), foot: rows.slice(footFrom) };\n}\n\n/**\n * The columns \"Highlight this column\" marks: the start column of each FIRST-row cell carrying\n * `highlight` (spans counted). A renderer stamps `data-highlight` on every cell starting there.\n */\nexport function highlightedColumns(table: PortableTextTable): Set<number> {\n return markedColumns(table, \"highlight\");\n}\n\n/** The columns \"Visitors can sort columns\" marks (a FIRST-row cell's `sortable`), as `highlightedColumns`. */\nexport function sortableColumns(table: PortableTextTable): Set<number> {\n return markedColumns(table, \"sortable\");\n}\n\nfunction markedColumns(table: PortableTextTable, flag: \"highlight\" | \"sortable\"): Set<number> {\n const out = new Set<number>();\n const first = Array.isArray(table.rows) ? table.rows[0] : undefined;\n let at = 0;\n for (const cell of isRecord(first) && Array.isArray(first.cells) ? first.cells : []) {\n if (isRecord(cell) && cell[flag] === true) out.add(at);\n at += isRecord(cell) && typeof cell.span === \"number\" && Number.isInteger(cell.span) && cell.span > 1 ? cell.span : 1;\n }\n return out;\n}\n\n/** A run of columns, or of body rows, that share one group; `label` is absent for an ungrouped run. */\nexport type TableColumnRun = { width: number; key?: string; label?: string };\nexport type TableRowRun = { rows: unknown[]; key?: string; label?: string };\n\n/**\n * How a table's groups lay out, or two empty lists when it has none.\n *\n * Shared by both renderers (`@bettercms-ai/ui` draws the same runs), so they cannot disagree\n * about where a group starts. A group key that names no group is ignored here rather than\n * thrown on: the read is permissive, and the write refuses it by name (`tableProblems`).\n *\n * `columns` is empty unless some first-row cell names a known column group; `rows` is empty\n * unless some body row names a known row group. `width` is the widest row, spans counted.\n */\nexport function tableGroupLayout(table: PortableTextTable): { columns: TableColumnRun[]; rows: TableRowRun[]; width: number } {\n const rows: unknown[] = Array.isArray(table.rows) ? table.rows : [];\n const labels = (list: unknown): Map<string, string> =>\n new Map(\n (Array.isArray(list) ? list : [])\n .filter((g): g is Record<string, unknown> => isRecord(g) && typeof g._key === \"string\")\n .map((g) => [g._key as string, typeof g.label === \"string\" ? g.label : \"\"]),\n );\n const cellsOf = (row: unknown): unknown[] => (isRecord(row) && Array.isArray(row.cells) ? row.cells : []);\n const spanOf = (cell: unknown): number =>\n isRecord(cell) && typeof cell.span === \"number\" && Number.isInteger(cell.span) && cell.span > 1 ? cell.span : 1;\n const width = rows.reduce<number>((max, row) => Math.max(max, cellsOf(row).reduce<number>((n, c) => n + spanOf(c), 0)), 0);\n\n const colLabels = labels(table.columnGroups);\n const columns: TableColumnRun[] = [];\n for (const cell of cellsOf(rows[0])) {\n const key = isRecord(cell) && typeof cell.group === \"string\" && colLabels.has(cell.group) ? cell.group : undefined;\n const last = columns[columns.length - 1];\n if (last && last.key === key) last.width += spanOf(cell);\n else columns.push({ width: spanOf(cell), ...(key === undefined ? {} : { key, label: colLabels.get(key) }) });\n }\n\n const rowLabels = labels(table.rowGroups);\n // The body only: a footer (totals) run sits in `<tfoot>`, outside every row group.\n const { body } = tableSections(table);\n const runs: TableRowRun[] = [];\n for (const row of body) {\n const key = isRecord(row) && typeof row.group === \"string\" && rowLabels.has(row.group) ? row.group : undefined;\n const last = runs[runs.length - 1];\n if (last && last.key === key) last.rows.push(row);\n else runs.push({ rows: [row], ...(key === undefined ? {} : { key, label: rowLabels.get(key) }) });\n }\n\n return {\n columns: columns.some((r) => r.key !== undefined) ? columns : [],\n rows: runs.some((r) => r.key !== undefined) ? runs : [],\n width,\n };\n}\n\n/* ── Typed cells (the V2 table's column types, src/schema/table.ts) ─────────────────────── */\n\nconst COLUMN_KEYS = new Set([\"type\", \"description\", \"unit\", \"currency\", \"decimals\", \"align\", \"trueLabel\", \"falseLabel\", \"max\"]);\nconst NUMERIC_TYPES = new Set<string>([\"number\", \"money\", \"rating\"]);\n\nexport const isColumnType = (v: unknown): v is PortableTextTableColumnType => (PT_TABLE_COLUMN_TYPES as readonly unknown[]).includes(v);\n\n/** A stored `column` read permissively: each V2 key kept when well formed, a type this table offers; nothing left is nothing. */\nexport function columnOf(v: unknown): PortableTextTableColumn | undefined {\n if (!isRecord(v)) return undefined;\n const str = (x: unknown) => (typeof x === \"string\" && x !== \"\" ? x : undefined);\n const int = (x: unknown, lo: number, hi: number) => (Number.isInteger(x) && (x as number) >= lo && (x as number) <= hi ? (x as number) : undefined);\n const c: PortableTextTableColumn = {\n ...(isColumnType(v.type) ? { type: v.type } : {}),\n ...(str(v.description) ? { description: str(v.description) } : {}),\n ...(str(v.unit) ? { unit: str(v.unit) } : {}),\n ...(typeof v.currency === \"string\" && /^[A-Z]{3}$/.test(v.currency) ? { currency: v.currency } : {}),\n ...(int(v.decimals, 0, 10) !== undefined ? { decimals: int(v.decimals, 0, 10) } : {}),\n ...(v.align === \"start\" || v.align === \"center\" || v.align === \"end\" ? { align: v.align } : {}),\n ...(str(v.trueLabel) ? { trueLabel: str(v.trueLabel) } : {}),\n ...(str(v.falseLabel) ? { falseLabel: str(v.falseLabel) } : {}),\n ...(int(v.max, 1, 10) !== undefined ? { max: int(v.max, 1, 10) } : {}),\n };\n return Object.keys(c).length ? c : undefined;\n}\n\n/** Each column's `column`, by column index (spans counted), from the FIRST-row cells. */\nexport function tableColumns(table: PortableTextTable): (PortableTextTableColumn | undefined)[] {\n const out: (PortableTextTableColumn | undefined)[] = [];\n const first = Array.isArray(table.rows) ? table.rows[0] : undefined;\n for (const cell of isRecord(first) && Array.isArray(first.cells) ? first.cells : []) {\n const w = isRecord(cell) && typeof cell.span === \"number\" && Number.isInteger(cell.span) && cell.span > 1 ? cell.span : 1;\n const col = isRecord(cell) ? columnOf(cell.column) : undefined;\n for (let i = 0; i < w; i += 1) out.push(col);\n }\n return out;\n}\n\n/**\n * A table that draws V2's typed markup: its first row names a `column`, or a row or cell carries a type or a value (a \"Write\n * words instead\" cell in a link column is typed even when no column is).\n */\nexport function isTypedTable(table: PortableTextTable): boolean {\n if (tableColumns(table).some(Boolean)) return true;\n return (Array.isArray(table.rows) ? (table.rows as unknown[]) : []).some(\n (r) => isRecord(r) && (r.type !== undefined || (Array.isArray(r.cells) && r.cells.some((c: unknown) => isRecord(c) && (c.type !== undefined || c.value !== undefined)))),\n );\n}\n\n/** V2 `cellType`: the cell's own type, else the row's, else the column's (text when none says). */\nexport const cellTypeOf = (col: PortableTextTableColumn | undefined, row: unknown, cell: unknown): PortableTextTableColumnType =>\n (isRecord(cell) && isColumnType(cell.type) ? cell.type : undefined) ?? (isRecord(row) && isColumnType(row.type) ? row.type : undefined) ?? col?.type ?? \"text\";\n\n/** V2 `alignOf` (schema/table.ts:365): numbers, money and ratings align end unless the column says otherwise. */\nexport const columnAlign = (col: PortableTextTableColumn | undefined, type: PortableTextTableColumnType): \"start\" | \"center\" | \"end\" =>\n col?.align ?? (NUMERIC_TYPES.has(type) ? \"end\" : \"start\");\n\n/** The types whose cell is a value rather than words: V2 renders them from the value (cellHtml). */\nexport const isValueType = (t: PortableTextTableColumnType): boolean => t === \"boolean\" || NUMERIC_TYPES.has(t);\n\nconst TRUE_WORDS = new Set([\"✓\", \"✔\", \"✔️\", \"☑\", \"✅\", \"yes\", \"y\", \"true\", \"included\", \"available\", \"supported\", \"x ✓\", \"check\", \"●\"]);\nconst FALSE_WORDS = new Set([\"✗\", \"✕\", \"✖\", \"❌\", \"×\", \"x\", \"no\", \"n\", \"false\", \"not included\", \"unavailable\", \"not available\", \"unsupported\", \"—\", \"–\", \"-\", \"○\"]);\n/** V2 `toBool` (schema/table.ts:315). */\nexport function toBool(v: unknown): boolean | null {\n if (typeof v === \"boolean\") return v;\n if (typeof v !== \"string\") return null;\n const s = v.trim().toLowerCase();\n return TRUE_WORDS.has(s) ? true : FALSE_WORDS.has(s) ? false : null;\n}\n/** V2 `toNumber` (schema/table.ts:273). */\nexport function toNumber(v: unknown): number | null {\n if (typeof v === \"number\") return Number.isFinite(v) ? v : null;\n if (typeof v !== \"string\") return null;\n const s = v.replace(/[,\\s]/g, \"\").replace(/[^\\d.+-]/g, \"\");\n if (!s || !/^[+-]?\\d*\\.?\\d+$/.test(s)) return null;\n return Number(s);\n}\n/** V2 `formatNumber` (schema/table.ts:262). */\nexport const formatNumber = (n: number, decimals?: number): string =>\n new Intl.NumberFormat(\"en-US\", decimals != null ? { minimumFractionDigits: decimals, maximumFractionDigits: decimals } : { maximumFractionDigits: 6 }).format(n);\n/** V2 `formatMoney` (schema/table.ts:265). */\nexport function formatMoney(n: number, currency = \"USD\", decimals?: number): string {\n try {\n const whole = Number.isInteger(n) && decimals == null;\n return new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency, minimumFractionDigits: whole ? 0 : decimals ?? 2, maximumFractionDigits: whole ? 0 : decimals ?? 2 }).format(n);\n } catch {\n return `${formatNumber(n, decimals)} ${currency}`;\n }\n}\n/** V2 `boolLabels` (schema/table.ts:252). */\nexport const boolLabels = (col: PortableTextTableColumn | undefined) => ({ yes: col?.trueLabel || \"Included\", no: col?.falseLabel || \"Not included\" });\n\n/**\n * V2 `cellText` (schema/table.ts:282) for a value type: the words a Number, Price, Yes / No or Rating cell says, which is what a\n * typed cell's `content` holds. `v` is the typed value, or the cell's words when it has none.\n */\nexport function typedCellText(col: PortableTextTableColumn | undefined, type: PortableTextTableColumnType, v: unknown): string {\n if (v == null || v === \"\") return \"\";\n const words = String(v).replace(/\\s+/g, \" \").trim();\n switch (type) {\n case \"boolean\": {\n const b = toBool(v);\n return b == null ? words : b ? boolLabels(col).yes : boolLabels(col).no;\n }\n case \"number\": {\n const n = toNumber(v);\n return n == null ? words : `${formatNumber(n, col?.decimals)}${col?.unit ? (/^[%°]/.test(col.unit) ? col.unit : ` ${col.unit}`) : \"\"}`;\n }\n case \"money\": {\n const n = toNumber(v);\n return n == null ? words : formatMoney(n, col?.currency || \"USD\", col?.decimals);\n }\n case \"rating\": {\n const n = toNumber(v);\n return n == null ? words : `${formatNumber(n, 1).replace(/\\.0$/, \"\")} out of ${col?.max ?? 5}`;\n }\n default:\n return words;\n }\n}\n\nconst escHtml = (s: string): string => s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n\n/**\n * What a cell of a TYPED table (one whose first row names a `column`) draws, shared by both renderers so they cannot disagree:\n * a head-row cell → `lead` data-align (V2: not when start), `tail` the column's description as `title`\n * (V2 renderTableHtml's `<th scope=\"col\" data-align data-highlight title>`, schema/table.ts:564)\n * any other cell → `tail` data-type (its own type), data-value / data-empty, data-align, and `inner`, V2 cellHtml's markup\n * (schema/table.ts:369-437) when the cell is empty or of a value type; text and link cells draw their blocks.\n * Attribute values are raw (the caller escapes them); `inner` is markup, its text already escaped.\n */\nexport function typedCellView(\n column: PortableTextTableColumn | undefined,\n row: unknown,\n cell: Record<string, unknown>,\n inHead: boolean,\n): { lead: [string, string][]; tail: [string, string][]; inner?: string } {\n if (inHead) {\n const align = columnAlign(column, column?.type ?? \"text\");\n return { lead: align !== \"start\" ? [[\"data-align\", align]] : [], tail: column?.description ? [[\"title\", column.description]] : [] };\n }\n const type = cellTypeOf(column, row, cell);\n const words = portableTextWords(cell.content);\n const v = typeof cell.value === \"number\" || typeof cell.value === \"boolean\" ? cell.value : words;\n const tail: [string, string][] = isColumnType(cell.type) ? [[\"data-type\", cell.type]] : [];\n let inner: string | undefined;\n if (v === \"\") {\n inner = cell.header === true ? \"\" : '<span aria-hidden=\"true\" class=\"bt-dash\">—</span><span class=\"bt-sr\">No value</span>';\n tail.push([\"data-empty\", \"\"]);\n } else if (type === \"boolean\") {\n const b = toBool(v);\n if (b == null) inner = escHtml(words);\n else {\n const { yes, no } = boolLabels(column);\n inner = `<span aria-hidden=\"true\" class=\"bt-bool\" data-bool=\"${b}\">${b ? \"✓\" : \"–\"}</span><span class=\"bt-sr\">${escHtml(b ? yes : no)}</span>`;\n tail.push([\"data-value\", String(b)]);\n }\n } else if (type === \"rating\") {\n const n = toNumber(v);\n if (n == null) inner = escHtml(words);\n else {\n inner = `<span aria-hidden=\"true\" class=\"bt-star\">★</span> ${escHtml(formatNumber(n, 1).replace(/\\.0$/, \"\"))}<span class=\"bt-dim\"> / ${column?.max ?? 5}</span>`;\n tail.push([\"data-value\", String(n)]);\n }\n } else if (type === \"number\" || type === \"money\") {\n const n = toNumber(v);\n inner = escHtml(typedCellText(column, type, v));\n if (n != null) tail.push([\"data-value\", String(n)]);\n }\n const align = columnAlign(column, type);\n if (align !== \"start\") tail.push([\"data-align\", align]);\n return { lead: [], tail, ...(inner === undefined ? {} : { inner }) };\n}\n\n/** A cell's words: the text of its blocks, whitespace collapsed (as `portableTextToPlainText` reads a paragraph). */\nfunction portableTextWords(content: unknown): string {\n return (Array.isArray(content) ? content : [])\n .map((b) => (isRecord(b) && Array.isArray(b.children) ? b.children.map((c) => (isRecord(c) && typeof c.text === \"string\" ? c.text : \"\")).join(\"\") : \"\"))\n .join(\" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n/** Why a cell's typed keys cannot be stored (`column` only on the first row, a known type, a value its type holds). */\nfunction typedCellProblems(cell: Record<string, unknown>, row: Record<string, unknown>, r: number, c: number, col: PortableTextTableColumn | undefined): PortableTextTableProblem[] {\n const out: PortableTextTableProblem[] = [];\n const bad = (k: string | string[], message: string, code = \"table_value_invalid\") =>\n out.push({ path: [\"rows\", r, \"cells\", c, ...(Array.isArray(k) ? k : [k])], code, message: `${code}: ${message}` });\n if (cell.column !== undefined) {\n const v = cell.column;\n if (r !== 0) bad(\"column\", \"a column's type is named on its cell in the first row only\");\n else if (!isRecord(v)) bad(\"column\", \"column must be an object { type, unit?, currency?, decimals?, align?, trueLabel?, falseLabel?, max?, description? }\");\n else {\n for (const k of Object.keys(v)) if (!COLUMN_KEYS.has(k)) bad([\"column\", k], `unknown key \"${k}\" (${[...COLUMN_KEYS].join(\", \")})`);\n if (v.type !== undefined && !isColumnType(v.type)) bad([\"column\", \"type\"], `type must be one of ${PT_TABLE_COLUMN_TYPES.join(\", \")}`);\n const text = (k: string, max: number) => {\n if (v[k] === undefined) return;\n if (typeof v[k] !== \"string\") bad([\"column\", k], `${k} must be a string`);\n else if ((v[k] as string).length > max) bad([\"column\", k], `${k} is ${(v[k] as string).length} characters; at most ${max}`, \"table_limit_exceeded\");\n };\n text(\"description\", PT_TABLE_LIMITS.notesChars);\n text(\"unit\", 16);\n text(\"trueLabel\", PT_TABLE_LIMITS.labelChars);\n text(\"falseLabel\", PT_TABLE_LIMITS.labelChars);\n if (v.currency !== undefined && (typeof v.currency !== \"string\" || !/^[A-Z]{3}$/.test(v.currency))) bad([\"column\", \"currency\"], \"currency must be an ISO 4217 code such as USD\");\n if (v.decimals !== undefined && (!Number.isInteger(v.decimals) || (v.decimals as number) < 0 || (v.decimals as number) > 10)) bad([\"column\", \"decimals\"], \"decimals must be a whole number from 0 to 10\");\n if (v.max !== undefined && (!Number.isInteger(v.max) || (v.max as number) < 1 || (v.max as number) > 10)) bad([\"column\", \"max\"], \"max must be a whole number from 1 to 10\");\n if (v.align !== undefined && v.align !== \"start\" && v.align !== \"center\" && v.align !== \"end\") bad([\"column\", \"align\"], 'align must be \"start\", \"center\" or \"end\"');\n }\n }\n if (cell.type !== undefined && !isColumnType(cell.type)) bad(\"type\", `type must be one of ${PT_TABLE_COLUMN_TYPES.join(\", \")}`);\n if (cell.value !== undefined) {\n const type = cellTypeOf(col, row, cell);\n if (type === \"boolean\" ? typeof cell.value !== \"boolean\" : NUMERIC_TYPES.has(type) ? typeof cell.value !== \"number\" || !Number.isFinite(cell.value) : true) {\n bad(\"value\", `a ${type} cell's value must be ${type === \"boolean\" ? \"true or false\" : NUMERIC_TYPES.has(type) ? \"a number\" : \"absent (its words are its content)\"}`);\n }\n }\n return out;\n}\n","import { PT_TYPES } from \"./types\";\nimport { isPortableTextTable } from \"./table\";\n\n/**\n * Key takeaways (`PortableTextKeyTakeaways`, types.ts): the limits, the write check, and the\n * read every renderer shares.\n *\n * The limits are the V2 kit's (`ARTICLE_SCHEMAS.takeaways` in `controls/rich-text/article.ts`):\n * a title of at most 40 characters and at most 8 points. V2's `min: 1` on points is the\n * editor's, not the write's: its editor always holds a line to type in, and the value it renders\n * while that line is still empty has no point in it. Refusing that would refuse an autosave\n * mid-typing, so an empty list stores, and renders as V2 renders it.\n */\nexport const PT_KEY_TAKEAWAYS_LIMITS = { titleChars: 40, points: 8 } as const;\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\n/** One reason a stored key-takeaways block is refused, at its own path inside the block. */\nexport type PortableTextKeyTakeawaysProblem = { path: Array<string | number>; code: string; message: string };\n\nconst KEYS = new Set([\"_type\", \"_key\", \"title\", \"points\"]);\n\n/**\n * Every reason `node` cannot be stored, each named with a code: `key_takeaways_invalid` (a\n * shape problem) or `key_takeaways_limit_exceeded` (a V2 limit).\n *\n * Only a `keyTakeaways` block is judged. The type is new, so no value stored before it existed\n * can hold one: there is nothing to grandfather.\n */\nexport function keyTakeawaysProblems(node: unknown): PortableTextKeyTakeawaysProblem[] {\n const out: PortableTextKeyTakeawaysProblem[] = [];\n if (!isRecord(node) || node._type !== PT_TYPES.keyTakeaways) return out;\n const bad = (path: Array<string | number>, message: string, code = \"key_takeaways_invalid\") =>\n out.push({ path, code, message: `${code}: ${message}` });\n for (const k of Object.keys(node)) if (!KEYS.has(k)) bad([k], `unknown key \"${k}\" (title, points)`);\n if (node.title !== undefined) {\n if (typeof node.title !== \"string\") bad([\"title\"], \"title must be a string\");\n else if (node.title.length > PT_KEY_TAKEAWAYS_LIMITS.titleChars) {\n bad([\"title\"], `title is ${node.title.length} characters; at most ${PT_KEY_TAKEAWAYS_LIMITS.titleChars}`, \"key_takeaways_limit_exceeded\");\n }\n }\n if (!Array.isArray(node.points)) {\n bad([\"points\"], \"points must be an array of strings\");\n return out;\n }\n node.points.forEach((p, i) => {\n if (typeof p !== \"string\") bad([\"points\", i], `points[${i}] must be a string`);\n });\n if (node.points.length > PT_KEY_TAKEAWAYS_LIMITS.points) {\n bad([\"points\"], `${node.points.length} points; at most ${PT_KEY_TAKEAWAYS_LIMITS.points}`, \"key_takeaways_limit_exceeded\");\n }\n return out;\n}\n\n/**\n * `keyTakeawaysProblems` for every key-takeaways block in a rich-text value — top level, and\n * inside table cells — with the block's `_key` so a refusal names the block an author has to find.\n */\nexport function portableTextKeyTakeawaysProblems(value: unknown): Array<PortableTextKeyTakeawaysProblem & { block: string }> {\n const out: Array<PortableTextKeyTakeawaysProblem & { block: string }> = [];\n const walk = (blocks: unknown) => {\n if (!Array.isArray(blocks)) return;\n for (const node of blocks) {\n if (isPortableTextTable(node)) {\n for (const row of node.rows as unknown[]) {\n if (!isRecord(row) || !Array.isArray(row.cells)) continue;\n for (const cell of row.cells) if (isRecord(cell)) walk(cell.content);\n }\n continue;\n }\n if (!isRecord(node) || node._type !== PT_TYPES.keyTakeaways) continue;\n const block = typeof node._key === \"string\" ? node._key : \"\";\n for (const p of keyTakeawaysProblems(node)) out.push({ ...p, block });\n }\n };\n walk(value);\n return out;\n}\n\n/**\n * What a reader sees of a key-takeaways block: the title, trimmed (\"\" for none), and the points,\n * each trimmed, the empty ones left out (V2 `renderArticle(\"takeaways\")`). Read defensively: a\n * value that never passed the write check renders what it can.\n */\nexport function keyTakeawaysView(value: unknown): { title: string; points: string[] } {\n const v = isRecord(value) ? value : {};\n const title = typeof v.title === \"string\" ? v.title.trim() : \"\";\n const points = (Array.isArray(v.points) ? v.points : [])\n .map((p) => (typeof p === \"string\" ? p.trim() : \"\"))\n .filter(Boolean);\n return { title, points };\n}\n","import {\n focalInCrop,\n formatImageRect,\n imageValueUrl,\n normalizeImageCrop,\n normalizeImageFocal,\n objectPosition,\n parseImageRect,\n type ImageCrop,\n type ImageFocal,\n} from \"@bettercms-ai/image-url\";\n/**\n * Where a rich-text image sits and how wide it is — `align` and `size` on a `builtin:image`\n * block's fields.\n *\n * WHY ONE TABLE. The editor draws the image with `align` (RichTextImageBlock's ALIGN_CLASS) and\n * every delivered rendition is written by `portableTextToHtml` — which used to ignore `align`\n * entirely, so an author centred an image and the published article showed it full width. The\n * numbers here are the editor's, so the two cannot drift again: a centred image is 78% of the\n * column, a left/right one 62%, full is the whole column. `size` overrides that width when set.\n *\n * The rendition carries classes (`bcms-rt-img--{align}`, `bcms-rt-img--{size}`) for a site that\n * wants to restyle it, AND inline max-width/margins so it looks right on a site with no CSS for\n * them at all — which is every site today.\n */\n\nexport const RICHTEXT_IMAGE_ALIGNS = [\"full\", \"center\", \"left\", \"right\"] as const;\nexport type RichTextImageAlign = (typeof RICHTEXT_IMAGE_ALIGNS)[number];\n\nexport const RICHTEXT_IMAGE_SIZES = [\"small\", \"medium\", \"large\", \"full\"] as const;\nexport type RichTextImageSize = (typeof RICHTEXT_IMAGE_SIZES)[number];\n\n/** Column share when `size` is unset — the editor's ALIGN_CLASS max-widths. */\nexport const RICHTEXT_IMAGE_ALIGN_MAX_WIDTH: Record<RichTextImageAlign, string> = {\n full: \"100%\",\n center: \"78%\",\n left: \"62%\",\n right: \"62%\",\n};\n\n/** Column share for an explicit `size`. Wins over the align default. */\nexport const RICHTEXT_IMAGE_SIZE_MAX_WIDTH: Record<RichTextImageSize, string> = {\n small: \"33%\",\n medium: \"50%\",\n large: \"75%\",\n full: \"100%\",\n};\n\nexport interface RichTextImageLayout {\n align: RichTextImageAlign;\n size: RichTextImageSize | null;\n /** `bcms-rt-img bcms-rt-img--{align}[ bcms-rt-img--{size}]` */\n className: string;\n /** Inline CSS for the `<figure>`: max-width plus the margins that place it. */\n figureStyle: string;\n}\n\nconst oneOf = <T extends string>(list: readonly T[], v: unknown): T | null =>\n typeof v === \"string\" && (list as readonly string[]).includes(v) ? (v as T) : null;\n\n/**\n * The layout a block's fields ask for, or `null` when they ask for none — an image with neither\n * a known `align` nor a known `size` renders exactly as it always did. Unknown values are\n * ignored, never echoed: both come from stored data and end up in an attribute.\n */\nexport function richTextImageLayout(fields: Record<string, unknown>): RichTextImageLayout | null {\n const alignValue = oneOf(RICHTEXT_IMAGE_ALIGNS, fields.align);\n const size = oneOf(RICHTEXT_IMAGE_SIZES, fields.size);\n if (!alignValue && !size) return null;\n const align = alignValue ?? \"full\";\n const maxWidth = size ? RICHTEXT_IMAGE_SIZE_MAX_WIDTH[size] : RICHTEXT_IMAGE_ALIGN_MAX_WIDTH[align];\n // left hugs the start, right the end; centre and full centre (a sized full image is centred).\n const [ml, mr] = align === \"left\" ? [\"0\", \"auto\"] : align === \"right\" ? [\"auto\", \"0\"] : [\"auto\", \"auto\"];\n return {\n align,\n size,\n className: `bcms-rt-img bcms-rt-img--${align}${size ? ` bcms-rt-img--${size}` : \"\"}`,\n figureStyle: `max-width:${maxWidth};margin-left:${ml};margin-right:${mr}`,\n };\n}\n\n/**\n * RT9: a rich-text image's framing, the platform's own image vocabulary (`@bettercms-ai/image-url`):\n * `crop`, how much to cut from each edge as 0–1 fractions of the source, and `focal`, a 0–1 point\n * of the source. Both optional on a `builtin:image`'s fields and on a gallery image.\n *\n * The crop is applied the way every lane applies it, through `imageValueUrl` (`?rect=` on a\n * BetterCMS media url; any other url is drawn uncropped); the focal point is `object-position`\n * inside that crop. What is stored rides the `<img>` as `data-src` / `data-crop` / `data-focal`\n * whenever there is a frame, so the ingest reads back exactly what was stored.\n */\nexport interface RichTextImageFrame {\n /** The url to draw: the stored one, with the crop applied when it can be. */\n src: string;\n crop: ImageCrop | null;\n focal: ImageFocal | null;\n /** `object-position` for the focal point, or null when there is none. */\n position: string | null;\n /** The `data-*` attributes carrying what is stored, in render order; empty when there is no frame. */\n data: [string, string][];\n}\n\nexport function richTextImageFrame(src: string, crop: unknown, focal: unknown): RichTextImageFrame {\n const c = normalizeImageCrop(crop);\n const f = normalizeImageFocal(focal);\n if (!c && !f) return { src, crop: null, focal: null, position: null, data: [] };\n const drawn = imageValueUrl({ url: src, crop: c, focal: f }) ?? src;\n const data: [string, string][] = [];\n if (drawn !== src) data.push([\"data-src\", src]);\n if (c) data.push([\"data-crop\", formatImageRect(c)]);\n if (f) data.push([\"data-focal\", `${f.x},${f.y}`]);\n return { src: drawn, crop: c, focal: f, position: f ? objectPosition(focalInCrop(f, c)) : null, data };\n}\n\n/** The stored framing an `<img>` carries back (`richTextImageFrame`'s `data`), for the ingest. */\nexport function readImageFrame(img: { getAttribute(name: string): string | null }): { src: string | null; crop: ImageCrop | null; focal: ImageFocal | null } {\n const xy = (img.getAttribute(\"data-focal\") ?? \"\").split(\",\").map(Number);\n return {\n src: img.getAttribute(\"data-src\"),\n crop: parseImageRect(img.getAttribute(\"data-crop\")),\n focal: xy.length === 2 ? normalizeImageFocal({ x: xy[0], y: xy[1] }) : null,\n };\n}\n","import { PT_TYPES } from \"./types\";\nimport { isPortableTextTable } from \"./table\";\nimport { safeHref } from \"./href\";\nimport { richTextImageFrame } from \"./image-layout\";\n\n/**\n * RT4: the gallery (`PortableTextGallery`, types.ts): the limits, the write check, and the read\n * both renderers share.\n *\n * The limits are the V2 kit's (`ARTICLE_SCHEMAS.gallery` in `controls/rich-text/article.ts`): 2 to\n * 24 images, a caption of at most 160 characters. As with key takeaways, V2's `min: 2` and its\n * required image are the editor's, not the write's: an author adds images one at a time, and\n * refusing the autosave in between would refuse the gallery they are building. So a gallery of\n * one, or an image not yet chosen, stores; the view leaves an unchosen image out, as V2 does.\n *\n * The image is the rich-text image's own `{ src, alt }` (+ an optional media id), so whatever\n * reads a rich-text image's src and alt reads a gallery image's the same way.\n */\nexport const PT_GALLERY_LIMITS = { minImages: 2, images: 24, captionChars: 160 } as const;\nexport const GALLERY_LAYOUTS = [\"grid\", \"carousel\"] as const;\nexport type GalleryLayout = (typeof GALLERY_LAYOUTS)[number];\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\n/** One reason a stored gallery is refused, at its own path inside the block. */\nexport type PortableTextGalleryProblem = { path: Array<string | number>; code: string; message: string };\n\nconst KEYS = new Set([\"_type\", \"_key\", \"layout\", \"images\"]);\nconst ITEM_KEYS = new Set([\"_key\", \"image\", \"caption\"]);\nconst IMAGE_KEYS = new Set([\"src\", \"alt\", \"mediaId\", \"crop\", \"focal\"]);\nconst fraction = (v: unknown): v is number => typeof v === \"number\" && Number.isFinite(v) && v >= 0 && v <= 1;\n\n/**\n * Every reason `node` cannot be stored, each named with a code: `gallery_invalid` (a shape\n * problem) or `gallery_limit_exceeded` (a V2 limit). Only a `gallery` block is judged; the type is\n * new, so there is nothing stored before it to grandfather.\n */\nexport function galleryProblems(node: unknown): PortableTextGalleryProblem[] {\n const out: PortableTextGalleryProblem[] = [];\n if (!isRecord(node) || node._type !== PT_TYPES.gallery) return out;\n const bad = (path: Array<string | number>, message: string, code = \"gallery_invalid\") =>\n out.push({ path, code, message: `${code}: ${message}` });\n for (const k of Object.keys(node)) if (!KEYS.has(k)) bad([k], `unknown key \"${k}\" (layout, images)`);\n if (!(GALLERY_LAYOUTS as readonly unknown[]).includes(node.layout)) bad([\"layout\"], 'layout must be \"grid\" or \"carousel\"');\n if (!Array.isArray(node.images)) {\n bad([\"images\"], \"images must be an array\");\n return out;\n }\n node.images.forEach((item, i) => {\n const at = [\"images\", i];\n if (!isRecord(item)) return bad(at, `images[${i}] must be an object`);\n for (const k of Object.keys(item)) if (!ITEM_KEYS.has(k)) bad([...at, k], `unknown key \"${k}\" (image, caption)`);\n if (typeof item._key !== \"string\" || !item._key) bad([...at, \"_key\"], `images[${i}] needs a _key`);\n if (item.caption !== undefined) {\n if (typeof item.caption !== \"string\") bad([...at, \"caption\"], \"caption must be a string\");\n else if (item.caption.length > PT_GALLERY_LIMITS.captionChars) {\n bad([...at, \"caption\"], `caption is ${item.caption.length} characters; at most ${PT_GALLERY_LIMITS.captionChars}`, \"gallery_limit_exceeded\");\n }\n }\n const image = item.image;\n if (image === undefined || image === null) return;\n if (!isRecord(image)) return bad([...at, \"image\"], \"image must be { src, alt }\");\n for (const k of Object.keys(image)) if (!IMAGE_KEYS.has(k)) bad([...at, \"image\", k], `unknown key \"${k}\" (src, alt, mediaId)`);\n if (typeof image.src !== \"string\") bad([...at, \"image\", \"src\"], \"src must be a string\");\n if (typeof image.alt !== \"string\") bad([...at, \"image\", \"alt\"], \"alt must be a string\");\n if (image.mediaId !== undefined && typeof image.mediaId !== \"string\") bad([...at, \"image\", \"mediaId\"], \"mediaId must be a string\");\n // RT9: the rich-text image's framing, image-url's shape: edges cut as 0–1 fractions, a 0–1 point.\n const crop = image.crop;\n if (crop !== undefined && crop !== null) {\n const c = isRecord(crop) ? crop : {};\n if (![c.top, c.right, c.bottom, c.left].every(fraction) || (c.top as number) + (c.bottom as number) >= 1 || (c.left as number) + (c.right as number) >= 1) {\n bad([...at, \"image\", \"crop\"], \"crop must be { top, right, bottom, left }, 0–1 fractions that leave some picture\");\n }\n }\n const focal = image.focal;\n if (focal !== undefined && focal !== null && !(isRecord(focal) && fraction(focal.x) && fraction(focal.y))) {\n bad([...at, \"image\", \"focal\"], \"focal must be { x, y }, 0–1 fractions\");\n }\n });\n if (node.images.length > PT_GALLERY_LIMITS.images) {\n bad([\"images\"], `${node.images.length} images; at most ${PT_GALLERY_LIMITS.images}`, \"gallery_limit_exceeded\");\n }\n return out;\n}\n\n/**\n * `galleryProblems` for every gallery in a rich-text value (top level, and inside table cells),\n * with the block's `_key` so a refusal names the block an author has to find.\n */\nexport function portableTextGalleryProblems(value: unknown): Array<PortableTextGalleryProblem & { block: string }> {\n const out: Array<PortableTextGalleryProblem & { block: string }> = [];\n const walk = (blocks: unknown) => {\n if (!Array.isArray(blocks)) return;\n for (const node of blocks) {\n if (isPortableTextTable(node)) {\n for (const row of node.rows as unknown[]) {\n if (!isRecord(row) || !Array.isArray(row.cells)) continue;\n for (const cell of row.cells) if (isRecord(cell)) walk(cell.content);\n }\n continue;\n }\n if (!isRecord(node) || node._type !== PT_TYPES.gallery) continue;\n const block = typeof node._key === \"string\" ? node._key : \"\";\n for (const p of galleryProblems(node)) out.push({ ...p, block });\n }\n };\n walk(value);\n return out;\n}\n\n/**\n * The same widened check the rich-text image's `<img>` gets (html.ts `mediaSrc`): http(s), or a\n * site-relative path with no scheme, whitespace or protocol-relative `//`.\n */\nexport const galleryMediaSrc = (raw: string): string | null =>\n safeHref(raw) ?? (raw !== \"\" && !raw.startsWith(\"//\") && !/[:\\s]/.test(raw) ? raw : null);\n\n/**\n * The layout, as inline CSS both renderers write (ui turns each into a React style object). A grid\n * of equal cells; a carousel is one image per view in a scroll-snap row, its no-JS fallback being\n * itself.\n */\nexport const GALLERY_STYLES: Record<GalleryLayout, { ul: string; li: string; figure: string; img: string }> = {\n grid: {\n ul: \"display:grid;grid-template-columns:repeat(auto-fill,minmax(12rem,1fr));gap:1rem;list-style:none;margin:0;padding:0\",\n li: \"margin:0\",\n figure: \"margin:0\",\n img: \"display:block;width:100%;height:auto\",\n },\n carousel: {\n ul: \"display:flex;gap:1rem;overflow-x:auto;scroll-snap-type:x mandatory;list-style:none;margin:0;padding:0\",\n li: \"flex:0 0 100%;scroll-snap-align:start;margin:0\",\n figure: \"margin:0\",\n img: \"display:block;width:100%;height:auto\",\n },\n};\n\n/**\n * What a reader sees of a gallery: its layout (grid unless it says carousel) and each image that\n * has a safe src, with its alt and trimmed caption. An unchosen image is left out, as V2's\n * `renderArticle(\"gallery\")` leaves it out. Read defensively: a value that never passed the write\n * check renders what it can.\n */\nexport type GalleryViewImage = { key: string; src: string; alt: string; caption: string; position: string | null; data: [string, string][] };\n\nexport function galleryView(value: unknown): { layout: GalleryLayout; images: GalleryViewImage[] } {\n const v = isRecord(value) ? value : {};\n const layout: GalleryLayout = v.layout === \"carousel\" ? \"carousel\" : \"grid\";\n const images: GalleryViewImage[] = [];\n (Array.isArray(v.images) ? v.images : []).forEach((item, i) => {\n if (!isRecord(item) || !isRecord(item.image)) return;\n const src = galleryMediaSrc(typeof item.image.src === \"string\" ? item.image.src.trim() : \"\");\n if (!src) return;\n // RT9: the crop and focal point, as a rich-text image draws them (image-layout.ts).\n const frame = richTextImageFrame(src, item.image.crop, item.image.focal);\n images.push({\n key: typeof item._key === \"string\" && item._key ? item._key : `i${i}`,\n src: frame.src,\n position: frame.position,\n data: frame.data,\n alt: typeof item.image.alt === \"string\" ? item.image.alt : \"\",\n caption: typeof item.caption === \"string\" ? item.caption.trim() : \"\",\n });\n });\n return { layout, images };\n}\n","import { PT_TYPES, type PortableText } from \"./types\";\n\n/**\n * RT7: columns (`PortableTextColumns`, types.ts), the first block that holds Portable Text of its\n * own: `{ gap, columns: [{ _key, content }] }`. A V2 column is an editable run of text, so a\n * column holds text blocks only, and never another block (R5: depth 1). The column count is\n * derived (`data-cols`), never stored.\n *\n * A stored `bcmsBlock` under `builtin:columns` (plain text per column, `gap: small|medium|large`)\n * is read the same way, so it renders instead of the empty marker it used to; nothing writes it.\n * A legacy block in any other shape stays a marker.\n */\nexport const PT_COLUMNS_LIMITS = { min: 2, max: 4 } as const;\nexport const COLUMNS_GAPS = [\"s\", \"m\", \"l\"] as const;\nexport type ColumnsGap = (typeof COLUMNS_GAPS)[number];\nexport const BUILTIN_COLUMNS_KEY = \"builtin:columns\" as const;\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\n/** The inline layout both renderers write (a hosted page has no stylesheet for it): legacy columns' flex row. */\nexport const COLUMNS_STYLES: Record<ColumnsGap, { row: string; col: string }> = {\n s: { row: \"display:flex;flex-wrap:wrap;gap:0.75rem\", col: \"flex:1 1 14rem;min-width:0\" },\n m: { row: \"display:flex;flex-wrap:wrap;gap:1.25rem\", col: \"flex:1 1 14rem;min-width:0\" },\n l: { row: \"display:flex;flex-wrap:wrap;gap:2rem\", col: \"flex:1 1 14rem;min-width:0\" },\n};\n\n/** A legacy column's plain text as text blocks: a paragraph per blank-line-separated run. */\nconst legacyContent = (text: string, key: string): PortableText =>\n text\n .split(/\\n{2,}/)\n .filter((p) => p.trim() !== \"\")\n .map((p, i) => ({ _type: \"block\", _key: `${key}p${i}`, style: \"normal\", markDefs: [], children: [{ _type: \"span\", _key: `${key}s${i}`, text: p, marks: [] }] }) as never);\n\n/**\n * What a reader sees of a columns block, ours or a legacy `builtin:columns`: its gap and each\n * column's content. Null for anything else. Read defensively: a column without content renders empty.\n */\nexport function columnsView(node: unknown): { gap: ColumnsGap; columns: { key: string; content: PortableText }[] } | null {\n if (!isRecord(node)) return null;\n if (node._type === PT_TYPES.columns) {\n const gap: ColumnsGap = node.gap === \"s\" || node.gap === \"l\" ? node.gap : \"m\";\n const columns = (Array.isArray(node.columns) ? node.columns : []).map((c, i) => ({\n key: isRecord(c) && typeof c._key === \"string\" && c._key ? c._key : `c${i}`,\n content: (isRecord(c) && Array.isArray(c.content) ? c.content : []) as PortableText,\n }));\n return { gap, columns };\n }\n if (node._type === PT_TYPES.block && node.schemaKey === BUILTIN_COLUMNS_KEY) {\n const f = isRecord(node.fields) ? node.fields : {};\n // Only the shape the legacy renderer wrote (plain text per column) reads as columns; any other\n // stays the marker, which keeps \"something is here\" visible rather than drawing it empty.\n if (!Array.isArray(f.columns) || !f.columns.every((c) => typeof c === \"string\")) return null;\n const gap: ColumnsGap = f.gap === \"small\" ? \"s\" : f.gap === \"large\" ? \"l\" : \"m\";\n const columns = (Array.isArray(f.columns) ? f.columns : []).slice(0, PT_COLUMNS_LIMITS.max).map((c, i) => ({\n key: `c${i}`,\n content: legacyContent(typeof c === \"string\" ? c : \"\", `c${i}`),\n }));\n return { gap, columns };\n }\n return null;\n}\n\n/** One reason a stored columns block is refused, at its own path inside the block. */\nexport type PortableTextColumnsProblem = { path: Array<string | number>; code: string; message: string };\n\nconst KEYS = new Set([\"_type\", \"_key\", \"gap\", \"columns\"]);\n\n/**\n * Every reason `node` cannot be stored: `columns_invalid` (a shape problem, a column holding\n * anything but text blocks, which includes another columns block (R5) and a footnote) or\n * `columns_limit_exceeded` (outside V2's 2 to 4 columns). A derived `cols` is refused by R8, by name.\n */\nexport function columnsProblems(node: unknown): PortableTextColumnsProblem[] {\n const out: PortableTextColumnsProblem[] = [];\n if (!isRecord(node) || node._type !== PT_TYPES.columns) return out;\n const bad = (path: Array<string | number>, message: string, code = \"columns_invalid\") =>\n out.push({ path, code, message: `${code}: ${message}` });\n for (const k of Object.keys(node)) if (!KEYS.has(k) && k !== \"cols\") bad([k], `unknown key \"${k}\" (gap, columns)`);\n if (!(COLUMNS_GAPS as readonly unknown[]).includes(node.gap)) bad([\"gap\"], 'gap must be \"s\", \"m\" or \"l\"');\n if (!Array.isArray(node.columns)) {\n bad([\"columns\"], \"columns must be an array\");\n return out;\n }\n const n = node.columns.length;\n if (n < PT_COLUMNS_LIMITS.min || n > PT_COLUMNS_LIMITS.max) {\n bad([\"columns\"], `${n} columns; ${PT_COLUMNS_LIMITS.min} to ${PT_COLUMNS_LIMITS.max}`, \"columns_limit_exceeded\");\n }\n node.columns.forEach((col, i) => {\n const at = [\"columns\", i];\n if (!isRecord(col)) return bad(at, `columns[${i}] must be an object`);\n if (typeof col._key !== \"string\" || !col._key) bad([...at, \"_key\"], `columns[${i}] needs a _key`);\n for (const k of Object.keys(col)) if (k !== \"_key\" && k !== \"content\") bad([...at, k], `unknown key \"${k}\" (content)`);\n if (!Array.isArray(col.content)) return bad([...at, \"content\"], \"content must be an array of text blocks\");\n col.content.forEach((b, j) => {\n const bp = [...at, \"content\", j];\n if (!isRecord(b) || b._type !== \"block\") {\n return bad(bp, `a column holds text blocks only, not ${isRecord(b) && typeof b._type === \"string\" ? `\"${b._type}\"` : \"this\"}`);\n }\n (Array.isArray(b.children) ? b.children : []).forEach((c, k) => {\n if (isRecord(c) && c._type === PT_TYPES.footnote) bad([...bp, \"children\", k], \"a footnote belongs in the body text, not a column\");\n });\n });\n });\n return out;\n}\n\n/** `columnsProblems` for every columns block at the top level of a value, with its block key. */\nexport function portableTextColumnsProblems(value: unknown): Array<PortableTextColumnsProblem & { block: string }> {\n const out: Array<PortableTextColumnsProblem & { block: string }> = [];\n for (const node of Array.isArray(value) ? value : []) {\n if (!isRecord(node) || node._type !== PT_TYPES.columns) continue;\n const block = typeof node._key === \"string\" ? node._key : \"\";\n for (const p of columnsProblems(node)) out.push({ ...p, block });\n }\n return out;\n}\n","import { EMBEDDED_COMPONENT_KEY } from \"./types\";\n\n/**\n * A component embedded in rich text, kept as a SEPARATE COPY (V2 \"Keep as a separate copy\" /\n * \"Make a separate copy\", bettercms-v2-demo features/blocks/instances.ts `instanceOps.detach`).\n *\n * Linked (every value stored before this existed):\n * { _type: \"bcmsBlock\", schemaKey: \"component\", fields: { componentId, overrides?, variant? } }\n * → renders the shared component with this place's overrides; edits to the component reach it.\n *\n * Detached:\n * { _type: \"bcmsBlock\", schemaKey: \"component\", fields: {\n * componentId, the component it was copied from (kept for \"Use shared version again\")\n * detached: true,\n * props, the values readers saw when it was copied (the component's ⊕ this place's)\n * blocks, the block tree readers see, resolved and frozen: no `component` block inside\n * sourcePublishedAt, the source's `lastPublishedAt` when it was copied (0454; older copies hold its first `publishedAt`): the version it was taken from\n * variant?\n * } }\n * → renders `blocks` and never looks the component up again.\n *\n * V2 keeps only `props` because its renderer draws a catalog type from props. Here a component's\n * markup is its own block tree, so a copy that kept only props would still follow the shared\n * component's structure; `blocks` is what makes the copy separate.\n */\nexport interface DetachedComponentCopy {\n /** The node's `_key`. */\n id: string;\n componentId: string;\n props: Record<string, unknown>;\n blocks: unknown[];\n sourcePublishedAt?: string;\n variant?: string;\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> => !!v && typeof v === \"object\" && !Array.isArray(v);\n\nconst fieldsOf = (value: unknown): Record<string, unknown> | null =>\n isRecord(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord(value.fields) ? value.fields : null;\n\n/** True when the node is an embedded component stored as a separate copy (whatever else it holds). */\nexport function isDetachedComponent(value: unknown): boolean {\n return fieldsOf(value)?.detached === true;\n}\n\n/** The separate copy a node stands for, or null (a linked embed, any other node, a malformed copy). */\nexport function detachedComponentCopy(value: unknown): DetachedComponentCopy | null {\n const f = fieldsOf(value);\n if (!f || f.detached !== true || !Array.isArray(f.blocks)) return null;\n return {\n id: isRecord(value) && typeof value._key === \"string\" ? value._key : \"\",\n componentId: typeof f.componentId === \"string\" ? f.componentId : \"\",\n props: isRecord(f.props) ? f.props : {},\n blocks: f.blocks,\n ...(typeof f.sourcePublishedAt === \"string\" ? { sourcePublishedAt: f.sourcePublishedAt } : {}),\n ...(typeof f.variant === \"string\" ? { variant: f.variant } : {}),\n };\n}\n\n/** One reason a stored copy is refused, at its path inside the node's `fields`. */\nexport type ComponentCopyProblem = { path: Array<string | number>; code: string; message: string };\n\n/**\n * Every reason a separate copy cannot be stored, named `component_copy_invalid` (the shape) or\n * `component_copy_not_frozen` (its blocks still reference a component, so it would keep\n * following one).\n *\n * 🔴 ONLY A NODE THAT CARRIES `detached` IS JUDGED. A linked embed answers `[]`, so every value\n * stored before copies existed saves exactly as it did.\n */\nexport function componentCopyProblems(node: unknown): ComponentCopyProblem[] {\n const f = fieldsOf(node);\n if (!f || f.detached === undefined) return [];\n const out: ComponentCopyProblem[] = [];\n const bad = (path: Array<string | number>, message: string, code = \"component_copy_invalid\") =>\n out.push({ path, code, message: `${code}: ${message}` });\n if (f.detached !== true) bad([\"detached\"], \"detached must be true (leave it out for a linked component)\");\n if (typeof f.componentId !== \"string\" || !f.componentId) bad([\"componentId\"], \"needs the componentId it was copied from\");\n if (!isRecord(f.props)) bad([\"props\"], \"props must be an object of the values readers see\");\n if (!Array.isArray(f.blocks)) {\n bad([\"blocks\"], \"needs blocks, the copy readers see; get_component { detachedCopy } returns them\");\n } else {\n const live = componentRefs(f.blocks);\n if (live.length) {\n bad([\"blocks\"], `still holds ${live.length} component block(s) (${live.slice(0, 3).join(\", \")}), so it would keep following the shared one`, \"component_copy_not_frozen\");\n }\n }\n if (f.sourcePublishedAt !== undefined && (typeof f.sourcePublishedAt !== \"string\" || Number.isNaN(Date.parse(f.sourcePublishedAt)))) {\n bad([\"sourcePublishedAt\"], \"sourcePublishedAt must be an ISO date\");\n }\n if (isRecord(f.overrides) && Object.keys(f.overrides).length) {\n bad([\"overrides\"], \"a separate copy keeps its values in props; overrides belong to a linked component\");\n }\n return out;\n}\n\n/** `componentCopyProblems` for every embedded component at the top of a rich-text value, with its `_key`. */\nexport function portableTextComponentCopyProblems(value: unknown): Array<ComponentCopyProblem & { block: string }> {\n if (!Array.isArray(value)) return [];\n return value.flatMap((node) => {\n const block = isRecord(node) && typeof node._key === \"string\" ? node._key : \"\";\n return componentCopyProblems(node).map((p) => ({ ...p, block }));\n });\n}\n\n/** The component ids a block tree still references, at any depth. */\nfunction componentRefs(blocks: unknown, out: string[] = [], depth = 0): string[] {\n if (depth > 32) return out;\n if (Array.isArray(blocks)) {\n for (const b of blocks) componentRefs(b, out, depth + 1);\n return out;\n }\n if (!isRecord(blocks)) return out;\n if (blocks.type === \"component\") {\n const p = isRecord(blocks.props) ? blocks.props : {};\n out.push(typeof p.componentId === \"string\" && p.componentId ? p.componentId : \"(no id)\");\n }\n for (const v of Object.values(blocks)) if (v && typeof v === \"object\") componentRefs(v, out, depth + 1);\n return out;\n}\n\n/**\n * What a separate copy says, one line per value, for search and plain text: its own copy is\n * content this page owns, unlike a linked embed, whose words live in the component.\n * Strings (markup stripped), numbers, and a link's or button's label; nothing else.\n */\nexport function componentCopyLines(copy: Pick<DetachedComponentCopy, \"props\">): string[] {\n const line = (v: unknown): string | null => {\n if (typeof v === \"number\" && Number.isFinite(v)) return String(v);\n if (typeof v === \"string\") {\n const t = v.replace(/<[^>]+>/g, \" \").replace(/&nbsp;/g, \" \").replace(/\\s+/g, \" \").trim();\n return t || null;\n }\n if (isRecord(v)) return line(v.label ?? v.text ?? v.title);\n return null;\n };\n return Object.values(copy.props).map(line).filter((s): s is string => !!s);\n}\n","import { isPortableTextBlock, isPortableTextSpan, PT_TYPES, type PortableText, type PortableTextTable } from \"./types\";\nimport { migrateTable, tableGroupLayout } from \"./table\";\nimport { keyTakeawaysView } from \"./key-takeaways\";\nimport { galleryView } from \"./gallery\";\nimport { columnsView } from \"./columns\";\nimport { componentCopyLines, detachedComponentCopy } from \"./component-copy\";\n\n/**\n * Portable Text → plain text.\n *\n * Replaces `lexicalJsonToText`, and is the one place the repo's oldest formatting trap gets\n * settled: joining spans with `\"\"` versus `\" \"`. Spans are runs WITHIN one paragraph, so they\n * join with nothing — inserting spaces would put a gap in the middle of a bolded word. Blocks\n * are separate paragraphs, so they join with a newline.\n */\nexport function portableTextToPlainText(value: PortableText | null | undefined): string {\n if (!Array.isArray(value)) return \"\";\n const lines: string[] = [];\n for (const node of value) {\n if (isPortableTextBlock(node)) {\n lines.push(node.children.map((c) => (isPortableTextSpan(c) ? c.text : \"\")).join(\"\"));\n continue;\n }\n if (node._type === PT_TYPES.code && typeof (node as Record<string, unknown>).code === \"string\") {\n lines.push((node as Record<string, unknown>).code as string);\n }\n /**\n * A TABLE is the exception, and it has to be: a reader sees every cell. Left out, the\n * corpus audit compared a table's stored text against a conversion that reported none and\n * called 104 sound values a text loss. Cells join with a space and rows with a newline, the\n * same shape `portableTextToHtml` gives them.\n *\n * Both spellings arrive here — the `table` and the pre-P1c `bcmsBlock` — because this runs\n * over values that never passed through the reader, the corpus audit's above all.\n * `migrateTable` answers null for everything else, so the cost of asking is one comparison.\n *\n * Every other embedded block — an image, an embed, a LINKED component — resolves against a\n * project this function does not have, so it still contributes nothing.\n */\n /**\n * A pull quote is words a reader sees — the quote, then who said it — so search and the\n * audit count them. The attribution line is the one the Markdown export writes.\n */\n if (node._type === PT_TYPES.pullQuote) {\n lines.push(...pullQuoteLines(node as Record<string, unknown>));\n continue;\n }\n /** Key takeaways are words a reader sees too: the title, then one line per point. */\n if (node._type === PT_TYPES.keyTakeaways) {\n const { title, points } = keyTakeawaysView(node);\n lines.push(...(title ? [title] : []), ...points);\n continue;\n }\n /** A gallery's captions are words a reader sees; its images are not. */\n if (node._type === PT_TYPES.gallery) {\n lines.push(...galleryView(node).images.map((x) => x.caption).filter(Boolean));\n continue;\n }\n /** A column's text is words a reader sees, in column order (a legacy `builtin:columns` too). */\n const cols = columnsView(node);\n if (cols) {\n for (const c of cols.columns) {\n const text = portableTextToPlainText(c.content);\n if (text) lines.push(text);\n }\n continue;\n }\n const table = migrateTable(node);\n if (table) {\n lines.push(...tableLines(table));\n continue;\n }\n // A separate copy of a component holds its own words (a linked one's live in the component).\n const copy = detachedComponentCopy(node);\n if (copy) {\n lines.push(...componentCopyLines(copy));\n continue;\n }\n // Dividers and other embedded blocks contribute no text, which is what a reader wants.\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * One line per row, cells joined with a space.\n *\n * A cell's own blocks go back through this function — a cell is a rich-text field, so a link or\n * a second paragraph inside one is text a reader sees — and its newlines flatten to spaces,\n * because a row is a LINE and a cell that broke it would make the audit's before/after\n * comparison depend on where a paragraph happened to fall.\n */\nconst tableLines = (table: PortableTextTable): string[] => {\n const rows = Array.isArray(table.rows) ? table.rows : [];\n const rowLine = (row: (typeof rows)[number]): string =>\n (Array.isArray(row?.cells) ? row.cells : [])\n .map((cell) => portableTextToPlainText(cell?.content).replace(/\\s+/g, \" \").trim())\n .join(\" \")\n .trim();\n /**\n * Caption, column-group labels, then the rows with each row group's label before its run, then\n * the note: the order the html draws them in. A table without them is one line per row, as\n * before they existed.\n */\n const layout = tableGroupLayout(table);\n const one = (s: unknown): string => (typeof s === \"string\" ? s.replace(/\\s+/g, \" \").trim() : \"\");\n const head = layout.rows.length ? rows.slice(0, rows.length - layout.rows.reduce((n, r) => n + r.rows.length, 0)) : rows;\n const lines = [\n one(table.caption),\n layout.columns.map((r) => one(r.label)).filter(Boolean).join(\" \"),\n ...head.map(rowLine),\n ...layout.rows.flatMap((run) => [...(run.label === undefined ? [] : [one(run.label)]), ...(run.rows as typeof rows).map(rowLine)]),\n one(table.notes),\n // footnotes, numbered as the html numbers them (V2 tableSearchText reads them too)\n ...(Array.isArray(table.footnotes) ? table.footnotes : []).map((f, i) => one(`[${i + 1}] ${typeof f?.text === \"string\" ? f.text : \"\"}`)),\n ];\n // Only the NEW lines are dropped when empty: an empty row has always contributed its \"\" line.\n const fnCount = Array.isArray(table.footnotes) ? table.footnotes.length : 0;\n const extras = new Set([0, 1, lines.length - 1 - fnCount]);\n return lines.filter((l, i) => l !== \"\" || !extras.has(i));\n};\n\n/** The quote, then `— name, role` when either is set. */\nconst pullQuoteLines = (v: Record<string, unknown>): string[] => {\n const text = (x: unknown): string => (typeof x === \"string\" ? x.trim() : \"\");\n const by = [text(v.name), text(v.role)].filter(Boolean).join(\", \");\n return [text(v.quote), ...(by ? [`— ${by}`] : [])].filter(Boolean);\n};\n","import { slugify } from \"@bettercms-ai/types\";\nimport { PT_TYPES } from \"./types\";\nimport { isPortableTextTable } from \"./table\";\n\n/**\n * RT5: the table of contents (`PortableTextTableOfContents`, types.ts): the one `slugify`, the\n * outline a value's headings make, the write check, and the tree both renderers draw.\n *\n * Stored is what the author sets, `{ title?, depth }`; the outline is derived from the value's\n * headings on every render and never stored (R8 refuses `outline`). Every top-level h2 and h3 is\n * given its outline id on every render, with or without a table of contents, so a link to a\n * heading keeps working when one is added or removed.\n */\nexport const PT_TOC_LIMITS = { titleChars: 40 } as const;\nexport const TOC_DEPTHS = [\"2\", \"3\"] as const;\nexport type TocDepth = (typeof TOC_DEPTHS)[number];\n\n/** The id a heading's words make: V2's `slugify`, whose one spelling lives in @bettercms-ai/types. */\nexport { slugify };\n\nexport interface OutlineItem {\n id: string;\n text: string;\n depth: 2 | 3;\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\n/** A heading id kept from an imported page: a plain HTML id a link can name (`#…`) without escaping. */\nexport const isHeadingAnchor = (v: unknown): v is string => typeof v === \"string\" && /^[A-Za-z0-9][A-Za-z0-9_:.-]{0,127}$/.test(v);\n\n/** Does this value hold a heading anchor? A comparison reads the page with anchors only when it does (ingest `headingAnchors`). */\nexport const hasHeadingAnchors = (value: unknown): boolean =>\n Array.isArray(value) && value.some((n) => isRecord(n) && n._type === \"block\" && isHeadingAnchor(n.anchor));\n\nconst headingText = (node: Record<string, unknown>): string =>\n (Array.isArray(node.children) ? node.children : [])\n .map((c) => (isRecord(c) && typeof c.text === \"string\" ? c.text : \"\"))\n .join(\"\")\n .replace(/​/g, \"\")\n .trim();\n\n/**\n * The value's top-level h2 and h3, in order, each with a de-duplicated id (V2 `outlineOf`): a\n * heading with no words is left out. A heading's own `anchor` (an imported page's id, RT9) is its\n * id, the first time it appears; every other heading's slug steps around the anchors. `nodes` maps\n * each heading node to its id, which is how a renderer finds the id of the heading it is drawing.\n */\nexport function outlineOf(value: unknown): { items: OutlineItem[]; nodes: WeakMap<object, string> } {\n const heads: { node: Record<string, unknown>; text: string; anchor: string | null }[] = [];\n const used = new Set<string>();\n for (const node of Array.isArray(value) ? value : []) {\n if (!isRecord(node) || node._type !== \"block\" || node.listItem || (node.style !== \"h2\" && node.style !== \"h3\")) continue;\n const text = headingText(node);\n if (!text) continue;\n const anchor = isHeadingAnchor(node.anchor) && !used.has(node.anchor) ? node.anchor : null;\n if (anchor) used.add(anchor);\n heads.push({ node, text, anchor });\n }\n const items: OutlineItem[] = [];\n const nodes = new WeakMap<object, string>();\n for (const { node, text, anchor } of heads) {\n let id = anchor;\n if (!id) {\n const base = slugify(text);\n id = base;\n for (let n = 2; used.has(id); n++) id = `${base}-${n}`;\n used.add(id);\n }\n nodes.set(node, id);\n items.push({ id, text, depth: node.style === \"h2\" ? 2 : 3 });\n }\n return { items, nodes };\n}\n\nexport type TocEntry = OutlineItem & { children: OutlineItem[] };\n\n/**\n * The list a table of contents draws (V2 `renderArticle(\"toc\")`): only h2 at depth \"2\"; an h3\n * nests under the entry before it, and opens the list only when nothing comes before it.\n */\nexport function tocTree(items: readonly OutlineItem[], depth: TocDepth): TocEntry[] {\n const out: TocEntry[] = [];\n for (const it of items) {\n if (it.depth === 3 && depth === \"2\") continue;\n const last = out[out.length - 1];\n if (it.depth === 3 && last) last.children.push(it);\n else out.push({ ...it, children: [] });\n }\n return out;\n}\n\n/** What a reader sees of the block itself: its title (trimmed), the label its nav carries, and its depth. */\nexport function tocView(value: unknown): { title: string; label: string; depth: TocDepth } {\n const v = isRecord(value) ? value : {};\n const title = typeof v.title === \"string\" ? v.title.trim() : \"\";\n return { title, label: title || \"Table of contents\", depth: v.depth === \"2\" ? \"2\" : \"3\" };\n}\n\n/** One reason a stored table of contents is refused, at its own path inside the block. */\nexport type PortableTextTocProblem = { path: Array<string | number>; code: string; message: string };\n\nconst KEYS = new Set([\"_type\", \"_key\", \"title\", \"depth\"]);\n\n/**\n * Every reason `node` cannot be stored: `toc_invalid` (a shape problem) or `toc_limit_exceeded`\n * (V2's 40-character title). A derived `outline` is refused by the R8 check, by name.\n */\nexport function tableOfContentsProblems(node: unknown): PortableTextTocProblem[] {\n const out: PortableTextTocProblem[] = [];\n if (!isRecord(node) || node._type !== PT_TYPES.tableOfContents) return out;\n const bad = (path: Array<string | number>, message: string, code = \"toc_invalid\") =>\n out.push({ path, code, message: `${code}: ${message}` });\n for (const k of Object.keys(node)) if (!KEYS.has(k) && k !== \"outline\") bad([k], `unknown key \"${k}\" (title, depth)`);\n if (!(TOC_DEPTHS as readonly unknown[]).includes(node.depth)) bad([\"depth\"], 'depth must be \"2\" or \"3\"');\n if (node.title !== undefined) {\n if (typeof node.title !== \"string\") bad([\"title\"], \"title must be a string\");\n else if (node.title.length > PT_TOC_LIMITS.titleChars) {\n bad([\"title\"], `title is ${node.title.length} characters; at most ${PT_TOC_LIMITS.titleChars}`, \"toc_limit_exceeded\");\n }\n }\n return out;\n}\n\n/** `tableOfContentsProblems` for every table of contents in a value (top level, and in table cells), with its block key. */\nexport function portableTextTableOfContentsProblems(value: unknown): Array<PortableTextTocProblem & { block: string }> {\n const out: Array<PortableTextTocProblem & { block: string }> = [];\n const walk = (blocks: unknown) => {\n if (!Array.isArray(blocks)) return;\n for (const node of blocks) {\n if (isPortableTextTable(node)) {\n for (const row of node.rows as unknown[]) {\n if (!isRecord(row) || !Array.isArray(row.cells)) continue;\n for (const cell of row.cells) if (isRecord(cell)) walk(cell.content);\n }\n continue;\n }\n if (!isRecord(node) || node._type !== PT_TYPES.tableOfContents) continue;\n const block = typeof node._key === \"string\" ? node._key : \"\";\n for (const p of tableOfContentsProblems(node)) out.push({ ...p, block });\n }\n };\n walk(value);\n return out;\n}\n","import { PT_TYPES } from \"./types\";\nimport { isPortableTextTable } from \"./table\";\n\n/**\n * RT6: the footnote (`PortableTextFootnote`, types.ts), an inline object in body text that stores\n * only its `note` (plain text, as V2 keeps it on the marker). Everything else is derived on every\n * render: its number (its place in text order across the value's top-level text blocks), the\n * `fnref-N` / `fn-N` ids, the end list and its back links.\n *\n * Not the table's footnote (`PortableTextTableFootnote`), which belongs to a table and stays apart.\n */\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\n/**\n * Each footnote in the value's top-level text blocks, numbered in text order (V2\n * `renumberFootnotes`), and the notes in that order. `nodes` maps each marker to its number, which\n * is how a renderer finds the number of the marker it is drawing.\n */\nexport function footnoteOrder(value: unknown): { notes: string[]; nodes: WeakMap<object, number> } {\n const notes: string[] = [];\n const nodes = new WeakMap<object, number>();\n for (const node of Array.isArray(value) ? value : []) {\n if (!isRecord(node) || node._type !== \"block\" || !Array.isArray(node.children)) continue;\n for (const child of node.children) {\n if (!isRecord(child) || child._type !== PT_TYPES.footnote) continue;\n nodes.set(child, notes.push(typeof child.note === \"string\" ? child.note : \"\"));\n }\n }\n return { notes, nodes };\n}\n\n/** One reason a stored footnote is refused, with the block it sits in. */\nexport type PortableTextFootnoteProblem = { block: string; path: Array<string | number>; code: string; message: string };\n\nconst KEYS = new Set([\"_type\", \"_key\", \"note\"]);\n\n/**\n * Every reason a value's footnotes cannot be stored, by block and path: `footnote_invalid` for a\n * shape problem, and for a footnote in a table cell (V2 refuses one inside a block: a cell has no\n * place in the body's numbering). A derived `number` is refused by the R8 check, by name.\n */\nexport function portableTextFootnoteProblems(value: unknown): PortableTextFootnoteProblem[] {\n const out: PortableTextFootnoteProblem[] = [];\n const bad = (block: string, path: Array<string | number>, message: string) =>\n out.push({ block, path, code: \"footnote_invalid\", message: `footnote_invalid: ${message}` });\n const blockKey = (n: Record<string, unknown>) => (typeof n._key === \"string\" ? n._key : \"\");\n const each = (blocks: unknown, inCell: string | null) => {\n for (const node of Array.isArray(blocks) ? blocks : []) {\n if (isPortableTextTable(node)) {\n for (const row of node.rows as unknown[]) {\n if (!isRecord(row) || !Array.isArray(row.cells)) continue;\n for (const cell of row.cells) if (isRecord(cell)) each(cell.content, blockKey(node as Record<string, unknown>));\n }\n continue;\n }\n if (!isRecord(node) || node._type !== \"block\" || !Array.isArray(node.children)) continue;\n node.children.forEach((child, i) => {\n if (!isRecord(child) || child._type !== PT_TYPES.footnote) return;\n const block = inCell ?? blockKey(node);\n const path = inCell === null ? [\"children\", i] : [blockKey(node), \"children\", i];\n if (inCell !== null) bad(block, path, \"a footnote belongs in the body text, not a table cell\");\n for (const k of Object.keys(child)) if (!KEYS.has(k) && k !== \"number\") bad(block, [...path, k], `unknown key \"${k}\" (note)`);\n if (typeof child.note !== \"string\") bad(block, [...path, \"note\"], \"note must be a string\");\n });\n }\n };\n each(value, null);\n return out;\n}\n","import type { ResolvedAuthor, ResolvedPost, RichTextResolved } from \"@bettercms-ai/types\";\nimport { PT_TYPES } from \"./types\";\nimport { safeHref } from \"./href\";\n\n/**\n * RT8: the two blocks that point at entries. An author bio stores its author as a reference;\n * related posts store a title, a mode, a count and (hand-picked) references. What a reader sees\n * of the entries (names, titles, urls, covers, bios) is derived, and NEVER stored (G-RT8): the\n * caller resolves it whenever a page is produced (delivery, the hosted apply, a draft preview) and\n * passes `RichTextResolved` in. The renderers never fetch. Three branches, the same on both:\n * - no `resolved` at all: a placeholder aside carrying only what is stored, so a stored html\n * cache stays a pure function of the value and reads back exactly;\n * - resolved, nothing to show (no post resolves, the author is missing): nothing;\n * - resolved: V2's aside, filled.\n */\nexport const PT_RELATED_LIMITS = { titleChars: 40, minCount: 1, maxCount: 6, posts: 6 } as const;\nexport const RELATED_MODES = [\"topic\", \"newest\", \"manual\"] as const;\nexport type RelatedMode = (typeof RELATED_MODES)[number];\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\nconst refOf = (v: unknown): string => (isRecord(v) && typeof v._ref === \"string\" ? v._ref : \"\");\n\n/** The same widened check a rich-text image gets: http(s), or a site-relative path. */\nconst imageSrc = (raw: string): string | null =>\n safeHref(raw) ?? (raw !== \"\" && !raw.startsWith(\"//\") && !/[:\\s]/.test(raw) ? raw : null);\n\n/**\n * What a reader sees of a related-posts block: its title, mode and count, the hand-picked ids\n * (V2's `data-posts`), and the posts to list, in order: the block's own `posts` when manual,\n * else the ids the server chose (`resolved.related[_key]`), at most `count`. An id the server\n * could not resolve (deleted, unpublished) is skipped, and so is a post whose url is refused.\n */\nexport function relatedPostsView(value: unknown, resolved: RichTextResolved = {}) {\n const v = isRecord(value) ? value : {};\n const title = typeof v.title === \"string\" ? v.title.trim() : \"\";\n const mode: RelatedMode = v.mode === \"manual\" || v.mode === \"newest\" ? v.mode : \"topic\";\n const n = typeof v.count === \"number\" && Number.isInteger(v.count) ? v.count : 3;\n const count = Math.min(PT_RELATED_LIMITS.maxCount, Math.max(PT_RELATED_LIMITS.minCount, n));\n const picked = (Array.isArray(v.posts) ? v.posts : []).map(refOf).filter(Boolean);\n const key = typeof v._key === \"string\" ? v._key : \"\";\n const ids = mode === \"manual\" ? picked : (resolved.related?.[key] ?? []).slice(0, count);\n const items: (ResolvedPost & { id: string })[] = [];\n for (const id of ids) {\n const post = resolved.posts?.[id];\n const url = post && typeof post.url === \"string\" ? safeHref(post.url) : null;\n if (post && typeof post.title === \"string\" && url) items.push({ ...post, id, url });\n }\n return { key, title, mode, count, picked, items };\n}\n\n/** What a reader sees of an author bio: the referenced id, and the author when the server resolved one. */\nexport function authorBioView(value: unknown, resolved: RichTextResolved = {}): { key: string; id: string; author: (ResolvedAuthor & { avatar?: string }) | null } {\n const key = isRecord(value) && typeof value._key === \"string\" ? value._key : \"\";\n const id = refOf(isRecord(value) ? value.author : undefined);\n const a = id ? resolved.authors?.[id] : undefined;\n if (!a || typeof a.name !== \"string\") return { key, id, author: null };\n const avatar = typeof a.avatar === \"string\" ? imageSrc(a.avatar.trim()) : null;\n return {\n key,\n id,\n author: {\n name: a.name,\n ...(avatar ? { avatar } : {}),\n ...(typeof a.bio === \"string\" && a.bio.trim() ? { bio: a.bio.trim() } : {}),\n },\n };\n}\n\n/** One reason a stored author bio or related-posts block is refused, with its block key. */\nexport type PortableTextReferenceProblem = { block: string; path: Array<string | number>; code: string; message: string };\n\n/**\n * Every reason a value's author bios and related posts cannot be stored, by block and path:\n * `author_bio_invalid`, `related_posts_invalid` (a shape problem, a duplicated pick) or\n * `related_posts_limit_exceeded` (V2's 40-character title, 1 to 6, at most 6 picks). As with key\n * takeaways, V2's required author and its \"at least one pick\" are the editor's, not the write's:\n * an author still choosing must be able to autosave. Derived keys are refused by R8, by name.\n */\nexport function portableTextReferenceProblems(value: unknown): PortableTextReferenceProblem[] {\n const out: PortableTextReferenceProblem[] = [];\n for (const node of Array.isArray(value) ? value : []) {\n if (!isRecord(node)) continue;\n const block = typeof node._key === \"string\" ? node._key : \"\";\n const bad = (path: Array<string | number>, code: string, message: string) => out.push({ block, path, code, message: `${code}: ${message}` });\n if (node._type === PT_TYPES.authorBio) {\n // name, avatar and bio are derived: R8 refuses them by name.\n for (const k of Object.keys(node)) if (![\"_type\", \"_key\", \"author\", \"name\", \"avatar\", \"bio\", \"resolved\"].includes(k)) bad([k], \"author_bio_invalid\", `unknown key \"${k}\" (author)`);\n if (node.author !== undefined && (!isRecord(node.author) || typeof node.author._ref !== \"string\" || !node.author._ref)) {\n bad([\"author\"], \"author_bio_invalid\", \"author must be a reference { _ref }\");\n }\n }\n if (node._type === PT_TYPES.relatedPosts) {\n const keys = [\"_type\", \"_key\", \"title\", \"mode\", \"count\", \"posts\", \"resolved\"];\n for (const k of Object.keys(node)) if (!keys.includes(k)) bad([k], \"related_posts_invalid\", `unknown key \"${k}\" (title, mode, count, posts)`);\n if (node.title !== undefined) {\n if (typeof node.title !== \"string\") bad([\"title\"], \"related_posts_invalid\", \"title must be a string\");\n else if (node.title.length > PT_RELATED_LIMITS.titleChars) {\n bad([\"title\"], \"related_posts_limit_exceeded\", `title is ${node.title.length} characters; at most ${PT_RELATED_LIMITS.titleChars}`);\n }\n }\n if (!(RELATED_MODES as readonly unknown[]).includes(node.mode)) bad([\"mode\"], \"related_posts_invalid\", 'mode must be \"topic\", \"newest\" or \"manual\"');\n if (typeof node.count !== \"number\" || !Number.isInteger(node.count) || node.count < PT_RELATED_LIMITS.minCount || node.count > PT_RELATED_LIMITS.maxCount) {\n bad([\"count\"], \"related_posts_limit_exceeded\", `count must be a whole number from ${PT_RELATED_LIMITS.minCount} to ${PT_RELATED_LIMITS.maxCount}`);\n }\n // Absent is no picks (an automatic block needs none).\n if (node.posts === undefined) continue;\n if (!Array.isArray(node.posts)) {\n bad([\"posts\"], \"related_posts_invalid\", \"posts must be an array of references\");\n continue;\n }\n const seen = new Set<string>();\n node.posts.forEach((p, i) => {\n const ref = refOf(p);\n if (!ref || !isRecord(p) || typeof p._key !== \"string\") return bad([\"posts\", i], \"related_posts_invalid\", \"a post is { _key, _ref }\");\n // A post's title, slug, url, cover and category are derived: R8 refuses them by name.\n for (const k of Object.keys(p)) if (![\"_key\", \"_ref\", \"title\", \"slug\", \"url\", \"cover\", \"category\"].includes(k)) bad([\"posts\", i, k], \"related_posts_invalid\", `unknown key \"${k}\" (_ref)`);\n if (seen.has(ref)) bad([\"posts\", i], \"related_posts_invalid\", `\"${ref}\" is picked twice`);\n seen.add(ref);\n });\n if (node.posts.length > PT_RELATED_LIMITS.posts) {\n bad([\"posts\"], \"related_posts_limit_exceeded\", `${node.posts.length} posts; at most ${PT_RELATED_LIMITS.posts}`);\n }\n }\n }\n return out;\n}\n\n/**\n * `relatedPosts.no_topic_field`: a `topic` block in a collection with no topic field has nothing to\n * rank by, so it is refused by name rather than quietly listing nothing. The caller knows the\n * collection's config and says whether it has one.\n */\nexport function relatedPostsTopicProblems(value: unknown, opts: { topicField: boolean }): PortableTextReferenceProblem[] {\n if (opts.topicField) return [];\n const out: PortableTextReferenceProblem[] = [];\n for (const node of Array.isArray(value) ? value : []) {\n if (!isRecord(node) || node._type !== PT_TYPES.relatedPosts || node.mode !== \"topic\") continue;\n out.push({\n block: typeof node._key === \"string\" ? node._key : \"\",\n path: [\"mode\"],\n code: \"relatedPosts.no_topic_field\",\n message: \"relatedPosts.no_topic_field: the collection has no topic field to rank related posts by; choose newest or hand-picked\",\n });\n }\n return out;\n}\n","import { EMBEDDED_COMPONENT_KEY, PORTABLE_TEXT_FORMAT, PT_TYPES, type PortableText } from \"./types\";\nimport { lexicalToPortableText } from \"./from-lexical\";\nimport { migrateTable } from \"./table\";\nimport { detachedComponentCopy, type DetachedComponentCopy } from \"./component-copy\";\nimport { randomKey, type KeyGenerator } from \"./key\";\nimport type { EmbedCard, ResolvedAuthor, ResolvedPost, RichTextEmbeds, RichTextResolved } from \"@bettercms-ai/types\";\n\n/**\n * THE reader. One function, every stored shape, and it never throws.\n *\n * ── What this replaces ───────────────────────────────────────────────────────────────────\n * Five readers, each guessing at the same six shapes, each maintained by hand, each with its\n * own idea of what an unrecognised value meant:\n *\n * `RichTextField.toEditorContent` (dashboard, the dock)\n * `pageDocAdapter.richtextToEditorString`(dashboard, the canvas)\n * the `LexicalEditor` import path (dashboard, parse-then-fallback)\n * `lexicalJsonToText` (dashboard, plain text)\n * `renderRichtextValue` (dashboard, reflection)\n *\n * A comment in the codebase says they \"must agree on every shape or a field loads on one\n * surface and not the other\". They did not agree, five separate times, and the symptom each\n * time was a field that opened blank and then autosaved the blank over real content. Keeping\n * five copies in sync by hand was the bug; there is now one copy, and it lives in a package\n * both repos import.\n *\n * ── Never throws, by contract ────────────────────────────────────────────────────────────\n * This runs on every read during the dual-read window. A throw here is a blank field, and a\n * blank field is one autosave away from being permanent. Unrecognised input returns `[]` and\n * is reported through `onUnreadable`, never raised.\n */\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\nconst looksLikeHtml = (s: string): boolean => /<[a-z!/][\\s\\S]*>/i.test(s);\n\nexport interface ReadOptions {\n keyGenerator?: KeyGenerator;\n /** HTML ingest, injected: this module must stay free of any DOM dependency. */\n fromHtml?: (html: string) => PortableText;\n /** Called instead of throwing. The migration turns this into a quarantine report. */\n onUnreadable?: (reason: string, value: unknown) => void;\n}\n\n/**\n * Make a Portable Text array OURS: a `_key` on every node, `markDefs` on every block.\n *\n * Values reach this from two places that predate the spec — a bare array persisted before\n * `_key` was minted, and the hand-rolled `{version:1, content}` tree the `ui` package's old\n * renderer emitted. Both are structurally Portable Text and neither carries markDefs, so\n * `@portabletext/react` would treat a block without them as malformed. Filling them in is\n * cheaper than a migration and makes every historical value renderable today.\n *\n * A TABLE is migrated here too, and that is the whole of the P1c read path: every table stored\n * before P1c is a generic `bcmsBlock` holding a string matrix, and every one of them comes back\n * out as a `table`. Nothing rewrites the database — the shape a reader sees is settled on the\n * way out, so a value converted years ago and a value pasted this morning are the same thing to\n * everything downstream.\n */\nexport function normalizePortableText(value: unknown, key: KeyGenerator = randomKey): PortableText {\n if (!Array.isArray(value)) return [];\n return value.flatMap((raw) => {\n if (!isRecord(raw) || typeof raw._type !== \"string\") return [];\n // Recursing through this same function is what gives a cell everything a block has: keys,\n // markDefs, and a nested table in the rare cell that holds one.\n const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));\n if (table) return [table as never];\n const node: Record<string, unknown> = { ...raw, _key: isRecord(raw) && typeof raw._key === \"string\" && raw._key ? raw._key : key() };\n if (node._type === \"block\") {\n if (!Array.isArray(node.markDefs)) node.markDefs = [];\n if (typeof node.style !== \"string\") node.style = \"normal\";\n node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {\n if (!isRecord(c)) return c;\n const child: Record<string, unknown> = { ...c, _key: typeof c._key === \"string\" && c._key ? c._key : key() };\n if (child._type === \"span\" && !Array.isArray(child.marks)) child.marks = [];\n return child;\n });\n }\n return [node as never];\n });\n}\n\nfunction plainBlocks(text: string, key: KeyGenerator): PortableText {\n if (text === \"\") return [];\n return text.split(\"\\n\").map((line) => ({\n _type: \"block\" as const,\n _key: key(),\n style: \"normal\",\n markDefs: [],\n children: [{ _type: \"span\" as const, _key: key(), text: line, marks: [] }],\n }));\n}\n\nfunction fromString(raw: string, key: KeyGenerator, opts: ReadOptions): PortableText {\n const trimmed = raw.trim();\n if (trimmed === \"\") return [];\n\n // A serialized Lexical state arrives as a JSON STRING, which is why this is tried before\n // the HTML sniff — `{\"root\":…}` contains no angle brackets but is not plain text either.\n if (trimmed.startsWith(\"{\")) {\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (isRecord(parsed) && isRecord(parsed.root)) {\n return lexicalToPortableText(parsed, { keyGenerator: key }).value;\n }\n } catch {\n // Not JSON after all. Fall through: it is text that happens to start with a brace.\n }\n }\n\n if (looksLikeHtml(trimmed) && opts.fromHtml) return opts.fromHtml(raw);\n\n // A bare string is LITERAL. It is NOT run through a markdown parser, because that is how\n // a line beginning \"#\" silently became a heading in copy the author never marked up.\n return plainBlocks(raw, key);\n}\n\n/**\n * Read any stored rich-text value as Portable Text.\n *\n * Shapes handled, in the order they are tested:\n * 1. `null` / `\"\"` → []\n * 2. `PortableText[]` → as-is\n * 3. `{format:\"portable-text-1\", value}` → `.value`\n * 4. `{format:\"lexical-…\", value:{root}}` → converted\n * 5. `{root:…}` bare Lexical state → converted\n * 6. `{format:\"bettercms-notion-doc\", blocks}` → walked, recursing per block\n * 7. `{html:\"…\"}` pre-envelope seed → ingested\n * 8. `\"{\\\"root\\\":…}\"` Lexical JSON string → parsed, converted\n * 9. `\"<p>…</p>\"` bare HTML → ingested\n * 10. `\"plain text\"` → one block per line, literal\n */\nexport function toPortableText(value: unknown, opts: ReadOptions = {}): PortableText {\n const key = opts.keyGenerator ?? randomKey;\n\n if (value == null || value === \"\") return [];\n if (Array.isArray(value)) return normalizePortableText(value, key);\n if (typeof value === \"string\") return fromString(value, key, opts);\n\n if (!isRecord(value)) {\n opts.onUnreadable?.(`unsupported ${typeof value}`, value);\n return [];\n }\n\n if (value.format === PORTABLE_TEXT_FORMAT && Array.isArray(value.value)) {\n return normalizePortableText(value.value, key);\n }\n\n // The hand-rolled `{version:1, content}` tree the old `ui` renderer emitted. Structurally\n // Portable Text, missing only `_key` and `markDefs`, so normalising is the whole conversion.\n if (value.version === 1 && Array.isArray(value.content)) {\n return normalizePortableText(value.content, key);\n }\n\n if (Array.isArray(value.blocks)) return fromNotionDoc(value.blocks, key, opts);\n\n // A Lexical envelope carries the state under `value`; a bare state IS the state.\n if (isRecord(value.value) && isRecord((value.value as Record<string, unknown>).root)) {\n return lexicalToPortableText(value.value, { keyGenerator: key }).value;\n }\n if (isRecord(value.root)) return lexicalToPortableText(value, { keyGenerator: key }).value;\n\n // The pre-envelope seed. Last, so a real envelope is never mistaken for one.\n if (typeof value.html === \"string\") return fromString(value.html, key, opts);\n\n opts.onUnreadable?.(\"unrecognised object shape\", value);\n return [];\n}\n\n/**\n * The `document` / Body format — `{format:\"bettercms-notion-doc\", version:1, blocks}`.\n *\n * Its blocks are heterogeneous and one of them (`richtext`) nests a whole rich-text value of\n * its own, so this recurses through `toPortableText` rather than duplicating the ladder. That\n * recursion is the point: a Body containing a rich-text block containing a link now produces\n * real markDefs, which the old renderer could not express at any depth.\n */\nfunction fromNotionDoc(blocks: unknown[], key: KeyGenerator, opts: ReadOptions): PortableText {\n const out: PortableText = [];\n for (const raw of blocks) {\n if (!isRecord(raw)) continue;\n const type = typeof raw.type === \"string\" ? raw.type : \"\";\n\n if (type === \"paragraph\") {\n out.push(...plainBlocks(typeof raw.text === \"string\" ? raw.text : \"\", key));\n continue;\n }\n if (type === \"richtext\") {\n out.push(...toPortableText(raw.value, { ...opts, keyGenerator: key }));\n continue;\n }\n // `field`, `component` and `reference` blocks reference something that has to be RESOLVED\n // against the project before it renders. They keep their whole payload in a declared\n // block object so the resolution can happen at publish time, where the project is known.\n out.push({\n _type: PT_TYPES.block,\n _key: key(),\n schemaKey: type || \"block\",\n schemaLabel: typeof raw.label === \"string\" ? raw.label : \"\",\n fields: {\n ...(raw.value !== undefined ? { value: raw.value } : {}),\n ...(typeof raw.fieldType === \"string\" ? { fieldType: raw.fieldType } : {}),\n },\n });\n }\n return out;\n}\n\n/**\n * The structured value on a block's props, or null when there is none.\n *\n * Every delivery lane asks this same question — \"is there structured content, or do I fall\n * back to the html cache?\" — and each one answering it locally is how the lanes drifted in the\n * first place: the hosted renderer preferred `content`, the shared React renderer preferred\n * `content`, and Next and Astro did not look for it at all. One implementation, four callers.\n *\n * Accepts every spelling in the wild: a bare array, `{value}` (the new envelope), and\n * `{content}` (the legacy hand-rolled tree). EMPTY is treated as absent on purpose — an empty\n * array must fall back to `html` rather than render a blank region.\n */\nexport function readStructuredContent(props: unknown): PortableText | null {\n if (!isRecord(props)) return null;\n const candidate = props.content ?? props.value;\n const arr = Array.isArray(candidate)\n ? candidate\n : isRecord(candidate)\n ? Array.isArray(candidate.content)\n ? candidate.content\n : Array.isArray(candidate.value)\n ? candidate.value\n : null\n : null;\n return arr && arr.length > 0 ? (arr as PortableText) : null;\n}\n\n/**\n * The embeds a delivered value carries beside its html (`RichTextEmbeds`), from the same envelope\n * `readStructuredContent` reads; undefined when there are none. Delivered data is checked, not\n * trusted: a card keeps only string fields.\n */\nexport function readStructuredEmbeds(props: unknown): RichTextEmbeds | undefined {\n if (!isRecord(props)) return undefined;\n const candidate = props.content ?? props.value;\n const raw = isRecord(candidate) ? candidate.embeds : undefined;\n if (!isRecord(raw)) return undefined;\n const cards: Record<string, EmbedCard> = {};\n if (isRecord(raw.cards)) {\n for (const [url, c] of Object.entries(raw.cards)) {\n if (!isRecord(c) || typeof c.text !== \"string\" || typeof c.authorName !== \"string\") continue;\n cards[url] = {\n text: c.text,\n authorName: c.authorName,\n ...(typeof c.authorUrl === \"string\" ? { authorUrl: c.authorUrl } : {}),\n ...(typeof c.date === \"string\" ? { date: c.date } : {}),\n };\n }\n }\n return { cards };\n}\n\n/** The `component` block an embed in rich text stands for. */\nexport interface EmbeddedComponentBlock {\n type: \"component\";\n /** The node's `_key` — unique within its rich-text field only. */\n id: string;\n props: { componentId: string; overrides: Record<string, unknown> };\n}\n\n/**\n * A component embedded in rich text, as the ordinary `component` block every renderer already\n * draws — so an embed gets the same resolver, cycle guard and identity attributes as a placed\n * instance. Null for any other node, and for a separate copy (`fields.detached`): a copy is not\n * a reference, and resolving its `componentId` would draw the shared component it left.\n * `embeddedComponentRender` answers for both.\n */\nexport function embeddedComponentBlock(value: unknown): EmbeddedComponentBlock | null {\n if (!isRecord(value) || value.schemaKey !== EMBEDDED_COMPONENT_KEY || !isRecord(value.fields)) return null;\n if (value.fields.detached !== undefined) return null;\n const { componentId, overrides } = value.fields;\n return {\n type: \"component\",\n id: typeof value._key === \"string\" ? value._key : \"\",\n props: { componentId: String(componentId ?? \"\"), overrides: isRecord(overrides) ? overrides : {} },\n };\n}\n\n/**\n * What an embedded component renders as: a linked one, its `component` block (resolved by the\n * host); a separate copy, its own frozen blocks, which need no lookup. Null for any other node,\n * and for a copy whose blocks are missing (the write refuses one; a reader draws nothing for it).\n */\nexport function embeddedComponentRender(\n value: unknown,\n): { kind: \"linked\"; block: EmbeddedComponentBlock } | { kind: \"copy\"; copy: DetachedComponentCopy } | null {\n const block = embeddedComponentBlock(value);\n if (block) return { kind: \"linked\", block };\n const copy = detachedComponentCopy(value);\n return copy ? { kind: \"copy\", copy } : null;\n}\n\nconst strings = <K extends string>(o: Record<string, unknown>, keys: readonly K[]): Partial<Record<K, string>> =>\n Object.fromEntries(keys.filter((k) => typeof o[k] === \"string\").map((k) => [k, o[k]])) as Partial<Record<K, string>>;\n\n/**\n * RT8 (G-RT8): the entries a DELIVERED value's author bios and related posts point at, which the\n * delivery puts beside the value (`value.resolved`); undefined when there are none. Never read from\n * a stored envelope: nothing stores it. Checked, not trusted: a post keeps only its string fields\n * and needs a title and url, an author a name, and `related` only lists of ids.\n */\nexport function readStructuredResolved(props: unknown): RichTextResolved | undefined {\n if (!isRecord(props)) return undefined;\n const candidate = props.content ?? props.value;\n const raw = isRecord(candidate) ? candidate.resolved : undefined;\n if (!isRecord(raw)) return undefined;\n const posts: Record<string, ResolvedPost> = {};\n if (isRecord(raw.posts)) {\n for (const [id, p] of Object.entries(raw.posts)) {\n if (isRecord(p) && typeof p.title === \"string\" && typeof p.url === \"string\") {\n posts[id] = { title: p.title, url: p.url, ...strings(p, [\"cover\", \"category\"] as const) };\n }\n }\n }\n const authors: Record<string, ResolvedAuthor> = {};\n if (isRecord(raw.authors)) {\n for (const [id, a] of Object.entries(raw.authors)) {\n if (isRecord(a) && typeof a.name === \"string\") authors[id] = { name: a.name, ...strings(a, [\"avatar\", \"bio\", \"url\"] as const) };\n }\n }\n const related: Record<string, string[]> = {};\n if (isRecord(raw.related)) {\n for (const [key, ids] of Object.entries(raw.related)) {\n if (Array.isArray(ids)) related[key] = ids.filter((id): id is string => typeof id === \"string\");\n }\n }\n return { posts, authors, related };\n}\n","/**\n * What a video URL actually IS — a provider embed, a playable file, or nothing we will render.\n *\n * Moved here from the dashboard (`src/lib/richtext/builtInBlocks.ts`) because the same question\n * is asked on both sides of one value: the server serialises a `video` block into the published\n * page, and the editor draws the same block in the canvas. Two implementations disagreeing about\n * whether a link is a YouTube embed means the author sees one thing and the visitor another.\n *\n * 🔴 ALLOWLIST, NOT A GUESS. An unknown URL returns null rather than becoming an `<iframe src>`:\n * this value reaches a published page, and a frame pointing at arbitrary author input on the\n * tenant's own origin is the one thing a renderer must not mint by accident.\n */\nexport type ResolvedVideoSource = {\n kind: \"iframe\" | \"video\";\n src: string;\n provider: \"YouTube\" | \"Vimeo\" | \"Video file\";\n};\n\nconst videoId = (value: string | null | undefined): string | null =>\n value && /^[a-zA-Z0-9_-]{6,32}$/.test(value) ? value : null;\n\n/** Only known embed providers or direct video files become executable media elements. */\nexport function resolveVideoSource(value: unknown): ResolvedVideoSource | null {\n if (typeof value !== \"string\" || !value.trim()) return null;\n let parsed: URL;\n try {\n parsed = new URL(value.trim());\n } catch {\n return null;\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") return null;\n\n const host = parsed.hostname.toLowerCase().replace(/^www\\./, \"\");\n let id: string | null = null;\n if (host === \"youtu.be\") id = videoId(parsed.pathname.split(\"/\").filter(Boolean)[0]);\n if (host === \"youtube.com\" || host === \"m.youtube.com\" || host === \"youtube-nocookie.com\") {\n id = videoId(\n parsed.searchParams.get(\"v\")\n ?? parsed.pathname.match(/^\\/(?:embed|shorts)\\/([^/?#]+)/)?.[1],\n );\n }\n if (id) {\n return {\n kind: \"iframe\",\n src: `https://www.youtube-nocookie.com/embed/${id}`,\n provider: \"YouTube\",\n };\n }\n\n if (host === \"vimeo.com\" || host === \"player.vimeo.com\") {\n // Hand-rolled reverse scan, not `findLast`: this package's consumers compile against\n // libs older than ES2023, where `Array.prototype.findLast` is not declared.\n const segments: string[] = parsed.pathname.split(\"/\").filter(Boolean);\n let vimeoId: string | null = null;\n for (let i = segments.length - 1; i >= 0; i -= 1) {\n if (/^\\d+$/.test(segments[i]!)) {\n vimeoId = segments[i]!;\n break;\n }\n }\n if (vimeoId) {\n return {\n kind: \"iframe\",\n src: `https://player.vimeo.com/video/${vimeoId}`,\n provider: \"Vimeo\",\n };\n }\n }\n\n if (/\\.(?:m4v|mp4|og[gv]|webm)$/i.test(parsed.pathname)) {\n return { kind: \"video\", src: parsed.toString(), provider: \"Video file\" };\n }\n return null;\n}\n","/**\n * RT3: the embed. Stored `{ url, title, aspect }`; everything else is DERIVED from the url at\n * render: the provider, its shape and the frame src (never stored; R8 refuses them on write).\n *\n * The resolver is pure and the frame src comes only from code: each provider's player URL is\n * BUILT from ids parsed out of the link, for a host on this file's allowlist, over https. A link\n * no rule recognises is a link card, never an iframe of whatever it points at. The allowlist grows\n * from evidence (an import's `embed.legacy_iframe:<host>` lines), one pure parser per provider.\n *\n * An X post is a card: its words come from an oEmbed fetch the server makes when it builds the\n * html cache (never stored in the value). Without that data, it is a link card.\n */\nimport type { EmbedCard, RichTextEmbeds } from \"@bettercms-ai/types\";\nimport { resolveVideoSource } from \"./video\";\nimport { safeHref } from \"./href\";\n\nexport type { EmbedCard, RichTextEmbeds };\n\nexport const EMBED_ASPECTS = [\"16:9\", \"4:3\", \"1:1\", \"9:16\"] as const;\nexport type EmbedAspect = (typeof EMBED_ASPECTS)[number];\nexport const isEmbedAspect = (v: unknown): v is EmbedAspect => (EMBED_ASPECTS as readonly unknown[]).includes(v);\n/** V2's title cap: screen-reader only, never shown. */\nexport const EMBED_TITLE_MAX = 120;\n\nexport type EmbedProvider =\n | \"youtube\" | \"vimeo\" | \"x\" | \"codepen\" | \"figma\" | \"maps\" | \"spotify\" | \"podcast\" | \"audio\"\n | \"calendly\" | \"typeform\" | \"loom\" | \"airtable\" | \"google\"\n | \"link\";\n\n/** V2's detectEmbed (kit rich-text article.ts), verbatim: which provider a link is, and its label. */\nexport function detectEmbed(url: string): { provider: EmbedProvider; label: string; host: string } {\n const u = url.trim();\n let host = \"\";\n try {\n host = new URL(u).hostname.replace(/^www\\./, \"\");\n } catch {\n host = \"\";\n }\n const is = (re: RegExp) => re.test(u);\n if (is(/(?:youtube\\.com\\/(?:watch\\?|embed\\/|shorts\\/|live\\/)|youtu\\.be\\/)/i)) return { provider: \"youtube\", label: \"YouTube\", host };\n if (is(/vimeo\\.com\\/(?:video\\/)?\\d+/i)) return { provider: \"vimeo\", label: \"Vimeo\", host };\n if (is(/(?:twitter|x)\\.com\\/[^/]+\\/status\\/\\d+/i)) return { provider: \"x\", label: \"X post\", host };\n if (is(/codepen\\.io\\/[^/]+\\/(?:pen|embed)\\//i)) return { provider: \"codepen\", label: \"CodePen\", host };\n if (is(/figma\\.com\\/(?:file|design|proto|board)\\//i)) return { provider: \"figma\", label: \"Figma\", host };\n if (is(/(?:google\\.[a-z.]+\\/maps|maps\\.app\\.goo\\.gl|goo\\.gl\\/maps)/i)) return { provider: \"maps\", label: \"Google Maps\", host };\n if (is(/open\\.spotify\\.com\\/(?:episode|track|show|playlist|album)\\//i)) return { provider: \"spotify\", label: \"Spotify\", host };\n if (is(/podcasts\\.apple\\.com\\//i)) return { provider: \"podcast\", label: \"Apple Podcasts\", host };\n if (is(/\\.(?:mp3|m4a|wav|ogg|aac)(?:\\?|$)/i)) return { provider: \"audio\", label: \"Audio\", host };\n return { provider: \"link\", label: host || \"Link\", host };\n}\n\n/**\n * Past V2's list: the providers the import evidence asked for, and the PLAYER urls an imported\n * `<iframe>` carries (a YouTube nocookie frame, a Spotify /embed/ path) that V2's page-url patterns miss.\n */\nfunction detectMore(url: URL): { provider: EmbedProvider; label: string } | null {\n const host = url.hostname.toLowerCase().replace(/^www\\./, \"\");\n const path = url.pathname;\n if (host === \"youtube-nocookie.com\" && path.startsWith(\"/embed/\")) return { provider: \"youtube\", label: \"YouTube\" };\n if (host === \"open.spotify.com\" && /^\\/embed\\/(?:episode|track|show|playlist|album)\\//.test(path)) return { provider: \"spotify\", label: \"Spotify\" };\n if (host === \"calendly.com\") return { provider: \"calendly\", label: \"Calendly\" };\n if (host.endsWith(\".typeform.com\") && /^\\/to\\//.test(path)) return { provider: \"typeform\", label: \"Typeform\" };\n if (host === \"loom.com\" && /^\\/(?:share|embed)\\//.test(path)) return { provider: \"loom\", label: \"Loom\" };\n if (host === \"airtable.com\") return { provider: \"airtable\", label: \"Airtable\" };\n if (host === \"docs.google.com\") {\n const kind = path.split(\"/\")[1];\n const label = { forms: \"Google Forms\", document: \"Google Docs\", spreadsheets: \"Google Sheets\", presentation: \"Google Slides\" }[kind ?? \"\"];\n if (label) return { provider: \"google\", label };\n }\n return null;\n}\n\n/** How an embed renders, all of it derived from the stored url. */\nexport type ResolvedEmbed =\n | { kind: \"frame\"; provider: EmbedProvider; label: string; src: string; width: string; height: string }\n | { kind: \"audio\"; provider: \"audio\"; label: string; src: string }\n | { kind: \"card\"; provider: \"x\"; label: string; url: string }\n | { kind: \"link\"; provider: EmbedProvider; label: string; url: string | null };\n\n/** A video frame's default box per aspect: the size it has with no stylesheet at all. */\nconst FRAME_SIZE: Record<EmbedAspect, [string, string]> = {\n \"16:9\": [\"640\", \"360\"],\n \"4:3\": [\"640\", \"480\"],\n \"1:1\": [\"640\", \"640\"],\n \"9:16\": [\"360\", \"640\"],\n};\n\nconst ID = /^[A-Za-z0-9_-]{1,64}$/;\n/** A form, a calendar, a table or a document fills the column and is as tall as it needs. */\nconst FULL_WIDTH_HEIGHT: Partial<Record<EmbedProvider, string>> = { calendly: \"700\", typeform: \"500\", airtable: \"533\" };\n\n/** The provider's player url (and, for a full-width player, its height), or null when we cannot frame the link. */\nfunction frameOf(provider: EmbedProvider, url: URL): { src: string; height?: string } | null {\n const parts = url.pathname.split(\"/\").filter(Boolean);\n switch (provider) {\n case \"youtube\":\n case \"vimeo\": {\n // The video block's resolver: one place knows YouTube's and Vimeo's player urls.\n const video = resolveVideoSource(url.href);\n return video?.kind === \"iframe\" ? { src: video.src } : null;\n }\n case \"codepen\": {\n const [user, mode, id] = parts;\n return user && ID.test(user) && (mode === \"pen\" || mode === \"embed\") && id && ID.test(id)\n ? { src: `https://codepen.io/${user}/embed/${id}?default-tab=result` }\n : null;\n }\n case \"figma\": {\n const [mode, key] = parts;\n if (!mode || ![\"file\", \"design\", \"proto\", \"board\"].includes(mode) || !key || !ID.test(key)) return null;\n const frame = new URL(`https://embed.figma.com/${mode}/${key}`);\n const node = url.searchParams.get(\"node-id\");\n if (node && /^[0-9A-Za-z:_-]{1,64}$/.test(node)) frame.searchParams.set(\"node-id\", node);\n frame.searchParams.set(\"embed-host\", \"bettercms\");\n return { src: frame.href };\n }\n case \"maps\": {\n if (!/^(?:www\\.)?google\\.[a-z.]+$/i.test(url.hostname)) return null; // a short link needs a fetch to resolve\n const pb = url.searchParams.get(\"pb\");\n if (parts[0] === \"maps\" && parts[1] === \"embed\" && pb && /^[!A-Za-z0-9._:%+-]{1,2000}$/.test(pb)) {\n return { src: `https://www.google.com/maps/embed?pb=${encodeURIComponent(pb)}` };\n }\n const at = /@(-?\\d{1,3}(?:\\.\\d+)?),(-?\\d{1,3}(?:\\.\\d+)?)/.exec(url.pathname);\n const place = parts[0] === \"maps\" && parts[1] === \"place\" && parts[2] ? decodeURIComponent(parts[2]).replace(/\\+/g, \" \") : null;\n const q = at ? `${at[1]},${at[2]}` : (url.searchParams.get(\"q\") ?? place);\n if (!q || q.length > 200) return null;\n return { src: `https://maps.google.com/maps?q=${encodeURIComponent(q)}&output=embed` };\n }\n case \"spotify\": {\n const [type, id] = parts[0] === \"embed\" ? parts.slice(1) : parts;\n if (!type || ![\"episode\", \"track\", \"show\", \"playlist\", \"album\"].includes(type) || !id || !ID.test(id)) return null;\n return { src: `https://open.spotify.com/embed/${type}/${id}`, height: type === \"track\" || type === \"episode\" ? \"152\" : \"352\" };\n }\n case \"podcast\":\n return (url.hostname === \"podcasts.apple.com\" || url.hostname === \"embed.podcasts.apple.com\") && parts.length >= 2\n ? { src: `https://embed.podcasts.apple.com${url.pathname}${url.search}`, height: url.searchParams.has(\"i\") ? \"175\" : \"450\" }\n : null;\n case \"calendly\":\n return parts.length >= 1 && parts.length <= 2 && parts.every((p) => ID.test(p))\n ? { src: `https://calendly.com/${parts.join(\"/\")}?embed_type=Inline&hide_gdpr_banner=1` }\n : null;\n case \"typeform\": {\n const id = parts[0] === \"to\" ? parts[1] : undefined;\n return id && /^[A-Za-z0-9]{4,32}$/.test(id) ? { src: `https://form.typeform.com/to/${id}` } : null;\n }\n case \"loom\": {\n const id = parts[1];\n return (parts[0] === \"share\" || parts[0] === \"embed\") && id && /^[0-9a-f]{32}$/.test(id) ? { src: `https://www.loom.com/embed/${id}` } : null;\n }\n case \"airtable\": {\n const ids = parts[0] === \"embed\" ? parts.slice(1) : parts;\n return ids.length >= 1 && ids.length <= 3 && ids.every((p) => /^(?:app|shr|tbl|viw)[A-Za-z0-9]{14}$/.test(p)) && ids.some((p) => p.startsWith(\"shr\"))\n ? { src: `https://airtable.com/embed/${ids.join(\"/\")}` }\n : null;\n }\n case \"google\": {\n // /<kind>/d/e/<id>/… is a published file, /<kind>/d/<id>/… a shared one.\n const [kind, d, maybeE, maybeId] = parts;\n if (d !== \"d\") return null;\n const published = maybeE === \"e\";\n const id = published ? maybeId : maybeE;\n if (!id || !/^[A-Za-z0-9_-]{10,128}$/.test(id)) return null;\n const base = `https://docs.google.com/${kind}/d/${published ? `e/${id}` : id}`;\n if (kind === \"forms\") return { src: `${base}/viewform?embedded=true`, height: \"800\" };\n if (kind === \"document\") return { src: published ? `${base}/pub?embedded=true` : `${base}/preview`, height: \"600\" };\n if (kind === \"spreadsheets\") return { src: published ? `${base}/pubhtml?widget=true&headers=false` : `${base}/preview`, height: \"600\" };\n if (kind === \"presentation\") return { src: published ? `${base}/embed` : `${base}/preview` };\n return null;\n }\n default:\n return null;\n }\n}\n\n/** The X post id and handle in a status link, any of its spellings. */\nconst X_STATUS = /^\\/([A-Za-z0-9_]{1,15})\\/status\\/(\\d{1,25})(?:\\/|$)/;\nconst X_HOSTS = /^(?:www\\.|mobile\\.)?(?:twitter|x)\\.com$/i;\n\n/**\n * The url an embed's derived data is keyed by. An X post in any spelling (twitter.com or x.com,\n * www. or mobile., a tracking query, a /photo/1 tail, any handle case) is one\n * `https://x.com/<handle>/status/<id>`; any other link is itself, normalised and without its\n * fragment. Null for a link no renderer would follow.\n */\nexport function canonicalEmbedUrl(raw: unknown): string | null {\n const url = typeof raw === \"string\" ? raw.trim() : \"\";\n const safe = url ? safeHref(url) : null;\n if (!safe || !/^https?:\\/\\//i.test(safe)) return null;\n let parsed: URL;\n try {\n parsed = new URL(safe);\n } catch {\n return null;\n }\n const status = X_HOSTS.test(parsed.hostname) ? X_STATUS.exec(parsed.pathname) : null;\n if (status) return `https://x.com/${status[1].toLowerCase()}/status/${status[2]}`;\n parsed.hash = \"\";\n return parsed.href;\n}\n\n/**\n * The embed's rendering, derived from its stored url and aspect. Pure. An unsafe url is a link\n * card with no href; one no rule recognises (or a short link) is a link card to itself.\n */\nexport function resolveEmbed(rawUrl: unknown, aspect: unknown = \"16:9\"): ResolvedEmbed {\n const url = typeof rawUrl === \"string\" ? rawUrl.trim() : \"\";\n const v2 = detectEmbed(url);\n const safe = url ? safeHref(url) : null;\n let parsed: URL | null = null;\n try {\n parsed = safe && /^https?:\\/\\//i.test(safe) ? new URL(safe) : null;\n } catch {\n parsed = null;\n }\n const { provider, label } = v2.provider === \"link\" && parsed ? (detectMore(parsed) ?? v2) : v2;\n const link: ResolvedEmbed = { kind: \"link\", provider, label, url: safe };\n if (!parsed) return link;\n if (provider === \"x\") return { kind: \"card\", provider: \"x\", label, url: canonicalEmbedUrl(parsed.href) ?? parsed.href };\n if (provider === \"audio\") return parsed.protocol === \"https:\" ? { kind: \"audio\", provider: \"audio\", label, src: parsed.href } : link;\n const frame = frameOf(provider, parsed);\n if (!frame) return link;\n const height = frame.height ?? FULL_WIDTH_HEIGHT[provider];\n if (height) return { kind: \"frame\", provider, label, src: frame.src, width: \"100%\", height };\n const [width, h] = FRAME_SIZE[isEmbedAspect(aspect) ? aspect : \"16:9\"];\n return { kind: \"frame\", provider, label, src: frame.src, width, height: h };\n}\n\n/** What a render needs that the value does not hold: the cards fetched for it. */\nexport type EmbedRender = Partial<RichTextEmbeds>;\n\n/** X's widget script, loaded once per page on a site that opted in (next `BcmsEmbedScripts`), never by a value's html. */\nexport const X_WIDGET_SCRIPT = \"https://platform.twitter.com/widgets.js\";\n\n/** The aspect nearest an imported frame's width and height (16:9 when it gives none). */\nexport function nearestEmbedAspect(width: unknown, height: unknown): EmbedAspect {\n const w = Number(width);\n const h = Number(height);\n if (!(w > 0) || !(h > 0)) return \"16:9\";\n const ratio = w / h;\n const ratios: [EmbedAspect, number][] = [[\"16:9\", 16 / 9], [\"4:3\", 4 / 3], [\"1:1\", 1], [\"9:16\", 9 / 16]];\n return ratios.reduce((best, cur) => (Math.abs(cur[1] - ratio) < Math.abs(best[1] - ratio) ? cur : best))[0];\n}\n","import type { PortableTextDocument, TextProps } from \"@bettercms-ai/types\";\nimport { PortableText } from \"./portableText\";\nimport { readStructuredContent } from \"@bettercms-ai/richtext\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface TextBlockProps extends TextProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n /** Portable Text source. WINS over `html`, which is the derived cache. */\n content?: PortableTextDocument | unknown[];\n}\n\nexport function Text({\n html,\n content,\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: TextBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const cls = `text-muted-foreground leading-relaxed ${alignClass} ${className}`;\n\n /**\n * 🔴 THE BINDING USED TO BE DROPPED HERE, and that made Portable Text uneditable.\n *\n * This was two mutually exclusive `return`s: the structured branch rendered `<PortableText>`\n * in a plain `<div>`, and `data-bcms-field` / `data-bcms-kind` were spread ONLY on the HTML\n * fallback below it. So the moment a block carried structured content it lost every editable\n * address — and `block-bindings.ts` suppressed `html` in the same case, leaving the block\n * with none at all. Structured content, the thing that exists to make rich text MORE\n * editable, made it completely uneditable.\n *\n * One container now, always carrying the binding; only the CHILDREN switch. And when\n * structured content is present the binding gets richer rather than poorer: `PortableText`\n * stamps a per-block address off each `_key`, so a click lands on one paragraph instead of\n * the whole field.\n */\n const structured = readStructuredContent({ content });\n\n return (\n <div\n className={cls}\n {...(structured ? {} : { \"data-bcms-field\": field, \"data-bcms-kind\": kind })}\n >\n {structured ? (\n <PortableText value={structured} bindingBase={field} />\n ) : (\n // eslint-disable-next-line react/no-danger\n <span dangerouslySetInnerHTML={{ __html: html }} />\n )}\n </div>\n );\n}\n\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import type { ImageProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface ImageBlockProps extends ImageProps, BcmsBindingProps {\n className?: string;\n sizes?: string;\n}\n\nexport function Image({\n src,\n alt,\n width,\n height,\n caption,\n className = \"\",\n sizes,\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: ImageBlockProps) {\n // A set width is the author's size for this image and must win over `w-full`; it stays a\n // ceiling (max-width) so a narrow column still shrinks it, and height follows the aspect ratio.\n const sized = typeof width === \"number\" && width > 0;\n return (\n <figure className={cn(sized ? \"max-w-full\" : \"w-full\", className)}>\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n data-bcms-field={field}\n data-bcms-kind={kind}\n style={sized ? { width: `${width}px`, maxWidth: \"100%\", height: \"auto\" } : undefined}\n className={cn(!sized && \"w-full\", \"h-auto rounded-md object-cover\")}\n loading=\"lazy\"\n />\n {caption && (\n <figcaption className=\"mt-2 text-sm text-muted-foreground text-center italic\">\n {caption}\n </figcaption>\n )}\n </figure>\n );\n}\n","import type { CSSProperties } from \"react\";\nimport type { ButtonProps, ButtonVariant } from \"@bettercms-ai/types\";\nimport { LINK_RELS, buttonIconOf, buttonIconSvg, buttonVariantOf, isScriptHref } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface ButtonBlockProps extends ButtonProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n /** The block style's paint (`splitButtonStyle(style).paint`), on the anchor itself. */\n style?: CSSProperties;\n}\n\nexport function Button({\n text,\n href,\n variant = \"primary\",\n size = \"md\",\n fullWidth = false,\n arrow = false,\n icon: iconProp,\n newTab = false,\n rel,\n action,\n align = \"left\",\n className = \"\",\n style,\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: ButtonBlockProps) {\n const baseStyles =\n \"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\";\n\n const variantStyles: Record<ButtonVariant, string> = {\n primary:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n tertiary: \"bg-transparent text-primary hover:bg-primary/10\",\n brand: \"bg-accent text-accent-foreground shadow hover:bg-accent/90\",\n dark: \"bg-foreground text-background shadow hover:bg-foreground/90\",\n light: \"border bg-background text-foreground shadow-sm hover:bg-muted\",\n outline: \"border border-current bg-transparent text-primary hover:bg-primary/10\",\n link: \"h-auto bg-transparent p-0 text-primary underline underline-offset-4\",\n };\n const icon = buttonIconOf(iconProp);\n\n const alignClass = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n }[align];\n\n // The hosted renderer's S/M/L (6px 14px / 10px 20px / 14px 28px) in this package's Tailwind\n // scale — approximate by design: M stays the historical `px-4 py-2`. Props arrive spread\n // from stored block JSON, so an unknown size falls back to M, as it does on every renderer.\n const sizeStyles = {\n sm: \"px-3 py-1.5 text-xs\",\n md: \"px-4 py-2\",\n lg: \"px-6 py-3 text-base\",\n };\n const sizeClass = size === \"sm\" || size === \"lg\" ? sizeStyles[size] : sizeStyles.md;\n // Full width is a block-level box across the column, label centred (`bcms-btn-full`).\n const fullClass = fullWidth === true ? \"flex w-full justify-center\" : \"\";\n\n // The hosted renderer's anchorTargetRel, as attributes: a new tab keeps `noopener noreferrer`,\n // a link relation (nofollow, sponsored, ugc) joins it in the same rel.\n const relTokens = [...(newTab === true ? [\"noopener\", \"noreferrer\"] : []), ...(LINK_RELS.includes(rel as (typeof LINK_RELS)[number]) ? [rel as string] : [])];\n\n // The site's script owns the click: a `<button type=\"button\">`, never an anchor with an invented href.\n if (action === \"code\") {\n return (\n <button type=\"button\" data-bcms-field={field} data-bcms-kind={kind} style={style} className={cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className)}>\n {icon && <span className=\"mr-2 inline-flex\" dangerouslySetInnerHTML={{ __html: buttonIconSvg(icon, \"bcms-btn-icon\") }} />}\n {text}\n {arrow === true && <span className=\"ml-2 inline-flex\" dangerouslySetInnerHTML={{ __html: buttonIconSvg(\"arrow-right\", \"bcms-btn-arrow\") }} />}\n </button>\n );\n }\n return (\n <a\n // Block props arrive from stored JSON and instance overrides: a script href renders as \"#\".\n href={typeof href === \"string\" && isScriptHref(href) ? \"#\" : href}\n {...(newTab === true ? { target: \"_blank\" } : {})}\n {...(relTokens.length ? { rel: relTokens.join(\" \") } : {})}\n data-bcms-field={field}\n data-bcms-kind={kind}\n style={style}\n className={cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className)}\n >\n {icon && <span className=\"mr-2 inline-flex\" dangerouslySetInnerHTML={{ __html: buttonIconSvg(icon, \"bcms-btn-icon\") }} />}\n {text}\n {arrow === true && <span className=\"ml-2 inline-flex\" dangerouslySetInnerHTML={{ __html: buttonIconSvg(\"arrow-right\", \"bcms-btn-arrow\") }} />}\n </a>\n );\n}\n","import type { SpacerProps } from \"@bettercms-ai/types\";\n\ninterface SpacerBlockProps extends SpacerProps {\n className?: string;\n}\n\nexport function Spacer({ height = 32, className = \"\" }: SpacerBlockProps) {\n return (\n <div\n className={`w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`}\n style={{ height: `${height}px` }}\n aria-hidden=\"true\"\n >\n <span className=\"text-xs text-muted-foreground/40 select-none\">\n {height}px\n </span>\n </div>\n );\n}\n","import type { ColumnsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface ColumnsBlockProps extends ColumnsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Columns({\n columns,\n gap = 16,\n className = \"\",\n renderBlock,\n}: ColumnsBlockProps) {\n return (\n <div\n className={cn(\"grid w-full\", className)}\n style={{\n gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,\n gap: `${gap}px`,\n }}\n >\n {columns.map((columnBlocks, colIndex) => (\n <div key={colIndex} className=\"flex flex-col gap-4 min-w-0\">\n {columnBlocks.map((block) => (\n <span key={block.id}>\n {renderBlock ? renderBlock(block) : null}\n </span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { VideoProps } from \"@bettercms-ai/types\";\nimport { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\ninterface VideoBlockProps extends VideoProps {\n className?: string;\n}\n\nfunction getEmbedUrl(url: string): { embedUrl: string; provider: \"youtube\" | \"vimeo\" | \"unknown\" } {\n // YouTube\n const ytMatch = url.match(\n /(?:youtube\\.com\\/(?:watch\\?v=|embed\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/\n );\n if (ytMatch) {\n return {\n embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,\n provider: \"youtube\",\n };\n }\n\n // Vimeo\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/);\n if (vimeoMatch) {\n return {\n embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}`,\n provider: \"vimeo\",\n };\n }\n\n return { embedUrl: url, provider: \"unknown\" };\n}\n\nexport function Video({\n url,\n poster,\n autoplay = false,\n loop = false,\n className = \"\",\n}: VideoBlockProps) {\n const [isLoaded, setIsLoaded] = useState(false);\n const { embedUrl, provider } = getEmbedUrl(url);\n\n // If not a recognized embed, render a native video tag\n if (provider === \"unknown\") {\n return (\n <video\n src={url}\n poster={poster}\n autoPlay={autoplay}\n loop={loop}\n controls\n className={className}\n playsInline\n />\n );\n }\n\n return (\n <div className={cn(\"w-full overflow-hidden rounded-md bg-black aspect-video\", className)}>\n {!isLoaded && poster && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={poster}\n alt=\"Video thumbnail\"\n className=\"w-full h-full object-cover absolute inset-0\"\n />\n )}\n <iframe\n src={`${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n loading=\"lazy\"\n onLoad={() => setIsLoaded(true)}\n className=\"w-full h-full\"\n />\n </div>\n );\n}\n","import type { SectionProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface SectionBlockProps extends SectionProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Section({\n children,\n background,\n paddingY,\n maxWidth,\n className = \"\",\n renderBlock,\n}: SectionBlockProps) {\n const inner = (children ?? []).map((block) => (\n <span key={block.id}>{renderBlock ? renderBlock(block) : null}</span>\n ));\n return (\n <section\n className={cn(\"w-full\", className)}\n style={{ background, paddingTop: paddingY, paddingBottom: paddingY }}\n >\n {maxWidth ? <div style={{ maxWidth, marginInline: \"auto\" }}>{inner}</div> : inner}\n </section>\n );\n}\n","import type { NavbarProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface NavbarBlockProps extends NavbarProps {\n className?: string;\n}\n\nexport function Navbar({ logo, links, cta, className = \"\" }: NavbarBlockProps) {\n return (\n <nav className={cn(\"flex items-center gap-6 py-4\", className)}>\n {logo?.src ? (\n <a href={logo.href ?? \"/\"} className=\"shrink-0\">\n <img src={logo.src} alt={logo.alt} className=\"h-8 w-auto\" />\n </a>\n ) : null}\n <div className=\"flex flex-1 gap-5\">\n {(links ?? []).map((l, i) => (\n <a key={i} href={l.href} className=\"text-inherit no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n {cta ? (\n <a\n href={cta.href}\n className=\"rounded-lg bg-black px-4 py-2 font-semibold text-white no-underline\"\n >\n {cta.text}\n </a>\n ) : null}\n </nav>\n );\n}\n","import type { FooterProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface FooterBlockProps extends FooterProps {\n className?: string;\n}\n\nexport function Footer({ columns, copyright, className = \"\" }: FooterBlockProps) {\n return (\n <footer className={cn(\"mt-12 border-t pt-8\", className)}>\n <div className=\"flex flex-wrap gap-12\">\n {(columns ?? []).map((col, i) => (\n <div key={i} className=\"flex flex-col gap-1.5\">\n {col.heading ? <h4 className=\"mb-2 font-semibold\">{col.heading}</h4> : null}\n {(col.links ?? []).map((l, j) => (\n <a key={j} href={l.href} className=\"text-neutral-500 no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n ))}\n </div>\n {copyright ? <p className=\"mt-6 text-sm text-neutral-500\">{copyright}</p> : null}\n </footer>\n );\n}\n","\"use client\";\n\nimport type { SliderProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface SliderBlockProps extends SliderProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Slider({ slides, className = \"\", renderBlock }: SliderBlockProps) {\n const [i, setI] = useState(0);\n const list = slides ?? [];\n const go = (n: number) => setI((n + list.length) % (list.length || 1));\n const btn =\n \"absolute top-1/2 -translate-y-1/2 flex h-9 w-9 items-center justify-center rounded-full border-0 bg-black/50 text-white cursor-pointer\";\n return (\n <div className={cn(\"relative w-full\", className)}>\n {list.map((s, idx) => (\n <div key={s.id} hidden={idx !== i} className=\"flex flex-col gap-4\">\n {(s.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n <button type=\"button\" aria-label=\"Previous\" onClick={() => go(i - 1)} className={cn(btn, \"left-2\")}>\n &#8249;\n </button>\n <button type=\"button\" aria-label=\"Next\" onClick={() => go(i + 1)} className={cn(btn, \"right-2\")}>\n &#8250;\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport type { TabsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface TabsBlockProps extends TabsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Tabs({ tabs, className = \"\", renderBlock }: TabsBlockProps) {\n const [active, setActive] = useState(0);\n const list = tabs ?? [];\n return (\n <div className={cn(\"w-full\", className)}>\n <div role=\"tablist\" className=\"mb-4 flex gap-1 border-b\">\n {list.map((t, idx) => (\n <button\n key={t.id}\n role=\"tab\"\n type=\"button\"\n aria-selected={idx === active}\n onClick={() => setActive(idx)}\n className={cn(\n \"cursor-pointer border-b-2 border-transparent bg-transparent px-4 py-2\",\n idx === active && \"border-black font-semibold\",\n )}\n >\n {t.label}\n </button>\n ))}\n </div>\n {list.map((t, idx) => (\n <div key={t.id} role=\"tabpanel\" hidden={idx !== active} className=\"flex flex-col gap-4\">\n {(t.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { ComponentProps as BcmsComponentProps, FormProps, CollectionProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\nconst box =\n \"rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500\";\n\n/**\n * Builder-preview placeholder for a `component` instance. Live instance resolution\n * (definition lookup + override merge) happens server-side in the delivery renderer\n * and in the page-builder, which pass a resolver — the standalone library preview\n * just shows what's referenced.\n */\nexport function ComponentInstance({\n componentId,\n className = \"\",\n}: BcmsComponentProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Component instance: <code>{componentId}</code>\n </div>\n );\n}\n\n/** Builder-preview placeholder for a `form` block (rendered live by @bettercms-ai/next). */\nexport function FormPlaceholder({ formId, className = \"\" }: FormProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Form: <code>{formId}</code>\n </div>\n );\n}\n\n/**\n * Builder-preview placeholder for a `collection` block.\n *\n * The builder canvas has no entries to list — the list is fetched per request by the site\n * renderer, deliberately, so that a published snapshot can never bake in a stale set of\n * posts. A placeholder is therefore the honest render here, not a limitation.\n */\nexport function CollectionPlaceholder({\n cardComponentId,\n className = \"\",\n}: CollectionProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Collection: entries of this page\n {cardComponentId ? <> as <code>{cardComponentId}</code></> : <> (default card)</>}\n </div>\n );\n}\n","import type { ContentBlock, BlockType } from \"@bettercms-ai/types\";\nimport { Heading } from \"./blocks/Heading\";\nimport { Text } from \"./blocks/Text\";\nimport { Image } from \"./blocks/Image\";\nimport { Button } from \"./blocks/Button\";\nimport { Spacer } from \"./blocks/Spacer\";\nimport { Columns } from \"./blocks/Columns\";\nimport { Video } from \"./blocks/Video\";\nimport { Section } from \"./blocks/Section\";\nimport { Navbar } from \"./blocks/Navbar\";\nimport { Footer } from \"./blocks/Footer\";\nimport { Slider } from \"./blocks/Slider\";\nimport { Tabs } from \"./blocks/Tabs\";\nimport { ComponentInstance, FormPlaceholder, CollectionPlaceholder } from \"./blocks/Placeholders\";\n\n/**\n * Block registry entry — maps a block type to its React component\n * and metadata for the block library UI.\n */\nexport interface BlockRegistryEntry {\n component: React.ComponentType<Record<string, unknown>>;\n label: string;\n icon: string;\n}\n\nexport const BLOCK_REGISTRY: Record<BlockType, BlockRegistryEntry> = {\n // The entries of the page this block sits on. No preview here by design: the list is\n // fetched per request by the site renderer, so the builder has nothing to show.\n collection: {\n component: CollectionPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Collection\",\n icon: \"list\",\n },\n heading: {\n component: Heading as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Heading\",\n icon: \"type\",\n },\n text: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Text\",\n icon: \"align-left\",\n },\n // Same payload, same renderer — deliberately. `richtext` differs from `text` only in how\n // an author EDITS it (a formatting surface vs a plain field); at delivery both are an HTML\n // string, so duplicating the component would be two things to keep in sync for no gain.\n richtext: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Rich text\",\n icon: \"pilcrow\",\n },\n image: {\n component: Image as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Image\",\n icon: \"image\",\n },\n button: {\n component: Button as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Button\",\n icon: \"square\",\n },\n spacer: {\n component: Spacer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Spacer\",\n icon: \"minus\",\n },\n columns: {\n component: Columns as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Columns\",\n icon: \"columns\",\n },\n video: {\n component: Video as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Video\",\n icon: \"play\",\n },\n section: {\n component: Section as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Section\",\n icon: \"layout\",\n },\n navbar: {\n component: Navbar as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Navbar\",\n icon: \"menu\",\n },\n footer: {\n component: Footer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Footer\",\n icon: \"panel-bottom\",\n },\n slider: {\n component: Slider as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Slider\",\n icon: \"gallery-horizontal\",\n },\n tabs: {\n component: Tabs as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Tabs\",\n icon: \"folder\",\n },\n component: {\n component: ComponentInstance as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Component\",\n icon: \"component\",\n },\n form: {\n component: FormPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Form\",\n icon: \"clipboard-list\",\n },\n} as const;\n\nexport const BLOCK_TYPES = Object.keys(BLOCK_REGISTRY) as BlockType[];\n\nexport type { ContentBlock, BlockType };\n"],"mappings":"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","names":["React","jsx","spanOf","align","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","isRecord","jsx","jsxs","React","span","jsx","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","Columns","jsx","jsxs","jsx","jsx","jsxs","jsx","jsxs","useState","jsx","jsxs","useState","jsx","jsxs","Fragment","jsx","jsxs","Columns"]}