handoff-app 1.2.2-7 → 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/cli.js +0 -0
  29. package/dist/commands/build/app.js +1 -2
  30. package/dist/commands/build/index.d.ts +17 -0
  31. package/dist/commands/build/index.js +66 -0
  32. package/dist/commands/checkout/index.d.ts +24 -0
  33. package/dist/commands/checkout/index.js +71 -0
  34. package/dist/commands/db/migrate.d.ts +11 -0
  35. package/dist/commands/db/migrate.js +33 -0
  36. package/dist/commands/index.js +8 -0
  37. package/dist/commands/publish/index.d.ts +21 -0
  38. package/dist/commands/publish/index.js +68 -0
  39. package/dist/config/defaults.js +15 -1
  40. package/dist/config/loader.js +6 -1
  41. package/dist/config/normalizers/declaration.js +4 -1
  42. package/dist/config/normalizers/page.d.ts +18 -0
  43. package/dist/config/normalizers/page.js +34 -0
  44. package/dist/config/normalizers/pattern.js +7 -1
  45. package/dist/config/runtime.d.ts +4 -0
  46. package/dist/config/runtime.js +53 -5
  47. package/dist/index.d.ts +65 -1
  48. package/dist/index.js +123 -3
  49. package/dist/nav/index.d.ts +83 -0
  50. package/dist/nav/index.js +9 -0
  51. package/dist/nav/registry.d.ts +15 -0
  52. package/dist/nav/registry.js +42 -0
  53. package/dist/nav/resolver.d.ts +8 -0
  54. package/dist/nav/resolver.js +192 -0
  55. package/dist/nav/workspace.d.ts +18 -0
  56. package/dist/nav/workspace.js +103 -0
  57. package/dist/pipeline/styles.js +7 -35
  58. package/dist/pipeline/token-transformers.d.ts +45 -0
  59. package/dist/pipeline/token-transformers.js +71 -0
  60. package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +17 -0
  61. package/dist/registry/asset-storage/adapters/vercel-blob.js +87 -0
  62. package/dist/registry/asset-storage/define.d.ts +10 -0
  63. package/dist/registry/asset-storage/define.js +13 -0
  64. package/dist/registry/asset-storage/resolve.d.ts +45 -0
  65. package/dist/registry/asset-storage/resolve.js +36 -0
  66. package/dist/registry/asset-storage/types.d.ts +51 -0
  67. package/dist/registry/asset-storage/types.js +9 -0
  68. package/dist/registry/assets/layout.d.ts +22 -0
  69. package/dist/registry/assets/layout.js +30 -0
  70. package/dist/registry/assets/sets.d.ts +18 -0
  71. package/dist/registry/assets/sets.js +44 -0
  72. package/dist/registry/assets/transfer.d.ts +45 -0
  73. package/dist/registry/assets/transfer.js +11 -0
  74. package/dist/registry/checkout/assets.d.ts +16 -0
  75. package/dist/registry/checkout/assets.js +150 -0
  76. package/dist/registry/checkout/index.d.ts +33 -0
  77. package/dist/registry/checkout/index.js +610 -0
  78. package/dist/registry/checkout/tokens.d.ts +15 -0
  79. package/dist/registry/checkout/tokens.js +171 -0
  80. package/dist/registry/client.d.ts +61 -0
  81. package/dist/registry/client.js +211 -0
  82. package/dist/registry/connection.d.ts +30 -0
  83. package/dist/registry/connection.js +36 -0
  84. package/dist/registry/db/adapter.d.ts +43 -0
  85. package/dist/registry/db/adapter.js +59 -0
  86. package/dist/registry/db/client.d.ts +35 -0
  87. package/dist/registry/db/client.js +94 -0
  88. package/dist/registry/db/index.d.ts +5 -0
  89. package/dist/registry/db/index.js +24 -0
  90. package/dist/registry/db/migrate.d.ts +23 -0
  91. package/dist/registry/db/migrate.js +64 -0
  92. package/dist/registry/db/schema.d.ts +4869 -0
  93. package/dist/registry/db/schema.js +347 -0
  94. package/dist/registry/path.d.ts +4 -0
  95. package/dist/registry/path.js +31 -0
  96. package/dist/registry/publish/artifacts.d.ts +16 -0
  97. package/dist/registry/publish/artifacts.js +132 -0
  98. package/dist/registry/publish/assets.d.ts +16 -0
  99. package/dist/registry/publish/assets.js +129 -0
  100. package/dist/registry/publish/errors.d.ts +5 -0
  101. package/dist/registry/publish/errors.js +26 -0
  102. package/dist/registry/publish/index.d.ts +27 -0
  103. package/dist/registry/publish/index.js +126 -0
  104. package/dist/registry/publish/package.d.ts +27 -0
  105. package/dist/registry/publish/package.js +257 -0
  106. package/dist/registry/publish/publish-build.d.ts +12 -0
  107. package/dist/registry/publish/publish-build.js +43 -0
  108. package/dist/registry/publish/tokens.d.ts +18 -0
  109. package/dist/registry/publish/tokens.js +112 -0
  110. package/dist/registry/tokens/sets.d.ts +59 -0
  111. package/dist/registry/tokens/sets.js +109 -0
  112. package/dist/registry/tokens/transfer.d.ts +48 -0
  113. package/dist/registry/tokens/transfer.js +10 -0
  114. package/dist/registry/transfer.d.ts +75 -0
  115. package/dist/registry/transfer.js +11 -0
  116. package/dist/store/filesystem-assets.d.ts +17 -0
  117. package/dist/store/filesystem-assets.js +147 -0
  118. package/dist/store/filesystem.d.ts +76 -0
  119. package/dist/store/filesystem.js +208 -0
  120. package/dist/store/index.d.ts +20 -0
  121. package/dist/store/index.js +38 -0
  122. package/dist/store/registry.d.ts +77 -0
  123. package/dist/store/registry.js +350 -0
  124. package/dist/store/source-files.d.ts +24 -0
  125. package/dist/store/source-files.js +147 -0
  126. package/dist/store/types.d.ts +185 -0
  127. package/dist/store/types.js +12 -0
  128. package/dist/transformers/plugins/csf-render.js +17 -8
  129. package/dist/transformers/plugins/handlebars-previews.js +10 -3
  130. package/dist/transformers/plugins/ssr-render.js +116 -49
  131. package/dist/transformers/preview/component/api.js +3 -1
  132. package/dist/transformers/preview/component/artifacts.js +8 -1
  133. package/dist/transformers/preview/component/builder.js +13 -5
  134. package/dist/transformers/preview/component/css.js +9 -1
  135. package/dist/transformers/preview/component/javascript.d.ts +9 -3
  136. package/dist/transformers/preview/component/javascript.js +45 -17
  137. package/dist/transformers/preview/component/shared-artifacts.d.ts +83 -0
  138. package/dist/transformers/preview/component/shared-artifacts.js +92 -0
  139. package/dist/transformers/preview/component.js +5 -2
  140. package/dist/transformers/preview/pattern/builder.js +28 -9
  141. package/dist/transformers/preview/pattern/html.d.ts +13 -10
  142. package/dist/transformers/preview/pattern/html.js +62 -27
  143. package/dist/transformers/preview/types.d.ts +38 -0
  144. package/dist/transformers/utils/handlebars.d.ts +3 -0
  145. package/dist/transformers/utils/handlebars.js +19 -5
  146. package/dist/types/config.d.ts +118 -25
  147. package/dist/utils/menu-shell.d.ts +60 -0
  148. package/dist/utils/menu-shell.js +221 -0
  149. package/dist/utils/stable-stringify.d.ts +2 -0
  150. package/dist/utils/stable-stringify.js +8 -0
  151. package/docs/cli.md +4 -2
  152. package/drizzle/0000_init_registry_storage.sql +95 -0
  153. package/drizzle/0001_add_review_metadata.sql +2 -0
  154. package/drizzle/0002_add_pages.sql +28 -0
  155. package/drizzle/0003_add_tokens.sql +29 -0
  156. package/drizzle/0004_add_assets.sql +37 -0
  157. package/drizzle/meta/0000_snapshot.json +642 -0
  158. package/drizzle/meta/0001_snapshot.json +654 -0
  159. package/drizzle/meta/0002_snapshot.json +835 -0
  160. package/drizzle/meta/0003_snapshot.json +1023 -0
  161. package/drizzle/meta/0004_snapshot.json +1252 -0
  162. package/drizzle/meta/_journal.json +41 -0
  163. package/drizzle.config.ts +16 -0
  164. package/package.json +10 -1
  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
