@yahoo/uds-create-config 2.45.0 → 3.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 (376) hide show
  1. package/README.md +129 -0
  2. package/dist/ai-lanes.d.ts +23 -0
  3. package/dist/ai-lanes.js +34 -0
  4. package/dist/authoring/define-component.d.ts +85 -0
  5. package/dist/configs/CanvasConfig.d.ts +3237 -0
  6. package/dist/configs/CanvasConfig.js +332 -0
  7. package/dist/configs/build-options.d.ts +10 -0
  8. package/dist/configs/build-options.js +68 -0
  9. package/dist/configs/platform.d.ts +15 -0
  10. package/dist/configs/platform.js +17 -0
  11. package/dist/configs/react-native-system.d.ts +3026 -0
  12. package/dist/configs/react-native-system.js +64 -0
  13. package/dist/configs/system.d.ts +2771 -0
  14. package/dist/configs/system.js +74 -0
  15. package/dist/css/properties.generated.d.ts +14 -0
  16. package/dist/css/properties.generated.js +6260 -0
  17. package/dist/css/value-type.d.ts +48 -0
  18. package/dist/css/value-type.js +131 -0
  19. package/dist/css/values.d.ts +33 -0
  20. package/dist/css/values.js +116 -0
  21. package/dist/entities/native/NativeModifier.d.ts +25 -0
  22. package/dist/entities/native/NativeModifier.js +41 -0
  23. package/dist/entities/native/NativeSettings.d.ts +44 -0
  24. package/dist/entities/native/NativeSettings.js +39 -0
  25. package/dist/entities/native/NativeStyleProperty.d.ts +355 -0
  26. package/dist/entities/native/NativeStyleProperty.js +28 -0
  27. package/dist/entities/native/NativeToken.d.ts +31 -0
  28. package/dist/entities/native/NativeToken.js +49 -0
  29. package/dist/entities/system/CanvasRole.d.ts +34 -0
  30. package/dist/entities/system/CanvasRole.js +45 -0
  31. package/dist/entities/system/Component.d.ts +1287 -0
  32. package/dist/entities/system/Component.js +1840 -0
  33. package/dist/entities/system/Composite.d.ts +12 -0
  34. package/dist/entities/system/Composite.js +26 -0
  35. package/dist/entities/system/Device.d.ts +11 -0
  36. package/dist/entities/system/Device.js +18 -0
  37. package/dist/entities/system/File.d.ts +72 -0
  38. package/dist/entities/system/File.js +136 -0
  39. package/dist/entities/system/Font.d.ts +48 -0
  40. package/dist/entities/system/Font.js +56 -0
  41. package/dist/entities/system/GlobalStyle.d.ts +26 -0
  42. package/dist/entities/system/GlobalStyle.js +54 -0
  43. package/dist/entities/system/Guidance.d.ts +85 -0
  44. package/dist/entities/system/Guidance.js +59 -0
  45. package/dist/entities/system/GuidanceStyle.d.ts +14 -0
  46. package/dist/entities/system/GuidanceStyle.js +8 -0
  47. package/dist/entities/system/Icon.d.ts +46 -0
  48. package/dist/entities/system/Icon.js +46 -0
  49. package/dist/entities/system/LinkedSystem.d.ts +504 -0
  50. package/dist/entities/system/LinkedSystem.js +814 -0
  51. package/dist/entities/system/Modifier.d.ts +92 -0
  52. package/dist/entities/system/Modifier.js +120 -0
  53. package/dist/entities/system/Motion.d.ts +28 -0
  54. package/dist/entities/system/Motion.js +48 -0
  55. package/dist/entities/system/Operation.d.ts +100 -0
  56. package/dist/entities/system/Operation.js +236 -0
  57. package/dist/entities/system/Package.d.ts +42 -0
  58. package/dist/entities/system/Package.js +75 -0
  59. package/dist/entities/system/Settings.d.ts +94 -0
  60. package/dist/entities/system/Settings.js +166 -0
  61. package/dist/entities/system/StyleProperty.d.ts +135 -0
  62. package/dist/entities/system/StyleProperty.js +438 -0
  63. package/dist/entities/system/Token.d.ts +67 -0
  64. package/dist/entities/system/Token.js +129 -0
  65. package/dist/entities/system/Tool.d.ts +134 -0
  66. package/dist/entities/system/Tool.js +124 -0
  67. package/dist/entities/system/color.d.ts +64 -0
  68. package/dist/entities/system/color.js +123 -0
  69. package/dist/entities/system/component-module.d.ts +63 -0
  70. package/dist/entities/system/component-module.js +258 -0
  71. package/dist/entities/system/copy-plan.d.ts +98 -0
  72. package/dist/entities/system/copy-plan.js +451 -0
  73. package/dist/entities/system/defineComponent.d.ts +428 -0
  74. package/dist/entities/system/defineComponent.js +337 -0
  75. package/dist/entities/system/element.d.ts +101 -0
  76. package/dist/entities/system/element.js +171 -0
  77. package/dist/entities/system/icon-library.d.ts +64 -0
  78. package/dist/entities/system/icon-library.js +112 -0
  79. package/dist/entities/system/icon-metadata.d.ts +72 -0
  80. package/dist/entities/system/icon-metadata.js +111 -0
  81. package/dist/entities/system/link-declaration.d.ts +27 -0
  82. package/dist/entities/system/link-declaration.js +64 -0
  83. package/dist/entities/system/link-plan.d.ts +160 -0
  84. package/dist/entities/system/link-plan.js +770 -0
  85. package/dist/entities/system/runtime-modules.d.ts +25 -0
  86. package/dist/entities/system/runtime-modules.js +38 -0
  87. package/dist/entities/system/style-bag.d.ts +20 -0
  88. package/dist/entities/system/style-bag.js +246 -0
  89. package/dist/entities/system/token-index.d.ts +89 -0
  90. package/dist/entities/system/token-index.js +112 -0
  91. package/dist/framework/Config.d.ts +2472 -0
  92. package/dist/framework/Config.js +5023 -0
  93. package/dist/framework/Entity.d.ts +127 -0
  94. package/dist/framework/Entity.js +125 -0
  95. package/dist/framework/authoring.d.ts +24 -0
  96. package/dist/framework/authoring.js +7 -0
  97. package/dist/framework/change-hooks.d.ts +119 -0
  98. package/dist/framework/change-hooks.js +131 -0
  99. package/dist/framework/changes.d.ts +88 -0
  100. package/dist/framework/changes.js +246 -0
  101. package/dist/framework/class-names.d.ts +260 -0
  102. package/dist/framework/class-names.js +630 -0
  103. package/dist/framework/coalesce.d.ts +25 -0
  104. package/dist/framework/coalesce.js +60 -0
  105. package/dist/framework/config-op.d.ts +76 -0
  106. package/dist/framework/config-op.js +43 -0
  107. package/dist/framework/config-operation-kind.d.ts +14 -0
  108. package/dist/framework/config-operation-kind.js +14 -0
  109. package/dist/framework/config-ops.d.ts +34 -0
  110. package/dist/framework/config-ops.js +187 -0
  111. package/dist/framework/defineConfig.d.ts +196 -0
  112. package/dist/framework/defineConfig.js +100 -0
  113. package/dist/framework/defineDerivedEntity.d.ts +74 -0
  114. package/dist/framework/defineDerivedEntity.js +83 -0
  115. package/dist/framework/defineEntity.d.ts +318 -0
  116. package/dist/framework/defineEntity.js +183 -0
  117. package/dist/framework/defineSubEntity.d.ts +148 -0
  118. package/dist/framework/defineSubEntity.js +121 -0
  119. package/dist/framework/derived-mutations.d.ts +20 -0
  120. package/dist/framework/derived-mutations.js +38 -0
  121. package/dist/framework/entity-search.d.ts +29 -0
  122. package/dist/framework/entity-search.js +117 -0
  123. package/dist/framework/inline-sources.d.ts +49 -0
  124. package/dist/framework/inline-sources.js +388 -0
  125. package/dist/framework/layer-styles.d.ts +25 -0
  126. package/dist/framework/layer-styles.js +49 -0
  127. package/dist/framework/memo.d.ts +48 -0
  128. package/dist/framework/memo.js +84 -0
  129. package/dist/framework/overlay.d.ts +49 -0
  130. package/dist/framework/overlay.js +17 -0
  131. package/dist/framework/plan.d.ts +49 -0
  132. package/dist/framework/plan.js +73 -0
  133. package/dist/framework/projections.d.ts +480 -0
  134. package/dist/framework/projections.js +974 -0
  135. package/dist/framework/prop-surface.d.ts +132 -0
  136. package/dist/framework/prop-surface.js +278 -0
  137. package/dist/framework/ref-graph.d.ts +71 -0
  138. package/dist/framework/ref-graph.js +158 -0
  139. package/dist/framework/ref-integrity.d.ts +55 -0
  140. package/dist/framework/ref-integrity.js +171 -0
  141. package/dist/framework/registered.d.ts +95 -0
  142. package/dist/framework/registry-dir.d.ts +42 -0
  143. package/dist/framework/registry-dir.js +48 -0
  144. package/dist/framework/registry.d.ts +300 -0
  145. package/dist/framework/registry.js +104 -0
  146. package/dist/framework/rejection.d.ts +58 -0
  147. package/dist/framework/rejection.js +111 -0
  148. package/dist/framework/rename-inference.d.ts +26 -0
  149. package/dist/framework/rename-inference.js +50 -0
  150. package/dist/framework/render-spec.d.ts +296 -0
  151. package/dist/framework/render-spec.js +809 -0
  152. package/dist/framework/schema-version.d.ts +140 -0
  153. package/dist/framework/schema-version.js +214 -0
  154. package/dist/framework/schemas.d.ts +109 -0
  155. package/dist/framework/schemas.js +615 -0
  156. package/dist/framework/session.d.ts +196 -0
  157. package/dist/framework/session.js +367 -0
  158. package/dist/framework/signature.d.ts +15 -0
  159. package/dist/framework/signature.js +83 -0
  160. package/dist/framework/snapshot.d.ts +115 -0
  161. package/dist/framework/snapshot.js +277 -0
  162. package/dist/framework/source-integrity.d.ts +226 -0
  163. package/dist/framework/source-integrity.js +478 -0
  164. package/dist/framework/sources.d.ts +211 -0
  165. package/dist/framework/sources.js +89 -0
  166. package/dist/framework/utils/compiled-schema.d.ts +23 -0
  167. package/dist/framework/utils/compiled-schema.js +46 -0
  168. package/dist/framework/utils/content-key.d.ts +15 -0
  169. package/dist/framework/utils/content-key.js +33 -0
  170. package/dist/framework/utils/enumerated.d.ts +13 -0
  171. package/dist/framework/utils/enumerated.js +19 -0
  172. package/dist/framework/utils/field-path.d.ts +95 -0
  173. package/dist/framework/utils/field-path.js +136 -0
  174. package/dist/framework/utils/field-schema.d.ts +55 -0
  175. package/dist/framework/utils/field-schema.js +183 -0
  176. package/dist/framework/utils/group.d.ts +37 -0
  177. package/dist/framework/utils/group.js +13 -0
  178. package/dist/framework/utils/package-path.d.ts +37 -0
  179. package/dist/framework/utils/package-path.js +61 -0
  180. package/dist/framework/utils/refs.d.ts +194 -0
  181. package/dist/framework/utils/refs.js +403 -0
  182. package/dist/framework/validate-spec.d.ts +27 -0
  183. package/dist/framework/validate-spec.js +45 -0
  184. package/dist/framework/value-domain.d.ts +26 -0
  185. package/dist/framework/value-domain.js +202 -0
  186. package/dist/framework/views-facade.d.ts +15 -0
  187. package/dist/framework/views-facade.js +19 -0
  188. package/dist/index.d.ts +90 -42
  189. package/dist/index.js +86 -37
  190. package/dist/migrations/2.0.0/raw-css.d.ts +11 -0
  191. package/dist/migrations/2.0.0/raw-css.js +72 -0
  192. package/dist/migrations/2.0.0/v1-artifact.d.ts +1559 -0
  193. package/dist/migrations/2.0.0/v1-artifact.js +1018 -0
  194. package/dist/migrations/20260908171448_settings_as_kind.d.ts +22 -0
  195. package/dist/migrations/20260908171448_settings_as_kind.js +89 -0
  196. package/dist/migrations/20260908194245_visible_state_pointer.d.ts +19 -0
  197. package/dist/migrations/20260908194245_visible_state_pointer.js +115 -0
  198. package/dist/migrations/20260909183001_icon_knockout_variants.d.ts +18 -0
  199. package/dist/migrations/20260909183001_icon_knockout_variants.js +8 -0
  200. package/dist/migrations/20260910212453_linked_system_link_ops.d.ts +13 -0
  201. package/dist/migrations/20260910212453_linked_system_link_ops.js +8 -0
  202. package/dist/migrations/index.d.ts +2 -0
  203. package/dist/migrations/index.js +2 -0
  204. package/dist/react-native/style-keys.generated.d.ts +10 -0
  205. package/dist/react-native/style-keys.generated.js +167 -0
  206. package/dist/renderer/RendererErrorBoundary.d.ts +32 -5
  207. package/dist/renderer/RendererErrorBoundary.js +59 -10
  208. package/dist/renderer/UdsRenderer.d.ts +11 -10
  209. package/dist/renderer/UdsRenderer.js +1 -1
  210. package/dist/renderer/assetEntries.d.ts +37 -0
  211. package/dist/renderer/assetEntries.js +39 -0
  212. package/dist/renderer/assetRenderable.d.ts +14 -6
  213. package/dist/renderer/assetRenderable.js +8 -2
  214. package/dist/renderer/componentEntry.d.ts +58 -0
  215. package/dist/renderer/componentEntry.js +71 -0
  216. package/dist/renderer/icon-library.d.ts +20 -0
  217. package/dist/renderer/index.d.ts +7 -7
  218. package/dist/renderer/index.js +5 -5
  219. package/dist/renderer/primitives/SlotRenderer.d.ts +1 -1
  220. package/dist/renderer/primitives/SlotRenderer.js +1 -1
  221. package/dist/renderer/runtimeComponent.d.ts +33 -0
  222. package/dist/renderer/runtimeComponent.js +113 -0
  223. package/dist/renderer/spec-content.d.ts +43 -0
  224. package/dist/renderer/spec-content.js +95 -0
  225. package/dist/renderer/wrapRegistry.d.ts +22 -37
  226. package/dist/renderer/wrapRegistry.js +10 -20
  227. package/dist/renderer/wrappers/event-bridge.js +6 -5
  228. package/dist/renderer/wrappers/hex-normalize.js +6 -5
  229. package/dist/renderer/wrappers/html-aliases.d.ts +7 -2
  230. package/dist/renderer/wrappers/html-aliases.js +36 -19
  231. package/dist/renderer/wrappers/inline-styles.js +7 -6
  232. package/dist/spec/asset-jsx.d.ts +18 -5
  233. package/dist/spec/asset-jsx.js +27 -16
  234. package/dist/spec/collapse-text-labels.d.ts +5 -15
  235. package/dist/spec/collapse-text-labels.js +26 -19
  236. package/dist/spec/empty-node-slots.d.ts +56 -34
  237. package/dist/spec/empty-node-slots.js +87 -47
  238. package/dist/spec/index.d.ts +21 -10
  239. package/dist/spec/index.js +21 -10
  240. package/dist/spec/jsxToSpec.d.ts +14 -9
  241. package/dist/spec/jsxToSpec.js +20 -3
  242. package/dist/spec/preview-elements.d.ts +63 -0
  243. package/dist/spec/preview-elements.js +89 -0
  244. package/dist/spec/rewrite-refs.d.ts +58 -0
  245. package/dist/spec/rewrite-refs.js +250 -0
  246. package/dist/spec/specToJsx.js +18 -29
  247. package/dist/tsconfig.tsbuildinfo +1 -1
  248. package/package.json +39 -52
  249. package/dist/AssetGroup.d.ts +0 -77
  250. package/dist/AssetGroup.js +0 -125
  251. package/dist/Component.d.ts +0 -333
  252. package/dist/Component.js +0 -1061
  253. package/dist/ComponentGroup.d.ts +0 -22
  254. package/dist/ComponentGroup.js +0 -51
  255. package/dist/CompositeStyle.d.ts +0 -30
  256. package/dist/CompositeStyle.js +0 -59
  257. package/dist/Config.d.ts +0 -528
  258. package/dist/Config.js +0 -1712
  259. package/dist/Mode.d.ts +0 -41
  260. package/dist/Mode.js +0 -81
  261. package/dist/Modifier.d.ts +0 -51
  262. package/dist/Modifier.js +0 -97
  263. package/dist/MotionDef.d.ts +0 -81
  264. package/dist/MotionDef.js +0 -212
  265. package/dist/Props.d.ts +0 -332
  266. package/dist/Props.js +0 -35
  267. package/dist/Provider.d.ts +0 -20
  268. package/dist/Provider.js +0 -14
  269. package/dist/StyleProp.d.ts +0 -145
  270. package/dist/StyleProp.js +0 -239
  271. package/dist/Token.d.ts +0 -76
  272. package/dist/Token.js +0 -133
  273. package/dist/TokenGroup.d.ts +0 -33
  274. package/dist/TokenGroup.js +0 -69
  275. package/dist/asset-kind.d.ts +0 -55
  276. package/dist/asset-kind.js +0 -29
  277. package/dist/asset-value.d.ts +0 -23
  278. package/dist/asset-value.js +0 -40
  279. package/dist/brands.d.ts +0 -30
  280. package/dist/brands.js +0 -20
  281. package/dist/captureCallerPath.d.ts +0 -48
  282. package/dist/captureCallerPath.js +0 -95
  283. package/dist/colorExpressions.d.ts +0 -131
  284. package/dist/colorExpressions.js +0 -148
  285. package/dist/component-referrers.d.ts +0 -30
  286. package/dist/component-referrers.js +0 -40
  287. package/dist/config-diagnostics.d.ts +0 -21
  288. package/dist/config-diagnostics.js +0 -12
  289. package/dist/defineAssetGroup.d.ts +0 -200
  290. package/dist/defineAssetGroup.js +0 -323
  291. package/dist/defineProvider.d.ts +0 -29
  292. package/dist/defineProvider.js +0 -60
  293. package/dist/element-marker.d.ts +0 -63
  294. package/dist/element-marker.js +0 -113
  295. package/dist/entity-utils.d.ts +0 -56
  296. package/dist/entity-utils.js +0 -105
  297. package/dist/extension.d.ts +0 -55
  298. package/dist/extension.js +0 -214
  299. package/dist/factories.d.ts +0 -863
  300. package/dist/factories.js +0 -475
  301. package/dist/font-face.d.ts +0 -25
  302. package/dist/font-face.js +0 -72
  303. package/dist/foreign-component-name.d.ts +0 -21
  304. package/dist/foreign-component-name.js +0 -42
  305. package/dist/interpolate.d.ts +0 -20
  306. package/dist/interpolate.js +0 -10
  307. package/dist/jsx/__fixtures__/cross-component-preview.d.ts +0 -3
  308. package/dist/jsx/__fixtures__/cross-component-preview.js +0 -15
  309. package/dist/jsx/jsx-dev-runtime.d.ts +0 -15
  310. package/dist/jsx/jsx-dev-runtime.js +0 -11
  311. package/dist/jsx/jsx-runtime.d.ts +0 -48
  312. package/dist/jsx/jsx-runtime.js +0 -305
  313. package/dist/linked-system-pins.d.ts +0 -30
  314. package/dist/linked-system-pins.js +0 -45
  315. package/dist/linkedNodePrefixes.d.ts +0 -73
  316. package/dist/linkedNodePrefixes.js +0 -99
  317. package/dist/markers.d.ts +0 -235
  318. package/dist/markers.js +0 -70
  319. package/dist/motion-constants.d.ts +0 -37
  320. package/dist/motion-constants.js +0 -61
  321. package/dist/native/NativeConfig.d.ts +0 -56
  322. package/dist/native/NativeConfig.js +0 -162
  323. package/dist/native/assets.d.ts +0 -20
  324. package/dist/native/assets.js +0 -17
  325. package/dist/native/components.d.ts +0 -139
  326. package/dist/native/components.js +0 -67
  327. package/dist/native/index.d.ts +0 -18
  328. package/dist/native/index.js +0 -17
  329. package/dist/native/modes.d.ts +0 -10
  330. package/dist/native/modes.js +0 -11
  331. package/dist/native/types.d.ts +0 -95
  332. package/dist/native/types.js +0 -0
  333. package/dist/native/validation.d.ts +0 -8
  334. package/dist/native/validation.js +0 -885
  335. package/dist/native/values.d.ts +0 -7
  336. package/dist/native/values.js +0 -15
  337. package/dist/native.d.ts +0 -12
  338. package/dist/native.js +0 -10
  339. package/dist/refs.d.ts +0 -204
  340. package/dist/refs.js +0 -129
  341. package/dist/registry-aliases.d.ts +0 -33
  342. package/dist/registry-aliases.js +0 -35
  343. package/dist/registry-dir.d.ts +0 -22
  344. package/dist/registry-dir.js +0 -10
  345. package/dist/renderer/makeRegistry.d.ts +0 -34
  346. package/dist/renderer/makeRegistry.js +0 -52
  347. package/dist/renderer/makeUdsRenderer.d.ts +0 -13
  348. package/dist/renderer/makeUdsRenderer.js +0 -51
  349. package/dist/renderer/wrappers/component-slots.d.ts +0 -41
  350. package/dist/renderer/wrappers/component-slots.js +0 -66
  351. package/dist/renderer/wrappers/slot-resolution.d.ts +0 -25
  352. package/dist/renderer/wrappers/slot-resolution.js +0 -68
  353. package/dist/renderer/wrappers/void-elements.d.ts +0 -37
  354. package/dist/renderer/wrappers/void-elements.js +0 -40
  355. package/dist/spec/layer-props.d.ts +0 -52
  356. package/dist/spec/layer-props.js +0 -149
  357. package/dist/spec/preview-controls.d.ts +0 -44
  358. package/dist/spec/preview-controls.js +0 -139
  359. package/dist/spec/slot-refs.d.ts +0 -39
  360. package/dist/spec/slot-refs.js +0 -56
  361. package/dist/token-override-rows.d.ts +0 -68
  362. package/dist/token-override-rows.js +0 -233
  363. package/dist/tokenValueType.d.ts +0 -34
  364. package/dist/tokenValueType.js +0 -138
  365. package/dist/types/css-properties.d.ts +0 -233
  366. package/dist/types/css-properties.js +0 -14
  367. package/dist/types/css-property-keywords.d.ts +0 -156
  368. package/dist/types/css-property-keywords.js +0 -616
  369. package/dist/types/css-values.d.ts +0 -63
  370. package/dist/types/css-values.js +0 -16
  371. package/dist/types.d.ts +0 -1138
  372. package/dist/types.js +0 -12
  373. package/dist/units.d.ts +0 -14
  374. package/dist/units.js +0 -16
  375. package/dist/utils/index.d.ts +0 -4
  376. package/dist/utils/index.js +0 -4
