seemore 1.1.3 → 1.1.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
@@ -11,6 +11,9 @@
11
11
  <a href="https://marketplace.visualstudio.com/items?itemName=arifszn.seemore-vscode">
12
12
  <img src="https://img.shields.io/badge/VS_Code-Marketplace-007ACC?logo=visualstudiocode&logoColor=white"/>
13
13
  </a>
14
+ <a href="https://open-vsx.org/extension/arifszn/seemore-vscode">
15
+ <img src="https://img.shields.io/badge/Open_VSX-Registry-C160EF?logo=eclipseide&logoColor=white"/>
16
+ </a>
14
17
  <a href="https://github.com/arifszn/seemore/actions/workflows/ci.yml">
15
18
  <img src="https://github.com/arifszn/seemore/actions/workflows/ci.yml/badge.svg"/>
16
19
  </a>
@@ -48,7 +51,7 @@ seemore points at that folder and renders it as a proper site instead, without y
48
51
  Three ways to use it:
49
52
 
50
53
  - **[In your browser](#view-in-your-browser)**: `npx seemore` serves the folder you're standing in and updates live as you edit.
51
- - **[In VS Code](#view-in-vs-code)**: an extension puts the same site in a panel next to the file you're editing.
54
+ - **[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 — VS Code and VS Code-compatible editors like Cursor and Antigravity.
52
55
  - **[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.
53
56
 
54
57
  <p align="center">
@@ -65,9 +68,9 @@ Open a terminal in your folder of Markdown files, run `npx seemore`, and open th
65
68
 
66
69
  With no folder given, seemore serves the folder you're standing in. Point it at a subfolder with `npx seemore docs` if that's where your files live.
67
70
 
68
- ## View in VS Code
71
+ ## View in your code editor
69
72
 
70
- Install [seemore for VS Code](https://marketplace.visualstudio.com/items?itemName=arifszn.seemore-vscode) to get the same rendered site as a panel beside your editor. No terminal, no `npx`, no browser tab to manage.
73
+ 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. No terminal, no `npx`, no browser tab to manage. Open VSX also covers VS Code-compatible editors — Cursor, Antigravity, and others.
71
74
 
72
75
  1. Open any Markdown file.
73
76
  2. Click the seemore icon in the editor's title bar, or right-click a folder in the explorer and choose **Open Folder in seemore**.
@@ -93,28 +96,19 @@ Install [seemore for VS Code](https://marketplace.visualstudio.com/items?itemNam
93
96
  npx seemore build # static export to dist/ for any host
94
97
  ```
95
98
 
96
- 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 its own file, so deep links and reloads work everywhere without special host rules. seemore also writes the handful of files individual hosts look for:
97
-
98
- | File | Read by |
99
- | --- | --- |
100
- | `404.html` | GitHub Pages, Netlify, Cloudflare Pages, Vercel, S3 |
101
- | `_redirects` | Netlify, Cloudflare Pages |
102
- | `200.html` | Surge |
103
- | `.nojekyll` | GitHub Pages, so Jekyll doesn't drop every path starting with `_` |
104
-
105
- None of these are required for a host that isn't listed above. If your host serves a folder of files, it serves a seemore build.
99
+ 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.
106
100
 
107
- **GitHub Pages, one thing to know:** project sites live at `username.github.io/my-repo/`, not at the root. Tell seemore once:
101
+ **On GitHub Pages:** project sites live under `username.github.io/my-repo/`, not the root, so set `base` once:
108
102
 
109
103
  ```ts
110
104
  export default defineConfig({ base: '/my-repo/' });
111
105
  ```
112
106
 
113
- or `seemore build --base /my-repo/`. Under GitHub Actions with no `base` set, the build prints the exact line to add.
107
+ (or `--base /my-repo/` on the CLI). Building under GitHub Actions without it set prints the exact line to add.
114
108
 
115
109
  ## Configuration
116
110
 
117
- Optional. A folder with no config file builds correctly in the browser, in VS Code, and when built for publishing. If you want to adjust things, create `seemore.config.ts` next to your content:
111
+ Optional. A folder with no config file builds correctly in the browser, in your code editor, and when built for publishing. If you want to adjust things, create `seemore.config.ts` next to your content:
118
112
 
119
113
  ```ts
120
114
  // seemore.config.ts
@@ -166,7 +160,19 @@ Twelve built-in colour presets: `neutral` (default), `black`, `catppuccin`, `dus
166
160
 
167
161
  ### Features
168
162
 
169
- A flat list of switches for readers who want fine control. Prefix one with `!` to switch off something that's on by default.
163
+ A flat list of switches for readers who want fine control, set as an array on the `features` key in `seemore.config.ts`. To turn on something that's off by default, add its flag name. To turn off something that's on by default, add its flag name prefixed with `!`.
164
+
165
+ ```ts
166
+ // seemore.config.ts
167
+ export default defineConfig({
168
+ features: [
169
+ 'navigation.path', // off by default → this turns it on
170
+ '!navigation.instant.prefetch', // on by default → this turns it off
171
+ ],
172
+ });
173
+ ```
174
+
175
+ Flags you don't mention are left at their default, so you only ever list the ones you're changing.
170
176
 
171
177
  | Flag | Default | Effect |
172
178
  | --- | --- | --- |
@@ -181,6 +187,7 @@ A flat list of switches for readers who want fine control. Prefix one with `!` t
181
187
  | `toc.integrate` | off | Merge the table of contents into the sidebar |
182
188
  | `content.code.copy` | on | Copy button on code blocks |
183
189
  | `content.action.edit` | on with `editLink` | Edit-this-page link |
190
+ | `content.image.zoom` | on | Click-to-zoom on content images |
184
191
  | `search.suggest` | on | Inline query completion |
185
192
  | `search.highlight` | on | Highlight the query on the page you land on |
186
193
  | `social.cards` | off | Per-page OG images (needs `takumi-js`) |
@@ -189,7 +196,7 @@ Combinations that can't work together raise a config error naming both flags and
189
196
 
190
197
  ## Content
191
198
 
192
- `.md` and `.mdx` alike, with the format inferred per file, so plain Markdown never needs MDX syntax.
199
+ Supports `.md` and `.mdx` both.
193
200
 
194
201
  - GitHub Flavoured Markdown, admonitions (note / tip / warning boxes), step-by-step lists, and colour-highlighted code blocks
195
202
  - `[[wikilinks]]`, including `[[Page|label]]` and `[[Page#Heading]]`, the easiest way for you or your AI to link pages without relative paths to get right
@@ -198,6 +205,35 @@ Combinations that can't work together raise a config error naming both flags and
198
205
  - Sibling images inlined as hashed assets, sibling PDFs open in the browser's own viewer
199
206
  - Frontmatter (the `key: value` block at the top of a file) is validated, with errors that name the file and the field
200
207
 
208
+ ### Code blocks
209
+
210
+ Fences are highlighted at build time by [Shiki](https://shiki.style), in the theme's own colours.
211
+
212
+ Settings go on the fence line, after the language — ` ```ts title="server.ts" lineNumbers `:
213
+
214
+ | On the fence | Effect |
215
+ | --- | --- |
216
+ | `title="server.ts"` | Filename bar above the block |
217
+ | `lineNumbers` | Numbers down the side; `lineNumbers=5` starts the count at 5 |
218
+ | `noCopy` | No copy button on this one block |
219
+
220
+ Comments mark individual lines and never reach the page:
221
+
222
+ | In the code | Effect |
223
+ | --- | --- |
224
+ | `// [!code highlight]` | Marks the line |
225
+ | `// [!code ++]`, `// [!code --]` | Diff lines: green with a `+`, red with a `-` |
226
+ | `// [!code focus]` | Blurs every other line until the pointer is over the block |
227
+ | `// [!code word:needle]` | Marks that word everywhere it appears in the block |
228
+
229
+ The marker follows the language's own comment syntax, so `# [!code highlight]` in Python and `<!-- [!code highlight] -->` in HTML.
230
+
231
+ ### Components
232
+
233
+ An `.mdx` file can use `<Callout>`, `<Card>`, `<Cards>`, `<CodeBlockTabs>`, `<Mermaid>`, `<D2>` and `<Pdf>` without importing anything. The set is deliberately small: Markdown has no imports, so every component is one seemore ships to every site whether it is used or not, and these are the ones that pair with something Markdown already expresses. Anything else — fumadocs' `<Tabs>`, `<Accordions>`, `<Files>` among them — fails the build, naming the file and the component. In a plain `.md` file a tag is not JSX at all: it is dropped and its text kept, so components need the `.mdx` extension. The [Content page](https://arifszn.github.io/seemore/content) has the details.
234
+
235
+ Numbered headings — `## 1. Install it`, `## 2. Point it at a folder` — become a numbered sequence.
236
+
201
237
  ### Page addresses
202
238
 
203
239
  | File | Address |
@@ -214,9 +250,23 @@ Both `/guide` and `/guide/` work on every host.
214
250
 
215
251
  Pages are ordered by:
216
252
 
217
- 1. `meta.json` in the directory
218
- 2. Frontmatter `order`
219
- 3. Alphabetical by title
253
+ 1. `meta.json` in the directory — an explicit list, with `...` standing in for anything you didn't name:
254
+
255
+ ```json
256
+ // guide/meta.json
257
+ { "pages": ["getting-started", "installation", "..."] }
258
+ ```
259
+
260
+ 2. Frontmatter `order` — lower numbers first:
261
+
262
+ ```md
263
+ ---
264
+ title: Getting Started
265
+ order: 1
266
+ ---
267
+ ```
268
+
269
+ 3. Alphabetical by title, for anything left unordered by the two above
220
270
 
221
271
  ## CLI reference
222
272
 
@@ -238,7 +288,7 @@ Options
238
288
 
239
289
  ## Under the hood
240
290
 
241
- [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 VS Code extension runs the same CLI as a child process it manages. Bug reports and pull requests are welcome at [github.com/arifszn/seemore](https://github.com/arifszn/seemore).
291
+ [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. Bug reports and pull requests are welcome at [github.com/arifszn/seemore](https://github.com/arifszn/seemore).
242
292
 
243
293
  ## Licence
244
294
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seemore",
3
- "version": "1.1.3",
3
+ "version": "1.1.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,5 +1,6 @@
1
- import { StrictMode } from 'react';
2
- import { renderToString } from 'react-dom/server';
1
+ import { Writable } from 'node:stream';
2
+ import { StrictMode, type ReactNode } from 'react';
3
+ import { renderToPipeableStream } from 'react-dom/server';
3
4
  import { RouterProvider, createMemoryRouter } from 'react-router';
4
5
  import { config } from 'virtual:seemore/config';
5
6
  import { toBasename, withBase } from '../shared/base.js';
@@ -15,7 +16,7 @@ export interface RenderResult {
15
16
  /**
16
17
  * The prerender driver.
17
18
  *
18
- * The page's module is loaded first, so `use()` resolves synchronously and `renderToString`
19
+ * The page's module is loaded first, so `use()` resolves synchronously and the renderer
19
20
  * emits the complete article rather than a Suspense fallback. No route has a loader, so the
20
21
  * memory router is initialised the moment it is created.
21
22
  */
@@ -27,15 +28,82 @@ export async function render(url: string): Promise<RenderResult> {
27
28
  initialEntries: [withBase(config.base, url)],
28
29
  });
29
30
 
30
- const html = renderToString(
31
+ const { html, failures } = await renderToHtml(
31
32
  <StrictMode>
32
33
  <RouterProvider router={router} />
33
34
  </StrictMode>,
34
35
  );
35
36
 
37
+ // React hands a render error to the nearest Suspense boundary and carries on. Without this
38
+ // the page would be written out as the loading fallback — an empty shell — and the build
39
+ // would report it as a success.
40
+ if (failures.length > 0) throw prerenderError(url, failures[0]);
41
+
36
42
  return { html, head: head(url) };
37
43
  }
38
44
 
45
+ /**
46
+ * The whole tree as one string, plus anything that threw while rendering it.
47
+ *
48
+ * `renderToString` would be shorter, but it swallows render errors: its own `onError` is
49
+ * internal and a failed subtree is silently replaced by its Suspense fallback. The streaming
50
+ * renderer reports them, and piping only once `onAllReady` has fired keeps the output the
51
+ * same complete markup — no fallbacks, no streaming scripts.
52
+ */
53
+ function renderToHtml(element: ReactNode): Promise<{ html: string; failures: unknown[] }> {
54
+ return new Promise((resolve) => {
55
+ const failures: unknown[] = [];
56
+ const chunks: Buffer[] = [];
57
+
58
+ const sink = new Writable({
59
+ write(chunk: Buffer, _encoding, done) {
60
+ chunks.push(Buffer.from(chunk));
61
+ done();
62
+ },
63
+ });
64
+ sink.on('finish', () => {
65
+ resolve({ html: Buffer.concat(chunks).toString('utf8'), failures });
66
+ });
67
+
68
+ const stream = renderToPipeableStream(element, {
69
+ onError(error: unknown) {
70
+ failures.push(error);
71
+ },
72
+ onAllReady() {
73
+ stream.pipe(sink);
74
+ },
75
+ // Nothing was rendered at all: there is no markup to hand back, and `failures` already
76
+ // holds the reason.
77
+ onShellError() {
78
+ resolve({ html: '', failures });
79
+ },
80
+ });
81
+ });
82
+ }
83
+
84
+ /** Components an MDX file can use without importing anything. */
85
+ const PROVIDED_COMPONENTS = 'Callout, Card, Cards, CodeBlockTabs, Mermaid, D2 and Pdf';
86
+
87
+ /**
88
+ * A page that threw, reported the way the rest of the build reports problems: the file it
89
+ * came from, what went wrong, and — for the common case of an MDX file reaching for a
90
+ * component that is not there — the fix.
91
+ */
92
+ function prerenderError(url: string, cause: unknown): Error {
93
+ const file = findRoute(url)?.file;
94
+ const message = cause instanceof Error ? cause.message : String(cause);
95
+ const undefinedComponent = /Expected component `(.+?)` to be defined/.exec(message);
96
+
97
+ const hint =
98
+ undefinedComponent === null
99
+ ? ''
100
+ : `\n\n \`<${undefinedComponent[1]}>\` is not one of the components seemore provides ` +
101
+ `(${PROVIDED_COMPONENTS}), and a Markdown file has no imports to add one with. ` +
102
+ `Remove it, or write the markup by hand.`;
103
+
104
+ return new Error(`${file ?? url} failed to render.\n\n ${message}${hint}`, { cause });
105
+ }
106
+
39
107
  export function listRoutes(): string[] {
40
108
  return routeEntries().map((entry) => entry.url);
41
109
  }
@@ -109,6 +109,34 @@ export function NotFound() {
109
109
  );
110
110
  }
111
111
 
112
+ /**
113
+ * A page whose own markup threw — an `.mdx` file reaching for a component seemore does not
114
+ * provide, most often. `seemore build` refuses to write such a page at all; here, in the dev
115
+ * server and on client-side navigation, the reason replaces the article, because React
116
+ * unmounts the whole app when nothing catches the error and a blank screen says nothing.
117
+ */
118
+ export function PageError({ message }: { message: string }) {
119
+ return (
120
+ <div className="seemore-shell">
121
+ <Header />
122
+ <div className="seemore-body">
123
+ <Sidebar />
124
+
125
+ <main className="seemore-main">
126
+ <article className="seemore-article prose">
127
+ <h1>This page failed to render</h1>
128
+ <pre>
129
+ <code>{message}</code>
130
+ </pre>
131
+ </article>
132
+
133
+ <SiteFooter />
134
+ </main>
135
+ </div>
136
+ </div>
137
+ );
138
+ }
139
+
112
140
  /**
113
141
  * The generated index: when no `index.md` or root `README.md` claims `/`, the home address
114
142
  * lists every page instead of apologising.
@@ -1,7 +1,7 @@
1
- import { Suspense } from 'react';
1
+ import { Component, Suspense, type ReactNode } from 'react';
2
2
  import { useLocation, type RouteObject } from 'react-router';
3
3
  import { decodePath } from '../shared/base.js';
4
- import { DocPage, DocsLayout, NotFound, Overview } from './layout/DocsLayout.js';
4
+ import { DocPage, DocsLayout, NotFound, Overview, PageError } from './layout/DocsLayout.js';
5
5
  import { useRouteEntry } from './lib/pages.js';
6
6
 
7
7
  /** The current route URL: React Router has already removed the basename. */
@@ -13,6 +13,16 @@ export function useRouteUrl(): string {
13
13
 
14
14
  function Page() {
15
15
  const url = useRouteUrl();
16
+ // Keyed by address, so navigating away from a page that threw starts clean rather than
17
+ // carrying its error to every page after it.
18
+ return (
19
+ <PageErrorBoundary key={url}>
20
+ <PageContent url={url} />
21
+ </PageErrorBoundary>
22
+ );
23
+ }
24
+
25
+ function PageContent({ url }: { url: string }) {
16
26
  const entry = useRouteEntry(url);
17
27
  if (entry !== undefined) return <DocPage entry={entry} />;
18
28
  // A folder with no `index.md` or root `README.md` still gets a home address: a generated
@@ -20,6 +30,23 @@ function Page() {
20
30
  return url === '/' ? <Overview /> : <NotFound />;
21
31
  }
22
32
 
33
+ /**
34
+ * The only error boundary in the app. Render errors come from page content — the rest of the
35
+ * tree is seemore's own — so this sits around the page and nothing else.
36
+ */
37
+ class PageErrorBoundary extends Component<{ children: ReactNode }, { message: string | undefined }> {
38
+ override state: { message: string | undefined } = { message: undefined };
39
+
40
+ static getDerivedStateFromError(error: unknown): { message: string } {
41
+ return { message: error instanceof Error ? error.message : String(error) };
42
+ }
43
+
44
+ override render() {
45
+ if (this.state.message !== undefined) return <PageError message={this.state.message} />;
46
+ return this.props.children;
47
+ }
48
+ }
49
+
23
50
  /**
24
51
  * One catch-all route, matched against `virtual:seemore/routes` at render time.
25
52
  *