@@ -226,3 +226,41 @@ export type PatternListObject = {
226
226
  id: string;
227
227
  path: string;
228
228
  } & PatternObject;
229
+ /**
230
+ * A documentation page authored as markdown. The frontmatter is parsed into these fields and the
231
+ * markdown body is the rendered content. Pages are workspace-authored under `<workingPath>/pages/`
232
+ * and are first-class registry entities (publish/checkout) alongside components and patterns. Unlike
233
+ * components/patterns there is no rendered-artifact pipeline: the raw markdown is stored and rendered
234
+ * at runtime.
235
+ */
236
+ export type PageObject = {
237
+ title: string;
238
+ description?: string;
239
+ group?: string;
240
+ /** Sort weight within the nav section (lower first). */
241
+ weight?: number;
242
+ /** Alternative title used in navigation. */
243
+ menuTitle?: string;
244
+ /** SEO overrides. */
245
+ metaTitle?: string;
246
+ metaDescription?: string;
247
+ /** When `false`, the page is hidden from navigation. */
248
+ enabled?: boolean;
249
+ /** External link target — a URL/path string, or `true` to flag the section as external. */
250
+ external?: string | boolean;
251
+ /** Optional declarative submenu structure (mirrors the markdown frontmatter `menu`). */
252
+ menu?: {
253
+ [key: string]: any;
254
+ };
255
+ };
256
+ export type PageListObject = {
257
+ /** Stable page id — its slug path (e.g. `guides/setup`), derived from the file location (id ≡ route). */
258
+ id: string;
259
+ /** Logical route the page is served at (e.g. `/guides/setup`). */
260
+ path: string;
261
+ /**
262
+ * Absolute filesystem path of the source `.md` (workspace store only). Workspace-only and stripped
263
+ * from the published transfer item — never persisted to or served from the registry.
264
+ */
265
+ sourcePath?: string;
266
+ } & PageObject;
@@ -1,5 +1,6 @@
1
1
  import { RegisterHandlebarsHelpersContext } from '../../types/config';
