@karimsa/mdxserve 0.2.0 → 0.4.0

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.
Files changed (57) hide show
  1. package/README.md +60 -10
  2. package/client/CodeBlock.tsx +24 -0
  3. package/client/DocView.tsx +9 -2
  4. package/client/ListingView.tsx +14 -10
  5. package/client/MarkdownTable.tsx +190 -0
  6. package/client/MdSection.tsx +56 -21
  7. package/client/MdSectionEditor.tsx +149 -2
  8. package/client/Mermaid.tsx +33 -7
  9. package/client/Settings.css +17 -0
  10. package/client/Settings.tsx +132 -0
  11. package/client/Table.tsx +2 -14
  12. package/client/app.css +339 -1
  13. package/client/block-state/README.md +39 -0
  14. package/client/block-state/storage.ts +73 -0
  15. package/client/block-state/useBlockState.ts +120 -0
  16. package/client/builtins/DataTable.tsx +269 -0
  17. package/client/builtins/chart-data.ts +22 -10
  18. package/client/builtins/index.ts +10 -0
  19. package/client/data-table/ColumnMenu.tsx +168 -0
  20. package/client/data-table/ColumnOptions.tsx +141 -0
  21. package/client/data-table/TableCell.tsx +76 -0
  22. package/client/data-table/TableHeaderCell.tsx +94 -0
  23. package/client/data-table/model.ts +194 -0
  24. package/client/data-table/navigation.ts +34 -0
  25. package/client/data-table/table-state.ts +196 -0
  26. package/client/data-table/useColumnMenu.ts +84 -0
  27. package/client/data-table/useTableState.ts +36 -0
  28. package/client/design/base/prose.css +4 -0
  29. package/client/diagrams/Dialog.tsx +577 -0
  30. package/client/diagrams/EditDialog.tsx +67 -0
  31. package/client/diagrams/Preferences.tsx +213 -0
  32. package/client/diagrams/edit-block.ts +93 -0
  33. package/client/diagrams/edit-context.ts +4 -0
  34. package/client/diagrams/session-id.ts +8 -0
  35. package/client/diagrams/slash.ts +94 -0
  36. package/client/diagrams/source.ts +73 -0
  37. package/client/diagrams/style.css +599 -0
  38. package/client/entry.tsx +2 -0
  39. package/client/mdx-components-base.ts +2 -2
  40. package/client/router.ts +8 -1
  41. package/client/shell/AppShell.tsx +38 -15
  42. package/client/shell/Sidebar.tsx +20 -2
  43. package/client/shell/StandaloneShell.tsx +19 -6
  44. package/client/shell/TocRail.tsx +4 -3
  45. package/client/shell/TopBar.tsx +17 -5
  46. package/client/state.ts +14 -0
  47. package/client/ui/ConfirmDeleteDialog.tsx +48 -91
  48. package/client/ui/ExpandModal.tsx +63 -104
  49. package/client/ui/Modal.tsx +123 -0
  50. package/client/ui/ResizeHandle.tsx +1 -1
  51. package/client/ui/SearchDialog.tsx +81 -88
  52. package/client/ui/modal.css +42 -0
  53. package/dist/cli.js +2191 -778
  54. package/dist/diagram-worker.js +17 -0
  55. package/dist/registry.json +297 -0
  56. package/package.json +42 -2
  57. package/skills/mdxserve/SKILL.md +56 -1
