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
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Normalized store abstraction.
3
+ *
4
+ * Both the workspace (filesystem) store and the future registry (database) store implement one
5
+ * read contract, so the docs read API and other consumers are storage-agnostic.
6
+ * Read methods are typed `Awaitable` so the filesystem implementation can stay synchronous
7
+ * while a database implementation is async behind the same interface.
8
+ *
9
+ * This module is types-only and provider-agnostic.
10
+ */
11
+ import type { TokenSetKind } from '../registry/tokens/sets';
12
+ import type { ComponentListObject, PageListObject, PatternListObject } from '../transformers/preview/types';
13
+ /** A value that may be returned directly or as a promise. */
14
+ export type Awaitable<T> = T | Promise<T>;
15
+ /**
16
+ * Kind of a text source file tracked by the store. Mirrors the registry text-file model.
17
+ * `declaration` is **workspace-only** — registry stores never return it
18
+ * and declarations are synthesized on checkout.
19
+ */
20
+ export type TextFileKind = 'declaration' | 'component' | 'template' | 'style' | 'script' | 'story' | 'docs' | 'markdown' | 'schema' | 'other';
21
+ export type RegistryTextFileKind = Exclude<TextFileKind, 'declaration'>;
22
+ /**
23
+ * A normalized text source file belonging to a component or pattern. Used for checkout,
24
+ * inspection, and publish — never to rebuild previews during docs serving.
25
+ */
26
+ export interface TextFileResource {
27
+ /** Registry-safe relative path of the file within the entity (e.g. `Badge.tsx`). */
28
+ path: string;
29
+ /** Absolute filesystem path the content was read from (workspace store only). */
30
+ absolutePath: string;
31
+ /** Classified file kind. */
32
+ kind: TextFileKind;
33
+ /** File content. */
34
+ content: string;
35
+ /** Content type to serve/transfer the file with. */
36
+ contentType: string;
37
+ }
38
+ export type RegistryTextFileResource = TextFileResource & {
39
+ kind: RegistryTextFileKind;
40
+ };
41
+ /**
42
+ * A reference to a source file resolvable by a store. For the filesystem store this is an
43
+ * absolute path; the contract keeps it opaque so a database store can use its own reference form.
44
+ */
45
+ export interface SourceReference {
46
+ /** Absolute filesystem path (workspace store). */
47
+ absolutePath: string;
48
+ }
49
+ /**
50
+ * Read contract implemented by every component store backing. Consumers depend only on this so
51
+ * they never need to know whether the filesystem or a database is active.
52
+ */
53
+ export interface ComponentStore {
54
+ /** All normalized component records, identical in shape to today's runtime records. */
55
+ list(): Awaitable<ComponentListObject[]>;
56
+ /** A single normalized component record by stable id, or `null` when absent. */
57
+ get(id: string): Awaitable<ComponentListObject | null>;
58
+ /** Read a source file by reference, or `null` when it cannot be resolved. */
59
+ getSource(ref: SourceReference): Awaitable<TextFileResource | null>;
60
+ /**
61
+ * The entity's declaration plus every `entries`-referenced source file, for checkout/publish.
62
+ * Returns an empty array when the entity is unknown.
63
+ */
64
+ getRelatedSourceFiles(id: string): Awaitable<TextFileResource[]>;
65
+ }
66
+ /**
67
+ * Read contract implemented by every pattern store backing. Same shape as {@link ComponentStore}
68
+ * over pattern records.
69
+ */
70
+ export interface PatternStore {
71
+ /** All normalized pattern records, identical in shape to today's runtime records. */
72
+ list(): Awaitable<PatternListObject[]>;
73
+ /** A single normalized pattern record by stable id, or `null` when absent. */
74
+ get(id: string): Awaitable<PatternListObject | null>;
75
+ /** Read a source file by reference, or `null` when it cannot be resolved. */
76
+ getSource(ref: SourceReference): Awaitable<TextFileResource | null>;
77
+ /**
78
+ * The pattern's declaration plus every `entries`-referenced source file, for checkout/publish.
79
+ * Returns an empty array when the pattern is unknown.
80
+ */
81
+ getRelatedSourceFiles(id: string): Awaitable<TextFileResource[]>;
82
+ }
83
+ /**
84
+ * Read contract implemented by every page store backing. Same shape as {@link PatternStore} over
85
+ * page records. A page's single related source file is its verbatim `.md` (kind `markdown`).
86
+ */
87
+ export interface PageStore {
88
+ /** All normalized page records. */
89
+ list(): Awaitable<PageListObject[]>;
90
+ /** A single normalized page record by stable id, or `null` when absent. */
91
+ get(id: string): Awaitable<PageListObject | null>;
92
+ /** Read a source file by reference, or `null` when it cannot be resolved. */
93
+ getSource(ref: SourceReference): Awaitable<TextFileResource | null>;
94
+ /** The page's verbatim `.md` source file, for checkout/publish. Empty when the page is unknown. */
95
+ getRelatedSourceFiles(id: string): Awaitable<TextFileResource[]>;
96
+ }
97
+ /**
98
+ * A logical token set as the store exposes it: its stable id, kind, and the exact extracted token
99
+ * slice (a `record` of `IColorObject[]`/…/`IFileComponentObject`). Consumers read the whole set and
100
+ * group/sort/look up in memory, so there is no individual-token accessor.
101
+ */
102
+ export interface TokenSetRecord {
103
+ id: string;
104
+ kind: TokenSetKind;
105
+ record: unknown;
106
+ }
107
+ /** A generated token artifact (CSS/SCSS/Style Dictionary/types/custom output) as the store returns it. */
108
+ export interface TokenArtifactResource {
109
+ /** Registry-safe relative output path under the tokens dir (e.g. `css/colors.css`). */
110
+ path: string;
111
+ /** Logical format label (`css`|`scss`|`types`|`styleDictionary`|custom). */
112
+ format: string;
113
+ content: string;
114
+ contentType: string;
115
+ }
116
+ /**
117
+ * Read contract implemented by every token store backing. The filesystem backing derives sets from
118
+ * the local `tokens.json` + generated files; the registry backing reads the `token_sets` /
119
+ * `token_artifacts` tables. Consumers depend only on this so token behavior is consistent across
120
+ * modes even though the storage differs.
121
+ */
122
+ export interface TokenStore {
123
+ /** All logical token sets (three foundation sets + one per component). */
124
+ listSets(): Awaitable<TokenSetRecord[]>;
125
+ /** A single set by stable id, or `null` when absent. */
126
+ getSet(id: string): Awaitable<TokenSetRecord | null>;
127
+ /** Every generated artifact owned by a set. Empty when the set is unknown or has no output. */
128
+ getArtifacts(id: string): Awaitable<TokenArtifactResource[]>;
129
+ /** One generated artifact for a set by logical format, or `null` when absent. */
130
+ getArtifact(id: string, format: string): Awaitable<TokenArtifactResource | null>;
131
+ }
132
+ /**
133
+ * Lightweight metadata for one published asset. Collection/list reads return only this shape - never
134
+ * the binary body - so large collections do not produce oversized responses.
135
+ */
136
+ export interface AssetMetadata {
137
+ /** Collection the asset belongs to (`icons`|`logos`|`fonts`). */
138
+ collection: string;
139
+ /** Registry-safe logical path within the collection (e.g. `assets/icons/add.svg`, `icons.zip`). */
140
+ path: string;
141
+ /** Human-facing asset name. */
142
+ name: string;
143
+ /** Content type to serve the asset with. */
144
+ contentType: string;
145
+ /** Byte length. */
146
+ size: number;
147
+ /** SHA-256 of the bytes (hex) - content identity + ETag. */
148
+ contentHash: string;
149
+ /** Free-form asset metadata carried from extraction (icon index, description, …). */
150
+ metadata?: Record<string, unknown> | null;
151
+ }
152
+ /**
153
+ * An asset's resolved content: inline bytes (filesystem store, or a DB-backed blob), or a redirect
154
+ * URL when an object-storage provider serves the content directly.
155
+ */
156
+ export interface AssetContentResource extends AssetMetadata {
157
+ /** Inline bytes, when the content is served from the filesystem or an inline DB blob. */
158
+ body?: Buffer;
159
+ /** Signed/public provider URL to redirect to, when an object-storage provider serves the content. */
160
+ redirectUrl?: string;
161
+ }
162
+ /**
163
+ * Read contract implemented by every asset store backing. The filesystem backing derives collections
164
+ * from the generated `public/api` tree + archives; the registry backing reads the `assets` /
165
+ * `asset_collections` tables and resolves content through the configured storage provider. List
166
+ * methods return metadata only; content is fetched one asset at a time.
167
+ */
168
+ export interface AssetStore {
169
+ /** All collections that have any published assets. */
170
+ listCollections(): Awaitable<string[]>;
171
+ /** Lightweight metadata for every asset in a collection (no bodies). Empty when unknown. */
172
+ listAssets(collection: string): Awaitable<AssetMetadata[]>;
173
+ /** Metadata for one asset by collection + logical path, or `null` when absent. */
174
+ getAsset(collection: string, path: string): Awaitable<AssetMetadata | null>;
175
+ /** One asset's resolved content (bytes or redirect), or `null` when absent. */
176
+ getAssetContent(collection: string, path: string): Awaitable<AssetContentResource | null>;
177
+ }
178
+ /** Convenience pairing of the stores backing one runtime. */
179
+ export interface HandoffStore {
180
+ components: ComponentStore;
181
+ patterns: PatternStore;
182
+ pages: PageStore;
183
+ tokens: TokenStore;
184
+ assets: AssetStore;
185
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * Normalized store abstraction.
4
+ *
5
+ * Both the workspace (filesystem) store and the future registry (database) store implement one
6
+ * read contract, so the docs read API and other consumers are storage-agnostic.
7
+ * Read methods are typed `Awaitable` so the filesystem implementation can stay synchronous
8
+ * while a database implementation is async behind the same interface.
9
+ *
10
+ * This module is types-only and provider-agnostic.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -22,7 +22,7 @@ const react_element_to_jsx_string_1 = __importDefault(require("react-element-to-
22
22
  const logger_1 = require("../../utils/logger");
23
23
  const docgen_1 = require("../docgen");
24
24
  const component_1 = require("../preview/component");
25
- const css_1 = require("../preview/component/css");
25
+ const shared_artifacts_1 = require("../preview/component/shared-artifacts");
26
26
  const html_1 = require("../utils/html");
27
27
  const module_1 = require("../utils/module");
28
28
  const schema_1 = require("../utils/schema");
@@ -235,15 +235,20 @@ function buildUsageSnippet(componentName, importStatement, element, properties,
235
235
  importStatement,
236
236
  });
237
237
  }
238
- function createHtmlDocument(componentId, previewTitle, renderedHtml) {
239
- var _a, _b, _c;
238
+ function createHtmlDocument(componentId, previewTitle, renderedHtml, sharedArtifacts, componentArtifacts) {
239
+ var _a;
240
+ const basePath = (_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : '';
241
+ // Shared/global and component-owned artifacts are referenced only when present, so a CSF preview
242
+ // never requests an absent optional artifact. CSF previews stay static; the global script runs
243
+ // only for its top-level side effects when a global JS entry exists.
244
+ const sharedStyleLinks = (0, shared_artifacts_1.renderSharedStyleLinks)(sharedArtifacts, basePath);
245
+ const componentStyleLink = (0, shared_artifacts_1.renderComponentStyleLink)(componentArtifacts, componentId, basePath);
246
+ const globalScriptTag = (0, shared_artifacts_1.renderGlobalScriptTag)(sharedArtifacts, basePath);
240
247
  return `<!DOCTYPE html>
241
248
  <html lang="en">
242
249
  <head>
243
- <meta charset="UTF-8" />
244
- <link rel="stylesheet" href="${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component/${css_1.MAIN_COMPONENT_CSS_FILE}" />
245
- <link rel="stylesheet" href="${(_b = process.env.HANDOFF_APP_BASE_PATH) !== null && _b !== void 0 ? _b : ''}/api/component/${componentId}.css" />
246
- <link rel="stylesheet" href="${(_c = process.env.HANDOFF_APP_BASE_PATH) !== null && _c !== void 0 ? _c : ''}/assets/css/preview.css" />
250
+ <meta charset="UTF-8" />${sharedStyleLinks ? `\n ${sharedStyleLinks}` : ''}${componentStyleLink ? `\n ${componentStyleLink}` : ''}
251
+ <link rel="stylesheet" href="${basePath}/assets/css/preview.css" />${globalScriptTag ? `\n ${globalScriptTag}` : ''}
247
252
  <title>${previewTitle}</title>
248
253
  </head>
249
254
  <body>
@@ -359,6 +364,10 @@ function csfRenderPlugin(componentData, _documentationComponents, handoff) {
359
364
  };
360
365
  }
361
366
  componentData.properties = (0, schema_1.ensureIds)(generatedProperties);
367
+ // Resolve which shared/global and component-owned artifacts exist so generated HTML references
368
+ // them only when present. These are built before component HTML, so this reflects final state.
369
+ const sharedArtifacts = (0, shared_artifacts_1.resolveSharedArtifactPresence)(handoff);
370
+ const componentArtifacts = (0, shared_artifacts_1.resolveComponentArtifactPresence)(handoff, componentId);
362
371
  let lastHtml = '';
363
372
  for (const [previewKey, preview] of Object.entries(componentData.previews)) {
364
373
  const storyKey = preview.sourcePreview && storyMap[preview.sourcePreview]
@@ -367,7 +376,7 @@ function csfRenderPlugin(componentData, _documentationComponents, handoff) {
367
376
  const storyValue = storyKey ? storyMap[storyKey] : undefined;
368
377
  const storyArgs = preview.values || {};
369
378
  const rendered = safeRenderToHtml(meta, storyValue, storyArgs);
370
- const html = yield (0, html_1.formatHtml)(createHtmlDocument(componentId, preview.title, rendered));
379
+ const html = yield (0, html_1.formatHtml)(createHtmlDocument(componentId, preview.title, rendered, sharedArtifacts, componentArtifacts));
371
380
  const fileName = `${componentId}-${previewKey}.html`;
372
381
  this.emitFile({
373
382
  type: 'asset',
@@ -17,6 +17,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
17
17
  const handlebars_1 = __importDefault(require("handlebars"));
18
18
  const path_1 = __importDefault(require("path"));
19
19
  const logger_1 = require("../../utils/logger");
20
+ const shared_artifacts_1 = require("../preview/component/shared-artifacts");
20
21
  const handlebars_2 = require("../utils/handlebars");
21
22
  const html_1 = require("../utils/html");
22
23
  const string_1 = require("../utils/string");
@@ -61,7 +62,7 @@ function processComponentInstances(componentData, documentationComponents) {
61
62
  * @param injectFieldWrappers - Whether to inject field wrappers for inspection
62
63
  * @returns Rendered HTML string
63
64
  */
64
- function renderHandlebarsTemplate(template, componentData, previewData, injectFieldWrappers, handoff) {
65
+ function renderHandlebarsTemplate(template, componentData, previewData, injectFieldWrappers, handoff, sharedArtifacts, componentArtifacts) {
65
66
  return __awaiter(this, void 0, void 0, function* () {
66
67
  var _a, _b;
67
68
  // Register Handlebars helpers with current injection state
@@ -72,6 +73,8 @@ function renderHandlebarsTemplate(template, componentData, previewData, injectFi
72
73
  title: componentData.title
73
74
  }, previewData, {
74
75
  includeSharedStyles: Boolean(componentData.sharedStyles),
76
+ sharedArtifacts,
77
+ componentArtifacts,
75
78
  });
76
79
  const compiled = handlebars_1.default.compile(template)(context);
77
80
  return yield (0, html_1.formatHtmlWithWrapper)(compiled);
@@ -143,13 +146,17 @@ function handlebarsPreviewsPlugin(componentData, documentationComponents, handof
143
146
  };
144
147
  }
145
148
  const generatedPreviews = {};
149
+ // Resolve which shared/global and component-owned artifacts exist so generated HTML references
150
+ // them only when present. These are built before component HTML, so this reflects final state.
151
+ const sharedArtifacts = (0, shared_artifacts_1.resolveSharedArtifactPresence)(handoff);
152
+ const componentArtifacts = (0, shared_artifacts_1.resolveComponentArtifactPresence)(handoff, componentId);
146
153
  // Generate previews for each variation
147
154
  for (const previewKey in componentData.previews) {
148
155
  try {
149
156
  const previewData = componentData.previews[previewKey];
150
157
  // Render both normal and inspect modes
151
- const normalModeHtml = yield renderHandlebarsTemplate(templateContent, componentData, previewData, false, handoff);
152
- const inspectModeHtml = yield renderHandlebarsTemplate(templateContent, componentData, previewData, true, handoff);
158
+ const normalModeHtml = yield renderHandlebarsTemplate(templateContent, componentData, previewData, false, handoff, sharedArtifacts, componentArtifacts);
159
+ const inspectModeHtml = yield renderHandlebarsTemplate(templateContent, componentData, previewData, true, handoff, sharedArtifacts, componentArtifacts);
153
160
  // Emit preview files
154
161
  emitPreviewFiles(componentId, previewKey, normalModeHtml, inspectModeHtml, (file) => this.emitFile(file));
155
162
  generatedPreviews[previewKey] = normalModeHtml;
@@ -19,9 +19,10 @@ const path_1 = __importDefault(require("path"));
19
19
  const react_1 = __importDefault(require("react"));
20
20
  const server_1 = __importDefault(require("react-dom/server"));
21
21
  const vite_1 = require("vite");
22
+ const url_1 = require("../../artifacts/url");
22
23
  const logger_1 = require("../../utils/logger");
23
24
  const docgen_1 = require("../docgen");
24
- const css_1 = require("../preview/component/css");
25
+ const shared_artifacts_1 = require("../preview/component/shared-artifacts");
25
26
  const build_1 = require("../utils/build");
26
27
  const html_1 = require("../utils/html");
27
28
  const module_1 = require("../utils/module");
@@ -40,6 +41,14 @@ const PLUGIN_CONSTANTS = {
40
41
  PROPS_SCRIPT_ID: '__APP_PROPS__',
41
42
  INSPECT_SUFFIX: '-inspect',
42
43
  };
44
+ /**
45
+ * Suffix of the component-owned client/hydration artifact.
46
+ * One artifact per component (`component/<id>.client.js`) drives hydration for every preview of
47
+ * that component; it is referenced via the canonical artifact URL.
48
+ */
49
+ const CLIENT_ARTIFACT_SUFFIX = 'client.js';
50
+ /** Logical artifact path of a component's client/hydration bundle. */
51
+ const clientArtifactPath = (componentId) => `component/${componentId}.${CLIENT_ARTIFACT_SUFFIX}`;
43
52
  /**
44
53
  * Loads and processes component schema using hierarchical approach
45
54
  * @param componentData - Component transformation data
@@ -88,43 +97,90 @@ function loadComponentSchemaAndModule(componentData, componentPath, handoff) {
88
97
  });
89
98
  }
90
99
  /**
91
- * Generates client-side hydration source code
100
+ * Generates client-side hydration source code for the component-owned client artifact.
101
+ *
102
+ * The bundle is shared by every preview of a component and is emitted once as
103
+ * `component/<id>.client.js`. It hydrates two shapes of mount point so the same artifact serves both
104
+ * a standalone preview document and a composed pattern document without per-fragment inline code:
105
+ *
106
+ * - The standalone preview's `#${ROOT_ELEMENT_ID}` element paired with the `#${PROPS_SCRIPT_ID}`
107
+ * props script (one mount per document).
108
+ * - Any number of pattern mount points marked `[data-handoff-component="<id>"]`, each carrying a
109
+ * `data-handoff-props` attribute naming its (namespaced) props script id. A pattern document
110
+ * references this artifact once per component and the bundle hydrates every matching mount.
111
+ *
112
+ * @param componentId - Component identifier used to match pattern mount points to this bundle
92
113
  * @param componentPath - Path to the component file
93
114
  * @returns Client-side hydration source code
94
115
  */
95
- function generateClientHydrationSource(componentPath) {
116
+ function generateClientHydrationSource(componentId, componentPath) {
96
117
  return `
97
118
  import React from 'react';
98
119
  import { hydrateRoot } from 'react-dom/client';
99
120
  import Component from '${(0, vite_1.normalizePath)(componentPath)}';
100
121
 
101
- const raw = document.getElementById('${PLUGIN_CONSTANTS.PROPS_SCRIPT_ID}')?.textContent || '{}';
102
- const props = JSON.parse(raw);
103
- hydrateRoot(document.getElementById('${PLUGIN_CONSTANTS.ROOT_ELEMENT_ID}'), <Component {...props} />);
122
+ const parseProps = (propsId) => {
123
+ const raw = propsId ? document.getElementById(propsId)?.textContent : '{}';
124
+ try {
125
+ return JSON.parse(raw || '{}');
126
+ } catch (_) {
127
+ return {};
128
+ }
129
+ };
130
+
131
+ const standaloneRoot = document.getElementById('${PLUGIN_CONSTANTS.ROOT_ELEMENT_ID}');
132
+ if (standaloneRoot && !standaloneRoot.hasAttribute('data-handoff-component')) {
133
+ hydrateRoot(standaloneRoot, <Component {...parseProps('${PLUGIN_CONSTANTS.PROPS_SCRIPT_ID}')} />);
134
+ }
135
+
136
+ const patternMounts = document.querySelectorAll('[data-handoff-component=' + JSON.stringify('${componentId}') + ']');
137
+ patternMounts.forEach((mount) => {
138
+ hydrateRoot(mount, <Component {...parseProps(mount.getAttribute('data-handoff-props'))} />);
139
+ });
104
140
  `;
105
141
  }
106
142
  /**
107
- * Generates complete HTML document with SSR content and hydration
143
+ * Generates complete HTML document with SSR content and a reference to the component-owned client
144
+ * artifact. The preview HTML carries only document structure, server-rendered markup, preview data,
145
+ * stylesheets, and standard artifact references. Interactive React previews treat the client
146
+ * artifact as a required reference, so the script tag surfaces a clear, visible failure if the
147
+ * artifact cannot be loaded.
108
148
  * @param componentId - Component identifier
109
149
  * @param previewTitle - Title for the preview
110
150
  * @param renderedHtml - Server-rendered HTML content
111
- * @param clientJs - Client-side JavaScript bundle
112
151
  * @param props - Component props as JSON
113
152
  * @returns Complete HTML document
114
153
  */
115
- function generateHtmlDocument(componentId, previewTitle, renderedHtml, clientJs, props) {
116
- var _a, _b, _c;
154
+ function generateHtmlDocument(componentId, previewTitle, renderedHtml, props, sharedArtifacts, componentArtifacts) {
155
+ var _a;
156
+ const basePath = (_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : '';
157
+ const clientArtifactUrl = (0, url_1.buildArtifactUrl)(clientArtifactPath(componentId), basePath);
158
+ const clientLoadErrorMessage = `Failed to load the React client artifact (${clientArtifactPath(componentId)}). This preview cannot hydrate.`;
159
+ // The handler is JS embedded in a double-quoted HTML attribute, so any `"` (e.g. from
160
+ // JSON.stringify) and `&` must be entity-encoded or the attribute terminates early and
161
+ // breaks HTML parsing. Browsers decode the entities before the JS engine runs the handler.
162
+ const clientLoadErrorHandler = `document.getElementById('${PLUGIN_CONSTANTS.ROOT_ELEMENT_ID}').setAttribute('data-handoff-client-error', '1');console.error(${JSON.stringify(clientLoadErrorMessage)});`
163
+ .replace(/&/g, '&amp;')
164
+ .replace(/"/g, '&quot;');
165
+ // Shared/global stylesheets (`component/main.css`) and the component's own stylesheet
166
+ // (`component/<id>.css`) are emitted only when present so absent optional artifacts never produce a
167
+ // missing-file browser request.
168
+ const sharedStyleLinks = (0, shared_artifacts_1.renderSharedStyleLinks)(sharedArtifacts, basePath);
169
+ const componentStyleLink = (0, shared_artifacts_1.renderComponentStyleLink)(componentArtifacts, componentId, basePath);
170
+ // The global script (`component/main.js`) is a classic tag placed before the client module so it
171
+ // runs before dependent component-specific scripts when present.
172
+ const globalScriptTag = (0, shared_artifacts_1.renderGlobalScriptTag)(sharedArtifacts, basePath);
117
173
  return `<!DOCTYPE html>
118
174
  <html lang="en">
119
175
  <head>
120
- <meta charset="UTF-8" />
121
- <link rel="stylesheet" href="${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component/${css_1.MAIN_COMPONENT_CSS_FILE}" />
122
- <link rel="stylesheet" href="${(_b = process.env.HANDOFF_APP_BASE_PATH) !== null && _b !== void 0 ? _b : ''}/api/component/${componentId}.css" />
123
- <link rel="stylesheet" href="${(_c = process.env.HANDOFF_APP_BASE_PATH) !== null && _c !== void 0 ? _c : ''}/assets/css/preview.css" />
124
- <script id="${PLUGIN_CONSTANTS.PROPS_SCRIPT_ID}" type="application/json">${JSON.stringify(props)}</script>
125
- <script type="module">
126
- ${clientJs}
127
- </script>
176
+ <meta charset="UTF-8" />${sharedStyleLinks ? `\n ${sharedStyleLinks}` : ''}${componentStyleLink ? `\n ${componentStyleLink}` : ''}
177
+ <link rel="stylesheet" href="${basePath}/assets/css/preview.css" />
178
+ <script id="${PLUGIN_CONSTANTS.PROPS_SCRIPT_ID}" type="application/json">${JSON.stringify(props)}</script>${globalScriptTag ? `\n ${globalScriptTag}` : ''}
179
+ <script
180
+ type="module"
181
+ src="${clientArtifactUrl}"
182
+ onerror="${clientLoadErrorHandler}"
183
+ ></script>
128
184
  <title>${previewTitle}</title>
129
185
  </head>
130
186
  <body>
@@ -206,44 +262,55 @@ function ssrRenderPlugin(componentData, documentationComponents, handoff) {
206
262
  }
207
263
  }
208
264
  }
265
+ // Build the component-owned client/hydration bundle once and emit it as a single
266
+ // `component/<id>.client.js` artifact. The hydration source only imports the component and
267
+ // reads props from the in-document `__APP_PROPS__` element, so it is identical across every
268
+ // preview of this component.
269
+ const clientHydrationSource = generateClientHydrationSource(componentId, componentPath);
270
+ const clientBuildConfig = Object.assign(Object.assign({}, build_1.DEFAULT_CLIENT_BUILD_CONFIG), { logLevel: 'silent', stdin: {
271
+ contents: clientHydrationSource,
272
+ resolveDir: process.cwd(),
273
+ loader: 'tsx',
274
+ }, plugins: [(0, build_1.createReactResolvePlugin)(handoff.workingPath, handoff.modulePath)] });
275
+ const finalClientBuildConfig = ((_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.clientBuildConfig)
276
+ ? handoff.config.hooks.clientBuildConfig(clientBuildConfig)
277
+ : clientBuildConfig;
278
+ let clientBundleJs;
279
+ try {
280
+ const bundledClient = yield esbuild_1.default.build(finalClientBuildConfig);
281
+ if (bundledClient.warnings.length > 0) {
282
+ const messages = yield esbuild_1.default.formatMessages(bundledClient.warnings, { kind: 'warning', color: true });
283
+ messages.forEach((msg) => logger_1.Logger.warn(msg));
284
+ }
285
+ clientBundleJs = bundledClient.outputFiles[0].text;
286
+ }
287
+ catch (error) {
288
+ // The client artifact is a required reference for interactive React previews. If it cannot be
289
+ // built, do not emit preview HTML that points at a missing artifact — fail clearly instead.
290
+ logger_1.Logger.error(`Failed to build client bundle for ${componentId}`);
291
+ if (error.errors) {
292
+ const messages = yield esbuild_1.default.formatMessages(error.errors, { kind: 'error', color: true });
293
+ messages.forEach((msg) => logger_1.Logger.error(msg));
294
+ }
295
+ return;
296
+ }
297
+ this.emitFile({
298
+ type: 'asset',
299
+ fileName: `${componentId}.${CLIENT_ARTIFACT_SUFFIX}`,
300
+ source: clientBundleJs,
301
+ });
209
302
  let finalHtml = '';
303
+ // Resolve which shared/global and component-owned artifacts exist so generated HTML references
304
+ // them only when present. These are built before component HTML, so this reflects final state.
305
+ const sharedArtifacts = (0, shared_artifacts_1.resolveSharedArtifactPresence)(handoff);
306
+ const componentArtifacts = (0, shared_artifacts_1.resolveComponentArtifactPresence)(handoff, componentId);
210
307
  // Generate previews for each variation
211
308
  for (const previewKey in componentData.previews) {
212
309
  const previewProps = componentData.previews[previewKey].values;
213
310
  // Server-side render the component
214
311
  const serverRenderedHtml = server_1.default.renderToString(react_1.default.createElement(ReactComponent, previewProps));
215
312
  const formattedHtml = yield (0, html_1.formatHtml)(serverRenderedHtml);
216
- // Generate client-side hydration code
217
- const clientHydrationSource = generateClientHydrationSource(componentPath);
218
- // Build client-side bundle
219
- const clientBuildConfig = Object.assign(Object.assign({}, build_1.DEFAULT_CLIENT_BUILD_CONFIG), { logLevel: 'silent', stdin: {
220
- contents: clientHydrationSource,
221
- resolveDir: process.cwd(),
222
- loader: 'tsx',
223
- }, plugins: [(0, build_1.createReactResolvePlugin)(handoff.workingPath, handoff.modulePath)] });
224
- // Apply user's client build config hook if provided
225
- const finalClientBuildConfig = ((_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.clientBuildConfig)
226
- ? handoff.config.hooks.clientBuildConfig(clientBuildConfig)
227
- : clientBuildConfig;
228
- let clientBundleJs;
229
- try {
230
- const bundledClient = yield esbuild_1.default.build(finalClientBuildConfig);
231
- if (bundledClient.warnings.length > 0) {
232
- const messages = yield esbuild_1.default.formatMessages(bundledClient.warnings, { kind: 'warning', color: true });
233
- messages.forEach((msg) => logger_1.Logger.warn(msg));
234
- }
235
- clientBundleJs = bundledClient.outputFiles[0].text;
236
- }
237
- catch (error) {
238
- logger_1.Logger.error(`Failed to build client bundle for ${componentId}`);
239
- if (error.errors) {
240
- const messages = yield esbuild_1.default.formatMessages(error.errors, { kind: 'error', color: true });
241
- messages.forEach((msg) => logger_1.Logger.error(msg));
242
- }
243
- continue;
244
- }
245
- // Generate complete HTML document
246
- finalHtml = generateHtmlDocument(componentId, componentData.previews[previewKey].title, formattedHtml, clientBundleJs, previewProps);
313
+ finalHtml = generateHtmlDocument(componentId, componentData.previews[previewKey].title, formattedHtml, previewProps, sharedArtifacts, componentArtifacts);
247
314
  // Emit preview files
248
315
  this.emitFile({
249
316
  type: 'asset',
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.readComponentMetadataApi = exports.readComponentApi = exports.writeComponentApi = exports.getAPIPath = exports.sanitizeComponentApiData = void 0;
16
16
  const fs_extra_1 = __importDefault(require("fs-extra"));
17
17
  const path_1 = __importDefault(require("path"));
18
+ const url_1 = require("../../../artifacts/url");
18
19
  const previews_1 = require("./previews");
19
20
  const sanitizeComponentApiData = (component) => {
20
21
  if (!component)
@@ -119,6 +120,7 @@ exports.readComponentApi = readComponentApi;
119
120
  * @returns The component summary or null if not found
120
121
  */
121
122
  const readComponentMetadataApi = (handoff, id) => __awaiter(void 0, void 0, void 0, function* () {
123
+ var _a;
122
124
  const componentData = yield (0, exports.readComponentApi)(handoff, id);
123
125
  if (!componentData) {
124
126
  return null;
@@ -137,7 +139,7 @@ const readComponentMetadataApi = (handoff, id) => __awaiter(void 0, void 0, void
137
139
  tags: componentData.tags ? componentData.tags : [],
138
140
  properties: componentData.properties,
139
141
  previews: (0, previews_1.getDocumentedPreviews)(componentData.previews),
140
- path: `/api/component/${id}.json`,
142
+ path: (0, url_1.buildComponentDetailUrl)(id, (_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''),
141
143
  };
142
144
  });
143
145
  exports.readComponentMetadataApi = readComponentMetadataApi;
@@ -21,6 +21,8 @@ const css_1 = require("./css");
21
21
  const javascript_1 = require("./javascript");
22
22
  const COMPONENT_JS_RESERVED_FILES = new Set([javascript_1.MAIN_COMPONENT_JS_FILE]);
23
23
  const COMPONENT_CSS_RESERVED_FILES = new Set([css_1.MAIN_COMPONENT_CSS_FILE, css_1.SHARED_COMPONENT_CSS_FILE]);
24
+ /** Suffix of a component-owned client/hydration artifact (`<id>.client.js`), see ssr-render plugin. */
25
+ const CLIENT_ARTIFACT_SUFFIX = '.client.js';
24
26
  const getComponentApiPath = (handoff) => path_1.default.resolve((0, api_1.getAPIPath)(handoff), 'component');
25
27
  exports.getComponentApiPath = getComponentApiPath;
26
28
  const getComponentPreviewKeys = (handoff, componentId) => __awaiter(void 0, void 0, void 0, function* () {
@@ -81,7 +83,9 @@ const syncComponentArtifacts = (handoff) => __awaiter(void 0, void 0, void 0, fu
81
83
  const parsed = path_1.default.parse(entry);
82
84
  if (parsed.ext === '.json' || parsed.ext === '.js' || parsed.ext === '.css') {
83
85
  if (!COMPONENT_JS_RESERVED_FILES.has(entry) && !COMPONENT_CSS_RESERVED_FILES.has(entry)) {
84
- discoveredArtifactIds.add(parsed.name);
86
+ // `<id>.client.js` is owned by `<id>`, not by a phantom `<id>.client` component — strip the
87
+ // compound suffix so the owner id matches the real component (path.parse would yield `<id>.client`).
88
+ discoveredArtifactIds.add(entry.endsWith(CLIENT_ARTIFACT_SUFFIX) ? entry.slice(0, -CLIENT_ARTIFACT_SUFFIX.length) : parsed.name);
85
89
  }
86
90
  }
87
91
  }
@@ -91,6 +95,9 @@ const syncComponentArtifacts = (handoff) => __awaiter(void 0, void 0, void 0, fu
91
95
  if (yield fs_extra_1.default.pathExists(path_1.default.resolve(componentPath, `${componentId}.js`))) {
92
96
  validFiles.add(`${componentId}.js`);
93
97
  }
98
+ if (yield fs_extra_1.default.pathExists(path_1.default.resolve(componentPath, `${componentId}${CLIENT_ARTIFACT_SUFFIX}`))) {
99
+ validFiles.add(`${componentId}${CLIENT_ARTIFACT_SUFFIX}`);
100
+ }
94
101
  if (yield fs_extra_1.default.pathExists(path_1.default.resolve(componentPath, `${componentId}.css`))) {
95
102
  validFiles.add(`${componentId}.css`);
96
103
  }
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.ComponentSegment = void 0;
27
27
  exports.processComponents = processComponents;
28
28
  const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
29
+ const url_1 = require("../../../artifacts/url");
29
30
  const cache_1 = require("../../../cache");
30
31
  const duration_1 = require("../../../utils/duration");
31
32
  const logger_1 = require("../../../utils/logger");
@@ -149,11 +150,18 @@ const createComponentBuildPlan = (segmentToProcess, existingData) => {
149
150
  */
150
151
  function processComponents(handoff, id, segmentToProcess, options) {
151
152
  return __awaiter(this, void 0, void 0, function* () {
152
- var _a, _b, _c, _d, _e, _f, _g;
153
+ var _a, _b, _c, _d;
153
154
  const result = [];
154
155
  const documentationObject = yield handoff.getDocumentationObject();
155
156
  const components = (_a = documentationObject === null || documentationObject === void 0 ? void 0 : documentationObject.components) !== null && _a !== void 0 ? _a : {};
156
- const runtimeComponents = (_d = (_c = (_b = handoff.runtimeConfig) === null || _b === void 0 ? void 0 : _b.entries) === null || _c === void 0 ? void 0 : _c.components) !== null && _d !== void 0 ? _d : {};
157
+ // Resolve the component set through the storage-agnostic store (v2). The filesystem store is a
158
+ // read view over `runtimeConfig.entries.components`, so these are the same records the build has
159
+ // always used — rebuilt into the id-keyed map the build logic below expects.
160
+ const runtimeComponents = {};
161
+ for (const component of yield handoff.store.components.list()) {
162
+ if (component)
163
+ runtimeComponents[component.id] = component;
164
+ }
157
165
  const allComponentIds = Object.keys(runtimeComponents);
158
166
  if (id && !runtimeComponents[id]) {
159
167
  yield (0, artifacts_1.removeComponentApi)(handoff, id);
@@ -188,7 +196,7 @@ function processComponents(handoff, id, segmentToProcess, options) {
188
196
  for (const componentId of allComponentIds) {
189
197
  const currentFileStates = yield (0, cache_1.computeComponentFileStates)(handoff, componentId);
190
198
  componentFileStatesMap.set(componentId, currentFileStates);
191
- const cachedEntry = (_e = cache === null || cache === void 0 ? void 0 : cache.components) === null || _e === void 0 ? void 0 : _e[componentId];
199
+ const cachedEntry = (_b = cache === null || cache === void 0 ? void 0 : cache.components) === null || _b === void 0 ? void 0 : _b[componentId];
192
200
  if (!cachedEntry) {
193
201
  logger_1.Logger.info(`Component '${componentId}': new component, will build`);
194
202
  componentsToBuild.add(componentId);
@@ -337,7 +345,7 @@ function processComponents(handoff, id, segmentToProcess, options) {
337
345
  * Run validation if explicitly requested and a hook is configured.
338
346
  * This allows custom logic to assess the validity of the generated component data.
339
347
  */
340
- if (buildPlan.validationMode && ((_g = (_f = handoff.config) === null || _f === void 0 ? void 0 : _f.hooks) === null || _g === void 0 ? void 0 : _g.validateComponent)) {
348
+ if (buildPlan.validationMode && ((_d = (_c = handoff.config) === null || _c === void 0 ? void 0 : _c.hooks) === null || _d === void 0 ? void 0 : _d.validateComponent)) {
341
349
  const validationResults = yield handoff.config.hooks.validateComponent(data);
342
350
  data.validations = validationResults;
343
351
  }
@@ -400,7 +408,7 @@ const buildComponentSummary = (id, data) => {
400
408
  tags: data.tags ? data.tags : [],
401
409
  properties: data.properties,
402
410
  previews: (0, previews_1.getDocumentedPreviews)(data.previews),
403
- path: `${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component/${id}.json`,
411
+ path: (0, url_1.buildComponentDetailUrl)(id, (_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''),
404
412
  };
405
413
  };
406
414
  exports.default = processComponents;