handoff-app 1.2.2-6 → 2.0.0-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 (349) hide show
  1. package/Changelog.md +37 -15
  2. package/README.md +372 -118
  3. package/dist/app-builder/build.d.ts +41 -2
  4. package/dist/app-builder/build.js +455 -18
  5. package/dist/app-builder/client-config.d.ts +15 -2
  6. package/dist/app-builder/client-config.js +43 -4
  7. package/dist/app-builder/config-diff/snapshot.d.ts +1 -6
  8. package/dist/app-builder/config-diff/snapshot.js +2 -9
  9. package/dist/app-builder/index.d.ts +1 -1
  10. package/dist/app-builder/index.js +3 -1
  11. package/dist/app-builder/nav-shell.d.ts +23 -0
  12. package/dist/app-builder/nav-shell.js +80 -0
  13. package/dist/app-builder/static-export.d.ts +17 -0
  14. package/dist/app-builder/static-export.js +181 -0
  15. package/dist/app-builder/vercel-output.d.ts +39 -0
  16. package/dist/app-builder/vercel-output.js +259 -0
  17. package/dist/app-builder/watchers/utils.d.ts +2 -0
  18. package/dist/app-builder/watchers.d.ts +14 -4
  19. package/dist/app-builder/watchers.js +123 -28
  20. package/dist/app-builder/websocket.js +1 -1
  21. package/dist/artifacts/index.d.ts +2 -0
  22. package/dist/artifacts/index.js +13 -0
  23. package/dist/artifacts/types.d.ts +136 -0
  24. package/dist/artifacts/types.js +24 -0
  25. package/dist/artifacts/url.d.ts +49 -0
  26. package/dist/artifacts/url.js +86 -0
  27. package/dist/cache/build-cache.js +48 -4
  28. package/dist/commands/build/app.js +1 -2
  29. package/dist/commands/build/index.d.ts +17 -0
  30. package/dist/commands/build/index.js +66 -0
  31. package/dist/commands/checkout/index.d.ts +24 -0
  32. package/dist/commands/checkout/index.js +71 -0
  33. package/dist/commands/db/migrate.d.ts +11 -0
  34. package/dist/commands/db/migrate.js +33 -0
  35. package/dist/commands/index.js +8 -0
  36. package/dist/commands/publish/index.d.ts +21 -0
  37. package/dist/commands/publish/index.js +68 -0
  38. package/dist/config/defaults.js +15 -1
  39. package/dist/config/loader.js +6 -1
  40. package/dist/config/normalizers/declaration.js +4 -1
  41. package/dist/config/normalizers/page.d.ts +18 -0
  42. package/dist/config/normalizers/page.js +34 -0
  43. package/dist/config/normalizers/pattern.js +7 -1
  44. package/dist/config/runtime.d.ts +4 -0
  45. package/dist/config/runtime.js +53 -5
  46. package/dist/index.d.ts +65 -1
  47. package/dist/index.js +123 -3
  48. package/dist/nav/index.d.ts +83 -0
  49. package/dist/nav/index.js +9 -0
  50. package/dist/nav/registry.d.ts +15 -0
  51. package/dist/nav/registry.js +42 -0
  52. package/dist/nav/resolver.d.ts +8 -0
  53. package/dist/nav/resolver.js +192 -0
  54. package/dist/nav/workspace.d.ts +18 -0
  55. package/dist/nav/workspace.js +103 -0
  56. package/dist/pipeline/styles.js +7 -35
  57. package/dist/pipeline/token-transformers.d.ts +45 -0
  58. package/dist/pipeline/token-transformers.js +71 -0
  59. package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +17 -0
  60. package/dist/registry/asset-storage/adapters/vercel-blob.js +87 -0
  61. package/dist/registry/asset-storage/define.d.ts +10 -0
  62. package/dist/registry/asset-storage/define.js +13 -0
  63. package/dist/registry/asset-storage/resolve.d.ts +45 -0
  64. package/dist/registry/asset-storage/resolve.js +36 -0
  65. package/dist/registry/asset-storage/types.d.ts +51 -0
  66. package/dist/registry/asset-storage/types.js +9 -0
  67. package/dist/registry/assets/layout.d.ts +22 -0
  68. package/dist/registry/assets/layout.js +30 -0
  69. package/dist/registry/assets/sets.d.ts +18 -0
  70. package/dist/registry/assets/sets.js +44 -0
  71. package/dist/registry/assets/transfer.d.ts +45 -0
  72. package/dist/registry/assets/transfer.js +11 -0
  73. package/dist/registry/checkout/assets.d.ts +16 -0
  74. package/dist/registry/checkout/assets.js +150 -0
  75. package/dist/registry/checkout/index.d.ts +33 -0
  76. package/dist/registry/checkout/index.js +610 -0
  77. package/dist/registry/checkout/tokens.d.ts +15 -0
  78. package/dist/registry/checkout/tokens.js +171 -0
  79. package/dist/registry/client.d.ts +61 -0
  80. package/dist/registry/client.js +211 -0
  81. package/dist/registry/connection.d.ts +30 -0
  82. package/dist/registry/connection.js +36 -0
  83. package/dist/registry/db/adapter.d.ts +43 -0
  84. package/dist/registry/db/adapter.js +59 -0
  85. package/dist/registry/db/client.d.ts +35 -0
  86. package/dist/registry/db/client.js +94 -0
  87. package/dist/registry/db/index.d.ts +5 -0
  88. package/dist/registry/db/index.js +24 -0
  89. package/dist/registry/db/migrate.d.ts +23 -0
  90. package/dist/registry/db/migrate.js +64 -0
  91. package/dist/registry/db/schema.d.ts +4869 -0
  92. package/dist/registry/db/schema.js +347 -0
  93. package/dist/registry/path.d.ts +4 -0
  94. package/dist/registry/path.js +31 -0
  95. package/dist/registry/publish/artifacts.d.ts +16 -0
  96. package/dist/registry/publish/artifacts.js +132 -0
  97. package/dist/registry/publish/assets.d.ts +16 -0
  98. package/dist/registry/publish/assets.js +129 -0
  99. package/dist/registry/publish/errors.d.ts +5 -0
  100. package/dist/registry/publish/errors.js +26 -0
  101. package/dist/registry/publish/index.d.ts +27 -0
  102. package/dist/registry/publish/index.js +126 -0
  103. package/dist/registry/publish/package.d.ts +27 -0
  104. package/dist/registry/publish/package.js +257 -0
  105. package/dist/registry/publish/publish-build.d.ts +12 -0
  106. package/dist/registry/publish/publish-build.js +43 -0
  107. package/dist/registry/publish/tokens.d.ts +18 -0
  108. package/dist/registry/publish/tokens.js +112 -0
  109. package/dist/registry/tokens/sets.d.ts +59 -0
  110. package/dist/registry/tokens/sets.js +109 -0
  111. package/dist/registry/tokens/transfer.d.ts +48 -0
  112. package/dist/registry/tokens/transfer.js +10 -0
  113. package/dist/registry/transfer.d.ts +75 -0
  114. package/dist/registry/transfer.js +11 -0
  115. package/dist/store/filesystem-assets.d.ts +17 -0
  116. package/dist/store/filesystem-assets.js +147 -0
  117. package/dist/store/filesystem.d.ts +76 -0
  118. package/dist/store/filesystem.js +208 -0
  119. package/dist/store/index.d.ts +20 -0
  120. package/dist/store/index.js +38 -0
  121. package/dist/store/registry.d.ts +77 -0
  122. package/dist/store/registry.js +350 -0
  123. package/dist/store/source-files.d.ts +24 -0
  124. package/dist/store/source-files.js +147 -0
  125. package/dist/store/types.d.ts +185 -0
  126. package/dist/store/types.js +12 -0
  127. package/dist/transformers/plugins/csf-render.js +17 -8
  128. package/dist/transformers/plugins/handlebars-previews.js +10 -3
  129. package/dist/transformers/plugins/ssr-render.js +116 -49
  130. package/dist/transformers/preview/component/api.js +3 -1
  131. package/dist/transformers/preview/component/artifacts.js +8 -1
  132. package/dist/transformers/preview/component/builder.js +13 -5
  133. package/dist/transformers/preview/component/css.js +9 -1
  134. package/dist/transformers/preview/component/javascript.d.ts +9 -3
  135. package/dist/transformers/preview/component/javascript.js +45 -17
  136. package/dist/transformers/preview/component/shared-artifacts.d.ts +83 -0
  137. package/dist/transformers/preview/component/shared-artifacts.js +92 -0
  138. package/dist/transformers/preview/component.js +5 -2
  139. package/dist/transformers/preview/pattern/builder.js +28 -9
  140. package/dist/transformers/preview/pattern/html.d.ts +13 -10
  141. package/dist/transformers/preview/pattern/html.js +62 -27
  142. package/dist/transformers/preview/types.d.ts +38 -0
  143. package/dist/transformers/utils/handlebars.d.ts +3 -0
  144. package/dist/transformers/utils/handlebars.js +19 -5
  145. package/dist/types/config.d.ts +118 -25
  146. package/dist/utils/menu-shell.d.ts +60 -0
  147. package/dist/utils/menu-shell.js +221 -0
  148. package/dist/utils/stable-stringify.d.ts +2 -0
  149. package/dist/utils/stable-stringify.js +8 -0
  150. package/docs/cli.md +4 -2
  151. package/drizzle/0000_init_registry_storage.sql +95 -0
  152. package/drizzle/0001_add_review_metadata.sql +2 -0
  153. package/drizzle/0002_add_pages.sql +28 -0
  154. package/drizzle/0003_add_tokens.sql +29 -0
  155. package/drizzle/0004_add_assets.sql +37 -0
  156. package/drizzle/meta/0000_snapshot.json +642 -0
  157. package/drizzle/meta/0001_snapshot.json +654 -0
  158. package/drizzle/meta/0002_snapshot.json +835 -0
  159. package/drizzle/meta/0003_snapshot.json +1023 -0
  160. package/drizzle/meta/0004_snapshot.json +1252 -0
  161. package/drizzle/meta/_journal.json +41 -0
  162. package/drizzle.config.ts +16 -0
  163. package/package.json +10 -1
  164. package/src/app/components/Component/ComponentLists.tsx +102 -102
  165. package/src/app/components/Component/Preview.tsx +3 -2
  166. package/src/app/components/Component/RegistryActions.tsx +150 -0
  167. package/src/app/components/Foundations/DisplayLogo.tsx +5 -3
  168. package/src/app/components/Layout/Header.tsx +21 -3
  169. package/src/app/components/Layout/Main.tsx +11 -3
  170. package/src/app/components/Navigation/MainNav.tsx +4 -2
  171. package/src/app/components/Navigation/MobileNav.tsx +4 -2
  172. package/src/app/components/Navigation/SideNav.tsx +46 -11
  173. package/src/app/components/NotFound.tsx +24 -0
  174. package/src/app/components/context/NavProvider.tsx +71 -0
  175. package/src/app/components/old/Header.tsx +3 -3
  176. package/src/app/components/util/index.ts +203 -380
  177. package/src/app/components/util/useCollectionAssets.ts +125 -0
  178. package/src/app/components/util/useFoundationTokens.ts +67 -0
  179. package/src/app/generated/default-pages.json +1 -0
  180. package/src/app/generated/nav-shell.json +1 -0
  181. package/src/app/lib/api/query.ts +6 -0
  182. package/src/app/lib/api/redact.ts +46 -0
  183. package/src/app/lib/asset-storage.ts +81 -0
  184. package/src/app/lib/docs-api/artifacts.ts +116 -0
  185. package/src/app/lib/docs-api/backend.ts +151 -0
  186. package/src/app/lib/docs-api/errors.ts +52 -0
  187. package/src/app/lib/docs-api/handler.ts +34 -0
  188. package/src/app/lib/docs-api/index.ts +7 -0
  189. package/src/app/lib/docs-api/records.ts +189 -0
  190. package/src/app/lib/docs-api/registry.ts +130 -0
  191. package/src/app/lib/docs-api/runtime-config.ts +173 -0
  192. package/src/app/lib/docs-api/serve.ts +37 -0
  193. package/src/app/lib/docs-api/token-detail.ts +19 -0
  194. package/src/app/lib/registry-api/allowlist.ts +163 -0
  195. package/src/app/lib/registry-api/asset-transfer.ts +363 -0
  196. package/src/app/lib/registry-api/auth.ts +68 -0
  197. package/src/app/lib/registry-api/entity-routes.ts +197 -0
  198. package/src/app/lib/registry-api/errors.ts +51 -0
  199. package/src/app/lib/registry-api/files.ts +135 -0
  200. package/src/app/lib/registry-api/handler.ts +96 -0
  201. package/src/app/lib/registry-api/index.ts +4 -0
  202. package/src/app/lib/registry-api/meta.ts +87 -0
  203. package/src/app/lib/registry-api/revalidate.ts +36 -0
  204. package/src/app/lib/registry-api/store.ts +348 -0
  205. package/src/app/lib/registry-api/token-transfer.ts +231 -0
  206. package/src/app/lib/registry-api/transfer.ts +513 -0
  207. package/src/app/lib/registry-api/validation.ts +74 -0
  208. package/src/app/lib/registry-connection.ts +58 -0
  209. package/src/app/lib/utils.ts +14 -0
  210. package/src/app/next.config.mjs +72 -13
  211. package/src/app/pages/[...slug]/index.tsx +74 -20
  212. package/src/app/pages/_app.tsx +5 -2
  213. package/src/app/pages/api/docs/artifacts/[...artifactPath].ts +16 -0
  214. package/src/app/pages/api/docs/assets/[collection]/[...path].ts +50 -0
  215. package/src/app/pages/api/docs/assets/[collection]/index.ts +22 -0
  216. package/src/app/pages/api/docs/components/[id]/assets/[asset].ts +33 -0
  217. package/src/app/pages/api/docs/components/[id]/inspect/[previewId].ts +20 -0
  218. package/src/app/pages/api/docs/components/[id]/previews/[previewId].ts +21 -0
  219. package/src/app/pages/api/docs/components/[id].ts +24 -0
  220. package/src/app/pages/api/docs/components.json.ts +12 -0
  221. package/src/app/pages/api/docs/nav.json.ts +48 -0
  222. package/src/app/pages/api/docs/patterns/[id]/preview.ts +19 -0
  223. package/src/app/pages/api/docs/patterns/[id].ts +24 -0
  224. package/src/app/pages/api/docs/patterns.json.ts +12 -0
  225. package/src/app/pages/api/docs/tokens/components/[id].ts +30 -0
  226. package/src/app/pages/api/docs/tokens/foundations/[type].ts +32 -0
  227. package/src/app/pages/api/registry/components/[id]/files/[...filePath].ts +19 -0
  228. package/src/app/pages/api/registry/components/[id]/files/index.ts +19 -0
  229. package/src/app/pages/api/registry/components/[id].ts +20 -0
  230. package/src/app/pages/api/registry/components/index.ts +19 -0
  231. package/src/app/pages/api/registry/health.ts +20 -0
  232. package/src/app/pages/api/registry/patterns/[id]/files/[...filePath].ts +19 -0
  233. package/src/app/pages/api/registry/patterns/[id]/files/index.ts +19 -0
  234. package/src/app/pages/api/registry/patterns/[id].ts +20 -0
  235. package/src/app/pages/api/registry/patterns/index.ts +19 -0
  236. package/src/app/pages/api/registry/transfer/[kind]/[id].ts +39 -0
  237. package/src/app/pages/api/registry/transfer/assets/[collection].ts +24 -0
  238. package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +23 -0
  239. package/src/app/pages/api/registry/transfer/assets/blobs/have.ts +11 -0
  240. package/src/app/pages/api/registry/transfer/assets/index.ts +10 -0
  241. package/src/app/pages/api/registry/transfer/tokens/[...setId].ts +26 -0
  242. package/src/app/pages/api/registry/transfer/tokens/index.ts +11 -0
  243. package/src/app/pages/assets/fonts.tsx +41 -17
  244. package/src/app/pages/assets/index.tsx +2 -2
  245. package/src/app/pages/assets/logos.tsx +2 -2
  246. package/src/app/pages/foundations/colors.tsx +18 -8
  247. package/src/app/pages/foundations/effects.tsx +18 -8
  248. package/src/app/pages/foundations/icons/[name]/index.tsx +26 -14
  249. package/src/app/pages/foundations/icons/index.tsx +13 -9
  250. package/src/app/pages/foundations/index.tsx +1 -1
  251. package/src/app/pages/foundations/logo.tsx +7 -4
  252. package/src/app/pages/foundations/typography.tsx +19 -9
  253. package/src/app/pages/index.tsx +1 -1
  254. package/src/app/pages/system/component/[component]/index.tsx +87 -17
  255. package/src/app/pages/system/index.tsx +13 -4
  256. package/src/app/pages/system/pattern/[pattern]/index.tsx +70 -14
  257. package/src/app/pages/system/tokens/components/[component]/index.tsx +42 -14
  258. package/src/app/pages/system/tokens/foundations/colors.tsx +7 -5
  259. package/src/app/pages/system/tokens/foundations/effects.tsx +7 -5
  260. package/src/app/pages/system/tokens/foundations/typography.tsx +8 -4
  261. package/src/app/tsconfig.json +2 -3
  262. package/src/app-builder/build.ts +555 -11
  263. package/src/app-builder/client-config.ts +61 -4
  264. package/src/app-builder/config-diff/snapshot.ts +1 -11
  265. package/src/app-builder/index.ts +1 -1
  266. package/src/app-builder/nav-shell.ts +74 -0
  267. package/src/app-builder/static-export.ts +215 -0
  268. package/src/app-builder/vercel-output.ts +297 -0
  269. package/src/app-builder/watchers/utils.ts +2 -0
  270. package/src/app-builder/watchers.ts +149 -25
  271. package/src/app-builder/websocket.ts +2 -2
  272. package/src/artifacts/README.md +14 -0
  273. package/src/artifacts/index.ts +17 -0
  274. package/src/artifacts/types.ts +146 -0
  275. package/src/artifacts/url.ts +90 -0
  276. package/src/cache/build-cache.ts +47 -2
  277. package/src/commands/build/app.ts +1 -2
  278. package/src/commands/build/index.ts +61 -0
  279. package/src/commands/checkout/index.ts +66 -0
  280. package/src/commands/db/migrate.ts +25 -0
  281. package/src/commands/index.ts +8 -0
  282. package/src/commands/publish/index.ts +63 -0
  283. package/src/config/defaults.ts +15 -0
  284. package/src/config/loader.ts +10 -1
  285. package/src/config/normalizers/declaration.ts +3 -0
  286. package/src/config/normalizers/page.ts +45 -0
  287. package/src/config/normalizers/pattern.ts +4 -1
  288. package/src/config/runtime.ts +48 -2
  289. package/src/index.ts +126 -3
  290. package/src/nav/index.ts +94 -0
  291. package/src/nav/registry.ts +54 -0
  292. package/src/nav/resolver.ts +217 -0
  293. package/src/nav/workspace.ts +87 -0
  294. package/src/pipeline/styles.ts +4 -35
  295. package/src/pipeline/token-transformers.ts +97 -0
  296. package/src/registry/asset-storage/adapters/vercel-blob.ts +51 -0
  297. package/src/registry/asset-storage/define.ts +15 -0
  298. package/src/registry/asset-storage/resolve.ts +72 -0
  299. package/src/registry/asset-storage/types.ts +46 -0
  300. package/src/registry/assets/layout.ts +38 -0
  301. package/src/registry/assets/sets.ts +48 -0
  302. package/src/registry/assets/transfer.ts +50 -0
  303. package/src/registry/checkout/assets.ts +126 -0
  304. package/src/registry/checkout/index.ts +596 -0
  305. package/src/registry/checkout/tokens.ts +152 -0
  306. package/src/registry/client.ts +234 -0
  307. package/src/registry/connection.ts +50 -0
  308. package/src/registry/db/adapter.ts +74 -0
  309. package/src/registry/db/client.ts +88 -0
  310. package/src/registry/db/index.ts +35 -0
  311. package/src/registry/db/migrate.ts +56 -0
  312. package/src/registry/db/schema.ts +444 -0
  313. package/src/registry/path.ts +26 -0
  314. package/src/registry/publish/artifacts.ts +147 -0
  315. package/src/registry/publish/assets.ts +129 -0
  316. package/src/registry/publish/errors.ts +34 -0
  317. package/src/registry/publish/index.ts +128 -0
  318. package/src/registry/publish/package.ts +272 -0
  319. package/src/registry/publish/publish-build.ts +51 -0
  320. package/src/registry/publish/tokens.ts +119 -0
  321. package/src/registry/tokens/sets.ts +132 -0
  322. package/src/registry/tokens/transfer.ts +53 -0
  323. package/src/registry/transfer.ts +82 -0
  324. package/src/store/filesystem-assets.ts +152 -0
  325. package/src/store/filesystem.ts +237 -0
  326. package/src/store/index.ts +62 -0
  327. package/src/store/registry.ts +334 -0
  328. package/src/store/source-files.ts +155 -0
  329. package/src/store/types.ts +210 -0
  330. package/src/transformers/plugins/csf-render.ts +31 -7
  331. package/src/transformers/plugins/handlebars-previews.ts +22 -3
  332. package/src/transformers/plugins/ssr-render.ts +145 -57
  333. package/src/transformers/preview/component/api.ts +2 -1
  334. package/src/transformers/preview/component/artifacts.ts +10 -1
  335. package/src/transformers/preview/component/builder.ts +9 -2
  336. package/src/transformers/preview/component/css.ts +10 -1
  337. package/src/transformers/preview/component/javascript.ts +55 -20
  338. package/src/transformers/preview/component/shared-artifacts.ts +146 -0
  339. package/src/transformers/preview/component.ts +5 -2
  340. package/src/transformers/preview/pattern/builder.ts +33 -7
  341. package/src/transformers/preview/pattern/html.ts +71 -27
  342. package/src/transformers/preview/types.ts +42 -0
  343. package/src/transformers/utils/handlebars.ts +31 -5
  344. package/src/types/config.ts +123 -25
  345. package/src/utils/menu-shell.ts +241 -0
  346. package/src/utils/stable-stringify.ts +7 -0
  347. package/templates/default/package.json.tpl +1 -1
  348. package/handoff.config.js +0 -90
  349. package/src/app/components/ComponentSearch.tsx +0 -93
