/** * Markdown heading-id slug SSOT (server-safe: no React, no DOM). * * This algorithm previously existed in THREE byte-identical copies: * - `components/ui/simple-markdown-renderer.tsx` (generateHeadingId) * - `components/ui/rich-markdown-renderer.tsx` (generateHeadingId) * - `utils/markdown-section-extractor.ts` (extractSections) * * The extractor is the PRODUCER of `sectionIds` and the renderers are the * CONSUMERS — if the two ever drift, deep-link anchors and scroll-spy * targets silently diverge. All three now call these helpers; the parity * test `components/ui/__tests__/markdown-parity.test.tsx` asserts * extractor-vs-renderer ID agreement over the fixture corpus. */ /** Emoji ranges stripped from heading text before slugification. */ export declare const HEADING_EMOJI_RE: RegExp; /** Remove emoji characters and trim. */ export declare function stripHeadingEmojis(text: string): string; /** * Core slug chain WITHOUT the emoji strip (the extractor exposes emoji * stripping as an option, so the two steps are kept separable): * lowercase → drop non-word/space/hyphen chars → spaces to hyphens → * trim leading/trailing hyphens. May return `''` for symbol-only input — * callers apply their own fallback (`section-N`). */ export declare function slugifyHeadingBase(text: string): string; /** The full default chain used by the renderers: emoji strip + slugify. */ export declare function slugifyHeadingText(text: string): string; /** * Sharing the slug chain alone was not enough. The PRODUCER * (`utils/markdown-section-extractor`) and the CONSUMER * (`components/ui/markdown/heading-ids`) each carried their own copy of * "which lines are headings" and "how do duplicates get suffixed", and the * copies drifted: * - the extractor toggled its code-block state on a bare * `line.startsWith('```')`, so a `~~~`-fenced (or wider-backtick, or * indented) block containing `## Setup` produced a SECTION from the * extractor and NO id from the renderer — `sectionIdMap` then missed * silently and the deep-link anchor pointed nowhere; * - the extractor anchored ATX at column 0 while the renderer allowed the * CommonMark 0..3-space indent; * - the dedupe counter was hand-copied in both files, each with a comment * saying "the two must agree". * Both now call `scanHeadings` + `createHeadingIdDeduper`. Server-safe: no * React, no DOM. */ export { createFenceTracker, isBlankLine, type FenceTracker, type FenceLineRole, } from './markdown-fences'; /** A heading the renderer will emit, located in the source. */ export interface ScannedHeading { /** 1-based line of the heading's FIRST line (setext: the title line). */ line: number; level: number; /** Raw title text, before `stripInlineMarkdown` / slugification. */ text: string; } export interface ScanHeadingsOptions { /** * Skip a leading YAML frontmatter block (`---` on line 1 through the next * `---`). Only a DOCUMENT-LEADING block counts: a bare `---` mid-document * is a thematic break or a setext underline, and treating it as a * frontmatter toggle (what the extractor used to do) silently swallowed * every heading until the next one. */ skipFrontmatter?: boolean; /** Include raw-HTML headings (`