2
2
  import { SlotMetadata } from '../preview/component';
3
+ import { type ComponentArtifactPresence, type SharedArtifactPresence } from '../preview/component/shared-artifacts';
3
4
  import { HandlebarsContext } from '../types';
4
5
  /**
5
6
  * Registers common Handlebars helpers, then runs optional `hooks.registerHandlebarsHelpers`.
@@ -29,4 +30,6 @@ export declare const createHandlebarsContext: (data: {
29
30
  values?: any;
30
31
  }, options?: {
31
32
  includeSharedStyles?: boolean;
33
+ sharedArtifacts?: SharedArtifactPresence;
34
+ componentArtifacts?: ComponentArtifactPresence;
32
35
  }) => HandlebarsContext;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createHandlebarsContext = exports.registerHandlebarsHelpers = void 0;
7
7
  const handlebars_1 = __importDefault(require("handlebars"));
8
8
  const logger_1 = require("../../utils/logger");
9
+ const shared_artifacts_1 = require("../preview/component/shared-artifacts");
9
10
  /**
10
11
  * Registers common Handlebars helpers, then runs optional `hooks.registerHandlebarsHelpers`.
11
12
  * @param data - Component data containing properties
@@ -66,14 +67,27 @@ exports.registerHandlebarsHelpers = registerHandlebarsHelpers;
66
67
  * @returns Handlebars context object
67
68
  */