@@ -0,0 +1,814 @@
1
+ import { rejectData } from "../../framework/rejection.js";
2
+ import { isPlainObject } from "../../framework/utils/field-path.js";
3
+ import { pathInput } from "../../framework/schemas.js";
4
+ import { kindBorrowable } from "../../framework/registry.js";
5
+ import { defineEntity } from "../../framework/defineEntity.js";
6
+ import { sourceUnreachable } from "../../framework/sources.js";
7
+ import { planCopy } from "./copy-plan.js";
8
+ import { planBreak, planLink, planUnlink, sourceRowStates } from "./link-plan.js";
9
+ import { z } from "zod";
10
+ //#region src/entities/system/LinkedSystem.ts
11
+ /**
12
+ * `LinkedSystem` (`yahoo-os`) — another team's design system, drawn from at an exact version.
13
+ *
14
+ * Two questions kept apart, and this entity answers only the first:
15
+ *
16
+ * • **What can I see?** — you link a system. Everything in it becomes visible and resolvable.
17
+ * • **What am I using?** — you write a ref (`token:color/brand@yahoo-os`). That's the only thing
18
+ * stored.
19
+ *
20
+ * Linking copies nothing and makes nothing yours; it puts a system within reach. That is what makes
21
+ * partial use the normal case rather than a mode: link 400 tokens, use 12, store 12 refs. The other
22
+ * 388 stay visible, for browsing and for later, without being in your system.
23
+ *
24
+ * **The key is a local slug**, not the source's database id. It is what every ref spells, so it has to
25
+ * be readable and to survive the source renaming itself. `systemId` carries durable identity
26
+ * underneath, and `name` is display only, so a source that renames strands no refs.
27
+ *
28
+ * **A repo-local link round-trips.** Push writes the resolved `systemId`/`version` beside the authored
29
+ * `configPath`, so Studio reads an ordinary link while `uds pull` can still restore the path form.
30
+ * Dropping `configPath` at push would make a pull rewrite `'../design-system'` into a published pin.
31
+ * The path is inert server-side, as `playground.pagesDir` is.
32
+ *
33
+ * **Being an entity means it needs no special support.** Create, update, delete, validation and
34
+ * `dependents` all derive from these three fields as they do for a token, so an upgrade is an ordinary
35
+ * update of one field with no separate manifest to keep in sync. It is also the install list: `uds
36
+ * pull` reads these entries and materializes each one.
37
+ */
38
+ /**
39
+ * An exact version — `2.4.0`, never `^2.4.0` or `latest`.
40
+ *
41
+ * A link is always pinned: you draw from Yahoo OS 2.4.0 rather than from "Yahoo OS", so their next
42
+ * release can't change what you render until you move the pin. A range would give that away, and it
43
+ * would need a resolution step and a lockfile to mean anything, neither of which exists. Spelled as a
44
+ * pattern rather than pulled from `semver`, so this package keeps its three dependencies.
45
+ */
46
+ const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
47
+ /**
48
+ * A link's slug, as a single path segment.
49
+ *
50
+ * The slug is more than a key: `uds install` writes one registry folder per link and names it this, so
51
+ * a slug that isn't a path segment is one the install step can't use, and `../..` would be a traversal
52
+ * handed to a file write. Stated here on the entity so the rule has one home — the install refuses
53
+ * with it, an authoring UI can say so before submitting, and an AI tool creating a link is bound by it
54
+ * too. Enforced only at install, a config would commit fine and break for whoever runs the build.
55
+ */
56
+ const LINK_SLUG = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/i;
57
+ /** Whether `slug` can be a link's local name — {@link LINK_SLUG}, as a predicate for a caller that wants
58
+ * to ask rather than to match. */
59
+ function isLinkSlug(slug) {
60
+ return LINK_SLUG.test(slug);
61
+ }
62
+ /** What a refused slug should say, in one place, so the install and a form word it identically. */
63
+ const LINK_SLUG_MESSAGE = "A link's slug is a single path segment — letters, digits, dot, dash and underscore — because it becomes a folder in the registry.";
64
+ /**
65
+ * Where a linked system resolved from, carrying the identity that origin actually has.
66
+ *
67
+ * Discriminated on `from` rather than a flat `{ systemId, version, from }`. A repo-local source has no
68
+ * published identity to report: its identity is the config path, and a built artifact carries no
69
+ * `systemId` or `version` at all. A flat shape would require two fields that one of the three origins
70
+ * can never fill, so a resolver would have to invent an id to satisfy it.
71
+ *
72
+ * Each origin therefore says what it knows, which is also what makes "what did this build against"
73
+ * answerable for both kinds of source.
74
+ *
75
+ * Declared here beside the kind and handed to the framework through `sourceResolution`. These three
76
+ * origins are what a linked system has; a `linkedPackages` kind would resolve from a lockfile and
77
+ * couldn't fill this union, which is why the framework holds only the `status` tag.
78
+ */
79
+ const resolvedSource = z.discriminatedUnion("from", [
80
+ z.object({
81
+ from: z.literal("disk"),
82
+ configPath: z.string().min(1)
83
+ }),
84
+ z.object({
85
+ from: z.literal("registry"),
86
+ systemId: z.string().min(1),
87
+ version: z.string().min(1)
88
+ }),
89
+ z.object({
90
+ from: z.literal("storage"),
91
+ systemId: z.string().min(1),
92
+ version: z.string().min(1)
93
+ })
94
+ ]);
95
+ /**
96
+ * Why a declared linked system couldn't be met — a code plus the data that case needs, and no prose.
97
+ *
98
+ * A closed set, so a surface switches exhaustively and adding a case is a compile error at every
99
+ * consumer. Collapsed into `undefined`, these all look alike, which is how a rail ends up saying a
100
+ * value "isn't loaded here" when it is loaded and the ref is broken.
101
+ *
102
+ * **No `message`.** The fix depends on the surface rather than the code: a build tells you to run
103
+ * `uds install` or to build the sibling first, and neither sentence means anything in a browser where
104
+ * `stale` is an update button. One string per code would put CLI instructions into Studio. What such a
105
+ * message really carries is the data here — `stale` can't be phrased at all without both versions.
106
+ *
107
+ * **`not-found` covers three situations on purpose.** A system the caller can't see and a version
108
+ * never published have to be indistinguishable, or the difference is a probe: ask for an id you
109
+ * suspect exists, and a separate code confirms it. A resolver may know which internally; it must not
110
+ * cross out in the value.
111
+ */
112
+ const sourceUnavailable = z.discriminatedUnion("code", [
113
+ z.object({ code: z.literal("not-installed") }),
114
+ z.object({
115
+ code: z.literal("unbuilt"),
116
+ configPath: z.string().min(1)
117
+ }),
118
+ z.object({
119
+ code: z.literal("stale"),
120
+ installed: z.string().min(1),
121
+ pinned: z.string().min(1)
122
+ }),
123
+ z.object({ code: z.literal("not-found") }),
124
+ z.object({ code: z.literal("unmaterializable") }),
125
+ sourceUnreachable
126
+ ]);
127
+ /** What one accessor of a copy names: every entry of the kind, or the source's paths — items or folders. */
128
+ const copyPaths = z.union([z.literal("*"), z.array(z.string().min(1)).min(1)]);
129
+ const copyInput = z.object({
130
+ path: pathInput("linked-system"),
131
+ /**
132
+ * What to take, keyed by accessor the way the pin's `link` is — `{ tokens: ['color/brand', 'color'],
133
+ * icons: '*' }` — or `'*'` for every kind the source offers and this config can hold. A path that
134
+ * names a folder in the source takes its members. Copied rows keep the source's own names.
135
+ */
136
+ data: z.union([z.literal("*"), z.record(z.string(), copyPaths)]),
137
+ /**
138
+ * What to do at a path this config already holds. `'skip'` (the default) keeps what is there and
139
+ * binds copied refs to it; `'replace'` writes the source's body over a row of yours, and over a row
140
+ * held through another pin when `data` names it. A row the copy only reaches through a ref is kept
141
+ * either way.
142
+ */
143
+ onConflict: z.enum(["skip", "replace"]).optional()
144
+ });
145
+ const linkInput = z.object({
146
+ path: pathInput("linked-system"),
147
+ /** What to draw, keyed by accessor the way the pin's `link` is, or `'*'` for everything the source
148
+ * offers. A path that names a folder in the source covers its members. */
149
+ data: z.union([z.literal("*"), z.record(z.string(), copyPaths)])
150
+ });
151
+ const unlinkInput = z.object({
152
+ path: pathInput("linked-system"),
153
+ /** What to stop drawing, in `link`'s shape; everything when omitted. */
154
+ data: z.union([z.literal("*"), z.record(z.string(), copyPaths)]).optional()
155
+ });
156
+ const breakInput = z.object({
157
+ path: pathInput("linked-system"),
158
+ /** Whether every value drawn through the pin becomes this config's own first. */
159
+ keep: z.boolean()
160
+ });
161
+ const breakPlanInput = z.object({
162
+ path: pathInput("linked-system"),
163
+ keep: z.boolean().optional(),
164
+ /** One entity or folder detached through `copy` rather than the whole pin. */
165
+ data: z.union([z.literal("*"), z.record(z.string(), copyPaths)]).optional()
166
+ });
167
+ const rowsInput = z.object({ path: pathInput("linked-system") });
168
+ /** The parsed op input as the plan takes it. */
169
+ function copyInputOf(input) {
170
+ return {
171
+ path: String(input.path),
172
+ data: input.data,
173
+ ...input.onConflict === void 0 ? {} : { onConflict: input.onConflict }
174
+ };
175
+ }
176
+ /** A `link` or `unlink` example: one entity the source offers, under the accessor this config files it by. */
177
+ function linkExample({ config, path }) {
178
+ const target = exampleBorrowable(config, path);
179
+ if (!target) return void 0;
180
+ return {
181
+ path,
182
+ data: { [config.accessorOf(target.kind) ?? target.kind]: [target.from] }
183
+ };
184
+ }
185
+ /** An example for an op that takes the pin alone, shown once the pin has a source to read. */
186
+ function pinExample({ config, path }) {
187
+ return exampleBorrowable(config, path) ? { path } : void 0;
188
+ }
189
+ /** A break's example, shown once the pin has a source to read. */
190
+ function breakExample({ config, path }) {
191
+ return exampleBorrowable(config, path) ? {
192
+ path,
193
+ keep: true
194
+ } : void 0;
195
+ }
196
+ /** A copy's example: one entity the source offers, under the accessor this config files it by. */
197
+ function copyExample({ config, path }) {
198
+ const target = exampleBorrowable(config, path);
199
+ if (!target) return void 0;
200
+ return {
201
+ path,
202
+ data: { [config.accessorOf(target.kind) ?? target.kind]: [target.from] }
203
+ };
204
+ }
205
+ /**
206
+ * A real borrow target on the other side of a pin, for the custom ops' examples.
207
+ *
208
+ * The derived default example is built from the standard `path`/`to` address shape, which describes a
209
+ * rename and satisfies none of these ops' own `required`. So each declares one, and each declares it
210
+ * from the catalogue rather than from a canned string, because an example naming a token the source
211
+ * doesn't have teaches a caller to write a refusal.
212
+ *
213
+ * A pin with nothing attached has no catalogue to read, so the op shows no example rather than an
214
+ * invented one.
215
+ */
216
+ function exampleBorrowable(config, slug) {
217
+ const source = config.sourceFor(slug);
218
+ if (!source) return void 0;
219
+ for (const kind of source.ownedKinds()) {
220
+ if (!config.entityOf(kind)) continue;
221
+ if (!kindBorrowable(config.entityOf(kind), {
222
+ config,
223
+ source
224
+ })) continue;
225
+ const [first] = source.list(kind);
226
+ if (first) return {
227
+ kind,
228
+ from: first.path
229
+ };
230
+ }
231
+ }
232
+ const pinOptions = z.record(z.string(), z.record(z.string(), z.strictObject({
233
+ override: z.record(z.string(), z.unknown()).optional(),
234
+ extend: z.record(z.string(), z.unknown()).optional(),
235
+ hidden: z.boolean().optional()
236
+ }).refine((entry) => entry.override !== void 0 || entry.extend !== void 0 || entry.hidden !== void 0, { message: "An `options` entry needs `override`, `extend` or `hidden`." }))).optional();
237
+ const LinkedSystem = defineEntity({
238
+ kind: "linked-system",
239
+ label: "Linked system",
240
+ labelPlural: "Linked systems",
241
+ schemas: {
242
+ /**
243
+ * Draw entities from a linked system: the pin's `link` declaration grows to cover them and the
244
+ * build mints a pointer row per entity, in this one op. A row of this config's own at a covered
245
+ * path is deleted so theirs shows through; a row held through another pin becomes an extension
246
+ * through this one. `link-plan` says what that replaces before anything is written.
247
+ */
248
+ link: {
249
+ input: linkInput,
250
+ readOnly: false,
251
+ label: "Link from",
252
+ description: "Draw entities from a linked system, following the source: `data` names them by accessor — `{ tokens: ['color/brand', 'color'] }` takes an entity and a folder's members, `{ tokens: '*' }` every token, and `'*'` the whole system. What this config already holds at those paths is replaced by theirs, so run `linked-system/link-plan` with the same input first to see what that is. To hold their values as your own instead, `copy`.",
253
+ scope: "item",
254
+ approval: "reference-change",
255
+ title: (entity) => `Link something from ${entity}`,
256
+ example: linkExample,
257
+ handler: (input, config) => {
258
+ const { patches } = planLink({
259
+ config,
260
+ input: {
261
+ path: String(input.path),
262
+ data: input.data
263
+ }
264
+ });
265
+ if (patches.length === 0) return config;
266
+ return config.applyAll(patches).withDerivedRows();
267
+ }
268
+ },
269
+ "link-plan": {
270
+ input: linkInput,
271
+ readOnly: true,
272
+ label: "Plan a link",
273
+ description: "What `link` with the same input replaces — rows of yours deleted so the source shows through, rows held through another pin turned into extensions, and members left alone because they are already here from the same origin. Writes nothing.",
274
+ scope: "item",
275
+ title: (entity) => `Plan a link from ${entity}`,
276
+ example: linkExample,
277
+ handler: (input, config) => {
278
+ const { patches: _patches, ...plan } = planLink({
279
+ config,
280
+ input: {
281
+ path: String(input.path),
282
+ data: input.data
283
+ }
284
+ });
285
+ return plan;
286
+ }
287
+ },
288
+ /**
289
+ * Stop drawing from a linked system, whole or in part. The declaration narrows and the rows it
290
+ * minted retire in the same op; the pin stays, so it can be linked again.
291
+ */
292
+ unlink: {
293
+ input: unlinkInput,
294
+ readOnly: false,
295
+ label: "Unlink from",
296
+ description: "Stop following a linked system's entities, in `link`'s shape or everything when `data` is omitted. The rows the declaration minted go with it, and so do the linked rows that point at them, since left linked they would bring them back; nothing of yours moves. A row of yours pointing at one of theirs refuses this — `copy` what is used first, or `break` with `keep: true`. `unlink-plan` names everything that goes.",
297
+ scope: "item",
298
+ approval: "reference-change",
299
+ title: (entity) => `Unlink from ${entity}`,
300
+ example: pinExample,
301
+ handler: (input, config) => {
302
+ const { patches, blocked } = planUnlink({
303
+ config,
304
+ input: {
305
+ path: String(input.path),
306
+ ...input.data === void 0 ? {} : { data: input.data }
307
+ }
308
+ });
309
+ if (blocked !== void 0) rejectData(blocked);
310
+ if (patches.length === 0) return config;
311
+ return config.applyAll(patches);
312
+ }
313
+ },
314
+ "unlink-plan": {
315
+ input: unlinkInput,
316
+ readOnly: true,
317
+ label: "Plan an unlink",
318
+ description: "What `unlink` with the same input removes — the minted rows the narrower declaration no longer reaches, with the linked rows taken along because they point at them named in `cascade` — and why the config would refuse it. Writes nothing.",
319
+ scope: "item",
320
+ title: (entity) => `Plan unlinking from ${entity}`,
321
+ example: pinExample,
322
+ handler: (input, config) => {
323
+ const { patches: _patches, ...plan } = planUnlink({
324
+ config,
325
+ input: {
326
+ path: String(input.path),
327
+ ...input.data === void 0 ? {} : { data: input.data }
328
+ }
329
+ });
330
+ return plan;
331
+ }
332
+ },
333
+ /**
334
+ * Break the link: the pin goes, and everything drawn through it either becomes this config's own
335
+ * or goes with it.
336
+ */
337
+ break: {
338
+ input: breakInput,
339
+ readOnly: false,
340
+ label: "Break link",
341
+ description: "Remove a linked system. With `keep: true` every value drawn through it is copied in as your own first, and a row of yours that pointed through the pin points at the copies; with `keep: false` the rows it minted go, and a row of yours still pointing through it refuses the break. Run `linked-system/break-plan` first to see every row it reaches.",
342
+ scope: "item",
343
+ approval: "destructive",
344
+ title: (entity) => `Break the link to ${entity}`,
345
+ example: breakExample,
346
+ handler: (input, config) => {
347
+ const { patches } = planBreak({
348
+ config,
349
+ input: {
350
+ path: String(input.path),
351
+ keep: Boolean(input.keep)
352
+ }
353
+ });
354
+ return config.applyAll(patches);
355
+ }
356
+ },
357
+ "break-plan": {
358
+ input: breakPlanInput,
359
+ readOnly: true,
360
+ label: "Plan a break",
361
+ description: "Every row a break reaches, and why the config would refuse it. `keep` plans `break` with the same input; `data` in `copy`'s shape plans detaching those entities alone — the rows the copy lands, the rows of yours it points at the copies, and the rows still linked that reference what moves. Writes nothing.",
362
+ scope: "item",
363
+ title: (entity) => `Plan breaking the link to ${entity}`,
364
+ example: breakExample,
365
+ handler: (input, config) => {
366
+ const { patches: _patches, ...plan } = planBreak({
367
+ config,
368
+ input: {
369
+ path: String(input.path),
370
+ ...input.keep === void 0 ? {} : { keep: Boolean(input.keep) },
371
+ ...input.data === void 0 ? {} : { data: input.data }
372
+ }
373
+ });
374
+ return plan;
375
+ }
376
+ },
377
+ /**
378
+ * The source's catalogue read against this config: one row per top-level folder or item the
379
+ * source offers, saying whether it is linked here and what linking it would replace.
380
+ */
381
+ rows: {
382
+ input: rowsInput,
383
+ readOnly: true,
384
+ label: "Source rows",
385
+ description: "Every top-level folder and item the linked system offers, read against this config: whether every member is linked through this pin, how many rows of yours a `link` would replace, and how many members are already here from the source's own origin. Needs the source attached; empty otherwise.",
386
+ scope: "item",
387
+ title: (entity) => `What ${entity} offers`,
388
+ example: pinExample,
389
+ handler: (input, config) => sourceRowStates({
390
+ config,
391
+ slug: String(input.path)
392
+ })
393
+ },
394
+ /**
395
+ * Take entities from a linked system as this config's own — the source's bodies, written once.
396
+ * A pin puts a system within reach and its `link` mints pointers; `copy` stores the value. A copied
397
+ * row is an ordinary authored row from the moment it lands: the source's later versions never reach
398
+ * it, and it renames and deletes like anything else here.
399
+ *
400
+ * A copy stands on its own. What a copied row points at comes with it — a component brings the
401
+ * style properties and tokens it names, a style property brings the token folder its values
402
+ * range over, a code component brings the files its module reaches — and every one of those refs
403
+ * lands bare, on the local copy. A path this config already holds as its own is reused rather
404
+ * than copied, and refs bind to it. Only a ref to a kind this config can't hold stays qualified,
405
+ * and the pin survives to answer it. Someone who wants theirs by reference writes `Button@slug`
406
+ * instead.
407
+ *
408
+ * A pointer at the path, minted through this pin, becomes the literal it stood for with its
409
+ * `extend` laid over the source's body. A folder's meta comes with its members, so a label the
410
+ * folder was inheriting becomes its own.
411
+ *
412
+ * `data` is keyed the way the pin's `link` is: by accessor, then `'*'` for every entry of the kind
413
+ * or the source's paths — an item, or a folder, which takes its members. `'*'` alone takes every
414
+ * kind the source offers and this config can hold. Kinds taken in one edit are copied together,
415
+ * which is what lets a copied component point at the copied tokens rather than back through the
416
+ * pin.
417
+ *
418
+ * Every row lands in one derive (`Config.applyAll`), and each is its own `create` or `replace`
419
+ * patch carrying the literal body, so a draft replays a copy onto a base with nothing attached.
420
+ */
421
+ copy: {
422
+ input: copyInput,
423
+ readOnly: false,
424
+ label: "Copy from",
425
+ description: "Take entities from a linked system as your own, holding the values the source has now and no longer following it. `data` names them by accessor: `{ tokens: ['color/brand', 'color'] }` takes an entity and a folder's members, `{ tokens: '*' }` every token, and `'*'` the whole system. What a copied entity points at comes with it, so run `linked-system/copy-plan` with the same input first and see everything that lands. To use theirs without owning it, declare it in the pin's `link` instead. A whole-system copy is thousands of rows, and on a repo-backed system each one is written into `uds.config.ts`.",
426
+ scope: "item",
427
+ approval: "reference-change",
428
+ title: (entity) => `Copy something from ${entity}`,
429
+ example: copyExample,
430
+ handler: (input, config) => {
431
+ const { patches } = planCopy({
432
+ config,
433
+ input: copyInputOf(input)
434
+ });
435
+ return config.applyAll(patches);
436
+ }
437
+ },
438
+ /**
439
+ * What that same `copy` call would do, without doing it — the rows that land, what comes along
440
+ * because something named points at it, and the rows of yours a copied ref binds to instead.
441
+ *
442
+ * A read, taking `copy`'s own input verbatim, so a surface plans the invocation it is about to
443
+ * send. The op is this plan applied, so the two cannot disagree.
444
+ */
445
+ "copy-plan": {
446
+ input: copyInput,
447
+ readOnly: true,
448
+ label: "Plan a copy",
449
+ description: "See what copying would bring before you do it — what lands as yours, what comes along because it is pointed at, and what you already hold that the copies will point at. Takes exactly the same input as `copy` and writes nothing. Run it before `copy`, and always before a `'*'` copy.",
450
+ scope: "item",
451
+ title: (entity) => `Plan a copy from ${entity}`,
452
+ example: copyExample,
453
+ handler: (input, config) => {
454
+ const { patches: _patches, ...plan } = planCopy({
455
+ config,
456
+ input: copyInputOf(input)
457
+ });
458
+ return plan;
459
+ }
460
+ }
461
+ },
462
+ fields: z.strictObject({
463
+ /**
464
+ * The sibling config this link resolves from — the repo-local form, and the primary field, so it is
465
+ * declared as a bare string.
466
+ *
467
+ * A config path rather than a directory, because `-c, --config <path>` is the convention everywhere
468
+ * else and a package may hold more than one config (web and native), so a directory would be
469
+ * ambiguous about which system you meant. Naming the config also lets install check
470
+ * `.uds/build-context.json`, so pointing at one config and reading another's output is an error
471
+ * rather than a silent wrong answer.
472
+ *
473
+ * ```ts
474
+ * linkedSystems: {
475
+ * ds: '../design-system/uds.config.ts', // repo-local
476
+ * yahooOs: LinkedSystem.item({ systemId, name, version }), // published
477
+ * }
478
+ * ```
479
+ *
480
+ * Both forms are declared in one place, because "which system" and "where from" are one decision
481
+ * per link. npm spells it the same way, with the value saying both (`"workspace:*"` against
482
+ * `"^2.4.0"`).
483
+ *
484
+ * It is `configPath` and not `path`, because an entity's identity is its `path`: `Entity` assigns
485
+ * fields onto itself, so a field called `path` would overwrite the slug and key the link by its own
486
+ * directory. Nor `workspace`, which is a product noun here (org → workspace → team → system). It is
487
+ * literally a config path, and it mirrors `githubConfigPath` on `systems`, the field push derives
488
+ * this link's identity against.
489
+ *
490
+ * **`configPath` wins for resolution.** When it is set, the build reads the sibling's artifact and
491
+ * ignores `version`: a repo-local link tracks whatever the sibling currently is, which is the point
492
+ * in a monorepo, and a built artifact carries no `systemId` or `version` to pin against.
493
+ */
494
+ configPath: z.string().min(1).optional(),
495
+ /**
496
+ * What this system draws from that source — a standing declaration, and the thing the build turns
497
+ * into rows.
498
+ * ```ts
499
+ * link: '*' // everything, every kind
500
+ * link: { tokens: '*', icons: '*' } // those kinds whole
501
+ * link: { tokens: ['color/brand', 'color/paper'] } // those entities
502
+ * link: { components: ['actions'] } // a folder: its members
503
+ * ```
504
+ * `Config.withDerivedRows` reads it and makes a pointer row per entity it covers — on every build,
505
+ * and inside the apply of any patch to this pin — so a link's cost in authored source is this one
506
+ * declaration whatever the source's size, and the rows come and go with it.
507
+ *
508
+ * Keyed by accessor (`tokens`, `components`) rather than by kind (`token`, `component`), because
509
+ * this is read in a config file, and a config file is keyed by accessor everywhere else —
510
+ * `register({ tokens, components })`, `sources: { linkedSystems: … }`. Ops and refs use the kind.
511
+ * `Config.accessorOf` maps between them, off the config type's own manifest, so no kind is named
512
+ * here. Paths are the source's own; a path that names a folder there covers its members.
513
+ *
514
+ * **Standing, so a source that grows is covered without re-running anything.** The declaration is
515
+ * read on every build, and what it produces is an ordinary stored row — indistinguishable from a
516
+ * borrow written by hand, which is what keeps the rename gate, `inlineAllSources` when it seals an
517
+ * artifact and `emitBorrowedComponents` when it emits a module all working unchanged.
518
+ *
519
+ * A path this config already holds is left alone, silently, whether it authored that row or
520
+ * borrowed it. Local shadows a source, and that is what makes deriving over a grown source
521
+ * additive rather than a set of conflicts to resolve. It is also how one of their entities is kept
522
+ * out: author your own at that path, or narrow what this declares.
523
+ *
524
+ * Deleting a row this covers is refused ({@link EntityKind.bulkAdopts} is what reads it): the
525
+ * build would make it again on the next pass, and a delete that comes back is worse than one that
526
+ * was refused. To own one of their entities, `copy` it; to stop drawing it, `unlink` it.
527
+ *
528
+ * The gestures that write this are the kind's own ops — `link`, `unlink`, `break` — each with a
529
+ * `-plan` read beside it, so a surface never assembles the declaration by hand.
530
+ *
531
+ * `'*'` means every kind that may be drawn on, which is every kind that may be borrowed — each
532
+ * kind says which it is (`EntityKind.linkable`). Nothing is subtracted: a pin and a `file` aren't
533
+ * borrowable, so no borrow of one exists for this to cover.
534
+ */
535
+ link: z.union([z.literal("*"), z.record(z.string(), z.union([z.literal("*"), z.array(z.string().min(1)).min(1)]))]).optional(),
536
+ /**
537
+ * What this system does to individual entities of the source, keyed by accessor, then by the path
538
+ * the source spells, then by verb.
539
+ *
540
+ * ```ts
541
+ * options: {
542
+ * tokens: {
543
+ * 'color/brand': {
544
+ * override: { modifiers: { dark: Token.ref('spectrum/violet/70') } },
545
+ * extend: { modifiers: { highContrast: Token.ref('spectrum/violet/90') } },
546
+ * },
547
+ * 'palette/legacy': { hidden: true },
548
+ * },
549
+ * }
550
+ * ```
551
+ *
552
+ * `override` changes a value the source defines and `extend` covers a case it does not. Both land in
553
+ * the row's `extend`, so a bulk adjustment and a hand-written `.extend(...)` store the same thing and
554
+ * nothing downstream can tell which spelling made it. Both may sit on one path: a token overridden
555
+ * under one modifier and extended under another is one entity pulled two ways.
556
+ *
557
+ * OPEN: `overlayBody` combines the two with a spread, so two verbs naming one top-level field keep
558
+ * only the `extend` half — the example above loses its dark override. They have to merge by member,
559
+ * the way the stored `extend` merges into the source's body.
560
+ *
561
+ * Which verb applies is derivable — comparing the body against the source says whether a leaf is
562
+ * new — and the build does compare, to check the declaration. It is declared anyway because that is
563
+ * what notices the source moving: a field they add later turns an extension into an override
564
+ * silently, and a declared verb is what has something to disagree with.
565
+ *
566
+ * `hidden` keeps the entity and stops offering it; references already resolving through it have to
567
+ * keep working, so nothing about the output changes. Carried on {@link SourceOverlay}, and read by no
568
+ * authoring surface yet.
569
+ *
570
+ * A field rather than accessor keys at the pin's top level, so the pin stays a closed object and a
571
+ * misspelled `systemId` is still refused. Only these keys are open, and they are accessor names
572
+ * for the reason `link` takes accessors: a config file is accessor-keyed everywhere else.
573
+ *
574
+ * OPEN: nothing checks them against the config's own accessors, so a misspelled `toknes` covers no
575
+ * path and says nothing.
576
+ */
577
+ options: pinOptions,
578
+ /**
579
+ * An npm package that ships a built system — `@acme/design-system`.
580
+ *
581
+ * The third origin, and the one a consumer outside the publisher's monorepo and off their Create
582
+ * instance actually has. Resolution walks the `node_modules` ancestry for the name and reads that
583
+ * package's own build output, so the package has to ship its `.uds/` directory.
584
+ *
585
+ * **A name, not a path into `node_modules`.** A path would hardcode a hoisting layout, which is the
586
+ * one thing about `node_modules` a consumer can't predict, and is why the resolver walks upward. A
587
+ * name also keeps the origin legible: a linking surface can say "from npm" instead of
588
+ * pattern-matching a directory.
589
+ *
590
+ * **npm does the pinning, so there is no `version` here.** A `package` link tracks whatever is
591
+ * installed, the way a `configPath` link tracks whatever the sibling currently is. `package.json`
592
+ * and the lockfile already decide it, so a second copy here would be a version nobody resolves
593
+ * against. `version` stays observed for this origin, as it is for a sibling.
594
+ */
595
+ package: z.string().min(1).optional(),
596
+ /**
597
+ * The source's database id — durable identity, which survives a rename on their side.
598
+ *
599
+ * Optional, because a repo-local link has none to give. Identity is a Create concept resolved from
600
+ * the repo binding, and the only thing that reads it is Studio, which is only reached by `uds push`.
601
+ * So push derives it for a `configPath` link and writes it into the artifact, and nothing local asks
602
+ * an author to hand-type an unverifiable id.
603
+ */
604
+ systemId: z.string().min(1).optional(),
605
+ /** Display only, never identity: what to call this source in the UI. Defaults to the slug. */
606
+ name: z.string().optional(),
607
+ /**
608
+ * The exact published version every ref through this link resolves against.
609
+ *
610
+ * For a `configPath` link this is **observed rather than pinned**: push records what the sibling was
611
+ * at push time so Studio can say which version it saw, and the local build still tracks current
612
+ * state. Read as a pin, a repo-local link would go stale the moment the sibling moved.
613
+ */
614
+ version: z.string().regex(EXACT_VERSION, "A link is pinned to an exact version (2.4.0), not a range").optional()
615
+ }),
616
+ computed: {
617
+ /**
618
+ * What to call this source — its `name`, or the slug when it declares none.
619
+ *
620
+ * The fallback belongs here because the default is a fact about the field: `name` is optional, and
621
+ * a source with none is the slug it's filed under. Left to consumers, every surface that renders a
622
+ * source implements the same `??`, and one of them eventually renders `undefined`.
623
+ */
624
+ displayName: ({ member, path }) => typeof member.name === "string" && member.name.length > 0 ? member.name : path },
625
+ /**
626
+ * The fields that decide which system a slug resolves against — the same ones `uds install` records
627
+ * in `pin.json`. `name` is display only, so a value borrowed through this link survives a rename of
628
+ * the label.
629
+ */
630
+ sourceIdentity: [
631
+ "configPath",
632
+ "package",
633
+ "systemId",
634
+ "version"
635
+ ],
636
+ /** Which system, at whatever version: two pins agreeing here draw on one origin. */
637
+ sourceOrigin: [
638
+ "configPath",
639
+ "package",
640
+ "systemId"
641
+ ],
642
+ /**
643
+ * A pin is never borrowed, only declared. Taking one from a source would make their dependencies
644
+ * yours, at their versions, including systems you have no access to and no way to see — and the slug
645
+ * would join `sourceSlugs()` as a source this config must resolve on its own. What motivates it needs
646
+ * none of that: a borrow whose value points at a system two hops away already resolves, because
647
+ * `ownerOf` follows the chain through the source that declared it.
648
+ *
649
+ * Wanting a direct dependency on what a source depends on is reasonable, and it is a pin of your own,
650
+ * with a version you chose.
651
+ */
652
+ linkable: false,
653
+ /**
654
+ * What a resolver for this kind may answer with. The framework supplies the `attached`/`unavailable`
655
+ * tag; these two say what each branch of this kind's answer carries.
656
+ */
657
+ sourceResolution: {
658
+ resolved: resolvedSource,
659
+ unavailable: sourceUnavailable
660
+ },
661
+ /**
662
+ * Whether this pin's `link` declaration covers one of the source's entities — the derive pass's
663
+ * question and the delete gate's, answered here because `link` is this kind's field and its shape
664
+ * is this kind's to interpret.
665
+ *
666
+ * `'*'` at either level covers everything under it; a list covers the paths it names, as the source
667
+ * spells them, and a named folder covers its members. Nothing excludes a kind here — a kind a
668
+ * whole-source take does not reach is one no borrow of it exists for (`EntityKind.linkable`), so
669
+ * the claim is unreachable rather than filtered.
670
+ */
671
+ bulkAdopts: ({ body, kind, accessor, path }) => {
672
+ const declared = body.link;
673
+ if (declared === void 0) return false;
674
+ if (declared === "*") return true;
675
+ const paths = declared[accessor ?? kind];
676
+ if (paths === void 0) return false;
677
+ if (paths === "*") return true;
678
+ return paths.some((declaredPath) => declaredPath === path || path.startsWith(`${declaredPath}/`));
679
+ },
680
+ bulkOverlay: ({ body, kind, accessor, path }) => {
681
+ const directive = directiveAt(body, accessor ?? kind, path);
682
+ if (!directive) return void 0;
683
+ return {
684
+ override: directive.override,
685
+ extend: directive.extend,
686
+ hidden: directive.hidden === true
687
+ };
688
+ },
689
+ /**
690
+ * The field-path address `options` stores one entry at — accessor, then the path the source
691
+ * spells, so an edit to a minted row lands on the same key {@link directiveAt} reads back.
692
+ */
693
+ bulkDirectives: (body) => {
694
+ const options = body.options;
695
+ if (!options) return [];
696
+ return Object.entries(options).flatMap(([accessor, entries]) => isPlainObject(entries) ? Object.keys(entries).map((path) => ({
697
+ accessor,
698
+ path
699
+ })) : []);
700
+ },
701
+ bulkOverlayWrite: ({ accessor, path, overlay }) => ({ [`options.${accessor}.${path}`]: overlay })
702
+ });
703
+ /** The `options` entry for one path, or `undefined` where the pin says nothing about it. */
704
+ function directiveAt(body, accessor, path) {
705
+ return body.options?.[accessor]?.[path];
706
+ }
707
+ /**
708
+ * The source an entity comes from — `undefined` for one wholly this config's own, and for one that
709
+ * involves two systems, since naming either would label the other wrongly. See `Entity.source`.
710
+ *
711
+ * This is the provenance read. It folds both ways an entity can involve another system — borrowed and
712
+ * locally extended, or authored here with a value pointing through a link — because a badge, an
713
+ * adornment and a "Source" row want the same answer either way. {@link borrowedItem} is the other read,
714
+ * the one a write gate wants, and picking the wrong one is quiet in both directions.
715
+ *
716
+ * Two hops: the entity's `source` answers which slug, and this kind answers for the slug. It lives
717
+ * beside the kind rather than on `Config` because the answer is one of these items, typed off the schema
718
+ * above. A `Config` method could only hand back a bare `Entity` or a bag naming the fields this kind
719
+ * happens to declare, and every consumer of that bag has to be found again when the kind gains one.
720
+ *
721
+ * Named for the item it answers about, because {@link Config.sourceFor} already answers the
722
+ * slug-to-config question and `sourceOf` already reads the slug off a ref.
723
+ */
724
+ function sourceOfItem({ config, kind, path }) {
725
+ const slug = config.resolve(kind, path)?.source;
726
+ return slug === void 0 ? void 0 : LinkedSystem.get(config, slug);
727
+ }
728
+ /**
729
+ * The source a group draws through — set only when every member agrees on one.
730
+ *
731
+ * The agreement rule is kind-agnostic and lives on {@link Config.groupSource}. This makes the same
732
+ * second hop {@link sourceOfItem} makes, so a surface gets the typed pin rather than a slug it would
733
+ * have to look up itself.
734
+ */
735
+ function sourceOfGroup({ config, kind, groupPath }) {
736
+ const slug = config.groupSource({
737
+ kind,
738
+ groupPath
739
+ });
740
+ return slug === void 0 ? void 0 : LinkedSystem.get(config, slug);
741
+ }
742
+ /**
743
+ * The source an item is — `undefined` for one this config authored, including one whose own fields
744
+ * point through a link.
745
+ *
746
+ * The write gate's reader, where {@link sourceOfItem} is the badge's. They are easy to swap, and the
747
+ * failure is quiet in both directions: gating on `sourceOfItem` locks a local alias its own author
748
+ * wrote, and badging on this one leaves a borrowed value looking native.
749
+ *
750
+ * To choose between them, ask whether the other side is affected. Retargeting a ref we declared changes
751
+ * nothing but our artifact, so it is ours; the fields of an item we borrowed are the source's, and a
752
+ * local edit to them can only be an overlay. See `Entity.borrowedFrom`.
753
+ */
754
+ function borrowedItem({ config, kind, path }) {
755
+ const slug = config.resolve(kind, path)?.borrowedFrom;
756
+ return slug === void 0 ? void 0 : LinkedSystem.get(config, slug);
757
+ }
758
+ /** The source a whole group is borrowed from — {@link borrowedItem}'s group form, and the same second
759
+ * hop {@link sourceOfGroup} makes. Set only when every member agrees, which is `Config`'s rule to
760
+ * state and not this one's. Gates what can be done to the folder: adding, renaming, reordering. */
761
+ function borrowedGroup({ config, kind, groupPath }) {
762
+ const slug = config.groupBorrowedFrom({
763
+ kind,
764
+ groupPath
765
+ });
766
+ return slug === void 0 ? void 0 : LinkedSystem.get(config, slug);
767
+ }
768
+ /**
769
+ * A config path as a source is indexed by — separators normalized and a leading `./` dropped, so
770
+ * `./a/b` and `a/b` name one source rather than two.
771
+ *
772
+ * Not exported. It is the grammar of the `configPath` field, and the question a caller has is
773
+ * {@link sourceSlugFor}, which applies it. A caller normalizing paths itself is a second copy of this
774
+ * rule, and the copies disagree the first time one forgets a case.
775
+ */
776
+ const configPathKey = (value) => value.replace(/\\/g, "/").replace(/^\.\//, "");
777
+ /**
778
+ * The slug this config already draws a source under, or `undefined` when it doesn't draw it at all.
779
+ *
780
+ * Takes either way a source can be named, because the same system reached two ways is the same system:
781
+ * a form offering to draw on one that is already a source should say so and name the slug it lives
782
+ * under, rather than offering a second declaration under another name. Which fields those are is this
783
+ * kind's to know ({@link LinkedSystem.sourceIdentity}); a caller that indexed them itself would be
784
+ * restating the schema.
785
+ */
786
+ function sourceSlugFor({ config, configPath, systemId }) {
787
+ if (!config) return void 0;
788
+ const wanted = configPath ? configPathKey(configPath) : void 0;
789
+ for (const entry of LinkedSystem.list(config)) {
790
+ if (wanted !== void 0 && entry.configPath !== void 0 && configPathKey(entry.configPath) === wanted) return entry.path;
791
+ if (systemId && entry.systemId === systemId) return entry.path;
792
+ }
793
+ }
794
+ /**
795
+ * A system's name as a starting slug — lowercase, a single path segment as {@link LINK_SLUG} wants,
796
+ * and not one this config has already taken.
797
+ *
798
+ * The suffix matters because the suggestion is applied on open and on every pick, before anyone has
799
+ * typed: suggesting a slug that already exists greets you with a validation error you didn't cause and
800
+ * a dead submit button. It is for two different systems whose names normalize to the same slug — the
801
+ * same system declared twice is refused outright, by {@link sourceSlugFor}.
802
+ */
803
+ function suggestLinkSlug({ name, taken }) {
804
+ const base = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
805
+ if (!base) return "";
806
+ if (!isLinkSlug(base)) return "";
807
+ if (!taken.has(base)) return base;
808
+ for (let suffix = 2;; suffix++) {
809
+ const candidate = `${base}-${suffix}`;
810
+ if (!taken.has(candidate)) return candidate;
811
+ }
812
+ }
813
+ //#endregion
814
+ export { LINK_SLUG, LINK_SLUG_MESSAGE, LinkedSystem, borrowedGroup, borrowedItem, isLinkSlug, resolvedSource, sourceOfGroup, sourceOfItem, sourceSlugFor, sourceUnavailable, suggestLinkSlug };