{"_id":"@catatsumuri/inkstream","_rev":"4-f8c32afabca2a4a829c5224067cd9136","name":"@catatsumuri/inkstream","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.0":{"name":"@catatsumuri/inkstream","version":"0.1.0","author":{"name":"catatsumuri"},"license":"MIT","_id":"@catatsumuri/inkstream@0.1.0","maintainers":[{"name":"catatsumuri","email":"catatsumuri@gmail.com"}],"homepage":"https://github.com/catatsumuri/inkstream#readme","bugs":{"url":"https://github.com/catatsumuri/inkstream/issues"},"bin":{"inkstream":"dist/cli.js"},"dist":{"shasum":"50f8deee5c3909f2742721d9dd23cc8cd283bfa0","tarball":"https://registry.npmjs.org/@catatsumuri/inkstream/-/inkstream-0.1.0.tgz","fileCount":164,"integrity":"sha512-7PZ96jKg3i/FcTtPWqcmGKFhb/hKlgI6pv7cJI3hZBbOtt+KRY2GuLm4hauV79DNoSXyhxqtx1c+xOnYncerSA==","signatures":[{"sig":"MEUCIH+oZxWy+RqV0/a9w9dYQ/gEqnhZfirOBghCInt3CzLxAiEArWGRSje42Ea+YIdje5xDqahCK7QtdMorc2AUezIoeqg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":317235},"type":"module","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./styles.css":"./dist/react/styles.css","./react/mermaid":{"types":"./dist/react/mermaid-diagram.d.ts","import":"./dist/react/mermaid-diagram.js"}},"gitHead":"e126dc042a759683b27429092f8cb0111b344fa3","scripts":{"test":"node --import tsx --test tests/*.test.ts","build":"tsc -p tsconfig.build.json && node scripts/finish-build.mjs","golden":"tsx golden/diff.ts","prepare":"npm run build","typecheck":"tsc --noEmit"},"_npmUser":{"name":"catatsumuri","email":"catatsumuri@gmail.com"},"repository":{"url":"git+https://github.com/catatsumuri/inkstream.git","type":"git"},"_npmVersion":"11.12.1","description":"Markdown engine for Mintlify-style JSX tags, Zenn directives, and GFM: AST-based tag pairing instead of line-based string preprocessing.","directories":{},"sideEffects":["*.css"],"_nodeVersion":"24.15.0","dependencies":{"vfile":"^6.0.3","unified":"^11.0.5","remark-gfm":"^4.0.1","@types/mdast":"^4.0.4","@types/unist":"^3.0.3","remark-parse":"^11.0.0","remark-rehype":"^11.1.2","remark-directive":"^4.0.0","hast-util-to-html":"^9.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.0","jsdom":"^29.1.1","react":"^19.2.0","shiki":"^4.3.1","mermaid":"^11.12.2","recharts":"^3.8.1","react-dom":"^19.2.0","typescript":"^5.9.3","@types/node":"^20.19.43","@types/jsdom":"^28.0.3","@types/react":"^19.2.0","lucide-react":"^0.475.0","react-markdown":"^10.1.0","@types/react-dom":"^19.2.3","mdast-util-to-hast":"^13.2.0"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","shiki":"^4.0.0","mermaid":"^11.0.0","recharts":"^3.0.0","lucide-react":">=0.400.0 <1.0.0","react-markdown":"^10.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"shiki":{"optional":true},"mermaid":{"optional":true},"recharts":{"optional":true},"lucide-react":{"optional":true},"react-markdown":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/inkstream_0.1.0_1784474052647_0.9167090635154862","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@catatsumuri/inkstream","version":"0.1.1","author":{"name":"catatsumuri"},"license":"MIT","_id":"@catatsumuri/inkstream@0.1.1","maintainers":[{"name":"catatsumuri","email":"catatsumuri@gmail.com"}],"homepage":"https://github.com/catatsumuri/inkstream#readme","bugs":{"url":"https://github.com/catatsumuri/inkstream/issues"},"bin":{"inkstream":"dist/cli.js"},"dist":{"shasum":"bd99b2a47518b2553bfb150f93c99ce25fe32780","tarball":"https://registry.npmjs.org/@catatsumuri/inkstream/-/inkstream-0.1.1.tgz","fileCount":164,"integrity":"sha512-q11jlszXZ+py0iJ0gKdJQKet1HpcwMWthYjtyq8YzdGDhZFPhcCPJze5wTdE08BNnQp/gw+13QHpszUukgCG6Q==","signatures":[{"sig":"MEUCIQCvr5gV8WcYJuyEt1n1JimBLY7aYepzq6A/LP1lGYdqSwIgCWm3cClRmm+GwK3c778mLvaa/ClPdtw6SD8irZTWWuk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":317294},"type":"module","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./styles.css":"./dist/react/styles.css","./react/mermaid":{"types":"./dist/react/mermaid-diagram.d.ts","import":"./dist/react/mermaid-diagram.js"}},"gitHead":"7ec67f11c83ac7852620883bc3bb026310bf078d","scripts":{"test":"node --import tsx --test tests/*.test.ts","build":"tsc -p tsconfig.build.json && node scripts/finish-build.mjs","golden":"tsx golden/diff.ts","prepare":"npm run build","typecheck":"tsc --noEmit"},"_npmUser":{"name":"catatsumuri","email":"catatsumuri@gmail.com"},"repository":{"url":"git+https://github.com/catatsumuri/inkstream.git","type":"git"},"_npmVersion":"11.12.1","description":"Markdown engine for Mintlify-style JSX tags, Zenn directives, and GFM: AST-based tag pairing instead of line-based string preprocessing.","directories":{},"sideEffects":["*.css"],"_nodeVersion":"24.15.0","dependencies":{"vfile":"^6.0.3","unified":"^11.0.5","remark-gfm":"^4.0.1","@types/mdast":"^4.0.4","@types/unist":"^3.0.3","remark-parse":"^11.0.0","remark-rehype":"^11.1.2","remark-directive":"^4.0.0","hast-util-to-html":"^9.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.0","jsdom":"^29.1.1","react":"^19.2.0","shiki":"^4.3.1","mermaid":"^11.12.2","recharts":"^3.8.1","react-dom":"^19.2.0","typescript":"^5.9.3","@types/node":"^20.19.43","@types/jsdom":"^28.0.3","@types/react":"^19.2.0","lucide-react":"^0.475.0","react-markdown":"^10.1.0","@types/react-dom":"^19.2.3","mdast-util-to-hast":"^13.2.0"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","shiki":"^4.0.0","mermaid":"^11.0.0","recharts":"^3.0.0","lucide-react":">=0.400.0 <1.0.0","react-markdown":"^10.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"shiki":{"optional":true},"mermaid":{"optional":true},"recharts":{"optional":true},"lucide-react":{"optional":true},"react-markdown":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/inkstream_0.1.1_1784508876113_0.9569422856446996","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@catatsumuri/inkstream","version":"0.1.2","author":{"name":"catatsumuri"},"license":"MIT","_id":"@catatsumuri/inkstream@0.1.2","maintainers":[{"name":"catatsumuri","email":"catatsumuri@gmail.com"}],"homepage":"https://github.com/catatsumuri/inkstream#readme","bugs":{"url":"https://github.com/catatsumuri/inkstream/issues"},"bin":{"inkstream":"dist/cli.js"},"dist":{"shasum":"85cfe21615919166e7614e8f3c6a292497215ed8","tarball":"https://registry.npmjs.org/@catatsumuri/inkstream/-/inkstream-0.1.2.tgz","fileCount":164,"integrity":"sha512-qifZdHYkx5GZ2fjjFhpehKAD4LNAMviKcJUagJagILVfGyZTsYYAzSWFu6t9/AFc6Q7e58P7QTjURLmE5FHJGw==","signatures":[{"sig":"MEUCIQDDci8A43aK3FuATY7S2vzuwauJq5ZLkgg0rLux/bLGiwIgSsMo1SngCIPWphQWuMVgMUevlgnPZ7cMD8WB2PiUsI8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":320054},"type":"module","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./styles.css":"./dist/react/styles.css","./react/mermaid":{"types":"./dist/react/mermaid-diagram.d.ts","import":"./dist/react/mermaid-diagram.js"}},"gitHead":"8f599aa1b0edb7cb6c260270bc9f75b18d31ed80","scripts":{"test":"node --import tsx --test tests/*.test.ts","build":"tsc -p tsconfig.build.json && node scripts/finish-build.mjs","golden":"tsx golden/diff.ts","prepare":"npm run build","typecheck":"tsc --noEmit"},"_npmUser":{"name":"catatsumuri","email":"catatsumuri@gmail.com"},"repository":{"url":"git+https://github.com/catatsumuri/inkstream.git","type":"git"},"_npmVersion":"11.12.1","description":"Markdown engine for Mintlify-style JSX tags, Zenn directives, and GFM: AST-based tag pairing instead of line-based string preprocessing.","directories":{},"sideEffects":["*.css"],"_nodeVersion":"24.15.0","dependencies":{"vfile":"^6.0.3","unified":"^11.0.5","remark-gfm":"^4.0.1","@types/mdast":"^4.0.4","@types/unist":"^3.0.3","remark-parse":"^11.0.0","remark-rehype":"^11.1.2","remark-directive":"^4.0.0","hast-util-to-html":"^9.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.0","jsdom":"^29.1.1","react":"^19.2.0","shiki":"^4.3.1","mermaid":"^11.12.2","recharts":"^3.8.1","react-dom":"^19.2.0","typescript":"^5.9.3","@types/node":"^20.19.43","@types/jsdom":"^28.0.3","@types/react":"^19.2.0","lucide-react":"^0.475.0","react-markdown":"^10.1.0","@types/react-dom":"^19.2.3","mdast-util-to-hast":"^13.2.0"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","shiki":"^4.0.0","mermaid":"^11.0.0","recharts":"^3.0.0","lucide-react":">=0.400.0 <1.0.0","react-markdown":"^10.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"shiki":{"optional":true},"mermaid":{"optional":true},"recharts":{"optional":true},"lucide-react":{"optional":true},"react-markdown":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/inkstream_0.1.2_1784513361122_0.15672300810569828","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@catatsumuri/inkstream","version":"0.1.3","description":"Markdown engine for Mintlify-style JSX tags, Zenn directives, and GFM: AST-based tag pairing instead of line-based string preprocessing.","license":"MIT","author":{"name":"catatsumuri"},"repository":{"type":"git","url":"git+https://github.com/catatsumuri/inkstream.git"},"homepage":"https://github.com/catatsumuri/inkstream#readme","bugs":{"url":"https://github.com/catatsumuri/inkstream/issues"},"type":"module","sideEffects":["*.css"],"publishConfig":{"access":"public"},"bin":{"inkstream":"dist/cli.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./react/mermaid":{"types":"./dist/react/mermaid-diagram.d.ts","import":"./dist/react/mermaid-diagram.js"},"./styles.css":"./dist/react/styles.css"},"scripts":{"build":"tsc -p tsconfig.build.json && node scripts/finish-build.mjs","prepare":"npm run build","test":"node --import tsx --test tests/*.test.ts","typecheck":"tsc --noEmit","golden":"tsx golden/diff.ts"},"engines":{"node":">=20"},"dependencies":{"@types/mdast":"^4.0.4","@types/unist":"^3.0.3","hast-util-to-html":"^9.0.5","remark-directive":"^4.0.0","remark-gfm":"^4.0.1","remark-parse":"^11.0.0","remark-rehype":"^11.1.2","unified":"^11.0.5","vfile":"^6.0.3"},"peerDependencies":{"lucide-react":">=0.400.0 <1.0.0","mermaid":"^11.0.0","react":"^18.0.0 || ^19.0.0","react-markdown":"^10.0.0","recharts":"^3.0.0","shiki":"^4.0.0"},"peerDependenciesMeta":{"lucide-react":{"optional":true},"mermaid":{"optional":true},"react":{"optional":true},"react-markdown":{"optional":true},"recharts":{"optional":true},"shiki":{"optional":true}},"devDependencies":{"@types/jsdom":"^28.0.3","@types/node":"^20.19.43","@types/react":"^19.2.0","@types/react-dom":"^19.2.3","jsdom":"^29.1.1","lucide-react":"^0.475.0","mdast-util-to-hast":"^13.2.0","mermaid":"^11.12.2","react":"^19.2.0","react-dom":"^19.2.0","react-markdown":"^10.1.0","recharts":"^3.8.1","shiki":"^4.3.1","tsx":"^4.23.0","typescript":"^5.9.3"},"gitHead":"acd627321a40d3b57a1d911ee07ae798c42fb56b","_id":"@catatsumuri/inkstream@0.1.3","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-mxbsLQLv/w1RmH4XO5TZG80kDnxSj1JBnunEXGttKhgGBly7IPygmR+AkIlgqQ7lJPJi415zy+ndnpBSk73tuw==","shasum":"6cb498f8473df09eb6ff4230d200c8aef7cdefa4","tarball":"https://registry.npmjs.org/@catatsumuri/inkstream/-/inkstream-0.1.3.tgz","fileCount":172,"unpackedSize":324917,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICWltUmMx6PDtWF+AYIf5fv6oMsBJO+crTEw1O1Q1YS4AiBDcJYjL59edMnE5JhrEfXNfXsSBlM3RwD4D3b96XgM1A=="}]},"_npmUser":{"name":"catatsumuri","email":"catatsumuri@gmail.com"},"directories":{},"maintainers":[{"name":"catatsumuri","email":"catatsumuri@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/inkstream_0.1.3_1784519226995_0.20057299099356052"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-19T15:14:12.528Z","modified":"2026-07-20T03:47:07.280Z","0.1.0":"2026-07-19T15:14:12.779Z","0.1.1":"2026-07-20T00:54:36.292Z","0.1.2":"2026-07-20T02:09:21.277Z","0.1.3":"2026-07-20T03:47:07.125Z"},"bugs":{"url":"https://github.com/catatsumuri/inkstream/issues"},"author":{"name":"catatsumuri"},"license":"MIT","homepage":"https://github.com/catatsumuri/inkstream#readme","repository":{"type":"git","url":"git+https://github.com/catatsumuri/inkstream.git"},"description":"Markdown engine for Mintlify-style JSX tags, Zenn directives, and GFM: AST-based tag pairing instead of line-based string preprocessing.","maintainers":[{"name":"catatsumuri","email":"catatsumuri@gmail.com"}],"readme":"# inkstream\n\nMarkdown engine for Mintlify-style JSX tags, Zenn directives, and GFM,\nbuilt as remark/rehype plugins over the mdast tree rather than a\nline-based string preprocessor. Supersedes an earlier draft (internally\ncalled \"v1\" below) that converted the same JSX tags to colon-fence\ndirectives with a ~1,850-line line-based preprocessor; that draft is\nfrozen and unpublished.\n\n## Pipeline\n\n1. `normalizeMintlifyBlocks(markdown)` — line-based pre-pass that surrounds\n   standalone tag lines with blank lines (skipping code fences) so remark\n   parses each tag as its own `html` flow node, strips the authoring\n   indentation inside open tags (up to the open tag's indent + 4, mirroring\n   v1) so indented tag bodies don't become indented code blocks, and\n   flattens JSX array attributes (`tags={[\"A\", \"B\"]}` → `tags=\"A,B\"`), which\n   would otherwise make the tag invalid HTML for remark.\n2. `remark-parse` — standard markdown parsing.\n3. `remarkMintlifyTags` — pairs open/close `html` nodes with a stack (once\n   for each parent's flow children, once for each paragraph's phrasing\n   children) and lifts the nodes between a pair into a `mintlifyContainer`\n   node carrying `name`, `attributes`, and `data.hName`/`hProperties` for\n   remark-rehype. Block tag names (`Note`, `Card`, `Steps`, ...) pair at\n   flow level or as a whole single-line paragraph; inline tag names\n   (`Badge`, `Tooltip`) pair mid-paragraph without disturbing surrounding\n   text.\n4. `remarkCodeFenceComponents` — converts ` ```tree `, ` ```quiz `, and\n   ` ```chart:bar `/` ```chart:radar ` fenced code blocks into\n   `mintlifyContainer` nodes carrying the parsed structure as one\n   JSON-string property (`tree`, `quiz`, `chart`) for a renderer component\n   to read. Malformed fences emit a vfile warning and are left as plain\n   code blocks.\n5. `normalizeZennDirectiveShorthand(markdown)` (run before step 1, alongside\n   `normalizeMintlifyBlocks`) + `remark-directive` + `remarkZennDirective` —\n   support for the native `:::message` / `:::details` authoring syntax an\n   author can write directly (as opposed to the Mintlify JSX tags, which v2\n   never routes through colon-fences at all). The shorthand normalizer\n   rewrites the friendly `:::message alert` / `:::details タイトル` forms\n   into the `{.class}` / `[label]` syntax `remark-directive` requires;\n   `remarkZennDirective` then reads the resulting `containerDirective` nodes\n   remark-directive produces. This is the one piece of the pipeline that\n   still needs a third-party directive parser, since colon-fence syntax\n   itself isn't something a tag-pairing plugin over `html` nodes can parse.\n   Like v1, the normalizer protects code fences *and* inline code spans, so\n   literal `` `:::message alert` `` examples in prose survive; it also\n   reduces Zenn's `@[card](url)` / `@[github](url)` embeds to bare URL\n   lines for a linkify-style renderer to pick up.\n6. `remarkGithubAlerts` — normalizes GitHub blockquote alerts (`> [!NOTE]`\n   etc.) onto the same `aside.msg` contract as the Mintlify callouts and\n   `:::message`.\n7. `remarkTreeTags` — converts a paired JSX `<Tree><Tree.Folder>…</Tree>`\n   block (captured by `remarkMintlifyTags`) into the same JSON-carrying\n   `tree` node the ` ```tree ` fence produces.\n8. `normalizeZennImages(markdown)` + `parseImageMetadata(url)` — Zenn's\n   image sizing/caption syntax (`![](url =250x)`, a `*caption*` line under\n   the image). The sizing suffix lives inside the markdown image\n   destination, where remark's parser refuses spaces, so this stays a\n   line-based step that encodes the metadata into query parameters; an\n   image renderer reads them back with `parseImageMetadata`.\n\n## Using the library: two layers\n\ninkstream is consumed at two distinct levels, and every feature belongs\nto exactly one of them. The boundary is the `.ink-markdown` element:\n**inside it is the library's job; outside it — and everything that needs\napp-specific knowledge — is the consumer's job.**\n\n### Layer 1 — Drop-in (zero config)\n\nThe `/react` subpath renders inkstream markdown in one line, and the core\npackage stays React-free:\n\n```tsx\nimport { InkstreamMarkdown } from '@catatsumuri/inkstream/react';\n\n<InkstreamMarkdown>{markdownSource}</InkstreamMarkdown>;\n```\n\nEverything below works with no props and no app-side code:\n\n- Headings with slug ids and copy-link anchors (h1–h4)\n- Callouts in all three syntaxes (Mintlify `<Note>` tags, `:::message`,\n  GitHub `> [!NOTE]` alerts) normalized onto one `aside.msg` contract\n- Every Mintlify component: Card/CardGroup, Steps, Tabs, Accordion,\n  Badge, Tooltip, Update, ResponseField/ParamField, CodeGroup, Tree\n- Code blocks: Shiki highlighting, copy button, wrap toggle, filename\n  headers (` ```php:index.php `), diff mode (` ```diff js:app.js `)\n- ` ```mermaid ` diagrams (lazy-loaded chunk, optional peer dependency)\n- ` ```tree `, ` ```quiz `, ` ```chart:bar ` / ` ```chart:radar ` fences\n- Zenn image sizing/captions (`![](url =250x)`, `*caption*` line)\n- YouTube embeds from standalone URLs\n- GitHub file embeds from standalone blob URLs\n  (`raw.githubusercontent.com` allows CORS, so the browser fetches\n  directly — no server help needed)\n- A default look via an optional stylesheet:\n  `import '@catatsumuri/inkstream/styles.css';`. Every rule targets a\n  stable `ink-*` class name inside `@layer inkstream`, so any unlayered\n  rule a consumer writes overrides it without a specificity fight, and\n  eight semantic colors (border/foreground/muted/primary/accent/card)\n  read from `--ink-*` custom properties with neutral fallbacks — see\n  the file's header comment for the full list and a theme-bridge\n  example.\n\nThe renderers themselves stay style-agnostic (only class names, no\ninline styles) — `styles.css` is what turns Layer 1's correct structure\ninto something that looks right without any app code. Skip the import\nentirely for unstyled markup, or import it and override just the\n`--ink-*` bridge (Layer 2) to match an existing design system.\n\n### Layer 2 — App integration surface\n\nThese features are split \"parsing in the library, knowledge in the app\":\nthe library ships the syntax support and an injection point, and degrades\ngracefully when the app doesn't provide one. kb_practice is the reference\nimplementation for each.\n\n| Integration point | What the app supplies | Without it | kb_practice reference |\n| --- | --- | --- | --- |\n| Theme bridge | `--ink-*` custom properties pointing at the app's own design tokens | The package's neutral (zinc-style) default colors | `resources/css/inkstream.css` maps `--ink-border` → `--border`, etc. |\n| `resolveWikilink` prop | `[[path]]` → URL (routing/DB lookup) | `[[...]]` stays literal text | title→id map; unresolved links route to the create form |\n| `ogpEndpoint` prop | Server-side OGP proxy (CORS blocks direct fetch) | URL-only link cards | `OgpController` (validation + 24h cache) |\n| `extractMarkdownHeadings` + `headingIdPrefix` | Table-of-contents UI outside `.ink-markdown` | No TOC (anchors still work) | `DocumentTableOfContents` scrollspy |\n| `components` prop | Per-tag renderer overrides | Default `ink-*` renderers | — |\n| Dark mode | `dark` class on the document root + CSS for the `--shiki-*` variables | Light theme only | Tailwind `.dark` convention |\n| Page behaviors | Anything tied to navigation lifecycle (e.g. hash-anchor scroll restore in an SPA, where content mounts after the browser's native jump) | Browser defaults | hash restore on Inertia navigation |\n\nCore-only consumers (no React) can use `normalizeInkstreamMarkdown` +\n`inkstreamRemarkPlugins` from the root entry point in any unified\npipeline — the golden corpus renderer does exactly this. `react` and\n`react-markdown` are optional peer dependencies, so this path pulls in\nno React at all.\n\n## What the AST approach fixes structurally\n\n- **No nesting limit** — v1 encoded depth in colon-fence length (7 levels\n  max, `10 − depth` colons); v2 nesting is just tree structure.\n- **Single-line tags** — `<Note>text</Note>` in one paragraph works.\n- **Self-closing tags** — `<Card title=\"...\" />`.\n- **Inline tags** — `<Badge>` / `<Tooltip>` pair mid-sentence.\n- **Error tolerance with diagnostics** — unmatched close tags stay literal,\n  unclosed tags auto-close at end of parent, and both emit vfile warnings\n  instead of failing silently. Malformed tree/quiz/chart fences fall back\n  to a plain code block with a warning instead of silently dropping data.\n- **No redundant raw-JSON dump** — v1's tree/quiz/chart directives leave\n  the source fence's `code` child in the tree *alongside* the JSON\n  attribute, so the raw JSON also renders as a visible `<pre><code>` block.\n  v2's `mintlifyContainer` has no children for these, so only the intended\n  component renders.\n\n## Not yet implemented (planned)\n\n- Per-component attribute schemas (currently reuses v1's global allowlist\n  for `hProperties`; the full parsed attribute map is kept on the node).\n- Tags inside blockquotes/lists currently require blank lines around them\n  (the normalizer only handles top-level tag lines); fixing this means\n  splitting multi-line `html` nodes or normalizing per container.\n- Multi-line JSX open tags (an open tag with attributes spread across\n  several lines; v1 joins them in `joinMultilineJsxTags`).\n- Attribute naming convention: v1 prefixes hProperties with\n  `data-<component>-<attr>` (e.g. `data-card-href`), presumably because its\n  output was raw custom HTML elements read via `dataset`. v2 intentionally\n  uses raw names (`href`) since containers render through React components\n  that read props directly — this is a deliberate v2 API change, not a gap\n  to close.\n- Registry publish. The build (`npm run build` → `dist/` with `.d.ts`,\n  exports pointing at it, `styles.css` copied alongside), the `inkstream`\n  CLI (see below), and CI (typecheck/test/build/golden, plus a check\n  that committed `dist/` matches a fresh build) are already in place;\n  this package isn't published to the npm registry yet (`\"private\":\n  true`) — consumed via a GitHub commit pin instead, which is why\n  `dist/` is committed rather than gitignored: npm's `prepare` script\n  (which would otherwise build it automatically on install) only runs\n  when the installing environment allows lifecycle scripts, and plenty\n  of reasonable npm configs set `ignore-scripts=true`. `prepare` still\n  runs `npm run build` for anyone who does allow scripts (and will\n  matter again once this is a real registry publish, where it runs on\n  the *publisher's* machine regardless of the installer's setting).\n\n## CLI\n\n```sh\nnpx --package=github:catatsumuri/inkstream inkstream <command> [file]\n```\n\nor, once installed as a project dependency, `npx inkstream ...` /\ndirectly via the `inkstream` bin.\n\n| Command | Output | Use case |\n| --- | --- | --- |\n| `inkstream render <file\\|->` | HTML | RSS feeds, OGP static pages, email bodies. Same pipeline as the golden corpus renderer, so no heading ids (those come from the React heading renderer, not this pipeline) |\n| `inkstream text <file\\|->` | Plain, human-readable text | Full-text search indexing, excerpts, OGP `description` — the raw markdown source is unusable for these as-is (`<Card title=...>`, ` ```quiz ` syntax noise) |\n| `inkstream headings <file\\|-> [--json] [--prefix=<p>]` | Indented outline, or a JSON array with `--json` | Server-side table-of-contents precomputation; same shape as `extractMarkdownHeadings` |\n\nEvery command reads markdown from the given file path, or from stdin\nwhen the path is `-` or omitted.\n\n`text` resolves the same syntax `render` does (Mintlify tags, Zenn\ndirectives, wikilinks) down to prose: a quiz fence contributes its\nquestion/options/explanation, a chart fence its title and labels (not\nthe numeric values), a tree fence its file and folder names, and a\nwikilink its label (or the path's last segment) — none of these have a\nsensible plain-text form otherwise, so they're handled explicitly rather\nthan silently dropped or dumping raw JSON. Wikilinks resolve without\nneeding a `resolveWikilink` callback, the same way heading slugs do.\n\n## Known limitations (not planned)\n\nEdge cases identified during review, deliberately left unhandled: no\noccurrence in this project's real documents justified the added parsing\nsurface, and each is easy to work around in authoring.\n\n- **Setext headings** (`Title` underlined by `===`/`---` instead of a\n  leading `#`) aren't picked up by `extractMarkdownHeadings` — a\n  table of contents silently omits them, even though they render fine\n  as `<h1>`/`<h2>`. Write headings with `#`/`##` instead.\n- **`>` inside a tag attribute value** (`<Card title=\"a > b\">`) breaks\n  `match-tags.ts`'s line-regex tag matcher, since it isn't a real HTML\n  parser. Avoid a literal `>` in attribute values (use `&gt;` or\n  rephrase).\n- **Single-quoted attribute values** (`<Card title='x'>`) aren't\n  parsed by `parseJsxAttributes`' bare/quoted branches (only `\"x\"` and\n  brace-wrapped `{'x'}` are). Use double quotes.\n\n## Golden corpus (`golden/`)\n\nSnapshot regression suite: renders every `golden/corpus/*.md` fixture\nthrough the full pipeline and compares the prettified HTML against the\ncommitted baseline in `golden/baseline/*.html`.\n\n```sh\nnpm run golden             # check; exits non-zero on any diff\nnpm run golden -- --update # re-render and accept the current output\n```\n\n- `golden/render-v2.ts` — the pipeline under test\n  (`normalizeInkstreamMarkdown` → `inkstreamRemarkPlugins` →\n  `remark-rehype` → `hast-util-to-html`), the same exports consumers use.\n- `golden/diff.ts` — prettifies output to one tag per line, line-diffs it\n  against the baseline, and writes the current output to\n  `golden/output/*.html` (gitignored) for inspection.\n\nFixtures `01`–`09` are synthetic; `10`–`14` are the five real syntax-guide\ndocuments from thinkstream's `SyntaxSeeder` (basic markdown, extended\nmarkdown/GFM, Zenn syntax, Mintlify syntax, thinkstream syntax) — a far\ntruer signal, and the corpus that surfaced the fence-state, inline-code,\nindentation, and array-attribute bugs the current pipeline fixes.\n\nThe corpus originally drove a v1-vs-v2 comparison (rendering the same\nfixtures through inkstream v1's directive pipeline); once every difference\nwas either fixed or decided as an intentional v2 change, the v1 renderer\nand its `file:` dependency were dropped and the v2 output became the\nbaseline.\n","readmeFilename":"README.md"}