zudoku 0.0.0-z6e949a6d → 0.0.0-z8ac421f0

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 (209) hide show
  1. package/dist/app/main.js +1 -1
  2. package/dist/app/main.js.map +1 -1
  3. package/dist/config/create-plugin.d.ts +2 -0
  4. package/dist/config/create-plugin.js +55 -0
  5. package/dist/config/create-plugin.js.map +1 -0
  6. package/dist/config/loader.js +2 -2
  7. package/dist/config/loader.js.map +1 -1
  8. package/dist/config/validators/InputNavigationSchema.d.ts +67 -67
  9. package/dist/config/validators/NavigationSchema.js +1 -0
  10. package/dist/config/validators/NavigationSchema.js.map +1 -1
  11. package/dist/config/validators/validate.d.ts +5 -4
  12. package/dist/config/validators/validate.js +2 -0
  13. package/dist/config/validators/validate.js.map +1 -1
  14. package/dist/flat-config.d.ts +1 -0
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +1 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/lib/components/Bootstrap.js +1 -2
  19. package/dist/lib/components/Bootstrap.js.map +1 -1
  20. package/dist/lib/components/Slot.test.js +1 -1
  21. package/dist/lib/components/Slot.test.js.map +1 -1
  22. package/dist/lib/components/Zudoku.d.ts +4 -1
  23. package/dist/lib/components/Zudoku.js +4 -7
  24. package/dist/lib/components/Zudoku.js.map +1 -1
  25. package/dist/lib/components/context/ZudokuContext.d.ts +2 -4
  26. package/dist/lib/components/context/ZudokuContext.js +2 -2
  27. package/dist/lib/components/context/ZudokuContext.js.map +1 -1
  28. package/dist/lib/components/context/ZudokuProvider.js +1 -1
  29. package/dist/lib/components/context/ZudokuProvider.js.map +1 -1
  30. package/dist/lib/components/context/ZudokuReactContext.d.ts +11 -0
  31. package/dist/lib/components/context/ZudokuReactContext.js +4 -0
  32. package/dist/lib/components/context/ZudokuReactContext.js.map +1 -0
  33. package/dist/lib/core/ZudokuContext.d.ts +2 -1
  34. package/dist/lib/core/ZudokuContext.js +3 -1
  35. package/dist/lib/core/ZudokuContext.js.map +1 -1
  36. package/dist/lib/core/__internal.d.ts +1 -0
  37. package/dist/lib/core/__internal.js +2 -0
  38. package/dist/lib/core/__internal.js.map +1 -1
  39. package/dist/lib/core/plugins.d.ts +5 -1
  40. package/dist/lib/core/plugins.js.map +1 -1
  41. package/dist/lib/core/transform-config.d.ts +4 -2
  42. package/dist/lib/core/transform-config.js +33 -13
  43. package/dist/lib/core/transform-config.js.map +1 -1
  44. package/dist/lib/core/transform-config.test.d.ts +1 -0
  45. package/dist/lib/core/transform-config.test.js +83 -0
  46. package/dist/lib/core/transform-config.test.js.map +1 -0
  47. package/dist/lib/errors/ErrorAlert.js +1 -2
  48. package/dist/lib/errors/ErrorAlert.js.map +1 -1
  49. package/dist/lib/hooks/useEvent.test.js +1 -1
  50. package/dist/lib/hooks/useEvent.test.js.map +1 -1
  51. package/dist/lib/plugins/openapi/ParamInfos.js +8 -5
  52. package/dist/lib/plugins/openapi/ParamInfos.js.map +1 -1
  53. package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
  54. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  55. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js +2 -2
  56. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js.map +1 -1
  57. package/dist/lib/ui/Alert.d.ts +3 -2
  58. package/dist/lib/ui/Alert.js +9 -5
  59. package/dist/lib/ui/Alert.js.map +1 -1
  60. package/dist/lib/ui/Secret.js +2 -2
  61. package/dist/lib/ui/Secret.js.map +1 -1
  62. package/dist/lib/util/flattenAllOf.js +6 -0
  63. package/dist/lib/util/flattenAllOf.js.map +1 -1
  64. package/dist/lib/util/flattenAllOf.test.js +26 -0
  65. package/dist/lib/util/flattenAllOf.test.js.map +1 -1
  66. package/dist/vite/config.js +5 -2
  67. package/dist/vite/config.js.map +1 -1
  68. package/dist/vite/plugin-config.js +16 -4
  69. package/dist/vite/plugin-config.js.map +1 -1
  70. package/dist/vite/plugin-theme.js +2 -1
  71. package/dist/vite/plugin-theme.js.map +1 -1
  72. package/dist/vite/prerender/prerender.js +3 -1
  73. package/dist/vite/prerender/prerender.js.map +1 -1
  74. package/dist/vite/prerender/worker.js +3 -1
  75. package/dist/vite/prerender/worker.js.map +1 -1
  76. package/lib/{ClaudeLogo-OpUSMQJe.js → ClaudeLogo-Br8C_vTq.js} +3 -3
  77. package/lib/{ClaudeLogo-OpUSMQJe.js.map → ClaudeLogo-Br8C_vTq.js.map} +1 -1
  78. package/lib/Drawer-Ch7927PF.js.map +1 -1
  79. package/lib/{HydrationBoundary-CNF2ZV3E.js → HydrationBoundary-CJu4vUlG.js} +6 -6
  80. package/lib/{HydrationBoundary-CNF2ZV3E.js.map → HydrationBoundary-CJu4vUlG.js.map} +1 -1
  81. package/lib/{MdxPage-dzCPGdvD.js → MdxPage-C0QFAsgv.js} +6 -6
  82. package/lib/{MdxPage-dzCPGdvD.js.map → MdxPage-C0QFAsgv.js.map} +1 -1
  83. package/lib/Mermaid-Chx5BPHn.js +104 -0
  84. package/lib/Mermaid-Chx5BPHn.js.map +1 -0
  85. package/lib/{OAuthErrorPage-D7n-_cqN.js → OAuthErrorPage-CFz_gBFx.js} +11 -10
  86. package/lib/{OAuthErrorPage-D7n-_cqN.js.map → OAuthErrorPage-CFz_gBFx.js.map} +1 -1
  87. package/lib/{OasProvider-DPPdikt_.js → OasProvider-BwIOIlky.js} +3 -3
  88. package/lib/{OasProvider-DPPdikt_.js.map → OasProvider-BwIOIlky.js.map} +1 -1
  89. package/lib/OperationList-DYRzbPJu.js +5908 -0
  90. package/lib/OperationList-DYRzbPJu.js.map +1 -0
  91. package/lib/{RouteGuard-BMbu_Yb7.js → RouteGuard-CVs3yvEs.js} +3 -3
  92. package/lib/{RouteGuard-BMbu_Yb7.js.map → RouteGuard-CVs3yvEs.js.map} +1 -1
  93. package/lib/{SchemaList-CRC8n5co.js → SchemaList-D4FEyoDV.js} +7 -7
  94. package/lib/{SchemaList-CRC8n5co.js.map → SchemaList-D4FEyoDV.js.map} +1 -1
  95. package/lib/{SchemaView-BR6dtnPg.js → SchemaView-ScvkhsYE.js} +116 -110
  96. package/lib/SchemaView-ScvkhsYE.js.map +1 -0
  97. package/lib/{Secret-BDBqq4p3.js → Secret-DUpgv4V3.js} +92 -72
  98. package/lib/Secret-DUpgv4V3.js.map +1 -0
  99. package/lib/{SignUp-ChqXj9vd.js → SignUp-Dug1jAGC.js} +4 -4
  100. package/lib/{SignUp-ChqXj9vd.js.map → SignUp-Dug1jAGC.js.map} +1 -1
  101. package/lib/{SyntaxHighlight-O-IZOPLg.js → SyntaxHighlight-BMu0b_hF.js} +8 -8
  102. package/lib/{SyntaxHighlight-O-IZOPLg.js.map → SyntaxHighlight-BMu0b_hF.js.map} +1 -1
  103. package/lib/{Toc-DQF7trHT.js → Toc-BiJ2YL0O.js} +2 -2
  104. package/lib/{Toc-DQF7trHT.js.map → Toc-BiJ2YL0O.js.map} +1 -1
  105. package/lib/{Zudoku-DA1yA-te.js → Zudoku-iyiXgWFY.js} +2101 -2121
  106. package/lib/Zudoku-iyiXgWFY.js.map +1 -0
  107. package/lib/ZudokuContext-CYyb_PB_.js +175 -0
  108. package/lib/ZudokuContext-CYyb_PB_.js.map +1 -0
  109. package/lib/ZudokuReactContext-DGJAP1sN.js +222 -0
  110. package/lib/ZudokuReactContext-DGJAP1sN.js.map +1 -0
  111. package/lib/chunk-EPOLDU6W-C6C8jAwd.js.map +1 -1
  112. package/lib/{circular-C4l1Kj1N.js → circular-BOpxmAie.js} +2 -2
  113. package/lib/{circular-C4l1Kj1N.js.map → circular-BOpxmAie.js.map} +1 -1
  114. package/lib/{createServer-DoRZ6tMa.js → createServer-BunbJzB5.js} +6 -4
  115. package/lib/{createServer-DoRZ6tMa.js.map → createServer-BunbJzB5.js.map} +1 -1
  116. package/lib/{errors-CYLN8SNc.js → errors-B77S9iOc.js} +2 -2
  117. package/lib/{errors-CYLN8SNc.js.map → errors-B77S9iOc.js.map} +1 -1
  118. package/lib/{firebase-DF-VVKB7.js → firebase-C7XKRGLf.js} +25 -24
  119. package/lib/{firebase-DF-VVKB7.js.map → firebase-C7XKRGLf.js.map} +1 -1
  120. package/lib/{hook-C35h0YhF.js → hook-Dz_n9SoE.js} +16 -15
  121. package/lib/{hook-C35h0YhF.js.map → hook-Dz_n9SoE.js.map} +1 -1
  122. package/lib/{index-DrAVvbXa.js → index-BDp2MTiq.js} +2 -2
  123. package/lib/{index-DrAVvbXa.js.map → index-BDp2MTiq.js.map} +1 -1
  124. package/lib/{index-Ck4TmzTO.js → index-Bx29qHVi.js} +52 -50
  125. package/lib/{index-Ck4TmzTO.js.map → index-Bx29qHVi.js.map} +1 -1
  126. package/lib/index-CrcNWbel.js.map +1 -1
  127. package/lib/index-DAWHN3cH.js.map +1 -1
  128. package/lib/index.esm-BYObtETB.js.map +1 -1
  129. package/lib/index.esm-Ca5zvoff.js.map +1 -1
  130. package/lib/{index.esm-B2cLXwjS.js → index.esm-Cth49JBv.js} +2 -2
  131. package/lib/index.esm-Cth49JBv.js.map +1 -0
  132. package/lib/jsx-runtime-BzflLqGi.js.map +1 -1
  133. package/lib/{mutation-BISOc7OM.js → mutation-B7eFBLZY.js} +2 -2
  134. package/lib/{mutation-BISOc7OM.js.map → mutation-B7eFBLZY.js.map} +1 -1
  135. package/lib/ui/Alert.js +32 -20
  136. package/lib/ui/Alert.js.map +1 -1
  137. package/lib/ui/Carousel.js.map +1 -1
  138. package/lib/ui/Secret.js +2 -2
  139. package/lib/ui/Secret.js.map +1 -1
  140. package/lib/ui/SyntaxHighlight.js +2 -2
  141. package/lib/useExposedProps-CzTDfXfq.js.map +1 -1
  142. package/lib/{useMutation-CFMGlAMW.js → useMutation-CErliDZ9.js} +5 -5
  143. package/lib/{useMutation-CFMGlAMW.js.map → useMutation-CErliDZ9.js.map} +1 -1
  144. package/lib/{useSuspenseQuery-CSB_rVek.js → useQuery-ht7aWJ3S.js} +432 -446
  145. package/lib/useQuery-ht7aWJ3S.js.map +1 -0
  146. package/lib/useSuspenseQuery-DQH4Bmc2.js +18 -0
  147. package/lib/useSuspenseQuery-DQH4Bmc2.js.map +1 -0
  148. package/lib/zudoku.__internal.js +520 -496
  149. package/lib/zudoku.__internal.js.map +1 -1
  150. package/lib/zudoku.auth-auth0.js +6 -5
  151. package/lib/zudoku.auth-auth0.js.map +1 -1
  152. package/lib/zudoku.auth-azureb2c.js +14 -13
  153. package/lib/zudoku.auth-azureb2c.js.map +1 -1
  154. package/lib/zudoku.auth-clerk.js +2 -2
  155. package/lib/zudoku.auth-firebase.js +4 -4
  156. package/lib/zudoku.auth-openid.js +7 -6
  157. package/lib/zudoku.auth-openid.js.map +1 -1
  158. package/lib/zudoku.auth-supabase.js +4 -4
  159. package/lib/zudoku.components.js +3 -3
  160. package/lib/zudoku.hooks.js +3 -3
  161. package/lib/zudoku.mermaid.js +3 -3
  162. package/lib/zudoku.plugin-api-catalog.js +28 -27
  163. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  164. package/lib/zudoku.plugin-api-keys.js +98 -96
  165. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  166. package/lib/zudoku.plugin-custom-pages.js +1 -1
  167. package/lib/zudoku.plugin-markdown.js +1 -1
  168. package/lib/zudoku.plugin-openapi.js +2 -2
  169. package/lib/zudoku.plugin-search-pagefind.js +19 -18
  170. package/lib/zudoku.plugin-search-pagefind.js.map +1 -1
  171. package/lib/zudoku.plugins.js.map +1 -1
  172. package/lib/zudoku.react-query.js +26 -25
  173. package/lib/zudoku.react-query.js.map +1 -1
  174. package/lib/zudoku.router.js.map +1 -1
  175. package/package.json +13 -7
  176. package/src/app/defaultTheme.css +4 -0
  177. package/src/app/main.css +2 -0
  178. package/src/app/main.tsx +1 -1
  179. package/src/lib/components/Bootstrap.tsx +1 -4
  180. package/src/lib/components/Slot.test.tsx +1 -1
  181. package/src/lib/components/Zudoku.tsx +18 -14
  182. package/src/lib/components/context/ZudokuContext.ts +2 -6
  183. package/src/lib/components/context/ZudokuProvider.tsx +1 -1
  184. package/src/lib/components/context/ZudokuReactContext.tsx +17 -0
  185. package/src/lib/core/ZudokuContext.ts +7 -1
  186. package/src/lib/core/__internal.tsx +2 -0
  187. package/src/lib/core/plugins.ts +7 -3
  188. package/src/lib/core/transform-config.test.tsx +99 -0
  189. package/src/lib/core/transform-config.ts +57 -19
  190. package/src/lib/errors/ErrorAlert.tsx +1 -6
  191. package/src/lib/hooks/useEvent.test.tsx +1 -1
  192. package/src/lib/plugins/openapi/ParamInfos.tsx +10 -5
  193. package/src/lib/plugins/openapi/ParameterListItem.tsx +1 -0
  194. package/src/lib/plugins/openapi/schema/SchemaPropertyItem.tsx +2 -0
  195. package/src/lib/ui/Alert.tsx +17 -5
  196. package/src/lib/ui/Secret.tsx +2 -2
  197. package/src/lib/util/flattenAllOf.test.ts +32 -0
  198. package/src/lib/util/flattenAllOf.ts +7 -0
  199. package/lib/Mermaid-JEnWyK0s.js +0 -103
  200. package/lib/Mermaid-JEnWyK0s.js.map +0 -1
  201. package/lib/OperationList-cEveQ_l5.js +0 -5822
  202. package/lib/OperationList-cEveQ_l5.js.map +0 -1
  203. package/lib/SchemaView-BR6dtnPg.js.map +0 -1
  204. package/lib/Secret-BDBqq4p3.js.map +0 -1
  205. package/lib/Zudoku-DA1yA-te.js.map +0 -1
  206. package/lib/ZudokuContext-C6wlLMUH.js +0 -389
  207. package/lib/ZudokuContext-C6wlLMUH.js.map +0 -1
  208. package/lib/index.esm-B2cLXwjS.js.map +0 -1
  209. package/lib/useSuspenseQuery-CSB_rVek.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.0.0-z6e949a6d",
