@docubook/flame 1.7.1 → 2.0.0-alpha.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/.docu/components/Context.tsx +1 -1
  2. package/.docu/components/Search.tsx +6 -6
  3. package/.docu/components/Sidebar.tsx +37 -8
  4. package/.docu/components/Theme.tsx +2 -2
  5. package/.docu/components/Toc.tsx +51 -5
  6. package/.docu/components/Typography.tsx +1 -1
  7. package/.docu/components/home/Hero.tsx +4 -4
  8. package/.docu/components/registry.ts +1 -1
  9. package/.docu/lib/build.deno.js +3 -3
  10. package/.docu/lib/{build.impl-TUVHCOIP.js → build.impl-WR24HDUT.js} +3 -3
  11. package/.docu/lib/build.node.js +3 -3
  12. package/.docu/lib/{chunk-ICNXW4Z4.js → chunk-2EYRWRDP.js} +66 -26
  13. package/.docu/lib/{chunk-PFXUCD2U.js → chunk-AJV2GEDF.js} +5 -4
  14. package/.docu/lib/{chunk-XMIEMHTQ.js → chunk-KEWRVASF.js} +3 -3
  15. package/.docu/lib/{chunk-B6LGUADD.js → chunk-QPF4DPZQ.js} +294 -177
  16. package/.docu/lib/chunk-UISOJ4RW.js +114 -0
  17. package/.docu/lib/{chunk-QPO4DS5F.js → chunk-VPV7KP7K.js} +56 -9
  18. package/.docu/lib/{chunk-QL6TQV3S.js → chunk-X7KPVCDN.js} +6 -6
  19. package/.docu/lib/clean.js +1 -1
  20. package/.docu/lib/deploy.deno.js +1 -1
  21. package/.docu/lib/deploy.node.js +1 -1
  22. package/.docu/lib/preview.deno.js +5 -3
  23. package/.docu/lib/preview.node.js +5 -3
  24. package/.docu/lib/server.deno.js +6 -4
  25. package/.docu/lib/server.node.js +6 -4
  26. package/.docu/node/build.impl.ts +72 -26
  27. package/.docu/node/build.ts +67 -17
  28. package/.docu/node/client.ts +64 -27
  29. package/.docu/node/deploy.shared.ts +5 -5
  30. package/.docu/node/deploy.ts +1 -1
  31. package/.docu/node/hydrate.node.ts +69 -4
  32. package/.docu/node/hydrate.ts +50 -1
  33. package/.docu/node/mdx-manifest.d.ts +5 -0
  34. package/.docu/node/mdx.ts +103 -17
  35. package/.docu/node/preview.deno.ts +1 -1
  36. package/.docu/node/preview.impl.ts +3 -3
  37. package/.docu/node/preview.node.ts +1 -1
  38. package/.docu/node/preview.ts +2 -2
  39. package/.docu/node/runtime/bun.ts +28 -0
  40. package/.docu/node/runtime/deno.ts +34 -0
  41. package/.docu/node/runtime/index.ts +4 -0
  42. package/.docu/node/runtime/node.ts +107 -0
  43. package/.docu/node/runtime/types.ts +19 -0
  44. package/.docu/node/search-indexer.ts +3 -2
  45. package/.docu/node/seo.ts +2 -2
  46. package/.docu/node/server-routes.ts +28 -13
  47. package/.docu/node/server.deno.ts +1 -1
  48. package/.docu/node/server.impl.ts +49 -3
  49. package/.docu/node/server.node.ts +1 -1
  50. package/.docu/node/server.ts +45 -1
  51. package/.docu/pages/docs/[[...slug]].tsx +13 -4
  52. package/.docu/pages/index.tsx +1 -1
  53. package/.docu/styles/globals.css +29 -20
  54. package/README.md +7 -48
  55. package/bin/cli.js +25 -7
  56. package/package.json +7 -6
  57. package/template/README.md +7 -48