@@ -0,0 +1,41 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1781979217509,
9
+ "tag": "0000_init_registry_storage",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "7",
15
+ "when": 1782045350977,
16
+ "tag": "0001_add_review_metadata",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "7",
22
+ "when": 1782713759115,
23
+ "tag": "0002_add_pages",
24
+ "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "7",
29
+ "when": 1783354008332,
30
+ "tag": "0003_add_tokens",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "7",
36
+ "when": 1783442253186,
37
+ "tag": "0004_add_assets",
38
+ "breakpoints": true
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'drizzle-kit';
2
+
3
+ /**
4
+ * Drizzle Kit config for generating the registry's package-owned migrations.
5
+ *
6
+ * Migrations are generated into `./drizzle` and shipped with the `handoff-app` package; the
7
+ * bundled runner (`handoff-app db:migrate`) applies them at install/deploy time. Both built-in
8
+ * adapters (`pg`, `neon`) target the Postgres dialect, so a single migration set serves both.
9
+ *
10
+ * Generate after schema changes with: `npx drizzle-kit generate`
11
+ */
12
+ export default defineConfig({
13
+ dialect: 'postgresql',
14
+ schema: './src/registry/db/schema.ts',
15
+ out: './drizzle',
16
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-app",
3
- "version": "1.2.2-6",
3
+ "version": "2.0.0-0",
4
4
  "description": "Automated documentation toolchain for building client side documentation from figma",
5
5
  "author ": {
6
6
  "name": "Convertiv",
@@ -53,6 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@clack/prompts": "^0.7.0",
56
+ "@neondatabase/serverless": "^1.1.0",
56
57
  "@radix-ui/react-accordion": "^1.2.8",
57
58
  "@radix-ui/react-collapsible": "^1.1.2",
58
59
  "@radix-ui/react-dialog": "^1.1.4",
@@ -78,6 +79,7 @@
78
79
  "@types/react-syntax-highlighter": "^15.5.7",
79
80
  "@types/refractor": "^3.0.2",
80
81
  "@types/ws": "^8.18.1",
82
+ "@vercel/blob": "^1.1.1",
81
83
  "@vitejs/plugin-react": "^4.5.2",
82
84
  "archiver": "^5.3.1",
83
85
  "chalk": "^4.1.2",
@@ -86,6 +88,7 @@
86
88
  "clsx": "^2.1.1",
87
89
  "cross-spawn": "^7.0.3",
88
90
  "dotenv": "^16.0.3",
91
+ "drizzle-orm": "^0.45.2",
89
92
  "esbuild": "^0.25.0",
90
93
  "eslint": "^8.23.1",
91
94
  "eslint-config-next": "^15.3.4",
@@ -101,6 +104,7 @@
101
104
  "next": "^15.3.4",
102
105
  "next-themes": "^0.4.4",
103
106
  "node-html-parser": "^6.1.4",
107
+ "pg": "^8.22.0",
104
108
  "postcss": "^8.4.16",
105
109
  "prettier": "^3.4.2",
106
110
  "react": "^19.1.0",
@@ -121,13 +125,18 @@
121
125
  "ws": "^8.18.3",
122
126
  "yargs": "^17.7.2"
123
127
  },
128
+ "peerDependencies": {
129
+ "typescript": ">=5.6 <7"
130
+ },
124
131
  "devDependencies": {
125
132
  "@types/archiver": "^5.3.1",
126
133
  "@types/cross-spawn": "^6.0.2",
127
134
  "@types/fs-extra": "^11.0.4",
128
135
  "@types/handlebars": "^4.1.0",
129
136
  "@types/node": "^20.11.18",
137
+ "@types/pg": "^8.20.0",
130
138
  "@types/yargs": "^17.0.33",
139
+ "drizzle-kit": "^0.31.10",
131
140
  "np": "^10.2.0",
132
141
  "prettier-plugin-tailwindcss": "^0.7.0",
133
142
  "sass-embedded": "^1.89.0",
@@ -76,114 +76,114 @@ export const ComponentList = ({
76
76
  return (
77
77
  <TooltipProvider delayDuration={0}>
78
78
  <div className="mx-auto w-full mb-4">
79
- <div className="flex justify-between bg-accent rounded-xl mb-8">
80
- <div className="mr-auto flex items-center gap-3 bg-accent p-2.5 rounded-md">
81
- <div className="relative">
82
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-gray-400" />
83
- <Input
84
- type="text"
85
- placeholder="Search components..."
86
- className="rounded-md pl-9 pr-3 py-1 text-sm border-none shadow-none"
87
- onChange={e => setSearch(e.currentTarget.value)}
88
- aria-label="Search components"
89
- style={{ minWidth: 200 }}
90
-
91
- />
92
- </div>
79
+ <div className="flex justify-between bg-accent rounded-xl mb-8">
80
+ <div className="mr-auto flex items-center gap-3 bg-accent p-2.5 rounded-md">
81
+ <div className="relative">
82
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-gray-400" />
83
+ <Input
84
+ type="text"
85
+ placeholder="Search components..."
86
+ className="rounded-md pl-9 pr-3 py-1 text-sm border-none shadow-none"
87
+ onChange={e => setSearch(e.currentTarget.value)}
88
+ aria-label="Search components"
89
+ style={{ minWidth: 200 }}
90
+
91
+ />
92
+ </div>
93
93
 
94
- </div>
95
- <div className="flex justify-end p-2.5 gap-2.5">
96
- <div className="flex items-center gap-0.5 border border-input bg-background shadow-xs hover:text-accent-foreground p-0.5 rounded-md">
97
- <Select
98
- value={category}
99
- onValueChange={val => {
100
- setCategory(val);
101
- }}
102
- aria-label="Filter by category"
103
- >
104
- <SelectTrigger className="rounded-md [&_span]:text-xs [&_svg]:ml-2.5 [&_svg]:size-3 py-1 px-2 pl-3 text-sm border-none shadow-none bg-transparent focus:border-blue-400 focus:outline-none">
105
- <SelectValue placeholder="All Categories" />
106
- </SelectTrigger>
107
- <SelectContent>
108
- <SelectItem value="all">All Categories</SelectItem>
109
- {categories.map((category) => (
110
- <SelectItem key={category} value={category}>
111
- {category}
112
- </SelectItem>
113
- ))}
114
- </SelectContent>
115
- </Select>
116
-
117
- <hr className="h-7 w-px bg-input" />
118
-
119
- <Tooltip>
120
- <TooltipTrigger asChild>
121
- <Button variant="ghost" className={cn("w-9", groupBy && 'bg-accent text-accent-foreground')} onClick={() => setGroupBy(!groupBy)}>
122
- <Rows className="size-3.5" strokeWidth={1.5} />
123
- <span className="sr-only">Group by category</span>
124
- </Button>
125
- </TooltipTrigger>
126
- <TooltipContent>
127
- Group by category
128
- </TooltipContent>
129
- </Tooltip>
130
94
  </div>
131
- <ToggleGroup className='flex gap-0.5 border border-input bg-background shadow-xs hover:text-accent-foreground p-0.5 rounded-md' type="single" value={layout} onValueChange={(value) => storeLayout(value)}>
132
- <Tooltip>
133
- <TooltipTrigger asChild>
134
- <ToggleGroupItem className={cn("size-9 py-2 px-2 flex items-center justify-center hover:bg-accent rounded-md", layout === 'grid' && 'bg-accent text-accent-foreground')} value="grid" aria-label="Grid layout" >
135
- <LayoutGrid className="size-3.5" strokeWidth={1.5} />
136
- </ToggleGroupItem>
137
- </TooltipTrigger>
138
- <TooltipContent>
139
- Grid layout
140
- </TooltipContent>
141
- </Tooltip>
142
- <Tooltip>
143
- <TooltipTrigger asChild>
144
- <ToggleGroupItem className={cn("size-9 py-2 px-2 flex items-center justify-center hover:bg-accent rounded-md", layout === 'single' && 'bg-accent text-accent-foreground')} value="single" aria-label="Single column layout" >
145
- <AlignJustify className="size-3.5" strokeWidth={1.5} />
146
- </ToggleGroupItem>
147
- </TooltipTrigger>
148
- <TooltipContent>
149
- Column layout
150
- </TooltipContent>
151
- </Tooltip>
152
- </ToggleGroup>
95
+ <div className="flex justify-end p-2.5 gap-2.5">
96
+ <div className="flex items-center gap-0.5 border border-input bg-background shadow-xs hover:text-accent-foreground p-0.5 rounded-md">
97
+ <Select
98
+ value={category}
99
+ onValueChange={val => {
100
+ setCategory(val);
101
+ }}
102
+ aria-label="Filter by category"
103
+ >
104
+ <SelectTrigger className="rounded-md [&_span]:text-xs [&_svg]:ml-2.5 [&_svg]:size-3 py-1 px-2 pl-3 text-sm border-none shadow-none bg-transparent focus:border-blue-400 focus:outline-none">
105
+ <SelectValue placeholder="All Categories" />
106
+ </SelectTrigger>
107
+ <SelectContent>
108
+ <SelectItem value="all">All Categories</SelectItem>
109
+ {categories.filter((category) => category !== '').map((category) => (
110
+ <SelectItem key={category} value={category}>
111
+ {category}
112
+ </SelectItem>
113
+ ))}
114
+ </SelectContent>
115
+ </Select>
116
+
117
+ <hr className="h-7 w-px bg-input" />
118
+
119
+ <Tooltip>
120
+ <TooltipTrigger asChild>
121
+ <Button variant="ghost" className={cn("w-9", groupBy && 'bg-accent text-accent-foreground')} onClick={() => setGroupBy(!groupBy)}>
122
+ <Rows className="size-3.5" strokeWidth={1.5} />
123
+ <span className="sr-only">Group by category</span>
124
+ </Button>
125
+ </TooltipTrigger>
126
+ <TooltipContent>
127
+ Group by category
128
+ </TooltipContent>
129
+ </Tooltip>
130
+ </div>
131
+ <ToggleGroup className='flex gap-0.5 border border-input bg-background shadow-xs hover:text-accent-foreground p-0.5 rounded-md' type="single" value={layout} onValueChange={(value) => storeLayout(value)}>
132
+ <Tooltip>
133
+ <TooltipTrigger asChild>
134
+ <ToggleGroupItem className={cn("size-9 py-2 px-2 flex items-center justify-center hover:bg-accent rounded-md", layout === 'grid' && 'bg-accent text-accent-foreground')} value="grid" aria-label="Grid layout" >
135
+ <LayoutGrid className="size-3.5" strokeWidth={1.5} />
136
+ </ToggleGroupItem>
137
+ </TooltipTrigger>
138
+ <TooltipContent>
139
+ Grid layout
140
+ </TooltipContent>
141
+ </Tooltip>
142
+ <Tooltip>
143
+ <TooltipTrigger asChild>
144
+ <ToggleGroupItem className={cn("size-9 py-2 px-2 flex items-center justify-center hover:bg-accent rounded-md", layout === 'single' && 'bg-accent text-accent-foreground')} value="single" aria-label="Single column layout" >
145
+ <AlignJustify className="size-3.5" strokeWidth={1.5} />
146
+ </ToggleGroupItem>
147
+ </TooltipTrigger>
148
+ <TooltipContent>
149
+ Column layout
150
+ </TooltipContent>
151
+ </Tooltip>
152
+ </ToggleGroup>
153
+ </div>
153
154
  </div>
154
- </div>
155
155
 
156
- {groupBy ? (
157
- <>
158
- {Object.keys(groupedList).map((group) => {
159
- return (
160
- <div key={`group-${group}`} className="mb-8">
161
- <HeadersType.H3 className="mb-4 text-lg font-medium border-b border-gray-200 pb-2">{group}</HeadersType.H3>
162
- <div
163
- className={cn(
164
- 'grid',
165
- layout === 'grid' ? 'grid-cols-1 gap-10 min-[800px]:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4' : 'grid-cols-1 gap-2'
166
- )}
167
- >
168
- {groupedList[group].map((component) => {
169
- return <ComponentsPageCard key={`component-${component.id}`} component={component} layout={layout} />;
170
- })}
156
+ {groupBy ? (
157
+ <>
158
+ {Object.keys(groupedList).map((group) => {
159
+ return (
160
+ <div key={`group-${group}`} className="mb-8">
161
+ <HeadersType.H3 className="mb-4 text-lg font-medium border-b border-gray-200 pb-2">{group}</HeadersType.H3>
162
+ <div
163
+ className={cn(
164
+ 'grid',
165
+ layout === 'grid' ? 'grid-cols-1 gap-10 min-[800px]:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4' : 'grid-cols-1 gap-2'
166
+ )}
167
+ >
168
+ {groupedList[group].map((component) => {
169
+ return <ComponentsPageCard key={`component-${component.id}`} component={component} layout={layout} />;
170
+ })}
171
+ </div>
171
172
  </div>
172
- </div>
173
- );
173
+ );
174
+ })}
175
+ </>
176
+ ) : (
177
+ <div
178
+ className={cn(
179
+ 'grid',
180
+ layout === 'grid' ? 'grid-cols-1 gap-10 min-[800px]:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4' : 'grid-cols-1 gap-2'
181
+ )}
182
+ > {list.map((component) => {
183
+ return <ComponentsPageCard key={`component-${component.id}`} component={component} layout={layout} />;
174
184
  })}
175
- </>
176
- ) : (
177
- <div
178
- className={cn(
179
- 'grid',
180
- layout === 'grid' ? 'grid-cols-1 gap-10 min-[800px]:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4' : 'grid-cols-1 gap-2'
181
- )}
182
- > {list.map((component) => {
183
- return <ComponentsPageCard key={`component-${component.id}`} component={component} layout={layout} />;
184
- })}
185
- </div>
186
- )}
185
+ </div>
186
+ )}
187
187
  </div>
188
188
  </TooltipProvider>
189
189
  );
@@ -1,3 +1,4 @@
1
+ import { buildArtifactUrl } from '@handoff/artifacts/url';
1
2
  import { SlotMetadata } from '@handoff/transformers/preview/component';
2
3
  import { PageSlice } from '@handoff/transformers/preview/types';
3
4
  import { PreviewObject } from '@handoff/types/preview';
@@ -314,7 +315,7 @@ export const ComponentDisplay: React.FC<{
314
315
  className="h-7 px-3 hover:bg-gray-300 [&_svg]:size-3"
315
316
  onClick={() => {
316
317
  // open in new tab
317
- window.open(`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/component/${previewUrl}`, '_blank');
318
+ window.open(buildArtifactUrl(`component/${previewUrl}`, process.env.HANDOFF_APP_BASE_PATH ?? ''), '_blank');
318
319
  }}
319
320
  variant="ghost"
320
321
  >
@@ -344,7 +345,7 @@ export const ComponentDisplay: React.FC<{
344
345
  display: 'block',
345
346
  margin: '0 auto',
346
347
  }}
347
- src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/component/${previewUrl}`}
348
+ src={buildArtifactUrl(`component/${previewUrl}`, process.env.HANDOFF_APP_BASE_PATH ?? '')}
348
349
  />
349
350
  </div>
350
351
  ) : (
@@ -0,0 +1,150 @@
1
+ 'use client';
2
+ import { CheckIcon, CopyIcon, Download } from 'lucide-react';
3
+ import React from 'react';
4
+
5
+ import type { RuntimeMode } from '@handoff/types/config';
6
+ import { cn } from '../../lib/utils';
7
+ import { Button } from '../ui/button';
8
+ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
9
+
10
+ type RegistryEntityType = 'component' | 'pattern';
11
+
12
+ interface RegistryActionsProps {
13
+ /** Resolved runtime mode projected to the client. */
14
+ mode: RuntimeMode;
15
+ /** Whether this is a connected workspace (workspace mode + resolvable registry URL). */
16
+ connected: boolean;
17
+ /** The kind of entity the page documents. */
18
+ entityType: RegistryEntityType;
19
+ /** Stable id of the component/pattern, used in the copyable command. */
20
+ id: string;
21
+ }
22
+
23
+ type RegistryAction = {
24
+ label: string;
25
+ title: string;
26
+ description: React.ReactNode;
27
+ command: string;
28
+ };
29
+
30
+ /**
31
+ * Registry transfer affordance for the single component/pattern page. Surfaces a context-appropriate
32
+ * helper based on the runtime:
33
+ * - registry mode -> "Checkout" instructions (pull the entity into a connected workspace);
34
+ * - connected workspace (workspace mode + registry URL) -> "Publish" instructions.
35
+ *
36
+ * A static export is never a live runtime, so the publish affordance is suppressed there using the
37
+ * same `HANDOFF_BUILD_TARGET` gate the runtime-mode badge uses. Renders nothing when no action
38
+ * applies (e.g. a plain, unconnected workspace).
39
+ */
40
+ export const RegistryActions: React.FC<RegistryActionsProps> = ({ mode, connected, entityType, id }) => {
41
+ // The static export is a frozen snapshot, not a live workspace, so the publish hint carries no
42
+ // meaning there and is omitted (mirrors the runtime-mode badge in the header).
43
+ const isStaticSnapshot = process.env.HANDOFF_BUILD_TARGET === 'static';
44
+
45
+ const action = resolveAction({ mode, connected, entityType, id, isStaticSnapshot });
46
+ if (!action) {
47
+ return null;
48
+ }
49
+
50
+ return (
51
+ <Popover>
52
+ <PopoverTrigger asChild>
53
+ <Button variant="outline" size="sm" className="font-normal [&_svg]:size-3!">
54
+ <Download aria-hidden="true" />
55
+ {action.label}
56
+ </Button>
57
+ </PopoverTrigger>
58
+ <PopoverContent align="start" className="w-[360px] p-4">
59
+ <div className="flex flex-col gap-3">
60
+ <div className="flex items-center gap-2">
61
+ <Download className="size-4 text-gray-700 dark:text-gray-200" aria-hidden="true" />
62
+ <p className="text-sm font-semibold text-gray-900 dark:text-gray-100">{action.title}</p>
63
+ </div>
64
+ <p className="text-sm text-muted-foreground">{action.description}</p>
65
+ <div className="border-t" />
66
+ <div className="flex items-stretch overflow-hidden rounded-md border">
67
+ <code className="flex flex-1 items-center truncate px-3 py-2 font-mono text-xs text-gray-700 dark:text-gray-300">
68
+ {action.command}
69
+ </code>
70
+ <CopyCommandButton command={action.command} />
71
+ </div>
72
+ <p className="text-xs text-muted-foreground">Requires a workspace configured with registry access.</p>
73
+ </div>
74
+ </PopoverContent>
75
+ </Popover>
76
+ );
77
+ };
78
+
79
+ /** Copy-to-clipboard control for the command box. No tooltip — just a copy/checkmark toggle. */
80
+ const CopyCommandButton: React.FC<{ command: string }> = ({ command }) => {
81
+ const [copied, setCopied] = React.useState(false);
82
+
83
+ const handleCopy = async () => {
84
+ try {
85
+ await navigator.clipboard.writeText(command);
86
+ setCopied(true);
87
+ setTimeout(() => setCopied(false), 1500);
88
+ } catch (err) {
89
+ console.error('Failed to copy command: ', err);
90
+ }
91
+ };
92
+
93
+ return (
94
+ <Button
95
+ type="button"
96
+ variant="ghost"
97
+ size="icon"
98
+ onClick={handleCopy}
99
+ aria-label={copied ? 'Copied' : 'Copy command'}
100
+ className="relative h-auto w-9 self-stretch rounded-none border-0 border-l shadow-none"
101
+ >
102
+ <span className={cn('transition-all', copied ? 'scale-100 opacity-100' : 'scale-0 opacity-0')}>
103
+ <CheckIcon className="stroke-gray-600" size={16} aria-hidden="true" />
104
+ </span>
105
+ <span className={cn('absolute transition-all', copied ? 'scale-0 opacity-0' : 'scale-100 opacity-100')}>
106
+ <CopyIcon size={16} aria-hidden="true" />
107
+ </span>
108
+ </Button>
109
+ );
110
+ };
111
+
112
+ const resolveAction = ({
113
+ mode,
114
+ connected,
115
+ entityType,
116
+ id,
117
+ isStaticSnapshot,
118
+ }: RegistryActionsProps & { isStaticSnapshot: boolean }): RegistryAction | null => {
119
+ if (mode === 'registry') {
120
+ return {
121
+ label: 'Checkout',
122
+ title: `Checkout ${entityType}`,
123
+ description: (
124
+ <>
125
+ Run this in a connected workspace to pull <strong className="font-medium text-foreground">{id}</strong> from the
126
+ registry.
127
+ </>
128
+ ),
129
+ command: `npm run checkout -- ${entityType} ${id}`,
130
+ };
131
+ }
132
+
133
+ if (mode === 'workspace' && connected && !isStaticSnapshot) {
134
+ return {
135
+ label: 'Publish',
136
+ title: `Publish ${entityType}`,
137
+ description: (
138
+ <>
139
+ Run this in your connected workspace to publish <strong className="font-medium text-foreground">{id}</strong> to
140
+ the registry.
141
+ </>
142
+ ),
143
+ command: `npm run publish -- ${entityType} ${id}`,
144
+ };
145
+ }
146
+
147
+ return null;
148
+ };
149
+
150
+ export default RegistryActions;
@@ -1,10 +1,12 @@
1
- import { Types as CoreTypes } from 'handoff-core';
2
1
  import React from 'react';
2
+ import type { DisplayableAsset } from '../util/useCollectionAssets';
3
3
 
4
- export const DisplayLogo: React.FC<{ logo: CoreTypes.IAssetObject; content?: string; correct?: boolean }> = ({ logo, content }) => {
4
+ export const DisplayLogo: React.FC<{ logo: DisplayableAsset; content?: string; correct?: boolean }> = ({ logo, content }) => {
5
+ // Registry serves the individual asset content URL; workspace/static serves the mirrored file.
6
+ const src = logo.src ?? `${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/assets/logos/${logo.path}`;
5
7
  return (
6
8
  <div className="flex flex-col gap-5">
7
- <img src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/assets/logos/${logo.path}`} alt="Usage Cards" className="rounded-3xl" />
9
+ <img src={src} alt="Usage Cards" className="rounded-3xl" />
8
10
  <div className="flex flex-row gap-4">
9
11
  <img src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/assets/images/check-circle.svg`} alt="Do" className="h-6 w-6" />
10
12
  <p className="leading-normal text-gray-500">
@@ -4,11 +4,22 @@ import { ModeToggle } from '../../components/ModeSwitcher';
4
4
  import { MainNav } from '../../components/Navigation/MainNav';
5
5
  import { MobileNav } from '../../components/Navigation/MobileNav';
6
6
  import { cn } from '../../lib/utils';
7
+ import { Badge } from '../ui/badge';
7
8
  import { useConfigContext } from '../context/ConfigContext';
8
9
 
10
+ const RUNTIME_MODE_LABEL: Record<'workspace' | 'registry', string> = {
11
+ workspace: 'Workspace',
12
+ registry: 'Registry',
13
+ };
14
+
9
15
  export function Header() {
10
16
  const context = useConfigContext();
11
17
  const [isScrolled, setIsScrolled] = useState(false);
18
+ const runtimeMode = context.config?.runtime?.mode ?? 'workspace';
19
+ const runtimeModeLabel = RUNTIME_MODE_LABEL[runtimeMode];
20
+ // The static export is a self-contained snapshot of the workspace, not a live runtime, so the
21
+ // runtime-mode badge carries no meaning there and is omitted.
22
+ const isStaticSnapshot = process.env.HANDOFF_BUILD_TARGET === 'static';
12
23
 
13
24
  useEffect(() => {
14
25
  const handleScroll = () => {
@@ -28,9 +39,16 @@ export function Header() {
28
39
  >
29
40
  <header className="border-grid container mx-auto w-full max-w-[1500px] bg-transparent px-8">
30
41
  <div className="mx-auto flex items-center justify-between @container">
31
- <Link href="/">
32
- <img className="max-h-5" src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/logo.svg`} alt={context.config?.app?.title} />
33
- </Link>
42
+ <div className="flex items-center gap-3">
43
+ <Link href="/">
44
+ <img className="max-h-5" src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/logo.svg`} alt={context.config?.app?.title} />
45
+ </Link>
46
+ {!isStaticSnapshot && (
47
+ <Badge variant={runtimeMode === 'registry' ? 'info' : 'default'} aria-label={`Runtime mode: ${runtimeModeLabel}`}>
48
+ {runtimeModeLabel}
49
+ </Badge>
50
+ )}
51
+ </div>
34
52
  <div className="hidden items-center gap-4 @2xl:flex">
35
53
  <MainNav />
36
54
  <ModeToggle />
@@ -5,6 +5,7 @@ import { Header } from '../../components/Layout/Header';
5
5
  import { ThemeProvider } from '../../components/util/theme-provider';
6
6
  import SideNav from '../Navigation/SideNav';
7
7
  import { ConfigContextProvider } from '../context/ConfigContext';
8
+ import { hasRenderableNav } from '../../lib/utils';
8
9
  import { SidebarInset, SidebarProvider } from '../ui/sidebar';
9
10
  import { SectionLink } from '../util';
10
11
 
@@ -21,6 +22,13 @@ interface LayoutComponentProps {
21
22
  fullWidthHero?: boolean;
22
23
  }
23
24
  export default function Layout<LayoutComponentProps>({ children, config, menu, metadata, current, fullWidthHero = false }) {
25
+ // In registry mode the per-page `current` is empty for lambda-rendered (fallback) pages, so mount
26
+ // the sidebar regardless and let SideNav resolve its section from the cached shell (and collapse
27
+ // itself to full width when that section has no renderable nav). Workspace/static know the section
28
+ // at render time, so drop the left column entirely for pages with no side-nav content (e.g. a
29
+ // standalone page with no children) and let the content go full width.
30
+ const isRegistry = config?.runtime?.mode === 'registry';
31
+ const showSidebar = isRegistry || hasRenderableNav(current as SectionLink);
24
32
  return (
25
33
  <div>
26
34
  <ConfigContextProvider defaultConfig={config} defaultMenu={menu}>
@@ -48,10 +56,10 @@ export default function Layout<LayoutComponentProps>({ children, config, menu, m
48
56
  <div className="mx-auto w-full">{children}</div>
49
57
  </div>
50
58
  </div>
51
- ) : current ? (
59
+ ) : showSidebar ? (
52
60
  <SidebarProvider>
53
61
  <div className="flex w-full">
54
- <SideNav menu={current} />
62
+ <SideNav />
55
63
  <SidebarInset className="relative bg-transparent py-8 pl-8 pr-8 md:pl-8 lg:gap-10 lg:py-16 lg:pl-16">
56
64
  <div className="mx-auto w-full">{children}</div>
57
65
  </SidebarInset>
@@ -59,7 +67,7 @@ export default function Layout<LayoutComponentProps>({ children, config, menu, m
59
67
  </SidebarProvider>
60
68
  ) : (
61
69
  <div className="flex w-full">
62
- <div className="relative bg-transparent px-16 py-8 lg:gap-10 lg:py-16 xl:grid">
70
+ <div className="relative w-full bg-transparent px-16 py-8 lg:gap-10 lg:py-16">
63
71
  <div className="mx-auto w-full">{children}</div>
64
72
  </div>
65
73
  </div>
@@ -10,6 +10,7 @@ import {
10
10
  } from '../../components/ui/navigation-menu';
11
11
  import { cn } from '../../lib/utils';
12
12
  import { useConfigContext } from '../context/ConfigContext';
13
+ import { useNav } from '../context/NavProvider';
13
14
 
14
15
  const trimSlashes = (input: string): string => {
15
16
  return input.replace(/^\/+|\/+$/g, '');
@@ -18,11 +19,12 @@ const trimSlashes = (input: string): string => {
18
19
  export function MainNav() {
19
20
  const context = useConfigContext();
20
21
  const router = useRouter();
22
+ const { menu } = useNav();
21
23
  return (
22
24
  <NavigationMenu>
23
25
  <NavigationMenuList>
24
- {context.menu &&
25
- context.menu.map((section) => {
26
+ {menu &&
27
+ menu.map((section) => {
26
28
  const isActive = trimSlashes(router.asPath).startsWith(trimSlashes(section.path));
27
29
  return (
28
30
  <NavigationMenuItem key={section.title}>