3
+ "version": "0.0.0-z8ac421f0",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -155,7 +155,6 @@
155
155
  "dependencies": {
156
156
  "@apidevtools/json-schema-ref-parser": "14.1.1",
157
157
  "@envelop/core": "5.3.2",
158
- "@fastify/deepmerge": "3.1.0",
159
158
  "@graphql-typed-document-node/core": "3.2.0",
160
159
  "@lekoarts/rehype-meta-as-attributes": "3.0.3",
161
160
  "@mdx-js/react": "3.1.1",
@@ -197,7 +196,7 @@
197
196
  "@tailwindcss/vite": "4.1.16",
198
197
  "@tanem/react-nprogress": "5.0.56",
199
198
  "@tanstack/react-query": "5.90.12",
200
- "@types/react": "19.2.7",
199
+ "@types/react": "19.2.10",
201
200
  "@types/react-dom": "19.2.3",
202
201
  "@vitejs/plugin-react": "5.1.0",
203
202
  "@x0k/json-schema-merge": "1.0.2",
@@ -237,7 +236,7 @@
237
236
  "posthog-node": "5.21.2",
238
237
  "react-error-boundary": "6.0.0",
239
238
  "react-hook-form": "7.66.0",
240
- "react-is": "19.2.3",
239
+ "react-is": "19.2.4",
241
240
  "react-markdown": "10.1.0",
242
241
  "react-router": "7.12.0",
243
242
  "rehype-mdx-import-media": "1.2.0",
@@ -290,8 +289,8 @@
290
289
  "esbuild": "0.27.0",
291
290
  "happy-dom": "20.0.10",
292
291
  "mdast-util-mdx": "3.0.0",
293
- "react": "19.2.3",
294
- "react-dom": "19.2.3",
292
+ "react": "19.2.4",
293
+ "react-dom": "19.2.4",
295
294
  "rollup-plugin-visualizer": "6.0.5",
296
295
  "tsx": "4.20.6",
297
296
  "typescript": "5.9.3",
@@ -309,6 +308,13 @@
309
308
  "react": ">=19.2.0",
310
309
  "react-dom": ">=19.2.0"
311
310
  },