@@ -76,7 +76,7 @@ export function Context({ className }: ContextProps) {
76
76
  onClick={() => navigate(contextPath)}
77
77
  className={cn(
78
78
  "relative flex cursor-pointer items-center gap-2 rounded px-2 py-2 text-sm",
79
- "text-left outline-none transition-colors",
79
+ "text-left transition-colors outline-none",
80
80
  isActive
81
81
  ? "bg-primary/20 text-primary"
82
82
  : "hover:bg-base-200 text-base-content/80 hover:text-base-content"
@@ -154,7 +154,7 @@ export default function Search({ className }: SearchProps) {
154
154
  boxClassName="w-[calc(100%-2rem)] max-w-[640px] p-0 mx-auto relative z-10"
155
155
  >
156
156
  <div onKeyDown={handleKeyDown}>
157
- <div className="px-4 pb-2 pt-4">
157
+ <div className="px-4 pt-4 pb-2">
158
158
  <div className="border-base-300 flex items-center gap-3 rounded-lg border px-4 py-2.5">
159
159
  <SearchIcon className="text-primary h-5 w-5 shrink-0" />
160
160
  <input
@@ -163,7 +163,7 @@ export default function Search({ className }: SearchProps) {
163
163
  onChange={handleQueryChange}
164
164
  placeholder="Search documentation..."
165
165
  autoFocus
166
- className="placeholder:text-base-content/40 h-8 w-full border-none bg-transparent text-base outline-none ring-0 focus:outline-none focus:ring-0"
166
+ className="placeholder:text-base-content/40 h-8 w-full border-none bg-transparent text-base ring-0 outline-none focus:ring-0 focus:outline-none"
167
167
  aria-label="Search documentation"
168
168
  />
169
169
  {query && (
@@ -187,7 +187,7 @@ export default function Search({ className }: SearchProps) {
187
187
  </div>
188
188
  </div>
189
189
 
190
- <div className="max-h-[400px] overflow-y-auto px-3 py-2">
190
+ <div className="max-h-100 overflow-y-auto px-3 py-2">
191
191
  {query.trim().length >= 2 && results.length === 0 && (
192
192
  <p className="text-base-content/50 py-8 text-center text-sm">
193
193
  No results for &ldquo;<span className="text-primary">{query}</span>&rdquo;
@@ -248,10 +248,10 @@ function SearchTrigger({ onClick }: { onClick: () => void }) {
248
248
  <SearchIcon className="h-4 w-4 shrink-0" />
249
249
  <span className="flex-1 text-left">Search</span>
250
250
  <div className="flex items-center gap-0.5">
251
- <Kbd className="bg-primary text-[12px text-base-300 dark:text-foreground rounded-md p-1.5 font-medium">
251
+ <Kbd className="bg-primary text-primary-foreground rounded-md p-1.5 text-[12px] font-medium">
252
252
  <FnKey.Cmd />
253
253
  </Kbd>
254
- <Kbd className="bg-primary text-base-300 dark:text-foreground rounded-md p-1.5 text-[12px] font-medium">
254
+ <Kbd className="bg-primary text-primary-foreground rounded-md p-1.5 text-[12px] font-medium">
255
255
  K
256
256
  </Kbd>
257
257
  </div>
@@ -289,7 +289,7 @@ function GroupedResults({
289
289
  <div className="flex flex-col gap-1">
290
290
  {groups.map((group) => (
291
291
  <div key={group.section}>
292
- <div className="text-base-content/50 px-2 pb-1 pt-3 text-xs font-semibold">
292
+ <div className="text-base-content/50 px-2 pt-3 pb-1 text-xs font-semibold">
293
293
  {group.section}
294
294
  </div>
295
295
  {group.items.map(({ result, globalIndex }) => {
@@ -92,13 +92,21 @@ export function MobileBar({
92
92
  useEffect(() => {
93
93
  if (!tocs.length) return;
94
94
 
95
+ // Match the CSS scroll-margin-top (scroll-mt-54 = 216px) that anchor
96
+ // jumps rest at, so the highlight agrees with where a jump lands.
97
+ const firstEl = document.getElementById(tocs[0].href.slice(1));
98
+ const offset = firstEl ? parseFloat(getComputedStyle(firstEl).scrollMarginTop) || 100 : 100;
99
+
95
100
  const handleScroll = () => {
96
101
  let currentId: string | null = null;
97
102
  for (const toc of tocs) {
98
103
  const id = toc.href.slice(1);
99
104
  const el = document.getElementById(id);
100
105
  if (!el) continue;
101
- if (el.getBoundingClientRect().top <= 100) {
106
+ // Round: smooth anchor scrolls land at sub-pixel positions (e.g.
107
+ // 216.4px), which would otherwise fail the <= offset comparison and
108
+ // leave the previous heading highlighted.
109
+ if (Math.round(el.getBoundingClientRect().top) <= offset) {
102
110
  currentId = id;
103
111
  } else {
104
112
  break;
@@ -109,7 +117,13 @@ export function MobileBar({
109
117
 
110
118
  handleScroll();
111
119
  window.addEventListener("scroll", handleScroll, { passive: true });
112
- return () => window.removeEventListener("scroll", handleScroll);
120
+ // Smooth anchor scrolls may not emit a trailing scroll event at the
121
+ // target position — recompute once the animation actually settles.
122
+ window.addEventListener("scrollend", handleScroll, { passive: true });
123
+ return () => {
124
+ window.removeEventListener("scroll", handleScroll);
125
+ window.removeEventListener("scrollend", handleScroll);
126
+ };
113
127
  }, [tocs]);
114
128
 
115
129
  useEffect(() => {
@@ -192,7 +206,10 @@ export function MobileBar({
192
206
  <MobileTocList
193
207
  tocs={tocs}
194
208
  activeId={activeId}
195
- onSelect={() => setTocExpanded(false)}
209
+ onSelect={(id) => {
210
+ setTocExpanded(false);
211
+ setActiveId(id);
212
+ }}
196
213
  />
197
214
  ) : (
198
215
  <div className="flex flex-col items-center py-6 text-center">
@@ -216,7 +233,7 @@ function MobileTocList({
216
233
  }: {
217
234
  tocs: TocItem[];
218
235
  activeId: string | null;
219
- onSelect: () => void;
236
+ onSelect: (id: string) => void;
220
237
  }) {
221
238
  return (
222
239
  <ul className="flex flex-col gap-0.5">
@@ -230,9 +247,21 @@ function MobileTocList({
230
247
  href={toc.href}
231
248
  onClick={(e) => {
232
249
  e.preventDefault();
233
- onSelect();
234
- const el = document.getElementById(id);
235
- if (el) el.scrollIntoView({ behavior: "smooth" });
250
+ onSelect(id);
251
+ history.replaceState(null, "", toc.href);
252
+ const targetId = id;
253
+ // Wait for the bar collapse re-render so the measurement is
254
+ // accurate, then jump with the same offset the observer uses
255
+ // (scrollIntoView ignores scroll-margin-top on iOS Safari).
256
+ requestAnimationFrame(() => {
257
+ const el = document.getElementById(targetId);
258
+ if (!el) return;
259
+ const margin = parseFloat(getComputedStyle(el).scrollMarginTop) || 100;
260
+ window.scrollTo({
261
+ top: el.getBoundingClientRect().top + window.scrollY - margin,
262
+ behavior: "smooth",
263
+ });
264
+ });
236
265
  }}
237
266
  className={cn(
238
267
  "block rounded px-2 py-1.5 text-sm transition-colors",
@@ -276,7 +305,7 @@ function MobileDrawer({
276
305
  return (
277
306
  <div className="fixed inset-0 z-[100]">
278
307
  <div className="absolute inset-0 bg-black/40" onClick={onClose} aria-hidden="true" />
279
- <div className="bg-base-100 absolute right-0 top-0 flex h-full w-80 max-w-[85vw] flex-col shadow-xl">
308
+ <div className="bg-base-100 absolute top-0 right-0 flex h-full w-80 max-w-[85vw] flex-col shadow-xl">
280
309
  <div className="border-base-200 flex items-center justify-between border-b px-4 py-3">
281
310
  <div className="flex items-center gap-2">
282
311
  <GitHubLink repoUrl={repoUrl} />
@@ -24,7 +24,7 @@ export function ThemeToggle({ className }: ThemeToggleProps) {
24
24
  >
25
25
  <Sun
26
26
  className={`h-4 w-4 transition-colors ${
27
- !isDark ? "text-base-300" : "text-foreground"
27
+ !isDark ? "text-primary-foreground" : "text-foreground"
28
28
  }`}
29
29
  />
30
30
  </button>
@@ -38,7 +38,7 @@ export function ThemeToggle({ className }: ThemeToggleProps) {
38
38
  >
39
39
  <Moon
40
40
  className={`h-4 w-4 transition-colors ${
41
- isDark ? "text-foreground" : "text-base-content/60"
41
+ isDark ? "text-primary-foreground" : "text-base-content/60"
42
42
  }`}
43
43
  />
44
44
  </button>
@@ -20,13 +20,29 @@ export default function Toc({ tocs }: TocProps) {
20
20
  activeIdRef.current = activeId;
21
21
  }, [activeId]);
22
22
 
23
+ // Single source of truth for the anchor offset — used by BOTH the scroll
24
+ // observer and the TOC click scroll so they can never disagree. Reads the
25
+ // CSS scroll-margin-top (prose-headings:scroll-mt-4 desktop /
26
+ // scroll-mt-16 mobile) so a change in one place updates both, and the
27
+ // heading lands exactly where the browser would put a native anchor jump.
28
+ function getAnchorOffset(): number {
29
+ if (typeof window === "undefined") return 100;
30
+ const first = tocs[0] ? document.getElementById(tocs[0].href.slice(1)) : null;
31
+ const margin = first ? parseFloat(getComputedStyle(first).scrollMarginTop) : 0;
32
+ return margin || 100;
33
+ }
34
+
23
35
  useEffect(() => {
24
- if (typeof document === "undefined" || !tocs.length) return;
36
+ // Desktop-only TOC — on mobile this island is display:none but still
37
+ // mounted; letting its observer run would hijack the URL hash while the
38
+ // mobile bar owns TOC behavior there.
39
+ if (typeof window === "undefined" || window.innerWidth < 1024) return;
40
+ if (!tocs.length) return;
25
41
 
26
42
  const isDesktop = window.innerWidth >= 1024;
27
43
  const container = isDesktop ? document.getElementById("scroll-container") : null;
28
44
  const scrollTarget = container || window;
29
- const offset = isDesktop ? 80 : 100;
45
+ const offset = getAnchorOffset();
30
46
 
31
47
  const handleScroll = () => {
32
48
  if (clickedIdRef.current) return;
@@ -101,7 +117,7 @@ export default function Toc({ tocs }: TocProps) {
101
117
 
102
118
  <div className="relative">
103
119
  <div className="relative text-sm">
104
- <div className="bg-base-300 absolute left-0 top-0 h-full w-px" />
120
+ <div className="bg-base-300 absolute top-0 left-0 h-full w-px" />
105
121
 
106
122
  <div className="flex flex-col">
107
123
  {tocs.map(({ href, level, text }) => {
@@ -143,7 +159,37 @@ export default function Toc({ tocs }: TocProps) {
143
159
  e.preventDefault();
144
160
  handleLinkClick(id);
145
161
  const el = document.getElementById(id);
146
- if (el) el.scrollIntoView({ behavior: "smooth" });
162
+ if (el) {
163
+ // Manual scroll with the same offset the observer uses —
164
+ // scrollIntoView ignores scroll-margin-top on iOS Safari
165
+ // window-scroll, landing the heading under the sticky
166
+ // mobile bar.
167
+ const offset = getAnchorOffset();
168
+ const scroller =
169
+ window.innerWidth >= 1024
170
+ ? document.getElementById("scroll-container")
171
+ : null;
172
+ const elTop = el.getBoundingClientRect().top;
173
+ if (scroller) {
174
+ // Container scroll space: element offset within the
175
+ // container minus the anchor offset (the container
176
+ // itself may sit below the viewport top, e.g. under
177
+ // the navbar).
178
+ scroller.scrollTo({
179
+ top:
180
+ elTop -
181
+ scroller.getBoundingClientRect().top +
182
+ scroller.scrollTop -
183
+ offset,
184
+ behavior: "smooth",
185
+ });
186
+ } else {
187
+ window.scrollTo({
188
+ top: elTop + window.scrollY - offset,
189
+ behavior: "smooth",
190
+ });
191
+ }
192
+ }
147
193
  }}
148
194
  className={cn(
149
195
  "flex flex-1 items-center py-2 transition-all duration-200",
@@ -153,7 +199,7 @@ export default function Toc({ tocs }: TocProps) {
153
199
  )}
154
200
  style={{ paddingLeft: `${levelPadding + 6}px` }}
155
201
  >
156
- <span className="line-clamp-2 break-words text-sm">{text}</span>
202
+ <span className="line-clamp-2 text-sm break-words">{text}</span>
157
203
  </a>
158
204
  </div>
159
205
  );
@@ -2,7 +2,7 @@ import { PropsWithChildren } from "react";
2
2
 
3
3
  export function Typography({ children }: PropsWithChildren) {
4
4
  return (
5
- <div className="prose prose-zinc dark:prose-invert prose-code:font-code dark:prose-code:bg-stone-900/25 prose-code:bg-stone-50 prose-pre:bg-background max-lg:prose-headings:scroll-mt-54 prose-headings:scroll-mt-4 prose-code:text-sm prose-code:leading-6 dark:prose-code:text-white prose-code:text-stone-800 prose-code:p-1 prose-code:rounded-md prose-code:border prose-img:rounded-md prose-img:border prose-code:before:content-none prose-code:after:content-none prose-code:px-1.5 prose-code:overflow-x-auto prose-img:my-3 prose-h2:my-4 prose-h2:mt-8 max-w-full! w-full pt-2">
5
+ <div className="prose prose-zinc dark:prose-invert prose-code:font-code dark:prose-code:bg-stone-900/25 prose-code:bg-stone-50 prose-pre:bg-background max-lg:prose-headings:scroll-mt-16 prose-headings:scroll-mt-4 prose-code:text-sm prose-code:leading-6 dark:prose-code:text-white prose-code:text-stone-800 prose-code:p-1 prose-code:rounded-md prose-code:border prose-img:rounded-md prose-img:border prose-code:before:content-none prose-code:after:content-none prose-code:px-1.5 prose-code:overflow-x-auto prose-img:my-3 prose-h2:my-4 prose-h2:mt-8 w-full max-w-full! pt-2">
6
6
  {children}
7
7
  </div>
8
8
  );
@@ -34,8 +34,8 @@ function renderActionButtonIcon(iconName: string | undefined, className?: string
34
34
 
35
35
  function ActionButton({ action }: ActionButtonProps) {
36
36
  const themeClasses = {
37
- primary: "bg-primary text-primary-content hover:bg-primary/90",
38
- secondary: "bg-secondary text-secondary-content hover:bg-secondary/90",
37
+ primary: "bg-primary text-primary-foreground hover:bg-primary/90",
38
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90",
39
39
  ghost: "bg-transparent text-muted-foreground border border-base-300 hover:bg-base-200",
40
40
  };
41
41
 
@@ -64,11 +64,11 @@ export function Hero({ hero, className }: HeroProps) {
64
64
  <div className={cn("mx-auto max-w-4xl px-6 py-32 sm:py-44", className)}>
65
65
  <div className="text-center">
66
66
  {tagline && <p className="text-primary mb-4 text-lg font-semibold">{tagline}</p>}
67
- <h1 className="text-balance text-5xl font-semibold tracking-tight sm:text-7xl">
67
+ <h1 className="text-5xl font-semibold tracking-tight text-balance sm:text-7xl">
68
68
  {headline}
69
69
  </h1>
70
70
  {description && (
71
- <p className="text-muted-foreground mt-8 text-pretty text-lg sm:text-xl">{description}</p>
71
+ <p className="text-muted-foreground mt-8 text-lg text-pretty sm:text-xl">{description}</p>
72
72
  )}
73
73
  {actions && actions.length > 0 && (
74
74
  <div className="mt-10 flex flex-wrap items-center justify-center gap-4">
@@ -1,5 +1,5 @@
1
1
  import type { ComponentType } from "react";
2
- import { createMdxComponents } from "@docubook/mdx-content";
2
+ import { createMdxComponents } from "@docubook/markdown";
3
3
  import { Dropdown, DropdownItem, DropdownLink } from "@docubook/ui-react/dropdown";
4
4
  import { PaginationDocs } from "@docubook/ui-react/pagination";
5
5
  import { Toggle, ToggleGroup } from "@docubook/ui-react/toggle";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runBuildCli
3
- } from "./chunk-ICNXW4Z4.js";
4
- import "./chunk-B6LGUADD.js";
3
+ } from "./chunk-2EYRWRDP.js";
4
+ import "./chunk-QPF4DPZQ.js";
5
5
  import "./chunk-EOK6KATZ.js";
6
- import "./chunk-PFXUCD2U.js";
6
+ import "./chunk-AJV2GEDF.js";
7
7
  import "./chunk-4IQXHHPF.js";
8
8
 
9
9
  // .docu/node/build.deno.ts
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  runBuild,
3
3
  runBuildCli
4
- } from "./chunk-ICNXW4Z4.js";
5
- import "./chunk-B6LGUADD.js";
4
+ } from "./chunk-2EYRWRDP.js";
5
+ import "./chunk-QPF4DPZQ.js";
6
6
  import "./chunk-EOK6KATZ.js";
7
- import "./chunk-PFXUCD2U.js";
7
+ import "./chunk-AJV2GEDF.js";
8
8
  import "./chunk-4IQXHHPF.js";
9
9
  export {
10
10
  runBuild,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runBuildCli
3
- } from "./chunk-ICNXW4Z4.js";
4
- import "./chunk-B6LGUADD.js";
3
+ } from "./chunk-2EYRWRDP.js";
4
+ import "./chunk-QPF4DPZQ.js";
5
5
  import "./chunk-EOK6KATZ.js";
6
- import "./chunk-PFXUCD2U.js";
6
+ import "./chunk-AJV2GEDF.js";
7
7
  import "./chunk-4IQXHHPF.js";
8
8
 
9
9
  // .docu/node/build.node.ts
@@ -7,13 +7,15 @@ import {
7
7
  buildClientBundle,
8
8
  captureException,
9
9
  compileMdx,
10
+ compileMdxModule,
10
11
  computeInlineThemeCss,
12
+ frontmatterField,
11
13
  generateSearchIndex,
12
14
  getGitLastModifiedBatch,
13
15
  htmlShell,
14
16
  initSentry,
15
17
  loadPlugins
16
- } from "./chunk-B6LGUADD.js";
18
+ } from "./chunk-QPF4DPZQ.js";
17
19
  import {
18
20
  cspHeader,
19
21
  generateNonce,
@@ -21,7 +23,7 @@ import {
21
23
  } from "./chunk-EOK6KATZ.js";
22
24
  import {
23
25
  logger
24
- } from "./chunk-PFXUCD2U.js";
26
+ } from "./chunk-AJV2GEDF.js";
25
27
  import {
26
28
  ASSETS_DIR,
27
29
  CACHE_FILE,
@@ -48,7 +50,7 @@ function buildSeoMeta(config, frontmatter, slug) {
48
50
  url,
49
51
  siteName: config.meta?.title || ""
50
52
  };
51
- const image = typeof frontmatter.image === "string" && frontmatter.image || config.meta?.ogImage;
53
+ const image = frontmatterField(frontmatter, "image") || config.meta?.ogImage;
52
54
  if (image) {
53
55
  try {
54
56
  result.image = new URL(image, image.startsWith("/") ? baseURL : `${baseURL}/docs/`).href;
@@ -121,8 +123,8 @@ ${msg}`, { cause: err });
121
123
  content
122
124
  });
123
125
  }
124
- const title = (typeof frontmatter.title === "string" ? frontmatter.title : "") || slug || "Docs";
125
- const description = typeof frontmatter.description === "string" ? frontmatter.description : "";
126
+ const title = frontmatterField(frontmatter, "title") || slug || "Docs";
127
+ const description = frontmatterField(frontmatter, "description");
126
128
  const slugParts = slug ? slug.split("/") : [];
127
129
  const page = React.createElement(
128
130
  DocsLayout,
@@ -131,12 +133,15 @@ ${msg}`, { cause: err });
131
133
  slug: slugParts,
132
134
  title,
133
135
  description,
134
- date: frontmatter.date || void 0,
135
- content: result.content,
136
+ date: frontmatterField(frontmatter, "date") || void 0,
137
+ // Render MDX content as its own root: client hydrates the island as a
138
+ // separate root, so SSR must be root-relative too or useId-based ids
139
+ // (mdx-compiler components) mismatch during hydration.
140
+ content: renderToString(result.content),
136
141
  tocs: result.tocs,
137
142
  filePath,
138
143
  repoUrl: docuConfig.repo?.url,
139
- compiledSource: result.compiledSource
144
+ mdxSlug: slug
140
145
  })
141
146
  );
142
147
  const body = renderToString(page);
@@ -146,7 +151,7 @@ ${msg}`, { cause: err });
146
151
  const depth = slug ? slug.split("/").length : 1;
147
152
  const favicon = docuConfig.meta?.favicon || "/docs/assets/images/favicon.ico";
148
153
  const seo = buildSeoMeta(docuConfig, frontmatter, slug || "");
149
- const csp = cspHeader(nonce, true);
154
+ const csp = cspHeader(nonce);
150
155
  let html = htmlShell({
151
156
  title,
152
157
  description,
@@ -200,9 +205,55 @@ async function runBuild() {
200
205
  const cache = args.force ? {} : await readCache();
201
206
  let built = 0;
202
207
  let skipped = 0;
208
+ const pluginsConfig = docuConfig.plugins ?? [];
209
+ const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;
210
+ if (builder) {
211
+ const plugins = await loadPlugins(pluginsConfig);
212
+ for (const plugin of plugins) {
213
+ await plugin.setup(builder);
214
+ }
215
+ await builder.runOnStart();
216
+ }
217
+ const mdxSources = {};
218
+ const prePassTasks = mdxFiles.map(async (file) => {
219
+ let raw;
220
+ try {
221
+ raw = await readFile(file.absPath, "utf-8");
222
+ } catch {
223
+ return;
224
+ }
225
+ let content = raw;
226
+ if (builder) {
227
+ const relPath = file.absPath.replace(PROJECT_ROOT + "/", "");
228
+ const transformed = await builder.runOnLoad(relPath, content);
229
+ if (transformed?.contents) content = transformed.contents;
230
+ }
231
+ const remarkPlugins = builder?.collectRemarkPlugins();
232
+ const rehypePlugins = builder?.collectRehypePlugins();
233
+ mdxSources[file.path] = await compileMdxModule(content, remarkPlugins, rehypePlugins);
234
+ });
235
+ await Promise.all(prePassTasks);
236
+ const indexMdxPath = join(DOCS_DIR, "index.mdx");
237
+ if (existsSync(indexMdxPath)) {
238
+ try {
239
+ const indexRaw = await readFile(indexMdxPath, "utf-8");
240
+ let indexContent = indexRaw;
241
+ if (builder) {
242
+ const relPath = indexMdxPath.replace(PROJECT_ROOT + "/", "");
243
+ const transformed = await builder.runOnLoad(relPath, indexContent);
244
+ if (transformed?.contents) indexContent = transformed.contents;
245
+ }
246
+ mdxSources[""] = await compileMdxModule(
247
+ indexContent,
248
+ builder?.collectRemarkPlugins(),
249
+ builder?.collectRehypePlugins()
250
+ );
251
+ } catch {
252
+ }
253
+ }
203
254
  logger.bundleStart();
204
255
  let t = performance.now();
205
- assetManifest = await buildClientBundle();
256
+ assetManifest = await buildClientBundle(mdxSources);
206
257
  logger.bundleDone(Math.round(performance.now() - t));
207
258
  inlineThemeCss = computeInlineThemeCss();
208
259
  const lastManifest = cache["__assets__"];
@@ -214,15 +265,6 @@ async function runBuild() {
214
265
  builtAt: Date.now()
215
266
  };
216
267
  }
217
- const pluginsConfig = docuConfig.plugins ?? [];
218
- const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;
219
- if (builder) {
220
- const plugins = await loadPlugins(pluginsConfig);
221
- for (const plugin of plugins) {
222
- await plugin.setup(builder);
223
- }
224
- await builder.runOnStart();
225
- }
226
268
  logger.spinner.start("Building pages...");
227
269
  t = performance.now();
228
270
  const allRelPaths = mdxFiles.map((f) => f.absPath.replace(PROJECT_ROOT + "/", ""));
@@ -301,9 +343,9 @@ async function runBuild() {
301
343
  await Promise.all(buildTasks.slice(i, i + CONCURRENCY).map((fn) => fn()));
302
344
  }
303
345
  try {
304
- const indexMdxPath = join(DOCS_DIR, "index.mdx");
305
- const indexRaw = await readFile(indexMdxPath, "utf-8");
306
- const indexRelPath = indexMdxPath.replace(PROJECT_ROOT + "/", "");
346
+ const indexMdxPath2 = join(DOCS_DIR, "index.mdx");
347
+ const indexRaw = await readFile(indexMdxPath2, "utf-8");
348
+ const indexRelPath = indexMdxPath2.replace(PROJECT_ROOT + "/", "");
307
349
  const indexHtml = await renderDocsPage(
308
350
  docuConfig,
309
351
  "",
@@ -332,8 +374,7 @@ async function runBuild() {
332
374
  body: renderToString(landingPage),
333
375
  favicon: landingFavicon,
334
376
  seo: landingSeo,
335
- /** unsafe-eval required by mdx-remote hydration — see above. */
336
- csp: cspHeader(landingNonce, true),
377
+ csp: cspHeader(landingNonce),
337
378
  css: assetManifest.css,
338
379
  js: assetManifest.js,
339
380
  nonce: landingNonce,
@@ -353,8 +394,7 @@ async function runBuild() {
353
394
  body: renderToString(notFoundPage),
354
395
  favicon: notFoundFavicon,
355
396
  headExtra: ['<meta name="robots" content="noindex,follow">'],
356
- /** unsafe-eval required by mdx-remote hydration — see above. */
357
- csp: cspHeader(notFoundNonce, true),
397
+ csp: cspHeader(notFoundNonce),
358
398
  css: assetManifest.css,
359
399
  js: assetManifest.js,
360
400
  nonce: notFoundNonce,
@@ -5,7 +5,7 @@ import {
5
5
  // package.json
6
6
  var package_default = {
7
7
  name: "@docubook/flame",
8
- version: "1.7.1",
8
+ version: "2.0.0-alpha.0",
9
9
  description: "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
10
10
  type: "module",
11
11
  bin: {
@@ -62,10 +62,10 @@ var package_default = {
62
62
  license: "MIT",
63
63
  dependencies: {
64
64
  "@docubook/core": "workspace:^",
65
- "@docubook/mdx-content": "workspace:^",
66
- "@docubook/runt": "workspace:^",
65
+ "@docubook/markdown": "workspace:^",
67
66
  "@docubook/themes-colors": "workspace:^",
68
67
  "@docubook/ui-react": "workspace:^",
68
+ "@mdx-js/react": "^3.0.1",
69
69
  "@tailwindcss/cli": "4.3.0",
70
70
  "@tailwindcss/typography": "0.5.16",
71
71
  "bun-plugin-tailwind": "^0.1.2",
@@ -74,7 +74,8 @@ var package_default = {
74
74
  "lucide-react": "^1.14.0",
75
75
  react: "^19.2.7",
76
76
  "react-dom": "^19.2.7",
77
- unified: "^11.0.0"
77
+ unified: "^11.0.0",
78
+ zod: "^4.4.3"
78
79
  },
79
80
  peerDependencies: {
80
81
  "@sentry/bun": "^10.0.0"
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-EOK6KATZ.js";
8
8
  import {
9
9
  logger
10
- } from "./chunk-PFXUCD2U.js";
10
+ } from "./chunk-AJV2GEDF.js";
11
11
  import {
12
12
  DIST_DIR
13
13
  } from "./chunk-4IQXHHPF.js";
@@ -58,7 +58,7 @@ async function runPreview(adapter) {
58
58
  headers: {
59
59
  "Content-Type": "text/html",
60
60
  ...SECURITY_HEADERS,
61
- "Content-Security-Policy": cspHeader(nonce, true)
61
+ "Content-Security-Policy": cspHeader(nonce)
62
62
  }
63
63
  });
64
64
  }
@@ -75,7 +75,7 @@ async function runPreview(adapter) {
75
75
  headers: {
76
76
  "Content-Type": "text/html",
77
77
  ...SECURITY_HEADERS,
78
- "Content-Security-Policy": cspHeader(nonce, true)
78
+ "Content-Security-Policy": cspHeader(nonce)
79
79
  }
80
80
  });
81
81
  }