68
69
  const createHandlebarsContext = (data, previewData, options) => {
69
- var _a;
70
+ var _a, _b, _c;
70
71
  const basePath = (_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : '';
71
- const sharedStylesLink = (options === null || options === void 0 ? void 0 : options.includeSharedStyles) ? `<link rel="stylesheet" href="${basePath}/api/component/shared.css">` : '';
72
+ // Shared/global artifacts (`component/shared.css`, `component/main.css`, `component/main.js`) and
73
+ // the component's own css/js are referenced only when present, so a failed/absent build never
74
+ // produces a dangling reference. Absent presence info conservatively omits the artifacts.
75
+ const presence = (_b = options === null || options === void 0 ? void 0 : options.sharedArtifacts) !== null && _b !== void 0 ? _b : { mainJs: false, mainCss: false, sharedCss: false };
76
+ const componentPresence = (_c = options === null || options === void 0 ? void 0 : options.componentArtifacts) !== null && _c !== void 0 ? _c : { css: false, js: false };
77
+ const sharedStyleLinks = (0, shared_artifacts_1.renderSharedStyleLinks)(presence, basePath, {
78
+ includeSharedStyles: options === null || options === void 0 ? void 0 : options.includeSharedStyles,
79
+ });
80
+ const componentStyleLink = (0, shared_artifacts_1.renderComponentStyleLink)(componentPresence, data.id, basePath);
81
+ // The global script loads before the component-specific script so global side effects run first.
82
+ const globalScriptTag = (0, shared_artifacts_1.renderGlobalScriptTag)(presence, basePath);
83
+ const componentScriptTag = (0, shared_artifacts_1.renderComponentScriptTag)(componentPresence, data.id, basePath);
72
84
  return {
73
- style: `${sharedStylesLink}<link rel="stylesheet" href="${basePath}/api/component/main.css">` +
74
- `<link rel="stylesheet" href="${basePath}/api/component/${data.id}.css">\n` +
85
+ style: `${sharedStyleLinks ? `${sharedStyleLinks}\n` : ''}` +
86
+ `${componentStyleLink ? `${componentStyleLink}\n` : ''}` +
75
87
  `<link rel="stylesheet" href="${basePath}/assets/css/preview.css">`,
76
- script: `<script src="${basePath}/api/component/${data.id}.js"></script>\n<script src="${basePath}/assets/js/preview.js"></script><script>var fields = ${JSON.stringify(data.properties)};</script>`,
88
+ script: `${globalScriptTag ? `${globalScriptTag}\n` : ''}` +
89
+ `${componentScriptTag ? `${componentScriptTag}\n` : ''}` +
90
+ `<script src="${basePath}/assets/js/preview.js"></script><script>var fields = ${JSON.stringify(data.properties)};</script>`,
77
91
  properties: previewData.values || {},
78
92
  fields: data.properties,
79
93
  title: data.title,
@@ -3,7 +3,7 @@ import type Handlebars from 'handlebars';
3
3
  import { Types as HandoffTypes } from 'handoff-core';
4
4
  import { InlineConfig } from 'vite';
5
5
  import { SlotMetadata } from '../transformers/preview/component';
6
- import { ComponentListObject, PatternListObject, TransformComponentTokensResult } from '../transformers/preview/types';
6
+ import { ComponentListObject, PageListObject, PatternListObject, TransformComponentTokensResult } from '../transformers/preview/types';
7
7
  import { ValidationResult } from './preview';
8
8
  export interface ImageStyle {
9
9
  name: string;
@@ -84,6 +84,98 @@ export interface NextAppConfig {
84
84
  websocket: number;
85
85
  };
86
86
  }
87
+ /**
88
+ * Configuration for entry points to assets and components that will be built.
89
+ */
90
+ export interface ConfigEntries {
91
+ /**
92
+ * Path to the main SCSS entry file
93
+ * @example "styles/main.scss"
94
+ */
95
+ scss?: string;
96
+ /**
97
+ * Path to the main JavaScript entry file
98
+ * @example "scripts/main.js"
99
+ */
100
+ js?: string;
101
+ /**
102
+ * Array of component paths to be included in the build
103
+ * @example ["components/button", "components/input"]
104
+ */
105
+ components?: string[];
106
+ /**
107
+ * Array of pattern paths to be included in the build.
108
+ * Patterns compose multiple component previews into single-page views.
109
+ * @example ["patterns/hero-section", "patterns"]
110
+ */
111
+ patterns?: string[];
112
+ }
113
+ /** Runtime mode. Resolved solely from `runtime.mode`; never inferred from env vars or connection settings. */
114
+ export type RuntimeMode = 'workspace' | 'registry';
115
+ /** Format used when synthesizing local workspace declarations. */
116
+ export type DeclarationFormat = 'ts' | 'js' | 'cjs' | 'json';
117
+ /**
118
+ * User-facing `runtime` configuration block. A single optional block that selects the runtime
119
+ * mode and carries mode-specific host/connection settings.
120
+ *
121
+ * Note: this is the authored shape. {@link RuntimeConfig} is the separate, resolved post-discovery
122
+ * shape used internally by the build/runtime.
123
+ */
124
+ export interface HandoffRuntimeConfig {
125
+ /**
126
+ * The sole determinant of runtime mode. Defaults to `'workspace'` when omitted.
127
+ * Mode is never inferred from environment variables, database settings, or token presence.
128
+ */
129
+ mode?: RuntimeMode;
130
+ /** Workspace-mode settings. */
131
+ workspace?: {
132
+ entries?: ConfigEntries;
133
+ declarationFormat?: DeclarationFormat;
134
+ };
135
+ /** Registry-mode host settings. Env-var values are stored as names, never as secrets. */
136
+ registry?: {
137
+ /** Name of the env var holding the database URL. @default "DATABASE_URL" */
138
+ databaseUrlEnv?: string;
139
+ /** Name of the env var holding the registry API token. @default "HANDOFF_REGISTRY_API_TOKEN" */
140
+ apiTokenEnv?: string;
141
+ database?: {
142
+ /** @default "pg" */
143
+ adapter?: 'pg' | 'neon';
144
+ };
145
+ /**
146
+ * Where published asset blobs (icons/logos/fonts/sprites/archives) are stored. Defaults to the
147
+ * built-in PostgreSQL `bytea` store when omitted. Secrets are never persisted here - only the
148
+ * *names* of the env vars their values are read from at request time.
149
+ */
150
+ assetStorage?: {
151
+ /**
152
+ * Active storage provider for new uploads. `database` keeps bytes inline in Postgres; use the
153
+ * pre-packaged `vercel-blob` adapter or a `custom` module for object storage.
154
+ * @default "database"
155
+ */
156
+ adapter?: 'database' | 'vercel-blob' | 'custom';
157
+ /** For `adapter: "custom"` - server-only module path default-exporting a `defineAssetStorage` adapter. */
158
+ module?: string;
159
+ /** For `adapter: "vercel-blob"` - env var name holding the Blob read/write token. @default "BLOB_READ_WRITE_TOKEN" */
160
+ tokenEnv?: string;
161
+ /** Max bytes kept inline in the database `bytea` column (larger uploads are rejected). @default 4194304 */
162
+ maxInlineBytes?: number;
163
+ /** Non-secret adapter options (bucket env-var names, region, a custom `providerId`, …). */
164
+ options?: Record<string, unknown>;
165
+ };
166
+ };
167
+ /**
168
+ * Connected-workspace settings pointing at a remote registry. A connected workspace is
169
+ * `mode: 'workspace'` plus this block — it is not a third runtime mode.
170
+ */
171
+ registryConnection?: {
172
+ url?: string;
173
+ /** @default "HANDOFF_REGISTRY_URL" */
174
+ urlEnv?: string;
175
+ /** @default "HANDOFF_REGISTRY_ACCESS_TOKEN" */
176
+ accessTokenEnv?: string;
177
+ };
178
+ }
87
179
  export interface Config {
88
180
  dev_access_token?: string | null | undefined;
89
181
  devAccessToken?: string | null | undefined;
@@ -112,32 +204,15 @@ export interface Config {
112
204
  * Configuration for the build pipeline
113
205
  */
114
206
  pipeline?: PipelineConfig;
207
+ /**
208
+ * Runtime configuration. `runtime.mode` is the sole determinant of runtime mode
209
+ * (`workspace` | `registry`), defaulting to `workspace` when omitted.
210
+ */
211
+ runtime?: HandoffRuntimeConfig;
115
212
  /**
116
213
  * Configuration for entry points to assets and components that will be built
117
214
  */
118
- entries?: {
119
- /**
120
- * Path to the main SCSS entry file
121
- * @example "styles/main.scss"
122
- */
123
- scss?: string;
124
- /**
125
- * Path to the main JavaScript entry file
126
- * @example "scripts/main.js"
127
- */
128
- js?: string;
129
- /**
130
- * Array of component paths to be included in the build
131
- * @example ["components/button", "components/input"]
132
- */
133
- components?: string[];
134
- /**
135
- * Array of pattern paths to be included in the build.
136
- * Patterns compose multiple component previews into single-page views.
137
- * @example ["patterns/hero-section", "patterns"]
138
- */
139
- patterns?: string[];
140
- };
215
+ entries?: ConfigEntries;
141
216
  /**
142
217
  * Configuration for asset zip file download links
143
218
  * @default { icons: "/icons.zip", logos: "/logos.zip" }
@@ -283,7 +358,22 @@ export interface Config {
283
358
  registerHandlebarsHelpers?: (context: RegisterHandlebarsHelpersContext) => void;
284
359
  };
285
360
  }
286
- export type ClientConfig = Pick<Config, 'app' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'assets_zip_links' | 'useVariables'>;
361
+ /**
362
+ * Non-secret runtime values projected to the browser. Only the resolved {@link RuntimeMode}
363
+ * crosses to the client; secrets and connection values never do.
364
+ */
365
+ export interface ClientRuntimeConfig {
366
+ mode: RuntimeMode;
367
+ /**
368
+ * True when the runtime is a connected workspace (`mode: 'workspace'` plus a resolvable
369
+ * `registryConnection` URL). Non-secret: it never carries the URL or token value, only whether a
370
+ * connection is configured. Drives the workspace-only publish affordance in the docs UI.
371
+ */
372
+ connected: boolean;
373
+ }
374
+ export type ClientConfig = Pick<Config, 'app' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'assets_zip_links' | 'useVariables'> & {
375
+ runtime: ClientRuntimeConfig;
376
+ };
287
377
  export interface RuntimeConfigComponentOptions {
288
378
  cssRootClass?: string;
289
379
  tokenNameSegments?: string[];
@@ -311,6 +401,9 @@ export interface RuntimeConfig {
311
401
  patterns: {
312
402
  [id: string]: PatternListObject;
313
403
  };
404
+ pages: {
405
+ [id: string]: PageListObject;
406
+ };
314
407
  };
315
408
  options: {
316
409
  [key: string]: RuntimeConfigComponentOptions;
@@ -0,0 +1,60 @@
1
+ import type { NavMenuItem, NavSubSection, SectionLink } from '../nav';
2
+ /**
3
+ * Build-time menu *shell* builder.
4
+ *
5
+ * The docs navigation is two things: a SHELL (which sections exist, their order/titles, and which
6
+ * submenu slots are filled at runtime from the registry) and the runtime ENTITY lists
7
+ * (components/patterns/token sets) that the resolver splices into those slots. The shell is
8
+ * markdown-driven and therefore static per build; the entities are mutable at runtime and the
9
+ * resolved tree is served by `/api/docs/nav.json`.
10
+ *
11
+ * On Vercel the registry function cannot read `config/docs` markdown at request time — those reads
12
+ * use absolute build-machine paths (`HANDOFF_MODULE_PATH/...`) that do not exist on the deploy host
13
+ * and that nft cannot trace. So the shell is frozen here at build time (where the markdown is
14
+ * present) into `generated/nav-shell.json`, which the API route imports statically (bundled, path
15
+ * independent). The resolver combines it with runtime-variable records before the client renders it.
16
+ *
17
+ * This module is intentionally dependency-light and PURE (fs + gray-matter, no DB, no React, no
18
+ * `@handoff` aliases) so the compiled build pipeline (`dist/`) can import it — `src/app` is excluded
19
+ * from `tsc` and is not importable from the builder.
20
+ *
21
+ * It emits the canonical section structure with empty entity lists and `dynamic` splice markers.
22
+ * `src/nav/resolver.ts` is the sole place that fills those markers in every runtime mode.
23
+ */
24
+ /** Back-compatible shell-builder names reconciled to the canonical navigation contract. */
25
+ export type MenuShellSubItem = NavMenuItem;
26
+ export type MenuShellSubSection = NavSubSection;
27
+ export type MenuShellSection = SectionLink;
28
+ export interface BuildMenuShellOptions {
29
+ /** Absolute path to the package docs root (`<modulePath>/config/docs`). */
30
+ docRoot: string;
31
+ /** Absolute path to the working-project page overrides (`<workingPath>/pages`), optional. */
32
+ workingPagesDir?: string;
33
+ /** Resolved app base path (e.g. `"foundations"` → no leading/trailing slash), optional. */
34
+ basePath?: string;
35
+ }
36
+ /**
37
+ * Paths that have dedicated route files and are therefore excluded from auto-scanned submenus.
38
+ * Single source of truth — the docs app imports this (re-exported there as `knownPaths`).
39
+ */
40
+ export declare const KNOWN_PATHS: string[];
41
+ /**
42
+ * The static Tokens submenu (Foundations only). Component token sets are mode-resolved runtime
43
+ * records, so the shell bakes only the deterministic Foundations entries. The resolver appends the
44
+ * component token-set records when it fills the dynamic slot. `basePath` must already be
45
+ * `"prefix/"`-normalized.
46
+ */
47
+ export declare const buildTokensFoundationsMenu: (basePath: string) => MenuShellSubItem[];
48
+ /**
49
+ * Recursively build menu entries from .md files in a directory (auto-scan for sections that do not
50
+ * declare a `menu` in frontmatter). Shared with the app — single source of truth for the auto-scan.
51
+ */
52
+ export declare const buildMenuFromDirectory: (dirPath: string, urlPrefix: string) => MenuShellSubItem[];
53
+ /**
54
+ * Build the full navigation shell from the docs markdown, with all registry entity slots
55
+ * (components/patterns/token sets) emitted empty and tagged `dynamic` for the resolver to fill.
56
+ *
57
+ * Returns the top-level `MenuShellSection[]` sorted by weight — the same shape the docs app's
58
+ * `SectionLink[]` uses, so it serializes directly into `generated/nav-shell.json`.
59
+ */
60
+ export declare const buildMenuShell: (options: BuildMenuShellOptions) => MenuShellSection[];
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.buildMenuShell = exports.buildMenuFromDirectory = exports.buildTokensFoundationsMenu = exports.KNOWN_PATHS = void 0;
30
+ const fs = __importStar(require("fs"));
31
+ const gray_matter_1 = __importDefault(require("gray-matter"));
32
+ const lodash_1 = require("lodash");
33
+ const path_1 = __importDefault(require("path"));
34
+ /**
35
+ * Paths that have dedicated route files and are therefore excluded from auto-scanned submenus.
36
+ * Single source of truth — the docs app imports this (re-exported there as `knownPaths`).
37
+ */
38
+ exports.KNOWN_PATHS = [
39
+ 'assets',
40
+ 'assets/fonts',
41
+ 'assets/icons',
42
+ 'assets/logos',
43
+ 'foundations',
44
+ 'foundations/colors',
45
+ 'foundations/icons',
46
+ 'foundations/effects',
47
+ 'foundations/logos',
48
+ 'foundations/logo',
49
+ 'foundations/typography',
50
+ 'system',
51
+ 'system/component',
52
+ 'system/tokens',
53
+ 'system/tokens/foundations',
54
+ 'system/tokens/foundations/colors',
55
+ 'system/tokens/foundations/effects',
56
+ 'system/tokens/foundations/typography',
57
+ 'system/tokens/components',
58
+ 'system/pattern',
59
+ ];
60
+ /** Normalize the base path to the resolver's `"prefix/"` form (or `""`). */
61
+ const normalizeBasePath = (basePath) => {
62
+ if (!basePath) {
63
+ return '';
64
+ }
65
+ return basePath.replace(/^\/+|\/+$/g, '') + '/';
66
+ };
67
+ /**
68
+ * The static Tokens submenu (Foundations only). Component token sets are mode-resolved runtime
69
+ * records, so the shell bakes only the deterministic Foundations entries. The resolver appends the
70
+ * component token-set records when it fills the dynamic slot. `basePath` must already be
71
+ * `"prefix/"`-normalized.
72
+ */
73
+ const buildTokensFoundationsMenu = (basePath) => [
74
+ {
75
+ title: 'Foundations',
76
+ path: `${basePath}system/tokens/foundations`,
77
+ menu: [
78
+ { title: 'Colors', path: `${basePath}system/tokens/foundations/colors` },
79
+ { title: 'Effects', path: `${basePath}system/tokens/foundations/effects` },
80
+ { title: 'Typography', path: `${basePath}system/tokens/foundations/typography` },
81
+ ],
82
+ },
83
+ ];
84
+ exports.buildTokensFoundationsMenu = buildTokensFoundationsMenu;
85
+ /**
86
+ * Recursively build menu entries from .md files in a directory (auto-scan for sections that do not
87
+ * declare a `menu` in frontmatter). Shared with the app — single source of truth for the auto-scan.
88
+ */
89
+ const buildMenuFromDirectory = (dirPath, urlPrefix) => {
90
+ var _a, _b, _c;
91
+ if (!fs.existsSync(dirPath))
92
+ return [];
93
+ const entries = fs.readdirSync(dirPath);
94
+ const items = [];
95
+ for (const entry of entries) {
96
+ const fullPath = path_1.default.join(dirPath, entry);
97
+ if (fs.lstatSync(fullPath).isDirectory()) {
98
+ const nestedItems = (0, exports.buildMenuFromDirectory)(fullPath, `${urlPrefix}/${entry}`);
99
+ if (nestedItems.length > 0) {
100
+ items.push({ title: (0, lodash_1.startCase)(entry), path: `${urlPrefix}/${entry}`, menu: nestedItems });
101
+ }
102
+ }
103
+ else if (entry.endsWith('.md') && entry !== 'index.md') {
104
+ const slug = entry.replace('.md', '');
105
+ const fullSlugPath = `${urlPrefix}/${slug}`.replace(/^\/+/, '');
106
+ if (exports.KNOWN_PATHS.indexOf(fullSlugPath) >= 0)
107
+ continue;
108
+ const contents = fs.readFileSync(fullPath, 'utf-8');
109
+ const { data: metadata } = (0, gray_matter_1.default)(contents);
110
+ if (metadata.enabled === false)
111
+ continue;
112
+ items.push({
113
+ title: (_b = (_a = metadata.menuTitle) !== null && _a !== void 0 ? _a : metadata.title) !== null && _b !== void 0 ? _b : (0, lodash_1.startCase)(slug),
114
+ path: `/${fullSlugPath}`,
115
+ weight: (_c = metadata.weight) !== null && _c !== void 0 ? _c : 0,
116
+ });
117
+ }
118
+ }
119
+ return items.sort((a, b) => { var _a, _b; return ((_a = a.weight) !== null && _a !== void 0 ? _a : 0) - ((_b = b.weight) !== null && _b !== void 0 ? _b : 0); });
120
+ };
121
+ exports.buildMenuFromDirectory = buildMenuFromDirectory;
122
+ /**
123
+ * Build the full navigation shell from the docs markdown, with all registry entity slots
124
+ * (components/patterns/token sets) emitted empty and tagged `dynamic` for the resolver to fill.
125
+ *
126
+ * Returns the top-level `MenuShellSection[]` sorted by weight — the same shape the docs app's
127
+ * `SectionLink[]` uses, so it serializes directly into `generated/nav-shell.json`.
128
+ */
129
+ const buildMenuShell = (options) => {
130
+ var _a, _b, _c;
131
+ const { docRoot, workingPagesDir } = options;
132
+ const basePath = normalizeBasePath(options.basePath);
133
+ if (!fs.existsSync(docRoot)) {
134
+ return [];
135
+ }
136
+ const docFiles = fs.readdirSync(docRoot);
137
+ let pageFiles = [];
138
+ if (workingPagesDir && fs.existsSync(workingPagesDir)) {
139
+ pageFiles = fs.readdirSync(workingPagesDir);
140
+ }
141
+ // De-duplicate; working-project pages win over package docs (same as the app).
142
+ const list = Array.from(new Set([...docFiles, ...pageFiles]));
143
+ const sections = [];
144
+ for (const fileName of list) {
145
+ const fromPages = pageFiles.includes(fileName);
146
+ const search = fromPages ? path_1.default.resolve(workingPagesDir, fileName) : path_1.default.resolve(docRoot, fileName);
147
+ if (fs.lstatSync(search).isDirectory() ||
148
+ search === path_1.default.resolve(docRoot, 'index.md') ||
149
+ (workingPagesDir && search === path_1.default.resolve(workingPagesDir, 'index.md')) ||
150
+ !fileName.endsWith('md')) {
151
+ continue;
152
+ }
153
+ const contents = fs.readFileSync(search, 'utf-8');
154
+ const { data: metadata } = (0, gray_matter_1.default)(contents);
155
+ if (metadata.enabled === false) {
156
+ continue;
157
+ }
158
+ const filepath = `/${fileName.replace('.md', '')}`;
159
+ const subSections = [];
160
+ if (metadata.menu) {
161
+ for (const key of Object.keys(metadata.menu)) {
162
+ const sub = metadata.menu[key];
163
+ if (sub.components) {
164
+ // Omit `type` entirely when there is no filter (`undefined` is not JSON-serializable).
165
+ const dynamic = typeof sub.components === 'string'
166
+ ? { kind: 'components', type: sub.components }
167
+ : { kind: 'components' };
168
+ subSections.push({ title: sub.title, menu: [], dynamic });
169
+ }
170
+ else if (sub.tokens) {
171
+ // Foundations are baked (deterministic); the resolver appends published component token
172
+ // sets into this slot when it builds the refreshed registry tree.
173
+ subSections.push({ title: 'Tokens', menu: (0, exports.buildTokensFoundationsMenu)(basePath), dynamic: { kind: 'token-components' } });
174
+ }
175
+ else if (sub.patterns) {
176
+ // Always keep the empty patterns slot in the shell for the resolver to fill on refresh.
177
+ subSections.push({ title: sub.title || 'Patterns', menu: [], dynamic: { kind: 'patterns' } });
178
+ }
179
+ else if (sub.enabled !== false) {
180
+ subSections.push(sub);
181
+ }
182
+ }
183
+ }
184
+ else {
185
+ // No frontmatter menu: auto-scan the matching directory (working pages win over docs).
186
+ const dirName = fileName.replace('.md', '');
187
+ const nestedFromDocs = (0, exports.buildMenuFromDirectory)(path_1.default.resolve(docRoot, dirName), `/${dirName}`);
188
+ const nestedFromPages = workingPagesDir
189
+ ? (0, exports.buildMenuFromDirectory)(path_1.default.resolve(workingPagesDir, dirName), `/${dirName}`)
190
+ : [];
191
+ const seenPaths = new Set();
192
+ const children = [];
193
+ for (const item of [...nestedFromPages, ...nestedFromDocs]) {
194
+ if (item.path && !seenPaths.has(item.path)) {
195
+ seenPaths.add(item.path);
196
+ children.push(item);
197
+ }
198
+ }
199
+ // Wrap the scanned children under one labeled group (no `path`) so the side nav renders them as
200
+ // links — a flat subsection carrying a `path` but no `menu` renders nothing. This matches the
201
+ // record-backed nesting produced by the resolver so all modes remain structurally identical.
202
+ if (children.length > 0) {
203
+ subSections.push({ title: (_a = metadata.menuTitle) !== null && _a !== void 0 ? _a : metadata.title, menu: children });
204
+ }
205
+ }
206
+ let external = false;
207
+ if (typeof metadata.external === 'string' &&
208
+ (metadata.external.startsWith('http://') || metadata.external.startsWith('https://') || metadata.external.startsWith('/'))) {
209
+ external = metadata.external;
210
+ }
211
+ sections.push({
212
+ title: (_b = metadata.menuTitle) !== null && _b !== void 0 ? _b : metadata.title,
213
+ external,
214
+ weight: (_c = metadata.weight) !== null && _c !== void 0 ? _c : 0,
215
+ path: filepath,
216
+ subSections,
217
+ });
218
+ }
219
+ return sections.sort((a, b) => a.weight - b.weight);
220
+ };
221
+ exports.buildMenuShell = buildMenuShell;
@@ -0,0 +1,2 @@
1
+ /** Serialize a value with object keys sorted at every nesting level. */
2
+ export declare const stableStringify: (value: unknown) => string;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stableStringify = void 0;
4
+ /** Serialize a value with object keys sorted at every nesting level. */
5
+ const stableStringify = (value) => JSON.stringify(value, (_, nestedValue) => nestedValue && typeof nestedValue === 'object' && !Array.isArray(nestedValue)
6
+ ? Object.fromEntries(Object.entries(nestedValue).sort(([left], [right]) => left.localeCompare(right)))
7
+ : nestedValue);
8
+ exports.stableStringify = stableStringify;
package/docs/cli.md CHANGED
@@ -51,11 +51,13 @@ Usage: handoff-app <cmd> <opts>
51
51
  Commands:
52
52
  fetch [opts] - Fetches the design tokens from the design system
53
53
 
54
- build - Using the current tokens, build various outputs
55
- build:app [opts] - Builds the design system static application
54
+ build [--target static|registry] [opts] - Builds the design system for the target (default: static export)
55
+ build:app [opts] - Builds the design system static application (alias of `build --target static`)
56
56
 
57
57
  start [opts] - Starts the design system in development mode
58
58
 
59
+ db:migrate [opts] - Runs registry database migrations (Drizzle / PostgreSQL). Reads the project config and database env vars, applies the package-owned migration set, and runs independently of `build`.
60
+
59
61
  make
60
62
  make:template <component> <state> [opts] - Creates a new template
61
63
  make:page <component> <state> [opts] - Creates a new page