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
@@ -1,7 +1,14 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
+ import { buildPatternDetailUrl } from '../../../artifacts/url';
3
4
  import Handoff from '../../../index';
4
5
  import { Logger } from '../../../utils/logger';
6
+ import {
7
+ resolveComponentArtifactPresence,
8
+ resolveSharedArtifactPresence,
9
+ type ComponentArtifactPresence,
10
+ type SharedArtifactPresence,
11
+ } from '../component/shared-artifacts';
5
12
  import { PatternListObject, PatternObject } from '../types';
6
13
  import { readPatternSummaryApi, syncPatternArtifacts, writePatternApi, writePatternHtml, writePatternSummaryApi } from './api';
7
14
  import { composePatternHtml } from './html';
@@ -37,7 +44,8 @@ async function buildPattern(
37
44
  patternId: string,
38
45
  pattern: PatternObject,
39
46
  componentOutputDir: string,
40
- basePath: string
47
+ basePath: string,
48
+ sharedArtifacts: SharedArtifactPresence
41
49
  ): Promise<PatternListObject | null> {
42
50
  const fragments: { componentId: string; html: string }[] = [];
43
51
  let hasErrors = false;
@@ -77,6 +85,15 @@ async function buildPattern(
77
85
  fragments.push({ componentId: ref.id, html });
78
86
  }
79
87
 
88
+ // Resolve component-owned artifact presence for each composed component so the pattern references
89
+ // only the stylesheets that actually exist (no dangling references for style-less components).
90
+ const componentArtifacts = new Map<string, ComponentArtifactPresence>();
91
+ for (const fragment of fragments) {
92
+ if (!componentArtifacts.has(fragment.componentId)) {
93
+ componentArtifacts.set(fragment.componentId, resolveComponentArtifactPresence(handoff, fragment.componentId));
94
+ }
95
+ }
96
+
80
97
  if (fragments.length === 0) {
81
98
  Logger.warn(`Pattern "${patternId}" produced no fragments. Skipping.`);
82
99
  return null;
@@ -86,14 +103,14 @@ async function buildPattern(
86
103
  Logger.warn(`Pattern "${patternId}" has missing components but will be composed from available fragments.`);
87
104
  }
88
105
 
89
- const composedHtml = composePatternHtml(patternId, pattern.title, fragments, basePath);
106
+ const composedHtml = composePatternHtml(patternId, pattern.title, fragments, basePath, sharedArtifacts, componentArtifacts);
90
107
  const patternUrl = `${patternId}.html`;
91
108
 
92
109
  const patternData: PatternListObject = {
93
110
  ...pattern,
94
111
  id: patternId,
95
112
  url: patternUrl,
96
- path: `${basePath}/api/pattern/${patternId}.json`,
113
+ path: buildPatternDetailUrl(patternId, basePath),
97
114
  };
98
115
 
99
116
  await writePatternHtml(patternId, composedHtml, handoff);
@@ -109,7 +126,13 @@ async function buildPattern(
109
126
  * so that all component preview HTML files already exist on disk.
110
127
  */
111
128
  export async function processPatterns(handoff: Handoff, options?: ProcessPatternsOptions): Promise<PatternListObject[]> {
112
- const runtimePatterns = handoff.runtimeConfig?.entries?.patterns ?? {};
129
+ // Resolve the pattern set through the storage-agnostic store (v2). The filesystem store is a read
130
+ // view over `runtimeConfig.entries.patterns`, so these are the same records the build has always
131
+ // used — rebuilt into the id-keyed map the build logic below expects.
132
+ const runtimePatterns: Record<string, PatternListObject> = {};
133
+ for (const pattern of await handoff.store.patterns.list()) {
134
+ runtimePatterns[pattern.id] = pattern;
135
+ }
113
136
  const patternIds = Object.keys(runtimePatterns);
114
137
  const onlyPatternIds = options?.onlyPatternIds;
115
138
  const isPartial = onlyPatternIds !== undefined;
@@ -122,6 +145,9 @@ export async function processPatterns(handoff: Handoff, options?: ProcessPattern
122
145
 
123
146
  const componentOutputDir = path.resolve(handoff.workingPath, 'public/api/component');
124
147
  const basePath = process.env.HANDOFF_APP_BASE_PATH ?? '';
148
+ // Resolve the shared/global artifacts once so composed patterns reference `component/main.css` and
149
+ // `component/main.js` only when present (deduplicated to a single global script per page).
150
+ const sharedArtifacts = resolveSharedArtifactPresence(handoff);
125
151
 
126
152
  if (!isPartial) {
127
153
  Logger.info(`Building ${patternIds.length} pattern(s)...`);
@@ -129,7 +155,7 @@ export async function processPatterns(handoff: Handoff, options?: ProcessPattern
129
155
 
130
156
  for (const patternId of patternIds) {
131
157
  const pattern = runtimePatterns[patternId];
132
- const built = await buildPattern(handoff, patternId, pattern, componentOutputDir, basePath);
158
+ const built = await buildPattern(handoff, patternId, pattern, componentOutputDir, basePath, sharedArtifacts);
133
159
  if (built) {
134
160
  result.push(built);
135
161
  }
@@ -159,7 +185,7 @@ export async function processPatterns(handoff: Handoff, options?: ProcessPattern
159
185
  for (const patternId of patternIds) {
160
186
  if (onlyPatternIds.has(patternId)) {
161
187
  const pattern = runtimePatterns[patternId];
162
- const built = await buildPattern(handoff, patternId, pattern, componentOutputDir, basePath);
188
+ const built = await buildPattern(handoff, patternId, pattern, componentOutputDir, basePath, sharedArtifacts);
163
189
  if (built) {
164
190
  existingById.set(patternId, built);
165
191
  } else {
@@ -167,7 +193,7 @@ export async function processPatterns(handoff: Handoff, options?: ProcessPattern
167
193
  }
168
194
  } else if (!existingById.has(patternId)) {
169
195
  const pattern = runtimePatterns[patternId];
170
- const built = await buildPattern(handoff, patternId, pattern, componentOutputDir, basePath);
196
+ const built = await buildPattern(handoff, patternId, pattern, componentOutputDir, basePath, sharedArtifacts);
171
197
  if (built) {
172
198
  existingById.set(patternId, built);
173
199
  }
@@ -1,34 +1,47 @@
1
1
  import escape from 'lodash/escape';
2
2
  import { parse } from 'node-html-parser';
3
+ import { SHARED_MAIN_CSS_ARTIFACT_PATH, SHARED_MAIN_JS_ARTIFACT_PATH } from '../../../artifacts';
4
+ import { buildArtifactUrl } from '../../../artifacts/url';
5
+ import type { ComponentArtifactPresence, SharedArtifactPresence } from '../component/shared-artifacts';
3
6
 
4
7
  /**
5
8
  * Composes multiple component preview HTML documents into a single-page HTML.
6
9
  *
7
- * Each component preview may be a React SSR document (with hydration scripts
8
- * and a `#root` / `#__APP_PROPS__` pair) or a static Handlebars document.
10
+ * Each component preview may be a React SSR document (server-rendered markup plus a `#root` /
11
+ * `#__APP_PROPS__` pair) or a static Handlebars document.
9
12
  *
10
- * This function namespaces the element ids per-fragment so multiple React
11
- * components can coexist and hydrate independently on the same page.
13
+ * React client/hydration bundles are component-owned artifacts (`component/<id>.client.js`). This
14
+ * composer copies no per-fragment bundle: it namespaces each fragment's root/props so multiple
15
+ * instances coexist, marks each root with the `data-handoff-component`/`data-handoff-props`
16
+ * attributes the component bundle hydrates by, and references each unique component's client artifact
17
+ * exactly once.
12
18
  *
13
- * Each hydration bundle gets its own `<script type="module">` tag for full
14
- * module-level isolation. This is critical because esbuild bundles React
15
- * into each script using module-scoped `var` declarations, and `hydrateRoot`
16
- * is asynchronous — sharing a single module scope would cause later bundles
17
- * to overwrite earlier React instances before hydration completes.
19
+ * Shared/global artifacts follow the shared-artifact model: `component/main.css` and
20
+ * `component/main.js` are referenced only when present, and the global script is emitted exactly once
21
+ * (deduplicated across fragments) so its top-level side effects run a single time, ahead of the
22
+ * per-component client bundles.
18
23
  */
19
24
  export const composePatternHtml = (
20
25
  _patternId: string,
21
26
  patternTitle: string,
22
27
  fragments: { componentId: string; html: string }[],
23
- basePath: string
28
+ basePath: string,
29
+ sharedArtifacts: SharedArtifactPresence,
30
+ componentArtifacts: Map<string, ComponentArtifactPresence>
24
31
  ): string => {
25
32
  const cssHrefs = new Set<string>();
26
33
  const bodyParts: string[] = [];
27
- const scriptTags: string[] = [];
34
+ const clientArtifactHrefs = new Set<string>();
28
35
 
29
- cssHrefs.add(`${basePath}/api/component/main.css`);
36
+ if (sharedArtifacts.mainCss) {
37
+ cssHrefs.add(buildArtifactUrl(SHARED_MAIN_CSS_ARTIFACT_PATH, basePath));
38
+ }
30
39
  cssHrefs.add(`${basePath}/assets/css/preview.css`);
31
40
 
41
+ // Canonical URL of the shared global script; used to strip any per-fragment reference so the
42
+ // global script is emitted (and executed) exactly once for the whole composed page.
43
+ const globalScriptUrl = buildArtifactUrl(SHARED_MAIN_JS_ARTIFACT_PATH, basePath);
44
+
32
45
  const seenComponents = new Set<string>();
33
46
 
34
47
  for (let i = 0; i < fragments.length; i++) {
@@ -36,7 +49,11 @@ export const composePatternHtml = (
36
49
 
37
50
  if (!seenComponents.has(componentId)) {
38
51
  seenComponents.add(componentId);
39
- cssHrefs.add(`${basePath}/api/component/${componentId}.css`);
52
+ // Reference a component's stylesheet only when it actually exists, so a pattern never requests
53
+ // an absent optional artifact for a component that declares no styles.
54
+ if (componentArtifacts.get(componentId)?.css) {
55
+ cssHrefs.add(buildArtifactUrl(`component/${componentId}.css`, basePath));
56
+ }
40
57
  }
41
58
 
42
59
  const doc = parse(html);
@@ -46,30 +63,40 @@ export const composePatternHtml = (
46
63
 
47
64
  // -- Extract props JSON (React SSR) ------------------------------------
48
65
  const propsScript = doc.querySelector('#__APP_PROPS__');
49
- let propsContent = '{}';
66
+ let propsContent: string | null = null;
50
67
  if (propsScript) {
51
68
  propsContent = propsScript.textContent || '{}';
52
69
  propsScript.remove();
53
70
  }
54
71
 
55
- // -- Extract and rewrite hydration scripts (React SSR) -----------------
56
- // Each script becomes its own <script type="module"> for full isolation.
57
- const moduleScripts = doc.querySelectorAll('script[type="module"]');
58
- for (const script of moduleScripts) {
59
- let code = script.textContent || '';
60
- if (code.trim()) {
61
- code = code
62
- .replace(/__APP_PROPS__/g, namespacedPropsId)
63
- .replace(/getElementById\(\s*["']root["']\s*\)/g, `getElementById("${namespacedRootId}")`);
64
- scriptTags.push(` <script type="module">\n${code}\n </script>`);
65
- }
72
+ // -- Drop the fragment's own client-artifact reference -----------------
73
+ // The bundle is referenced once per component for the whole page (below); a fragment must not
74
+ // carry its standalone-preview script tag into the composed document.
75
+ for (const script of doc.querySelectorAll('script[type="module"]')) {
66
76
  script.remove();
67
77
  }
68
78
 
79
+ // -- Drop the fragment's own global-script reference -------------------
80
+ // Static (Handlebars) fragments render the global `component/main.js` tag inside their body; left
81
+ // in place it would be inlined per fragment and run the global side effects multiple times. The
82
+ // page references the global script exactly once (below).
83
+ for (const script of doc.querySelectorAll('script')) {
84
+ if (script.getAttribute('src') === globalScriptUrl) {
85
+ script.remove();
86
+ }
87
+ }
88
+
69
89
  // -- Namespace the root element (React SSR) ----------------------------
90
+ // A React fragment exposes its mount point to the component bundle via stable data attributes.
70
91
  const rootEl = doc.querySelector('#root');
92
+ const isReactFragment = !!rootEl && propsContent !== null;
71
93
  if (rootEl) {
72
94
  rootEl.setAttribute('id', namespacedRootId);
95
+ if (isReactFragment) {
96
+ rootEl.setAttribute('data-handoff-component', componentId);
97
+ rootEl.setAttribute('data-handoff-props', namespacedPropsId);
98
+ clientArtifactHrefs.add(buildArtifactUrl(`component/${componentId}.client.js`, basePath));
99
+ }
73
100
  }
74
101
 
75
102
  // -- Extract body content ----------------------------------------------
@@ -80,9 +107,14 @@ export const composePatternHtml = (
80
107
  ? rootEl.outerHTML
81
108
  : doc.innerHTML || '';
82
109
 
110
+ const propsScriptTag =
111
+ propsContent !== null
112
+ ? ` <script id="${namespacedPropsId}" type="application/json">${propsContent}</script>\n`
113
+ : '';
114
+
83
115
  bodyParts.push(
84
116
  ` <div class="handoff-pattern-block" data-component="${componentId}" data-fragment="${i}">\n` +
85
- ` <script id="${namespacedPropsId}" type="application/json">${propsContent}</script>\n` +
117
+ propsScriptTag +
86
118
  ` ${bodyContent}\n` +
87
119
  ` </div>`
88
120
  );
@@ -92,6 +124,18 @@ export const composePatternHtml = (
92
124
  .map((href) => ` <link rel="stylesheet" href="${href}" />`)
93
125
  .join('\n');
94
126
 
127
+ // The global script is referenced once for the whole page (deduplicated across fragments) and
128
+ // placed before the per-component client bundles so its side effects run first and exactly once.
129
+ const globalScriptTag = sharedArtifacts.mainJs
130
+ ? ` <script src="${buildArtifactUrl(SHARED_MAIN_JS_ARTIFACT_PATH, basePath)}"></script>`
131
+ : '';
132
+
133
+ const clientScriptTags = Array.from(clientArtifactHrefs)
134
+ .map((href) => ` <script type="module" src="${href}"></script>`)
135
+ .join('\n');
136
+
137
+ const scriptTags = [globalScriptTag, clientScriptTags].filter(Boolean).join('\n');
138
+
95
139
  return `<!DOCTYPE html>
96
140
  <html lang="en">
97
141
  <head>
@@ -101,7 +145,7 @@ ${linkTags}
101
145
  </head>
102
146
  <body>
103
147
  ${bodyParts.join('\n')}
104
- ${scriptTags.join('\n')}
148
+ ${scriptTags}
105
149
  </body>
106
150
  </html>`;
107
151
  };
@@ -238,3 +238,45 @@ export type PatternListObject = {
238
238
  id: string;
239
239
  path: string;
240
240
  } & PatternObject;
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // Page types
244
+ // ---------------------------------------------------------------------------
245
+
246
+ /**
247
+ * A documentation page authored as markdown. The frontmatter is parsed into these fields and the
248
+ * markdown body is the rendered content. Pages are workspace-authored under `<workingPath>/pages/`
249
+ * and are first-class registry entities (publish/checkout) alongside components and patterns. Unlike
250
+ * components/patterns there is no rendered-artifact pipeline: the raw markdown is stored and rendered
251
+ * at runtime.
252
+ */
253
+ export type PageObject = {
254
+ title: string;
255
+ description?: string;
256
+ group?: string;
257
+ /** Sort weight within the nav section (lower first). */
258
+ weight?: number;
259
+ /** Alternative title used in navigation. */
260
+ menuTitle?: string;
261
+ /** SEO overrides. */
262
+ metaTitle?: string;
263
+ metaDescription?: string;
264
+ /** When `false`, the page is hidden from navigation. */
265
+ enabled?: boolean;
266
+ /** External link target — a URL/path string, or `true` to flag the section as external. */
267
+ external?: string | boolean;
268
+ /** Optional declarative submenu structure (mirrors the markdown frontmatter `menu`). */
269
+ menu?: { [key: string]: any };
270
+ };
271
+
272
+ export type PageListObject = {
273
+ /** Stable page id — its slug path (e.g. `guides/setup`), derived from the file location (id ≡ route). */
274
+ id: string;
275
+ /** Logical route the page is served at (e.g. `/guides/setup`). */
276
+ path: string;
277
+ /**
278
+ * Absolute filesystem path of the source `.md` (workspace store only). Workspace-only and stripped
279
+ * from the published transfer item — never persisted to or served from the registry.
280
+ */
281
+ sourcePath?: string;
282
+ } & PageObject;
@@ -2,6 +2,14 @@ import Handlebars from 'handlebars';
2
2
  import { RegisterHandlebarsHelpersContext } from '../../types/config';
3
3
  import { Logger } from '../../utils/logger';
4
4
  import { SlotMetadata } from '../preview/component';
5
+ import {
6
+ renderComponentScriptTag,
7
+ renderComponentStyleLink,
8
+ renderGlobalScriptTag,
9
+ renderSharedStyleLinks,
10
+ type ComponentArtifactPresence,
11
+ type SharedArtifactPresence,
12
+ } from '../preview/component/shared-artifacts';
5
13
  import { HandlebarsContext } from '../types';
6
14
 
7
15
  /**
@@ -74,17 +82,35 @@ export const registerHandlebarsHelpers = (
74
82
  export const createHandlebarsContext = (
75
83
  data: { id: string; properties: { [key: string]: SlotMetadata }; title: string },
76
84
  previewData: { values?: any },
77
- options?: { includeSharedStyles?: boolean }
85
+ options?: {
86
+ includeSharedStyles?: boolean;
87
+ sharedArtifacts?: SharedArtifactPresence;
88
+ componentArtifacts?: ComponentArtifactPresence;
89
+ }
78
90
  ): HandlebarsContext => {
79
91
  const basePath = process.env.HANDOFF_APP_BASE_PATH ?? '';
80
- const sharedStylesLink = options?.includeSharedStyles ? `<link rel="stylesheet" href="${basePath}/api/component/shared.css">` : '';
92
+ // Shared/global artifacts (`component/shared.css`, `component/main.css`, `component/main.js`) and
93
+ // the component's own css/js are referenced only when present, so a failed/absent build never
94
+ // produces a dangling reference. Absent presence info conservatively omits the artifacts.
95
+ const presence: SharedArtifactPresence = options?.sharedArtifacts ?? { mainJs: false, mainCss: false, sharedCss: false };
96
+ const componentPresence: ComponentArtifactPresence = options?.componentArtifacts ?? { css: false, js: false };
97
+ const sharedStyleLinks = renderSharedStyleLinks(presence, basePath, {
98
+ includeSharedStyles: options?.includeSharedStyles,
99
+ });
100
+ const componentStyleLink = renderComponentStyleLink(componentPresence, data.id, basePath);
101
+ // The global script loads before the component-specific script so global side effects run first.
102
+ const globalScriptTag = renderGlobalScriptTag(presence, basePath);
103
+ const componentScriptTag = renderComponentScriptTag(componentPresence, data.id, basePath);
81
104
 
82
105
  return {
83
106
  style:
84
- `${sharedStylesLink}<link rel="stylesheet" href="${basePath}/api/component/main.css">` +
85
- `<link rel="stylesheet" href="${basePath}/api/component/${data.id}.css">\n` +
107
+ `${sharedStyleLinks ? `${sharedStyleLinks}\n` : ''}` +
108
+ `${componentStyleLink ? `${componentStyleLink}\n` : ''}` +
86
109
  `<link rel="stylesheet" href="${basePath}/assets/css/preview.css">`,
87
- 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>`,
110
+ script:
111
+ `${globalScriptTag ? `${globalScriptTag}\n` : ''}` +
112
+ `${componentScriptTag ? `${componentScriptTag}\n` : ''}` +
113
+ `<script src="${basePath}/assets/js/preview.js"></script><script>var fields = ${JSON.stringify(data.properties)};</script>`,
88
114
  properties: previewData.values || {},
89
115
  fields: data.properties,
90
116
  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
 
9
9
  export interface ImageStyle {
@@ -80,6 +80,102 @@ export interface NextAppConfig {
80
80
  };
81
81
  }
82
82
 
83
+ /**
84
+ * Configuration for entry points to assets and components that will be built.
85
+ */
86
+ export interface ConfigEntries {
87
+ /**
88
+ * Path to the main SCSS entry file
89
+ * @example "styles/main.scss"
90
+ */
91
+ scss?: string;
92
+ /**
93
+ * Path to the main JavaScript entry file
94
+ * @example "scripts/main.js"
95
+ */
96
+ js?: string;
97
+ /**
98
+ * Array of component paths to be included in the build
99
+ * @example ["components/button", "components/input"]
100
+ */
101
+ components?: string[];
102
+ /**
103
+ * Array of pattern paths to be included in the build.
104
+ * Patterns compose multiple component previews into single-page views.
105
+ * @example ["patterns/hero-section", "patterns"]
106
+ */
107
+ patterns?: string[];
108
+ }
109
+
110
+ /** Runtime mode. Resolved solely from `runtime.mode`; never inferred from env vars or connection settings. */
111
+ export type RuntimeMode = 'workspace' | 'registry';
112
+
113
+ /** Format used when synthesizing local workspace declarations. */
114
+ export type DeclarationFormat = 'ts' | 'js' | 'cjs' | 'json';
115
+
116
+ /**
117
+ * User-facing `runtime` configuration block. A single optional block that selects the runtime
118
+ * mode and carries mode-specific host/connection settings.
119
+ *
120
+ * Note: this is the authored shape. {@link RuntimeConfig} is the separate, resolved post-discovery
121
+ * shape used internally by the build/runtime.
122
+ */
123
+ export interface HandoffRuntimeConfig {
124
+ /**
125
+ * The sole determinant of runtime mode. Defaults to `'workspace'` when omitted.
126
+ * Mode is never inferred from environment variables, database settings, or token presence.
127
+ */
128
+ mode?: RuntimeMode;
129
+ /** Workspace-mode settings. */
130
+ workspace?: {
131
+ entries?: ConfigEntries;
132
+ declarationFormat?: DeclarationFormat;
133
+ };
134
+ /** Registry-mode host settings. Env-var values are stored as names, never as secrets. */
135
+ registry?: {
136
+ /** Name of the env var holding the database URL. @default "DATABASE_URL" */
137
+ databaseUrlEnv?: string;
138
+ /** Name of the env var holding the registry API token. @default "HANDOFF_REGISTRY_API_TOKEN" */
139
+ apiTokenEnv?: string;
140
+ database?: {
141
+ /** @default "pg" */
142
+ adapter?: 'pg' | 'neon';
143
+ };
144
+ /**
145
+ * Where published asset blobs (icons/logos/fonts/sprites/archives) are stored. Defaults to the
146
+ * built-in PostgreSQL `bytea` store when omitted. Secrets are never persisted here - only the
147
+ * *names* of the env vars their values are read from at request time.
148
+ */
149
+ assetStorage?: {
150
+ /**
151
+ * Active storage provider for new uploads. `database` keeps bytes inline in Postgres; use the
152
+ * pre-packaged `vercel-blob` adapter or a `custom` module for object storage.
153
+ * @default "database"
154
+ */
155
+ adapter?: 'database' | 'vercel-blob' | 'custom';
156
+ /** For `adapter: "custom"` - server-only module path default-exporting a `defineAssetStorage` adapter. */
157
+ module?: string;
158
+ /** For `adapter: "vercel-blob"` - env var name holding the Blob read/write token. @default "BLOB_READ_WRITE_TOKEN" */
159
+ tokenEnv?: string;
160
+ /** Max bytes kept inline in the database `bytea` column (larger uploads are rejected). @default 4194304 */
161
+ maxInlineBytes?: number;
162
+ /** Non-secret adapter options (bucket env-var names, region, a custom `providerId`, …). */
163
+ options?: Record<string, unknown>;
164
+ };
165
+ };
166
+ /**
167
+ * Connected-workspace settings pointing at a remote registry. A connected workspace is
168
+ * `mode: 'workspace'` plus this block — it is not a third runtime mode.
169
+ */
170
+ registryConnection?: {
171
+ url?: string;
172
+ /** @default "HANDOFF_REGISTRY_URL" */
173
+ urlEnv?: string;
174
+ /** @default "HANDOFF_REGISTRY_ACCESS_TOKEN" */
175
+ accessTokenEnv?: string;
176
+ };
177
+ }
178
+
83
179
  export interface Config {
84
180
  dev_access_token?: string | null | undefined;
85
181
  devAccessToken?: string | null | undefined;
@@ -108,32 +204,15 @@ export interface Config {
108
204
  * Configuration for the build pipeline
109
205
  */
110
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;
111
212
  /**
112
213
  * Configuration for entry points to assets and components that will be built
113
214
  */
114
- entries?: {
115
- /**
116
- * Path to the main SCSS entry file
117
- * @example "styles/main.scss"
118
- */
119
- scss?: string;
120
- /**
121
- * Path to the main JavaScript entry file
122
- * @example "scripts/main.js"
123
- */
124
- js?: string;
125
- /**
126
- * Array of component paths to be included in the build
127
- * @example ["components/button", "components/input"]
128
- */
129
- components?: string[];
130
- /**
131
- * Array of pattern paths to be included in the build.
132
- * Patterns compose multiple component previews into single-page views.
133
- * @example ["patterns/hero-section", "patterns"]
134
- */
135
- patterns?: string[];
136
- };
215
+ entries?: ConfigEntries;
137
216
  /**
138
217
  * Configuration for asset zip file download links
139
218
  * @default { icons: "/icons.zip", logos: "/logos.zip" }
@@ -286,7 +365,23 @@ export interface Config {
286
365
  };
287
366
  }
288
367
 
289
- export type ClientConfig = Pick<Config, 'app' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'assets_zip_links' | 'useVariables'>;
368
+ /**
369
+ * Non-secret runtime values projected to the browser. Only the resolved {@link RuntimeMode}
370
+ * crosses to the client; secrets and connection values never do.
371
+ */
372
+ export interface ClientRuntimeConfig {
373
+ mode: RuntimeMode;
374
+ /**
375
+ * True when the runtime is a connected workspace (`mode: 'workspace'` plus a resolvable
376
+ * `registryConnection` URL). Non-secret: it never carries the URL or token value, only whether a
377
+ * connection is configured. Drives the workspace-only publish affordance in the docs UI.
378
+ */
379
+ connected: boolean;
380
+ }
381
+
382
+ export type ClientConfig = Pick<Config, 'app' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'assets_zip_links' | 'useVariables'> & {
383
+ runtime: ClientRuntimeConfig;
384
+ };
290
385
 
291
386
  export interface RuntimeConfigComponentOptions {
292
387
  cssRootClass?: string;
@@ -313,6 +408,9 @@ export interface RuntimeConfig {
313
408
  patterns: {
314
409
  [id: string]: PatternListObject;
315
410
  };
411
+ pages: {
412
+ [id: string]: PageListObject;
413
+ };
316
414
  };
317
415
  options: {
318
416
  [key: string]: RuntimeConfigComponentOptions;