seemore 1.12.2 → 1.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,9 +33,11 @@
33
33
  </p>
34
34
  </p>
35
35
 
36
- AI tools write Markdown faster than anyone can read it: specs, notes, guides, READMEs, whole folders of it. A folder of `.md` files is a dead end: nothing to click, nothing to search, no order.
36
+ AI tools write Markdown fast. They write specs, notes, guides, READMEs, and whole folders of files. No person can read that much Markdown at that speed.
37
37
 
38
- **seemore** points at that folder and renders it as a proper site instead, without you moving a single file or writing any code.
38
+ A folder of `.md` files has no order. You cannot click a link between files. You cannot search across them.
39
+
40
+ **seemore** points at that folder and renders it as a real site. It does not move your files. It does not need any code.
39
41
 
40
42
  <p align="center">
41
43
  <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/home.png" alt="The seemore site: a terminal typing npx seemore to serve a folder of notes at localhost:4040, with an arrow pointing to the browser preview" width="640"/>
@@ -43,24 +45,24 @@ AI tools write Markdown faster than anyone can read it: specs, notes, guides, RE
43
45
 
44
46
  ## Quick start
45
47
 
46
- Requires [Node.js](https://nodejs.org) 20 or newer. Nothing to install, no config file, no files to move.
48
+ seemore needs [Node.js](https://nodejs.org) 20 or newer. You do not install anything. You do not write a config file. You do not move any files.
47
49
 
48
50
  ```bash
49
51
  cd my-docs
50
52
  npx seemore # renders this folder at http://localhost:4040
51
53
  ```
52
54
 
53
- `npx seemore` runs seemore without adding it to your repository's dependencies.
55
+ `npx seemore` runs seemore without adding it to your project's dependencies.
54
56
 
55
- Open the address it prints. From there it's live: add, rename, retitle or delete a file and the site updates immediately, navigation and search included.
57
+ Open the address it prints. The site stays live after that. Add, rename, retitle, or delete a file, and the site updates at once. Navigation and search update too.
56
58
 
57
- The [seemore website](https://arifszn.github.io/seemore) is itself a folder of Markdown rendered by seemore. That's what your own folder will look like.
59
+ The [seemore website](https://arifszn.github.io/seemore) is a folder of Markdown, rendered by seemore. Your own folder will look the same way.
58
60
 
59
61
  <details open>
60
62
  <summary><strong>Or ask an AI agent to set it up</strong></summary>
61
63
  <br/>
62
64
 
63
- Not comfortable in a terminal? There's an [agent skill](https://github.com/arifszn/seemore-skill) that drives all of this for you.
65
+ If you do not want to use a terminal, use the [agent skill](https://github.com/arifszn/seemore-skill) instead. It does the setup for you.
64
66
 
65
67
  ### Claude Code (Plugin Marketplace)
66
68
 
@@ -74,9 +76,9 @@ Not comfortable in a terminal? There's an [agent skill](https://github.com/arifs
74
76
 
75
77
  ### Other agents (Codex, OpenCode, Antigravity, Gemini, Cursor, …)
76
78
 
77
- This is a standalone skill that works in any agent supporting the `SKILL.md` format. See **[INSTALL.md](https://github.com/arifszn/seemore-skill/blob/main/INSTALL.md)** for per-agent skills paths and copy-paste commands.
79
+ This skill works in any agent that supports the `SKILL.md` format. See **[INSTALL.md](https://github.com/arifszn/seemore-skill/blob/main/INSTALL.md)** for the skill path and commands for each agent.
78
80
 
79
- Or just tell your agent to install it by pasting this:
81
+ Or paste this to your agent:
80
82
 
81
83
  ```
82
84
  Fetch and follow the install instructions from
@@ -85,13 +87,13 @@ https://raw.githubusercontent.com/arifszn/seemore-skill/refs/heads/main/INSTALL.
85
87
 
86
88
  ### Usage
87
89
 
88
- Once it's installed, ask for what you want:
90
+ After you install the skill, tell your agent what you want:
89
91
 
90
92
  ```
91
93
  Turn this folder of notes into a docs site I can read in my browser
92
94
  ```
93
95
 
94
- Or invoke the skill by name:
96
+ Or call the skill by name:
95
97
 
96
98
  ```
97
99
  /seemore
@@ -101,49 +103,49 @@ Or invoke the skill by name:
101
103
 
102
104
  ## Three ways to use it
103
105
 
104
- - **[In your browser](#edit-from-the-browser)**: `npx seemore` serves the folder you're standing in and updates live as you edit.
105
- - **[In your code editor](#view-in-your-code-editor)**: an extension puts the same site in a panel next to the file you're editing, in VS Code and VS Code-compatible editors like Cursor and Antigravity.
106
- - **[As a static site](#publish-it-to-the-web)**: `npx seemore build` exports plain HTML you can host anywhere, so it doubles as a docs framework, not just a preview tool.
106
+ - **[In your browser](#edit-from-the-browser)**: `npx seemore` serves the folder you are in. The site updates live as you edit.
107
+ - **[In your code editor](#view-in-your-code-editor)**: an extension shows the same site in a panel next to your file, in VS Code and VS Code-compatible editors like Cursor and Antigravity.
108
+ - **[As a static site](#publish-it-to-the-web)**: `npx seemore build` exports plain HTML you can host anywhere. seemore is a docs framework, not only a preview tool.
107
109
 
108
110
  <p align="center">
109
- <video src="https://github.com/user-attachments/assets/0a27b76d-f765-47b7-a3de-4b6f3aefbb74" width="640" controls muted></video>
111
+ <video src="https://github.com/user-attachments/assets/4238308d-8152-4386-9095-0f135447475f" width="640" controls muted></video>
110
112
  </p>
111
113
 
112
114
  ## What you get
113
115
 
114
- Point seemore at anything already sitting in Markdown (AI-written notes, project docs, RFCs, API references, specs, an engineering handbook) and you get:
116
+ Point seemore at any Markdown you already have: AI-written notes, project docs, RFCs, API references, specs, or an engineering handbook. You get:
115
117
 
116
- - **Zero config**: no config file, no code, no files to move; a plain folder works in the browser, in your editor, and as a static build
117
- - **Live preview**: files appear, disappear and reorder as you edit them, navigation and search included
118
+ - **Zero config**: no config file, no code, no files to move. A plain folder works in the browser, in your editor, and as a static build
119
+ - **Live preview**: files appear, disappear and reorder as you edit them. Navigation and search update too
118
120
  - **Edit in place**: double-click any block in the preview to fix its Markdown
119
- - **Editor integration**: one extension covers VS Code, Cursor, Antigravity and other VS Code-compatible editors, remote workspaces included
120
- - **Documentation framework**: `seemore build` prerenders the whole site to HTML, ready to deploy on any host
121
+ - **Editor integration**: one extension covers VS Code, Cursor, Antigravity and other VS Code-compatible editors, including remote workspaces
122
+ - **Documentation framework**: `seemore build` renders the whole site to HTML, ready to deploy on any host
121
123
  - **Password protection**: protect the built site with one shared password, with no server required
122
124
  - **Page actions**: copy a page as Markdown, or export it as one self-contained HTML file to drop into Slack, email or an AI chat
123
- - **Search built in**: static full-text search with no server and no account, with shareable highlighted results; [Algolia](https://algolia.com) and [Orama Cloud](https://orama.com) drop in when you want a hosted index
125
+ - **Search built in**: static full-text search with no server and no account, with shareable highlighted results. [Algolia](https://algolia.com) and [Orama Cloud](https://orama.com) drop in when you want a hosted index
124
126
  - **Rich Markdown**: GitHub Flavoured Markdown, admonitions, steps, `[[wikilinks]]`, [Mermaid](https://mermaid.js.org) and [D2](https://d2lang.com) diagrams, click-to-zoom images, embedded PDFs
125
127
  - **Full MDX**: `.mdx` pages take real JSX, your own React components and CSS
126
- - **12 themes**: dark and light follow the system, with a toggle that remembers your choice; your own CSS always wins
128
+ - **12 themes**: dark and light follow the system, with a toggle that remembers your choice. Your own CSS always wins
127
129
 
128
- ### How it's different
130
+ ### How seemore is different
129
131
 
130
- - **Starts where your files already are**: most docs frameworks want a project, with a scaffold, a config file, a `docs/` layout and a build step wired into your repo. seemore wants a folder that already exists.
131
- - **Preview first, site generator second**: the same content serves locally, renders beside your editor, and builds to static HTML, with no separate setup for each.
132
- - **Nothing to migrate, nothing to undo**: your files are never moved or rewritten, so walking away costs nothing.
132
+ - **Starts where your files already are**: most docs frameworks need a project, with a scaffold, a config file, a `docs/` layout and a build step wired into your repo. seemore needs only a folder that already exists.
133
+ - **Preview first, site generator second**: the same content serves locally, renders beside your editor, and builds to static HTML. No separate setup is needed for each one.
134
+ - **Nothing to migrate, nothing to undo**: seemore never moves or rewrites your files. You can stop using it at any time, at no cost.
133
135
 
134
136
  ## Edit from the browser
135
137
 
136
- The preview is also an editor. Double-click any paragraph, heading, list item, quote or table cell and it opens in a small editor holding that block's **Markdown source**: `**bold**` stays `**bold**`, links stay links, tables stay tables. Fix the text, hit **Save**, and the change is written to the file on disk.
138
+ The preview is also an editor. Double-click any paragraph, heading, list item, quote or table cell. A small editor opens with that block's **Markdown source**: `**bold**` stays `**bold**`, links stay links, tables stay tables. Fix the text, press **Save**, and seemore writes the change to the file on disk.
137
139
 
138
140
  <p align="center">
139
141
  <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/inline-editor.png" alt="seemore rendering a folder of Markdown in the browser, with a paragraph's Markdown source open in the inline editor" width="640"/>
140
142
  </p>
141
143
 
142
- Inline editing is for local previews only.
144
+ Inline editing works only for local previews.
143
145
 
144
146
  ## View in your code editor
145
147
 
146
- Install **seemore** from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=arifszn.seemore-vscode) or [Open VSX](https://open-vsx.org/extension/arifszn/seemore-vscode) to get the same rendered site as a panel beside your editor, with no terminal, no `npx` and no browser tab to manage. The extension bundles the CLI, so nothing is downloaded or put on your PATH. Open VSX also covers VS Code-compatible editors such as Cursor and Antigravity.
148
+ Install **seemore** from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=arifszn.seemore-vscode) or [Open VSX](https://open-vsx.org/extension/arifszn/seemore-vscode). You get the same rendered site as a panel beside your editor, with no terminal, no `npx` and no browser tab to manage. The extension bundles the CLI, so nothing downloads and nothing goes on your PATH. Open VSX also covers VS Code-compatible editors such as Cursor and Antigravity.
147
149
 
148
150
  <p align="center">
149
151
  <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/vscode-extension.png" alt="VS Code with features.md open in the editor and the seemore panel beside it, rendering the same page with a paragraph's Markdown source open in the inline editor" width="640"/>
@@ -159,10 +161,10 @@ Install **seemore** from the [VS Code Marketplace](https://marketplace.visualstu
159
161
  npx seemore build # static export to dist/ for any host
160
162
  ```
161
163
 
162
- The result is a `dist/` folder of plain web files: drop it on [Netlify](https://netlify.com), [Surge](https://surge.sh), [Cloudflare Pages](https://pages.cloudflare.com) or [GitHub Pages](https://pages.github.com), or hand it to any web host. Every page is prerendered to its own `index.html`, next to a `404.html` that every static host honours, and host-specific files (`_redirects`, `200.html`, `.nojekyll`) are written for you.
164
+ The result is a `dist/` folder of plain web files. Drop it on [Netlify](https://netlify.com), [Surge](https://surge.sh), [Cloudflare Pages](https://pages.cloudflare.com) or [GitHub Pages](https://pages.github.com), or hand it to any web host. Every page is a separate `index.html`, next to a `404.html` that every static host reads. seemore also writes the files each host needs (`_redirects`, `200.html`, `.nojekyll`).
163
165
 
164
166
  > [!TIP]
165
- > Publishing to GitHub Pages? If your site lives at `username.github.io/my-repo/` rather than the root, tell seemore the subpath once with `base: '/my-repo/'`.
167
+ > Do you publish to GitHub Pages at `username.github.io/my-repo/`, not at the root? Set the subpath once with `base: '/my-repo/'`.
166
168
 
167
169
  ### Password-protect a site
168
170
 
@@ -182,27 +184,27 @@ $env:SEEMORE_PASSWORD='a-long-passphrase'; npx seemore build
182
184
  <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/password-protection.png" alt="The lock screen of a password-protected seemore site: the site's icon and title above a password field and an Unlock button" width="560"/>
183
185
  </p>
184
186
 
185
- By default, visitors stay unlocked for one day after their last visit. Change this with `remember`:
187
+ By default, a visitor stays unlocked for one day after their last visit. Change this with `remember`:
186
188
 
187
189
  ```ts
188
190
  auth: { remember: '7d' } // or '12h'
189
191
  ```
190
192
 
191
- Password protection works only for `seemore build`. To try the lock screen, build and serve `dist/` on `localhost`.
193
+ Password protection works only for `seemore build`. To test the lock screen, build the site and serve `dist/` on `localhost`.
192
194
 
193
195
  ### Share a single page to Slack, email
194
196
 
195
- To share a single page instead of a site, the **Actions** button above every page writes one self-contained HTML file (styles inlined, images embedded, diagrams kept) that opens offline from a double-click. The CLI does the same without a browser:
197
+ To share one page instead of a site, use the **Actions** button above the page. It writes one self-contained HTML file (styles inlined, images embedded, diagrams kept) that opens offline from a double-click. The CLI does the same without a browser:
196
198
 
197
199
  ```bash
198
200
  npx seemore export docs/spec.md # writes spec.html next to the Markdown
199
201
  ```
200
202
 
201
- More on all three in [publishing](https://arifszn.github.io/seemore/publishing).
203
+ Read more about all three methods on the [publishing](https://arifszn.github.io/seemore/publishing) page.
202
204
 
203
205
  ## Configuration
204
206
 
205
- Configuration is optional; a folder with no config file builds correctly everywhere. To adjust things, create `seemore.config.ts` next to your content:
207
+ Configuration is optional. A folder with no config file builds correctly everywhere. To change settings, create `seemore.config.ts` next to your content:
206
208
 
207
209
  ```ts
208
210
  // seemore.config.ts
@@ -225,7 +227,7 @@ export default {
225
227
  };
226
228
  ```
227
229
 
228
- `features` is a set of switches. Name the ones you want to change; flags you don't mention keep their default.
230
+ `features` is a set of switches. Name only the flags you want to change. Every other flag keeps its default value.
229
231
 
230
232
  | Flag | Default | Effect |
231
233
  | --- | --- | --- |
@@ -246,37 +248,37 @@ export default {
246
248
  | `search.highlight` | on | Highlight the query on the page you land on |
247
249
  | `social.cards` | off | Per-page OG images (needs `takumi-js`) |
248
250
 
249
- Every config key above is documented on the [configuration](https://arifszn.github.io/seemore/configuration) and [features](https://arifszn.github.io/seemore/features) pages.
251
+ The [configuration](https://arifszn.github.io/seemore/configuration) and [features](https://arifszn.github.io/seemore/features) pages document every key above.
250
252
 
251
253
  ### Themes
252
254
 
253
- Twelve built-in colour presets: `neutral` (default), `black`, `catppuccin`, `dusk`, `ocean`, `purple`, `ruby`, `solar`, `aspen`, `emerald`, `vitepress`, `shadcn`. Dark and light follow your system setting, with a toggle that remembers your choice. For anything else, put your own CSS in `css`; it's appended last, so it wins.
255
+ seemore has twelve built-in colour presets: `neutral` (default), `black`, `catppuccin`, `dusk`, `ocean`, `purple`, `ruby`, `solar`, `aspen`, `emerald`, `vitepress`, `shadcn`. Dark and light modes follow your system setting, with a toggle that remembers your choice. For anything else, put your own CSS in `css`. seemore appends it last, so it wins.
254
256
 
255
257
  | `neutral` (default) | `black` |
256
258
  | :--- | :--- |
257
259
  | <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/themes/neutral.png" alt="neutral theme" width="100%"> | <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/themes/black.png" alt="black theme" width="100%"> |
258
260
 
259
- All twelve are on the [themes page](https://arifszn.github.io/seemore/themes).
261
+ The [themes page](https://arifszn.github.io/seemore/themes) shows all twelve.
260
262
 
261
263
  ## Content
262
264
 
263
265
  Works with both `.md` and `.mdx`.
264
266
 
265
- - GitHub Flavoured Markdown, admonitions (note / tip / warning boxes), step-by-step lists, and colour-highlighted code blocks with optional filenames and line numbers
266
- - `[[wikilinks]]`, including `[[Page|label]]` and `[[Page#Heading]]`, the easiest way for you or your AI to link pages without relative paths to get right; plain relative `.md` links are resolved automatically too
267
- - **[Mermaid](https://mermaid.js.org)** and **[D2](https://d2lang.com)** diagrams, rendered live in the browser straight from a ` ```mermaid ` or ` ```d2 ` code fence
268
- - Sibling images inlined as hashed assets with click-to-zoom, and PDFs opened inline in the browser's own viewer
269
- - Page order comes from a `meta.json`, a frontmatter `order`, or the title, in that order
267
+ - GitHub Flavoured Markdown, admonitions (note, tip and warning boxes), step-by-step lists, and colour-highlighted code blocks with optional filenames and line numbers
268
+ - `[[wikilinks]]`, including `[[Page|label]]` and `[[Page#Heading]]`. Use these to link pages without getting relative paths right. Plain relative `.md` links also resolve automatically
269
+ - **[Mermaid](https://mermaid.js.org)** and **[D2](https://d2lang.com)** diagrams, rendered live in the browser from a ` ```mermaid ` or ` ```d2 ` code fence
270
+ - Sibling images, inlined as hashed assets with click-to-zoom, and PDFs, opened inline in the browser's own viewer
271
+ - Page order comes from a `meta.json` file, a frontmatter `order` field, or the title, checked in that order
270
272
 
271
273
  <p align="center">
272
274
  <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/diagrams.png" alt="A mermaid flowchart reading Markdown, seemore, Static site rendered live on the Diagrams page, with a D2 diagram of the same chain below it" width="640"/>
273
275
  </p>
274
276
 
275
- Every syntax above, rendered live, is on the [content page](https://arifszn.github.io/seemore/content).
277
+ The [content page](https://arifszn.github.io/seemore/content) renders every syntax above, live.
276
278
 
277
279
  ### PDF viewer
278
280
 
279
- Reference a PDF with image syntax, a sibling file or a remote URL, and it opens inline with a download link underneath:
281
+ Reference a PDF with image syntax. Use a sibling file or a remote URL. The PDF opens inline, with a download link underneath:
280
282
 
281
283
  ```md
282
284
  ![sample document](./assets/sample.pdf)
@@ -294,31 +296,31 @@ seemore build [dir] build a static site into dist/
294
296
  seemore export <file> export a page as a standalone HTML file
295
297
  ```
296
298
 
297
- Run `seemore --help` for the options, or see the [CLI reference](https://arifszn.github.io/seemore/cli-reference).
299
+ Run `seemore --help` to see the options, or see the [CLI reference](https://arifszn.github.io/seemore/cli-reference) page.
298
300
 
299
301
  ## FAQ
300
302
 
301
- **Do I have to install anything?** No, just run `npx seemore`.
303
+ **Do I have to install anything?** No. Just run `npx seemore`.
302
304
 
303
305
  **Does anything leave my machine?** No. The dev server, the build and the export all run locally, with no account and no telemetry. The only network calls are ones you configure yourself, such as a hosted search provider.
304
306
 
305
- **Will it move or rewrite my files?** No. seemore reads your folder where it is. It writes only when you save an inline edit, or when you run `build` or `export`.
307
+ **Does seemore move or rewrite my files?** No. seemore reads your folder in place. It writes to a file only when you save an inline edit, or run `build` or `export`.
306
308
 
307
- **Is the password protection real?** Yes. Site content is encrypted, so a copy of the build cannot be read without the password. There is no per-person access or revocation, and a short password can be guessed offline, so use a long one.
309
+ **Is the password protection real?** Yes. seemore encrypts the site content, so nobody can read a copy of the build without the password. There is no per-person access and no way to revoke one password alone. A short password can be guessed offline, so use a long one.
308
310
 
309
- **Is it a preview tool or a docs framework?** Both, from the same folder: `seemore` previews it, the extension renders it beside your editor, and `seemore build` publishes it.
311
+ **Is seemore a preview tool or a docs framework?** Both, from the same folder: `seemore` previews it, the extension renders it beside your editor, and `seemore build` publishes it.
310
312
 
311
313
  ## Under the hood
312
314
 
313
- [fumadocs](https://fumadocs.vercel.app) provides the interface presets, with [Shiki](https://shiki.style), [Mermaid](https://mermaid.js.org), [D2](https://d2lang.com), [Vite](https://vite.dev) and [React Router](https://reactrouter.com) underneath. The code editor extension runs the same CLI as a child process it manages.
315
+ [fumadocs](https://fumadocs.vercel.app) provides the interface presets. [Shiki](https://shiki.style), [Mermaid](https://mermaid.js.org), [D2](https://d2lang.com), [Vite](https://vite.dev) and [React Router](https://reactrouter.com) run underneath it. The code editor extension runs the same CLI as a child process it manages.
314
316
 
315
317
  ## Contributing
316
318
 
317
- Bug reports and pull requests are welcome. [CONTRIBUTING.md](https://github.com/arifszn/seemore/blob/main/CONTRIBUTING.md) covers how to get the repo running, the checks to pass before opening a pull request, and how releases work.
319
+ seemore welcomes bug reports and pull requests. [CONTRIBUTING.md](https://github.com/arifszn/seemore/blob/main/CONTRIBUTING.md) covers how to get the repo running, the checks a pull request must pass, and how releases work.
318
320
 
319
321
  ## Star it
320
322
 
321
- If seemore saved you some time, [give it a star](https://github.com/arifszn/seemore). That is how other people find it.
323
+ If seemore saved you time, [give it a star](https://github.com/arifszn/seemore). A star helps other people find it.
322
324
 
323
325
  ## Licence
324
326
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seemore",
3
- "version": "1.12.2",
3
+ "version": "1.12.4",
4
4
  "description": "Let AI write the Markdown. Let seemore show it better — zero config documentation framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,7 +1,8 @@
1
1
  import { Writable } from 'node:stream';
2
- import { StrictMode, type ReactNode } from 'react';
2
+ import { StrictMode, type ComponentProps, type ReactNode } from 'react';
3
3
  import { renderToPipeableStream } from 'react-dom/server';
4
4
  import { MemoryRouter, RouterProvider, createMemoryRouter } from 'react-router';
5
+ import { TabsContent } from 'fumadocs-ui/components/tabs';
5
6
  import { config } from 'virtual:seemore/config';
6
7
  import { toBasename, withBase } from '../shared/base.js';
7
8
  import { ogImagePath } from '../shared/og.js';
@@ -121,6 +122,11 @@ export interface ExportedArticle {
121
122
  * single-page export. Rendering the component directly, rather than extracting the
122
123
  * article from a full-page render, means no HTML parsing anywhere in the export path.
123
124
  *
125
+ * Tabs render force-mounted: Radix keeps only the active panel's content in the tree, and
126
+ * a file with three of its four tabs empty is no export at all. Force-mounted panels hide
127
+ * through fumadocs' own `data-[state=inactive]:hidden` rule, which the stylesheet the
128
+ * export inlines already carries, and the file's runtime does the switching.
129
+ *
124
130
  * Diagrams are absent here, as in every prerendered page (see `Mermaid.tsx`); the CLI
125
131
  * export inlines a runtime that renders them when the file is opened.
126
132
  */
@@ -132,12 +138,16 @@ export async function renderArticle(url: string): Promise<ExportedArticle> {
132
138
  if (page === undefined) throw new Error(`No page at ${url}.`);
133
139
 
134
140
  const Content = page.default;
141
+ const exportComponents = {
142
+ ...mdxComponents,
143
+ CodeBlockTab: (props: ComponentProps<typeof TabsContent>) => <TabsContent {...props} forceMount />,
144
+ };
135
145
  // A router is still required: content links go through react-router's `Link`, which
136
146
  // reads the routing context. A memory router with just this page is the smallest one.
137
147
  const { html, failures } = await renderToHtml(
138
148
  <StrictMode>
139
149
  <MemoryRouter initialEntries={[withBase(config.base, url)]} basename={toBasename(config.base)}>
140
- <Content components={mdxComponents} />
150
+ <Content components={exportComponents} />
141
151
  </MemoryRouter>
142
152
  </StrictMode>,
143
153
  );
@@ -216,6 +216,42 @@ function buildExportToc(article: Element): Element | undefined {
216
216
  return nav;
217
217
  }
218
218
 
219
+ /**
220
+ * Radix Tabs keeps only the active panel's content mounted, so the article's DOM alone
221
+ * misses every inactive tab. Walk each tablist, select each trigger in turn — which also
222
+ * gives any diagram inside the panel its scroll-into-view — and record the panel's HTML
223
+ * while it is up. The first trigger is re-selected so the page is left as it was found.
224
+ */
225
+ async function captureTabPanels(): Promise<Map<string, string>> {
226
+ const captured = new Map<string, string>();
227
+ const scrollX = window.scrollX;
228
+ const scrollY = window.scrollY;
229
+
230
+ for (const tablist of document.querySelectorAll('[role="tablist"]')) {
231
+ const triggers = Array.from(tablist.querySelectorAll<HTMLButtonElement>('button[role="tab"]'));
232
+ for (const trigger of triggers) {
233
+ // Radix activates a tab on `mousedown`, not `click` — and a React re-render means
234
+ // the panel's content appears a tick later, so both sides are handled by hand.
235
+ trigger.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window, button: 0 }));
236
+ const panel = document.getElementById(trigger.getAttribute('aria-controls') ?? '');
237
+ if (panel === null) continue;
238
+ await waitFor(() => panel.childElementCount > 0, 15_000);
239
+ panel.scrollIntoView({ behavior: 'instant', block: 'center' });
240
+ await waitFor(
241
+ () =>
242
+ panel.querySelector('.seemore-mermaid, .seemore-d2') === null ||
243
+ panel.querySelector('svg, .seemore-mermaid-error, .seemore-d2-error') !== null,
244
+ 15_000,
245
+ );
246
+ captured.set(panel.id, panel.innerHTML);
247
+ }
248
+ triggers[0]?.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window, button: 0 }));
249
+ }
250
+
251
+ window.scrollTo(scrollX, scrollY);
252
+ return captured;
253
+ }
254
+
219
255
  /** The article is the only thing taken, but dev leaves editor affordances inside it. */
220
256
  function cleanArticleForExport(article: Element): Element {
221
257
  const clone = article.cloneNode(true) as Element;
@@ -237,8 +273,9 @@ function escapeHtml(text: string): string {
237
273
  }
238
274
 
239
275
  /**
240
- * The exported file's runtime: theme toggle, code copy, heading anchor copy, click-to-zoom —
241
- * the behaviors kept, at roughly a kilobyte instead of the site bundle. Handed to React in
276
+ * The exported file's runtime: theme toggle, code copy, heading anchor copy, tabs,
277
+ * click-to-zoom — the behaviors kept, at roughly a kilobyte instead of the site bundle.
278
+ * Handed to React in
242
279
  * hydration on the live page; here each is a few lines against the static DOM.
243
280
  *
244
281
  * Kept free of `</script>`-shaped sequences by construction: it is inlined verbatim.
@@ -280,6 +317,27 @@ const RUNTIME = `(function () {
280
317
  });
281
318
  });
282
319
 
320
+ // Tabs are exported as the static Radix markup the live page hydrated; the file wires
321
+ // them back up by hand — a click selects within its tablist and swaps the panels.
322
+ document.querySelectorAll('[role="tablist"]').forEach(function (tablist) {
323
+ var triggers = [].slice.call(tablist.querySelectorAll('button[role="tab"]'));
324
+ triggers.forEach(function (trigger) {
325
+ trigger.addEventListener('click', function () {
326
+ triggers.forEach(function (other) {
327
+ var active = other === trigger;
328
+ other.setAttribute('aria-selected', active ? 'true' : 'false');
329
+ other.setAttribute('data-state', active ? 'active' : 'inactive');
330
+ other.setAttribute('tabindex', active ? '0' : '-1');
331
+ var panel = document.getElementById(other.getAttribute('aria-controls') || '');
332
+ if (panel) {
333
+ panel.hidden = !active;
334
+ panel.setAttribute('data-state', active ? 'active' : 'inactive');
335
+ }
336
+ });
337
+ });
338
+ });
339
+ });
340
+
283
341
  // The live site's TOC follows the reader with fumadocs' own scroll tracking; in the file,
284
342
  // the plainest version of the same behaviour — last heading above the fold wins.
285
343
  var tocLinks = [].slice.call(document.querySelectorAll('.seemore-export-toc a'));
@@ -398,7 +456,12 @@ export async function exportPageAsHtml(): Promise<void> {
398
456
 
399
457
  await prepareDiagrams();
400
458
 
459
+ const tabs = await captureTabPanels();
401
460
  const clone = cleanArticleForExport(article);
461
+ for (const [id, html] of tabs) {
462
+ const panel = clone.querySelector(`[id="${CSS.escape(id)}"]`);
463
+ if (panel !== null) panel.innerHTML = html;
464
+ }
402
465
  await inlineImages(clone);
403
466
 
404
467
  const toc = buildExportToc(clone);
@@ -6,7 +6,7 @@
6
6
  * bundled to an IIFE and inlined into the file, finishes the job when the page is opened:
7
7
  * each diagram is rendered from the source `pre` the site's own components leave in
8
8
  * prerendered output. It also binds the behaviors the export keeps — theme toggle, code
9
- * copy, click-to-zoom — so the file behaves like the browser-exported one.
9
+ * copy, tabs, click-to-zoom — so the file behaves like the browser-exported one.
10
10
  *
11
11
  * The browser export ships a hand-written twin of the behavior half (the `RUNTIME` string
12
12
  * in `exportPage.ts`): it needs no diagram half, because its diagrams are already SVG when
@@ -96,6 +96,27 @@ function bindBehaviors(): void {
96
96
  });
97
97
  }
98
98
 
99
+ // Tabs are exported as the static Radix markup the live page hydrated; the file wires
100
+ // them back up by hand — a click selects within its tablist and swaps the panels.
101
+ for (const tablist of document.querySelectorAll('[role="tablist"]')) {
102
+ const triggers = Array.from(tablist.querySelectorAll('button[role="tab"]'));
103
+ for (const trigger of triggers) {
104
+ trigger.addEventListener('click', () => {
105
+ for (const other of triggers) {
106
+ const active = other === trigger;
107
+ other.setAttribute('aria-selected', active ? 'true' : 'false');
108
+ other.setAttribute('data-state', active ? 'active' : 'inactive');
109
+ other.setAttribute('tabindex', active ? '0' : '-1');
110
+ const panel = document.getElementById(other.getAttribute('aria-controls') ?? '');
111
+ if (panel !== null) {
112
+ panel.hidden = !active;
113
+ panel.setAttribute('data-state', active ? 'active' : 'inactive');
114
+ }
115
+ }
116
+ });
117
+ }
118
+ }
119
+
99
120
  // The live site's TOC follows the reader with fumadocs' own scroll tracking; in the file,
100
121
  // the plainest version of the same behaviour — last heading above the fold wins.
101
122
  const tocLinks = Array.from(document.querySelectorAll<HTMLAnchorElement>('.seemore-export-toc a'));