@@ -0,0 +1,17 @@
1
+ // src/diagrams/adapters/validate-worker.ts
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ import { JSDOM } from "jsdom";
4
+ var dom = new JSDOM("<!doctype html><html><body></body></html>");
5
+ Object.assign(globalThis, { window: dom.window, document: dom.window.document });
6
+ try {
7
+ const { default: mermaid } = await import("mermaid");
8
+ mermaid.initialize({ startOnLoad: false, securityLevel: "strict" });
9
+ await mermaid.parse(workerData.source);
10
+ parentPort?.postMessage(null);
11
+ } catch (error) {
12
+ parentPort?.postMessage(
13
+ (error instanceof Error ? error.message : "Invalid Mermaid").slice(0, 2e3)
14
+ );
15
+ } finally {
16
+ dom.window.close();
17
+ }
@@ -1,6 +1,303 @@
1
1
  {
2
2
  "version": 1,
3
3
  "components": [
4
+ {
5
+ "name": "DataTable",
6
+ "description": "A bordered, sortable, filterable data table with typed columns, unit selection, grouped headers and optional bars.",
7
+ "whenToUse": "Use for datasets and quantitative comparisons; use native Markdown tables for facts and prose. Required stable document-unique id. Columns have key, label, type (text/number/percent/time/bytes/currency), optional group and bars. Percent values are fractions (0.15 means 15%). Time and bytes require unit; currency requires a format function and numeric data. Bars are opt-in and independently scaled by column. Reader state is local to the document and ID.",
8
+ "props": {
9
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
10
+ "type": "object",
11
+ "properties": {
12
+ "id": {
13
+ "type": "string",
14
+ "minLength": 1,
15
+ "description": "Required stable ID, unique within this document. Retain it across edits to preserve local sorting, filters and units."
16
+ },
17
+ "caption": {
18
+ "type": "string"
19
+ },
20
+ "columns": {
21
+ "minItems": 1,
22
+ "type": "array",
23
+ "items": {
24
+ "oneOf": [
25
+ {
26
+ "type": "object",
27
+ "properties": {
28
+ "key": {
29
+ "type": "string",
30
+ "minLength": 1
31
+ },
32
+ "label": {
33
+ "type": "string",
34
+ "minLength": 1
35
+ },
36
+ "group": {
37
+ "type": "string"
38
+ },
39
+ "align": {
40
+ "description": "Explicit alignment overrides the column type default.",
41
+ "type": "string",
42
+ "enum": [
43
+ "left",
44
+ "center",
45
+ "right"
46
+ ]
47
+ },
48
+ "bars": {
49
+ "description": "Opt-in magnitude bars; zero is always included in the scale.",
50
+ "type": "boolean"
51
+ },
52
+ "type": {
53
+ "type": "string",
54
+ "const": "text"
55
+ }
56
+ },
57
+ "required": [
58
+ "key",
59
+ "label",
60
+ "type"
61
+ ]
62
+ },
63
+ {
64
+ "type": "object",
65
+ "properties": {
66
+ "key": {
67
+ "type": "string",
68
+ "minLength": 1
69
+ },
70
+ "label": {
71
+ "type": "string",
72
+ "minLength": 1
73
+ },
74
+ "group": {
75
+ "type": "string"
76
+ },
77
+ "align": {
78
+ "description": "Explicit alignment overrides the column type default.",
79
+ "type": "string",
80
+ "enum": [
81
+ "left",
82
+ "center",
83
+ "right"
84
+ ]
85
+ },
86
+ "bars": {
87
+ "description": "Opt-in magnitude bars; zero is always included in the scale.",
88
+ "type": "boolean"
89
+ },
90
+ "type": {
91
+ "type": "string",
92
+ "const": "number"
93
+ }
94
+ },
95
+ "required": [
96
+ "key",
97
+ "label",
98
+ "type"
99
+ ]
100
+ },
101
+ {
102
+ "type": "object",
103
+ "properties": {
104
+ "key": {
105
+ "type": "string",
106
+ "minLength": 1
107
+ },
108
+ "label": {
109
+ "type": "string",
110
+ "minLength": 1
111
+ },
112
+ "group": {
113
+ "type": "string"
114
+ },
115
+ "align": {
116
+ "description": "Explicit alignment overrides the column type default.",
117
+ "type": "string",
118
+ "enum": [
119
+ "left",
120
+ "center",
121
+ "right"
122
+ ]
123
+ },
124
+ "bars": {
125
+ "description": "Opt-in magnitude bars; zero is always included in the scale.",
126
+ "type": "boolean"
127
+ },
128
+ "type": {
129
+ "type": "string",
130
+ "const": "percent"
131
+ }
132
+ },
133
+ "required": [
134
+ "key",
135
+ "label",
136
+ "type"
137
+ ]
138
+ },
139
+ {
140
+ "type": "object",
141
+ "properties": {
142
+ "key": {
143
+ "type": "string",
144
+ "minLength": 1
145
+ },
146
+ "label": {
147
+ "type": "string",
148
+ "minLength": 1
149
+ },
150
+ "group": {
151
+ "type": "string"
152
+ },
153
+ "align": {
154
+ "description": "Explicit alignment overrides the column type default.",
155
+ "type": "string",
156
+ "enum": [
157
+ "left",
158
+ "center",
159
+ "right"
160
+ ]
161
+ },
162
+ "bars": {
163
+ "description": "Opt-in magnitude bars; zero is always included in the scale.",
164
+ "type": "boolean"
165
+ },
166
+ "type": {
167
+ "type": "string",
168
+ "const": "time"
169
+ },
170
+ "unit": {
171
+ "type": "string",
172
+ "minLength": 1
173
+ }
174
+ },
175
+ "required": [
176
+ "key",
177
+ "label",
178
+ "type",
179
+ "unit"
180
+ ]
181
+ },
182
+ {
183
+ "type": "object",
184
+ "properties": {
185
+ "key": {
186
+ "type": "string",
187
+ "minLength": 1
188
+ },
189
+ "label": {
190
+ "type": "string",
191
+ "minLength": 1
192
+ },
193
+ "group": {
194
+ "type": "string"
195
+ },
196
+ "align": {
197
+ "description": "Explicit alignment overrides the column type default.",
198
+ "type": "string",
199
+ "enum": [
200
+ "left",
201
+ "center",
202
+ "right"
203
+ ]
204
+ },
205
+ "bars": {
206
+ "description": "Opt-in magnitude bars; zero is always included in the scale.",
207
+ "type": "boolean"
208
+ },
209
+ "type": {
210
+ "type": "string",
211
+ "const": "bytes"
212
+ },
213
+ "unit": {
214
+ "type": "string",
215
+ "minLength": 1
216
+ }
217
+ },
218
+ "required": [
219
+ "key",
220
+ "label",
221
+ "type",
222
+ "unit"
223
+ ]
224
+ },
225
+ {
226
+ "type": "object",
227
+ "properties": {
228
+ "key": {
229
+ "type": "string",
230
+ "minLength": 1
231
+ },
232
+ "label": {
233
+ "type": "string",
234
+ "minLength": 1
235
+ },
236
+ "group": {
237
+ "type": "string"
238
+ },
239
+ "align": {
240
+ "description": "Explicit alignment overrides the column type default.",
241
+ "type": "string",
242
+ "enum": [
243
+ "left",
244
+ "center",
245
+ "right"
246
+ ]
247
+ },
248
+ "bars": {
249
+ "description": "Opt-in magnitude bars; zero is always included in the scale.",
250
+ "type": "boolean"
251
+ },
252
+ "type": {
253
+ "type": "string",
254
+ "const": "currency"
255
+ },
256
+ "format": {
257
+ "description": "Required numeric-to-string currency formatter, e.g. value => `$${value}`"
258
+ }
259
+ },
260
+ "required": [
261
+ "key",
262
+ "label",
263
+ "type",
264
+ "format"
265
+ ]
266
+ }
267
+ ]
268
+ },
269
+ "description": "Ordered columns: key, label, type; optional group and bars. Time/bytes require a base unit. Percent data uses fractions (0.15 displays 15%). Currency requires format(value)."
270
+ },
271
+ "data": {
272
+ "type": "array",
273
+ "items": {
274
+ "type": "object",
275
+ "propertyNames": {
276
+ "type": "string"
277
+ },
278
+ "additionalProperties": {
279
+ "anyOf": [
280
+ {
281
+ "type": "string"
282
+ },
283
+ {
284
+ "type": "number"
285
+ },
286
+ {
287
+ "type": "null"
288
+ }
289
+ ]
290
+ }
291
+ }
292
+ }
293
+ },
294
+ "required": [
295
+ "id",
296
+ "columns",
297
+ "data"
298
+ ]
299
+ }
300
+ },
4
301
  {
5
302
  "name": "Callout",
6
303
  "description": "A boxed aside with an icon, used to draw attention to a note or warning inline with the text.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karimsa/mdxserve",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Serve a directory of Markdown/MDX files, like `serve` but for docs",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "scripts": {
27
27
  "build": "tsx scripts/build.ts",
28
28
  "registry": "tsx scripts/build-registry.ts",
29
- "dev": "tsx scripts/build-registry.ts && tsx src/index.ts serve -w example",
29
+ "dev": "tsx scripts/build.ts && tsx src/index.ts serve -w example",
30
30
  "typecheck": "tsc --noEmit",
31
31
  "format": "prettier --write .",
32
32
  "format:check": "prettier --check .",
@@ -43,6 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
+ "@floating-ui/dom": "1.8.0",
46
47
  "@fontsource-variable/jetbrains-mono": "5.3.0",
47
48
  "@fontsource-variable/manrope": "5.3.0",
48
49
  "@mdx-js/mdx": "3.1.1",
@@ -52,56 +53,95 @@
52
53
  "@tanstack/react-query": "5.101.4",
53
54
  "@tippyjs/react": "4.2.6",
54
55
  "@tiptap/core": "3.29.2",
56
+ "@tiptap/extension-blockquote": "3.29.2",
57
+ "@tiptap/extension-bold": "3.29.2",
58
+ "@tiptap/extension-bubble-menu": "3.29.2",
59
+ "@tiptap/extension-bullet-list": "3.29.2",
60
+ "@tiptap/extension-code": "3.29.2",
61
+ "@tiptap/extension-code-block": "3.29.2",
62
+ "@tiptap/extension-document": "3.29.2",
63
+ "@tiptap/extension-dropcursor": "3.29.2",
64
+ "@tiptap/extension-floating-menu": "3.29.2",
65
+ "@tiptap/extension-gapcursor": "3.29.2",
66
+ "@tiptap/extension-hard-break": "3.29.2",
67
+ "@tiptap/extension-heading": "3.29.2",
68
+ "@tiptap/extension-horizontal-rule": "3.29.2",
55
69
  "@tiptap/extension-image": "3.29.2",
70
+ "@tiptap/extension-italic": "3.29.2",
71
+ "@tiptap/extension-link": "3.29.2",
56
72
  "@tiptap/extension-list": "3.29.2",
73
+ "@tiptap/extension-list-item": "3.29.2",
74
+ "@tiptap/extension-list-keymap": "3.29.2",
75
+ "@tiptap/extension-ordered-list": "3.29.2",
76
+ "@tiptap/extension-paragraph": "3.29.2",
77
+ "@tiptap/extension-strike": "3.29.2",
57
78
  "@tiptap/extension-table": "3.29.2",
79
+ "@tiptap/extension-text": "3.29.2",
80
+ "@tiptap/extension-underline": "3.29.2",
81
+ "@tiptap/extensions": "3.29.2",
58
82
  "@tiptap/markdown": "3.29.2",
59
83
  "@tiptap/pm": "3.29.2",
60
84
  "@tiptap/react": "3.29.2",
61
85
  "@tiptap/starter-kit": "3.29.2",
86
+ "@tiptap/suggestion": "3.29.2",
62
87
  "@trpc/client": "11.18.0",
63
88
  "@trpc/server": "11.18.0",
64
89
  "@trpc/tanstack-react-query": "11.18.0",
65
90
  "@vitejs/plugin-react": "6.0.5",
91
+ "bytes": "3.1.2",
66
92
  "commander": "15.0.0",
93
+ "cross-spawn": "7.0.6",
67
94
  "date-fns": "4.4.0",
68
95
  "diff": "9.0.0",
69
96
  "framer-motion": "13.0.0",
70
97
  "hast-util-to-html": "9.0.5",
71
98
  "jotai": "2.20.2",
99
+ "jsdom": "27.4.0",
72
100
  "lockfile": "1.0.4",
73
101
  "lucide-react": "1.30.0",
74
102
  "mdast-util-from-markdown": "2.0.3",
103
+ "mdast-util-frontmatter": "2.0.1",
75
104
  "mdast-util-mdx": "3.0.0",
76
105
  "mdast-util-to-hast": "13.2.1",
77
106
  "mdast-util-to-string": "4.0.0",
78
107
  "mermaid": "10.9.8",
108
+ "micromark-extension-frontmatter": "2.0.0",
79
109
  "micromark-extension-mdxjs": "3.0.0",
80
110
  "minisearch": "7.2.0",
111
+ "ms": "2.1.3",
81
112
  "node-sqlite3-wasm": "0.8.60",
82
113
  "react": "19.2.8",
83
114
  "react-dom": "19.2.8",
84
115
  "react-hot-toast": "2.6.0",
85
116
  "rehype-pretty-code": "0.14.5",
86
117
  "rehype-slug": "6.0.0",
118
+ "remark-frontmatter": "5.0.0",
87
119
  "remark-gfm": "4.0.1",
120
+ "rollup": "4.64.0",
121
+ "sharp": "0.35.5",
88
122
  "shiki": "4.4.2",
89
123
  "svg-pan-zoom": "3.6.2",
90
124
  "tailwindcss": "4.3.3",
91
125
  "tippy.js": "6.3.7",
92
126
  "trash": "10.1.1",
127
+ "update-notifier": "7.3.1",
93
128
  "vfile-message": "4.0.3",
94
129
  "vite": "8.2.1",
95
130
  "zod": "4.4.3",
96
131
  "zx": "8.8.5"
97
132
  },
98
133
  "devDependencies": {
134
+ "@types/bytes": "3.1.6",
135
+ "@types/cross-spawn": "6.0.6",
136
+ "@types/jsdom": "30.0.0",
99
137
  "@types/lockfile": "1.0.4",
100
138
  "@types/mdast": "4.0.4",
139
+ "@types/ms": "2.1.0",
101
140
  "@types/node": "26.2.0",
102
141
  "@types/react": "19.2.18",
103
142
  "@types/react-dom": "19.2.4",
104
143
  "@types/svg-pan-zoom": "3.4.0",
144
+ "@types/update-notifier": "6.0.8",
105
145
  "@yarnpkg/types": "4.0.1",
106
146
  "esbuild": "0.28.2",
107
147
  "fast-check": "4.9.0",
@@ -30,7 +30,7 @@ add clarity, and prefer features that degrade gracefully everywhere else.
30
30
  error on them, and they draw data, which is `<Chart>`'s job (or a table). Mermaid is for a
31
31
  flow or a shape, not a dataset.
32
32
  4. **Builtin components** (`<Callout>`, `<Tabs>`, `<Badge>`, `<Tooltip>`, `<Button>`, `<Diff>`, `<Card>`,
33
- `<Kbd>`, `<FileTree>`, `<Chart>` (bar, line, area, histogram), `<Sparkline>`, `<Screenshot>`) only when they make
33
+ `<Kbd>`, `<FileTree>`, `<DataTable>`, `<Chart>` (bar, line, area, histogram), `<Sparkline>`, `<Screenshot>`) only when they make
34
34
  the content clearer: a warning the reader must not miss, per-OS or per-language variants of the
35
35
  same instructions, a status label, a small dataset that's clearer as a shape than a table.
36
36
  Outside mdxserve these show as raw tags, so use them sparingly and never for decoration.
@@ -134,6 +134,61 @@ Components contain ordinary Markdown: a `Tab` can hold lists, paragraphs, and co
134
134
  Leave a blank line between a component tag and a Markdown block inside it so the block is
135
135
  parsed as Markdown.
136
136
 
137
+ ## Tables: data versus facts
138
+
139
+ Use **`DataTable` for datasets**: measurements, metrics, budgets, benchmarks, and records
140
+ readers will compare, sort, or filter. Use **native Markdown tables for facts**: option
141
+ references, prose comparisons, and key/value documentation. Native tables get the same
142
+ bordered interface with conservative type detection; they do not automatically get bars.
143
+ Native tables use heading context for generated IDs. Same-header tables within the same
144
+ heading context remain interactive but do not persist preferences; use explicit `DataTable`
145
+ IDs when persistent state is required for those tables.
146
+ Both forms work identically in `.md` and `.mdx`. For documents mainly read elsewhere,
147
+ retain portable Markdown unless richer mdxserve content was requested.
148
+
149
+ Check `mdxserve components show DataTable` before first use. Supply a stable, descriptive
150
+ `id` unique within the document, and preserve it when updating data. Sorting, filters, and
151
+ unit preferences are saved locally under the document and ID. Give columns stable unique
152
+ `key` values; row objects use those keys. Missing values use `null`, never an invented zero.
153
+
154
+ ```mdx
155
+ <DataTable
156
+ id="endpoint-performance"
157
+ caption="Endpoint performance"
158
+ columns={[
159
+ { key: "endpoint", label: "Endpoint", type: "text" },
160
+ { key: "success", label: "Success", type: "percent" },
161
+ { key: "mean", label: "Mean", type: "time", unit: "ms", group: "Latency", bars: true },
162
+ { key: "peak", label: "Peak", type: "time", unit: "ms", group: "Latency", bars: true },
163
+ { key: "payload", label: "Payload", type: "bytes", unit: "B" },
164
+ { key: "cost", label: "Cost", type: "currency", format: (value) => `$${value.toFixed(2)}` }
165
+ ]}
166
+ data={[
167
+ { endpoint: "/search", success: 0.995, mean: 120, peak: 450, payload: 2048, cost: 12.50 },
168
+ { endpoint: "/export", success: 0.98, mean: 2400, peak: 8000, payload: 1048576, cost: 25 }
169
+ ]}
170
+ />
171
+ ```
172
+
173
+ - `text` uses strings; readers filter with a regular expression, case-insensitive.
174
+ - `number` uses finite numbers; readers filter by inclusive minimum and maximum.
175
+ - `percent` uses **fractions**: `0.15` displays **15%**. Filter inputs use displayed percentage
176
+ points (`15` or `15%`), not fractions.
177
+ - `time` uses numeric data and a required base `unit` accepted by `ms` (e.g. `ms`, `seconds`,
178
+ `hours`). One unit is chosen for the entire column; readers can override it. Filters accept
179
+ durations such as `500ms` or `2s`; bare numbers use the currently displayed unit.
180
+ - `bytes` uses numeric data and a required base `unit` accepted by `bytes` (`B`, `KB`, `MB`,
181
+ `GB`, `TB`, `PB`). Units are binary (1 KB = 1024 B). The whole column shares a display unit;
182
+ filters accept `10MB`, or bare numbers in the displayed unit.
183
+ - `currency` keeps numeric data and **requires `format: (value) => string`**. Include the
184
+ intended currency in that formatter or label. Filters expand `10K`, `2M`, `1B`, and `1T`.
185
+ - Adjacent columns with the same `group` get a shared top-level heading.
186
+ - Optional `align: "left" | "center" | "right"` overrides type-based alignment. Native Markdown tables preserve explicit GFM delimiter alignment.
187
+ - Set `bars: true` only when comparing magnitudes is meaningful. Omit it for IDs, years,
188
+ ranks, or ambiguous measurements. Each column has its own zero-inclusive scale, fixed
189
+ across sorting/filtering; negative values extend left of zero. Equal bar lengths across
190
+ different columns do not imply equal quantities.
191
+
137
192
  ## Plan files
138
193
 
139
194
  Plans are read in mdxserve and in plain text. Use headings for phases, a task list per phase,