311
+ "typesVersions": {
312
+ "*": {
313
+ "ui/*": [
314
+ "./src/lib/ui/*"
315
+ ]
316
+ }
317
+ },
312
318
  "peerDependenciesMeta": {
313
319
  "@azure/msal-browser": {
314
320
  "optional": true
@@ -337,7 +343,7 @@
337
343
  },
338
344
  "scripts": {
339
345
  "build": "tsc --project tsconfig.app.json",
340
- "build:dev": "esbuild './src/**/*.ts' --format=esm --platform=node --target=node22 --outdir=dist --splitting --log-level=warning",
346
+ "build:dev": "esbuild './src/**/*.ts' './src/**/*.tsx' --format=esm --platform=node --target=node22 --outdir=dist --splitting --log-level=warning",
341
347
  "build:vite": "vite build",
342
348
  "generate:types": "tsx scripts/generate-types.js && tsx scripts/generate-flat-config.js",
343
349
  "build:standalone:vite": "vite build --mode standalone --config vite.standalone.config.ts",
@@ -20,6 +20,8 @@
20
20
  --accent: oklch(0.967 0.001 286.375);
21
21
  --accent-foreground: oklch(0.21 0.006 285.885);
22
22
  --destructive: oklch(0.577 0.245 27.325);
23
+ --warning: oklch(0.84 0.16 84);
24
+ --warning-foreground: oklch(0.47 0.14 46.2);
23
25
  --border: oklch(0.92 0.004 286.32);
24
26
  --input: oklch(0.92 0.004 286.32);
25
27
  --ring: oklch(0.705 0.015 286.067);
@@ -54,6 +56,8 @@
54
56
  --accent: oklch(0.274 0.006 286.033);
55
57
  --accent-foreground: oklch(0.985 0 0);
56
58
  --destructive: oklch(0.704 0.191 22.216);
59
+ --warning: oklch(0.41 0.11 46);
60
+ --warning-foreground: oklch(0.99 0.02 95);
57
61
  --border: oklch(1 0 0 / 10%);
58
62
  --input: oklch(1 0 0 / 15%);
59
63
  --ring: oklch(0.552 0.016 285.938);
package/src/app/main.css CHANGED
@@ -8,6 +8,8 @@
8
8
  @custom-variant dark (&:is(.dark *));
9
9
 
10
10
  @theme inline {
11
+ --color-warning: var(--warning);
12
+ --color-warning-foreground: var(--warning-foreground);
11
13
  --color-background: var(--background);
12
14
  --color-foreground: var(--foreground);
13
15
  --color-card: var(--card);
package/src/app/main.tsx CHANGED
@@ -111,7 +111,7 @@ export const getRoutesByConfig = (config: ZudokuConfig): RouteObject[] => {
111
111
  return [
112
112
  {
113
113
  element: (
114
- <Zudoku {...options}>
114
+ <Zudoku {...options} env={import.meta.env}>
115
115
  <BuildCheck
116
116
  buildId={import.meta.env.ZUPLO_BUILD_ID}
117
117
  environmentType={import.meta.env.ZUPLO_ENVIRONMENT_TYPE}
@@ -12,7 +12,6 @@ import {
12
12
  StaticRouterProvider,
13
13
  } from "react-router";
14
14
  import { RouterProvider } from "react-router/dom";
15
- import { StaggeredRenderContext } from "../plugins/openapi/StaggeredRender.js";
16
15
  import { BypassProtectedRoutesContext } from "./context/BypassProtectedRoutesContext.js";
17
16
 
18
17
  const queryClient = new QueryClient({
@@ -36,9 +35,7 @@ const Bootstrap = ({
36
35
  <HydrationBoundary state={hydrate ? (window as any).DATA : undefined}>
37
36
  <BypassProtectedRoutesContext value={false}>
38
37
  <HelmetProvider>
39
- <StaggeredRenderContext.Provider value={{ stagger: !hydrate }}>
40
- <RouterProvider router={router} />
41
- </StaggeredRenderContext.Provider>
38
+ <RouterProvider router={router} />
42
39
  </HelmetProvider>
43
40
  </BypassProtectedRoutesContext>
44
41
  </HydrationBoundary>
@@ -20,7 +20,7 @@ import { Slot } from "./Slot.js";
20
20
 
21
21
  const createWrapper = (slots: Record<string, ReactNode> = {}) => {
22
22
  const queryClient = new QueryClient();
23
- const context = new ZudokuContext({}, queryClient);
23
+ const context = new ZudokuContext({}, queryClient, {});
24
24
 
25
25
  const wrapper = ({ children }: PropsWithChildren) => (
26
26
  <MemoryRouter initialEntries={["/", "/page"]}>
@@ -5,7 +5,7 @@ import { ThemeProvider } from "next-themes";
5
5
  import {
6
6
  memo,
7
7
  type PropsWithChildren,
8
- useContext,
8
+ Suspense,
9
9
  useEffect,
10
10
  useMemo,
11
11
  useState,
@@ -18,7 +18,6 @@ import {
18
18
  type ZudokuContextOptions,
19
19
  } from "../core/ZudokuContext.js";
20
20
  import { TopLevelError } from "../errors/TopLevelError.js";
21
- import { StaggeredRenderContext } from "../plugins/openapi/StaggeredRender.js";
22
21
  import { MdxComponents } from "../util/MdxComponents.js";
23
22
  import "../util/requestIdleCallbackPolyfill.js";
24
23
  import {
@@ -33,7 +32,13 @@ import { ZudokuProvider } from "./context/ZudokuProvider.js";
33
32
  let zudokuContext: ZudokuContext | undefined;
34
33
 
35
34
  const ZudokuInner = memo(
36
- ({ children, ...props }: PropsWithChildren<ZudokuContextOptions>) => {
35
+ ({
36
+ children,
37
+ env,
38
+ ...props
39
+ }: PropsWithChildren<
40
+ ZudokuContextOptions & { env: Record<string, string> }
41
+ >) => {
37
42
  const components = useMemo(
38
43
  () => ({ ...DEFAULT_COMPONENTS, ...props.overrides }),
39
44
  [props.overrides],
@@ -56,12 +61,7 @@ const ZudokuInner = memo(
56
61
  ...props.mdx?.components,
57
62
  };
58
63
  }, [props.mdx?.components, props.plugins]);
59
- const { stagger } = useContext(StaggeredRenderContext);
60
64
  const [didNavigate, setDidNavigate] = useState(false);
61
- const staggeredValue = useMemo(
62
- () => (didNavigate ? { stagger: true } : { stagger }),
63
- [stagger, didNavigate],
64
- );
65
65
  const navigation = useNavigation();
66
66
  const queryClient = useQueryClient();
67
67
 
@@ -72,7 +72,7 @@ const ZudokuInner = memo(
72
72
  setDidNavigate(true);
73
73
  }, [didNavigate, navigation.location]);
74
74
 
75
- zudokuContext ??= new ZudokuContext(props, queryClient);
75
+ zudokuContext ??= new ZudokuContext(props, queryClient, env);
76
76
 
77
77
  const heads = props.plugins?.flatMap((plugin) =>
78
78
  hasHead(plugin) ? (plugin.getHead?.({ location }) ?? []) : [],
@@ -81,8 +81,8 @@ const ZudokuInner = memo(
81
81
  return (
82
82
  <>
83
83
  <Helmet>{heads}</Helmet>
84
- <StaggeredRenderContext.Provider value={staggeredValue}>
85
- <ZudokuProvider context={zudokuContext}>
84
+ <ZudokuProvider context={zudokuContext}>
85
+ <Suspense fallback={<div>Zudoku Loading...</div>}>
86
86
  <RouterEventsEmitter />
87
87
  <SlotProvider slots={props.slots ?? props.UNSAFE_slotlets}>
88
88
  <MDXProvider components={mdxComponents}>
@@ -95,8 +95,8 @@ const ZudokuInner = memo(
95
95
  </ThemeProvider>
96
96
  </MDXProvider>
97
97
  </SlotProvider>
98
- </ZudokuProvider>
99
- </StaggeredRenderContext.Provider>
98
+ </Suspense>
99
+ </ZudokuProvider>
100
100
  </>
101
101
  );
102
102
  },
@@ -104,7 +104,11 @@ const ZudokuInner = memo(
104
104
 
105
105
  ZudokuInner.displayName = "ZudokuInner";
106
106
 
107
- const Zudoku = (props: ZudokuContextOptions) => {
107
+ const Zudoku = (
108
+ props: PropsWithChildren<
109
+ ZudokuContextOptions & { env: Record<string, string> }
110
+ >,
111
+ ) => {
108
112
  return (
109
113
  <ErrorBoundary FallbackComponent={TopLevelError}>
110
114
  <ZudokuInner {...props} />
@@ -1,16 +1,12 @@
1
1
  import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
2
- import { createContext, useContext, useEffect } from "react";
2
+ import { useContext, useEffect } from "react";
3
3
  import { matchPath, useLocation } from "react-router";
4
4
  import type { NavigationItem } from "../../../config/validators/NavigationSchema.js";
5
5
  import { useAuthState } from "../../authentication/state.js";
6
- import type { ZudokuContext } from "../../core/ZudokuContext.js";
7
6
  import { joinUrl } from "../../util/joinUrl.js";
8
7
  import { CACHE_KEYS, useCache } from "../cache.js";
9
8
  import { traverseNavigation } from "../navigation/utils.js";
10
-
11
- export const ZudokuReactContext = createContext<ZudokuContext | undefined>(
12
- undefined,
13
- );
9
+ import { ZudokuReactContext } from "./ZudokuReactContext.js";
14
10
 
15
11
  export const useZudoku = () => {
16
12
  const context = useContext(ZudokuReactContext);
@@ -2,7 +2,7 @@ import { useSuspenseQuery } from "@tanstack/react-query";
2
2
  import type { PropsWithChildren } from "react";
3
3
  import type { ZudokuContext } from "../../core/ZudokuContext.js";
4
4
  import { NO_DEHYDRATE } from "../cache.js";
5
- import { ZudokuReactContext } from "./ZudokuContext.js";
5
+ import { ZudokuReactContext } from "./ZudokuReactContext.js";
6
6
 
7
7
  export const ZudokuProvider = ({
8
8
  children,
@@ -0,0 +1,17 @@
1
+ import { type Context, createContext } from "react";
2
+ import type { ZudokuContext } from "../../core/ZudokuContext.js";
3
+
4
+ /**
5
+ * During SSR, Vite's module runner can load the same module multiple times
6
+ * (once for the main app, once for external plugins), creating duplicate
7
+ * React contexts that don't share state.
8
+ */
9
+ declare global {
10
+ var __ZUDOKU_CONTEXT: Context<ZudokuContext | undefined>;
11
+ }
12
+
13
+ globalThis.__ZUDOKU_CONTEXT ??= createContext<ZudokuContext | undefined>(
14
+ undefined,
15
+ );
16
+
17
+ export const ZudokuReactContext = globalThis.__ZUDOKU_CONTEXT;
@@ -132,10 +132,15 @@ export class ZudokuContext {
132
132
  public readonly getAuthState: () => AuthState;
133
133
  public readonly queryClient: QueryClient;
134
134
  public readonly options: ZudokuContextOptions;
135
+ public readonly env: Record<string, string | undefined>;
135
136
  private readonly navigationPlugins: NavigationPlugin[];
136
137
  private emitter = createNanoEvents<ZudokuEvents>();
137
138
 
138
- constructor(options: ZudokuContextOptions, queryClient: QueryClient) {
139
+ constructor(
140
+ options: ZudokuContextOptions,
141
+ queryClient: QueryClient,
142
+ env: Record<string, string | undefined>,
143
+ ) {
139
144
  const pluginProtectedRoutes = Object.fromEntries(
140
145
  (options.plugins ?? []).flatMap((plugin) => {
141
146
  if (!isNavigationPlugin(plugin)) return [];
@@ -152,6 +157,7 @@ export class ZudokuContext {
152
157
  };
153
158
 
154
159
  this.queryClient = queryClient;
160
+ this.env = env;
155
161
  this.options = { ...options, protectedRoutes };
156
162
  this.plugins = options.plugins ?? [];
157
163
  this.navigation = options.navigation ?? [];
@@ -17,6 +17,7 @@ import { StatusPage as StatusPageImport } from "../components/StatusPage.js";
17
17
  import { RouterError as RouterErrorImport } from "../errors/RouterError.js";
18
18
  import { ServerError as ServerErrorImport } from "../errors/ServerError.js";
19
19
  import { RouteGuard as RouteGuardImport } from "./RouteGuard.js";
20
+ import { runPluginTransformConfig as runPluginTransformConfigImport } from "./transform-config.js";
20
21
 
21
22
  export const Layout = LayoutImport;
22
23
  export const RouterError = RouterErrorImport;
@@ -28,3 +29,4 @@ export const Head = Helmet;
28
29
  export const StatusPage = StatusPageImport;
29
30
  export const BuildCheck = BuildCheckImport;
30
31
  export const Meta = MetaImport;
32
+ export const runPluginTransformConfig = runPluginTransformConfigImport;
@@ -68,11 +68,15 @@ export interface ConfigHookContext {
68
68
  configPath: string;
69
69
  }
70
70
 
71
+ export interface TransformConfigContext {
72
+ config: ZudokuConfig;
73
+ merge: <T extends Partial<ZudokuConfig>>(partial: T) => ZudokuConfig & T;
74
+ }
75
+
71
76
  export interface TransformConfigPlugin {
72
77
  transformConfig?: (
73
- config: ZudokuConfig,
74
- ctx: ConfigHookContext,
75
- ) => Partial<ZudokuConfig> | void | Promise<Partial<ZudokuConfig> | void>;
78
+ context: TransformConfigContext,
79
+ ) => ZudokuConfig | void | Promise<ZudokuConfig | void>;
76
80
  }
77
81
 
78
82
  export interface CommonPlugin {
@@ -0,0 +1,99 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { isPlainObject, mergeConfig } from "./transform-config.js";
3
+
4
+ describe("isPlainObject", () => {
5
+ test("returns true for plain objects", () => {
6
+ expect(isPlainObject({})).toBe(true);
7
+ expect(isPlainObject({ a: 1 })).toBe(true);
8
+ });
9
+
10
+ test("returns false for arrays", () => {
11
+ expect(isPlainObject([])).toBe(false);
12
+ expect(isPlainObject([1, 2, 3])).toBe(false);
13
+ });
14
+
15
+ test("returns false for null and undefined", () => {
16
+ expect(isPlainObject(null)).toBe(false);
17
+ expect(isPlainObject(undefined)).toBe(false);
18
+ });
19
+
20
+ test("returns false for class instances", () => {
21
+ expect(isPlainObject(new Date())).toBe(false);
22
+ expect(isPlainObject(new Map())).toBe(false);
23
+ expect(isPlainObject(/regex/)).toBe(false);
24
+ });
25
+ });
26
+
27
+ describe("mergeConfig", () => {
28
+ test("merges flat objects", () => {
29
+ const target = { a: 1, b: 2 };
30
+ const source = { b: 3, c: 4 };
31
+ expect(mergeConfig(target, source)).toEqual({ a: 1, b: 3, c: 4 });
32
+ });
33
+
34
+ test("merges nested objects", () => {
35
+ const target = { nested: { a: 1, b: 2 } } as Record<string, unknown>;
36
+ const source = { nested: { b: 3, c: 4 } };
37
+ expect(mergeConfig(target, source)).toEqual({
38
+ nested: { a: 1, b: 3, c: 4 },
39
+ });
40
+ });
41
+
42
+ test("replaces arrays instead of merging", () => {
43
+ const target = { arr: [1, 2, 3] };
44
+ const source = { arr: [4, 5] };
45
+ expect(mergeConfig(target, source)).toEqual({ arr: [4, 5] });
46
+ });
47
+
48
+ test("preserves React elements without deep cloning", () => {
49
+ const element = <div className="test">Hello</div>;
50
+ const target = { banner: { message: "old" } };
51
+ const source = { banner: { message: element } };
52
+
53
+ const result = mergeConfig(target, source);
54
+
55
+ // Should be the exact same reference, not a clone
56
+ expect(result.banner.message).toBe(element);
57
+ });
58
+
59
+ test("does not clone React element children", () => {
60
+ const child = <strong>Bold</strong>;
61
+ const element = <div>{child} text</div>;
62
+ const target = { site: { banner: {} } };
63
+ const source = { site: { banner: { message: element } } };
64
+
65
+ const result = mergeConfig(target, source);
66
+
67
+ // The element should be identical (same reference)
68
+ expect(result.site.banner.message).toBe(element);
69
+ // Children should be preserved exactly
70
+ expect(result.site.banner.message.props.children).toBe(
71
+ element.props.children,
72
+ );
73
+ });
74
+
75
+ test("handles null and undefined values", () => {
76
+ const target = { a: 1, b: 2 };
77
+ const source = { a: null, c: undefined };
78
+ expect(mergeConfig(target, source)).toEqual({
79
+ a: null,
80
+ b: 2,
81
+ c: undefined,
82
+ });
83
+ });
84
+
85
+ test("replaces non-plain objects", () => {
86
+ const date = new Date("2024-01-01");
87
+ const target = { date: new Date("2020-01-01") };
88
+ const source = { date };
89
+ const result = mergeConfig(target, source);
90
+ expect(result.date).toBe(date);
91
+ });
92
+
93
+ test("does not mutate target", () => {
94
+ const target = { a: 1, nested: { b: 2 } };
95
+ const source = { a: 2, nested: { c: 3 } };
96
+ mergeConfig(target, source);
97
+ expect(target).toEqual({ a: 1, nested: { b: 2 } });
98
+ });
99
+ });
@@ -1,28 +1,66 @@
1
- import createDeepmerge from "@fastify/deepmerge";
2
- import type { ConfigWithMeta } from "../../config/loader.js";
3
- import { type ConfigHookContext, isTransformConfigPlugin } from "./plugins.js";
4
-
5
- const mergeConfig = createDeepmerge({
6
- mergeArray: (opt) => (_, source) => opt.clone(source),
7
- });
8
-
9
- export const runTransformConfigHooks = async (
10
- config: ConfigWithMeta,
11
- ): Promise<ConfigWithMeta> => {
12
- const ctx = {
13
- mode: config.__meta.mode,
14
- rootDir: config.__meta.rootDir,
15
- configPath: config.__meta.configPath,
16
- } satisfies ConfigHookContext;
1
+ import { isValidElement } from "react";
2
+ import type { ZudokuConfig } from "../../config/validators/validate.js";
3
+ import { isTransformConfigPlugin } from "./plugins.js";
4
+
5
+ export const isPlainObject = (
6
+ value: unknown,
7
+ ): value is Record<string, unknown> =>
8
+ typeof value === "object" &&
9
+ value !== null &&
10
+ !Array.isArray(value) &&
11
+ Object.getPrototypeOf(value) === Object.prototype;
12
+
13
+ export const mergeConfig = <
14
+ T extends Record<string, unknown>,
15
+ S extends Record<string, unknown>,
16
+ >(
17
+ target: T,
18
+ source: S,
19
+ ): T & S => {
20
+ const result = { ...target } as T & S;
21
+
22
+ for (const key of Object.keys(source) as (keyof S)[]) {
23
+ const sourceValue = source[key];
24
+ const targetValue = target[key as keyof T];
25
+
26
+ // Don't merge React elements, arrays, or non-plain objects - just replace
27
+ if (
28
+ isValidElement(sourceValue) ||
29
+ Array.isArray(sourceValue) ||
30
+ !isPlainObject(sourceValue)
31
+ ) {
32
+ (result as Record<string, unknown>)[key as string] = sourceValue;
33
+ } else if (isPlainObject(targetValue)) {
34
+ (result as Record<string, unknown>)[key as string] = mergeConfig(
35
+ targetValue,
36
+ sourceValue,
37
+ );
38
+ } else {
39
+ (result as Record<string, unknown>)[key as string] = sourceValue;
40
+ }
41
+ }
42
+
43
+ return result;
44
+ };
45
+
46
+ export const runPluginTransformConfig = async <T extends ZudokuConfig>(
47
+ config: T,
48
+ ): Promise<T> => {
17
49
  const plugins = config.plugins ?? [];
18
50
 
19
51
  let result = config;
20
52
 
21
53
  for (const plugin of plugins.filter(isTransformConfigPlugin)) {
22
- const partial = await plugin.transformConfig?.(result, ctx);
23
- if (!partial) continue;
54
+ const merge = <T extends Record<string, unknown>>(partial: T) =>
55
+ mergeConfig(result, partial);
56
+
57
+ const transformed = await plugin.transformConfig?.({
58
+ config: result,
59
+ merge,
60
+ });
61
+ if (!transformed) continue;
24
62
 
25
- result = mergeConfig(result, partial) as ConfigWithMeta;
63
+ result = transformed as T;
26
64
  }
27
65
 
28
66
  return result;
@@ -1,4 +1,3 @@
1
- import { SyntaxHighlight } from "zudoku/ui/SyntaxHighlight.js";
2
1
  import { DeveloperHint } from "../components/DeveloperHint.js";
3
2
  import { Heading } from "../components/Heading.js";
4
3
  import { Typography } from "../components/Typography.js";
@@ -22,11 +21,7 @@ export function ErrorAlert({ error }: { error: unknown }) {
22
21
  Error: {message}
23
22
  {hint && <DeveloperHint className="mb-4">{hint}</DeveloperHint>}
24
23
  {stringError && (
25
- <SyntaxHighlight
26
- className="max-h-[400px] [&>pre]:p-4"
27
- language="js"
28
- code={stringError}
29
- />
24
+ <pre className="max-h-[400px] [&>pre]:p-4">{stringError}</pre>
30
25
  )}
31
26
  </Typography>
32
27
  );
@@ -13,7 +13,7 @@ import { useEvent } from "./useEvent.js";
13
13
 
14
14
  const createTestContext = () => {
15
15
  const queryClient = new QueryClient();
16
- const context = new ZudokuContext({}, queryClient);
16
+ const context = new ZudokuContext({}, queryClient, {});
17
17
  const wrapper = ({ children }: PropsWithChildren) => (
18
18
  <QueryClientProvider client={queryClient}>
19
19
  <ZudokuProvider context={context}>{children}</ZudokuProvider>
@@ -28,11 +28,16 @@ const Pattern = ({ pattern }: { pattern: string }) => {
28
28
  const getSchemaInfos = (schema?: SchemaObject) => {
29
29
  if (!schema) return [];
30
30
 
31
+ const items =
32
+ schema.type === "array" && typeof schema.items === "object"
33
+ ? schema.items
34
+ : undefined;
35
+
31
36
  return [
32
- schema.type === "array" && schema.items.type
33
- ? Array.isArray(schema.items.type)
34
- ? `(${schema.items.type.join(" | ")})[]`
35
- : `${schema.items.type}[]`
37
+ items?.type
38
+ ? Array.isArray(items.type)
39
+ ? `(${items.type.join(" | ")})[]`
40
+ : `${items.type}[]`
36
41
  : Array.isArray(schema.type)
37
42
  ? schema.type.join(" | ")
38
43
  : schema.type,
@@ -40,7 +45,7 @@ const getSchemaInfos = (schema?: SchemaObject) => {
40
45
  schema.enum && "enum",
41
46
  schema.const && "const",
42
47
  schema.format,
43
- schema.type === "array" && schema.items?.contentMediaType,
48
+ items?.contentMediaType,
44
49
  schema.minimum !== undefined && `min: ${schema.minimum}`,
45
50
  schema.maximum !== undefined && `max: ${schema.maximum}`,
46
51
  schema.minLength !== undefined && `minLength: ${schema.minLength}`,
@@ -90,6 +90,7 @@ export const ParameterListItem = ({
90
90
  </code>
91
91
  )}
92
92
  </ItemTitle>
93
+ {"\u200B"}
93
94
  <ParamInfos
94
95
  className="inline"
95
96
  schema={paramSchema}
@@ -53,6 +53,7 @@ export const SchemaPropertyItem = ({
53
53
  <ItemTitle className="inline me-2">
54
54
  <code>{name}</code>
55
55
  </ItemTitle>
56
+ {"\u200B"}
56
57
  <ParamInfos
57
58
  className="inline"
58
59
  schema={schema}
@@ -108,6 +109,7 @@ export const SchemaPropertyItem = ({
108
109
  <code>{name}</code>
109
110
  )}
110
111
  </ItemTitle>
112
+ {"\u200B"}
111
113
  <ParamInfos
112
114
  className="inline"
113
115
  schema={schema}
@@ -3,13 +3,15 @@ import type * as React from "react";
3
3
  import { cn } from "../util/cn.js";
4
4
 
5
5
  const alertVariants = cva(
6
- "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
6
+ "grid gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert",
7
7
  {
8
8
  variants: {
9
9
  variant: {
10
10
  default: "bg-card text-card-foreground",
11
11
  destructive:
12
- "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
12
+ "text-destructive bg-card bg-destructive/5 border-destructive/20 *:data-[slot=alert-description]:text-destructive-foreground *:[svg]:text-current",
13
+ warning:
14
+ "text-warning-foreground bg-card bg-warning/10 border-warning/50 *:data-[slot=alert-description]:text-warning-foreground *:[svg]:text-current",
13
15
  },
14
16
  },
15
17
  defaultVariants: {
@@ -38,7 +40,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
40
  <div
39
41
  data-slot="alert-title"
40
42
  className={cn(
41
- "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
43
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
42
44
  className,
43
45
  )}
44
46
  {...props}
@@ -54,7 +56,7 @@ function AlertDescription({
54
56
  <div
55
57
  data-slot="alert-description"
56
58
  className={cn(
57
- "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
59
+ "text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
58
60
  className,
59
61
  )}
60
62
  {...props}
@@ -62,4 +64,14 @@ function AlertDescription({
62
64
  );
63
65
  }
64
66
 
65
- export { Alert, AlertDescription, AlertTitle };
67
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
68
+ return (
69
+ <div
70
+ data-slot="alert-action"
71
+ className={cn("absolute top-2 right-2", className)}
72
+ {...props}
73
+ />
74
+ );
75
+ }
76
+
77
+ export { Alert, AlertTitle, AlertDescription, AlertAction };