openplanr 3.4.0 → 3.5.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 (336) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/cli/commands/design.d.ts +3 -0
  3. package/dist/cli/commands/design.d.ts.map +1 -0
  4. package/dist/cli/commands/design.js +137 -0
  5. package/dist/cli/commands/design.js.map +1 -0
  6. package/dist/cli/commands/groups/operations.d.ts.map +1 -1
  7. package/dist/cli/commands/groups/operations.js +2 -0
  8. package/dist/cli/commands/groups/operations.js.map +1 -1
  9. package/dist/dashboard/.vite/manifest.json +1 -1
  10. package/dist/dashboard/assets/{index-CvQAlBy3.js → index-B-l_941W.js} +2 -2
  11. package/dist/dashboard/assets/{index-CvQAlBy3.js.map → index-B-l_941W.js.map} +1 -1
  12. package/dist/dashboard/dashboard-manifest.json +13 -13
  13. package/dist/dashboard/index.html +1 -1
  14. package/dist/services/design-artifact-service.d.ts +22 -0
  15. package/dist/services/design-artifact-service.d.ts.map +1 -1
  16. package/dist/services/design-artifact-service.js +76 -16
  17. package/dist/services/design-artifact-service.js.map +1 -1
  18. package/dist/services/runtime-manager/runtime-package.js +1 -1
  19. package/dist/services/runtime-manager/runtime-package.js.map +1 -1
  20. package/docs/CLI.md +29 -0
  21. package/lib/host-packages/adapter-registry.json +5 -5
  22. package/lib/host-packages/capability-map.json +1 -1
  23. package/lib/host-packages/claude/.claude-plugin/marketplace.json +2 -2
  24. package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +1 -1
  25. package/lib/host-packages/claude/openplanr/.openplanr-content.json +24 -340
  26. package/lib/host-packages/claude/openplanr/README.md +8 -8
  27. package/lib/host-packages/claude/openplanr/skills/artifact/references/design-sharing.md +2 -2
  28. package/lib/host-packages/claude/openplanr/skills/design/SKILL.md +23 -12
  29. package/lib/host-packages/claude/openplanr/skills/design/references/design-system.md +2 -2
  30. package/lib/host-packages/claude/openplanr/skills/design/references/generation.md +3 -3
  31. package/lib/host-packages/claude/openplanr/skills/design/references/hosted-canvas.md +6 -3
  32. package/lib/host-packages/claude/openplanr/skills/design/references/review.md +6 -6
  33. package/lib/host-packages/claude/openplanr/skills/design/references/team-review.md +3 -4
  34. package/lib/host-packages/claude/openplanr/skills/design/references/utilities.md +78 -38
  35. package/lib/host-packages/claude/openplanr/skills/design-loop/SKILL.md +15 -7
  36. package/lib/host-packages/claude/openplanr/skills/design-loop/references/design-system.md +2 -2
  37. package/lib/host-packages/claude/openplanr/skills/design-loop/references/generation.md +3 -3
  38. package/lib/host-packages/claude/openplanr/skills/design-loop/references/review.md +6 -6
  39. package/lib/host-packages/claude/openplanr/skills/design-loop/references/team-review.md +3 -4
  40. package/lib/host-packages/claude/openplanr/skills/design-loop/references/utilities.md +78 -38
  41. package/lib/host-packages/claude/openplanr/skills/design-review/SKILL.md +25 -5
  42. package/lib/host-packages/claude/openplanr/skills/design-review/references/design-system.md +2 -2
  43. package/lib/host-packages/claude/openplanr/skills/design-review/references/generation.md +3 -3
  44. package/lib/host-packages/claude/openplanr/skills/design-review/references/review.md +6 -6
  45. package/lib/host-packages/claude/openplanr/skills/design-review/references/team-review.md +3 -4
  46. package/lib/host-packages/claude/openplanr/skills/design-review/references/utilities.md +78 -38
  47. package/lib/host-packages/claude/openplanr/skills/plan/SKILL.md +7 -3
  48. package/lib/host-packages/cursor/openplanr/.openplanr-content.json +23 -339
  49. package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
  50. package/lib/host-packages/cursor/openplanr/rules/planr-artifact/references/design-sharing.md +2 -2
  51. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/design-system.md +2 -2
  52. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/generation.md +3 -3
  53. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/hosted-canvas.md +6 -3
  54. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/review.md +6 -6
  55. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/team-review.md +3 -4
  56. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/utilities.md +78 -38
  57. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/design-system.md +2 -2
  58. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/generation.md +3 -3
  59. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/review.md +6 -6
  60. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/team-review.md +3 -4
  61. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/utilities.md +78 -38
  62. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop.mdc +15 -7
  63. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/design-system.md +2 -2
  64. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/generation.md +3 -3
  65. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/review.md +6 -6
  66. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/team-review.md +3 -4
  67. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/utilities.md +78 -38
  68. package/lib/host-packages/cursor/openplanr/rules/planr-design-review.mdc +25 -5
  69. package/lib/host-packages/cursor/openplanr/rules/planr-design.mdc +23 -12
  70. package/lib/host-packages/cursor/openplanr/rules/planr-plan.mdc +7 -3
  71. package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
  72. package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +1 -1
  73. package/lib/host-packages/openai/openplanr/.openplanr-content.json +27 -343
  74. package/lib/host-packages/openai/openplanr/skills/artifact/references/design-sharing.md +2 -2
  75. package/lib/host-packages/openai/openplanr/skills/design/SKILL.md +23 -12
  76. package/lib/host-packages/openai/openplanr/skills/design/openplanr.skill.json +0 -11
  77. package/lib/host-packages/openai/openplanr/skills/design/references/design-system.md +2 -2
  78. package/lib/host-packages/openai/openplanr/skills/design/references/generation.md +3 -3
  79. package/lib/host-packages/openai/openplanr/skills/design/references/hosted-canvas.md +6 -3
  80. package/lib/host-packages/openai/openplanr/skills/design/references/review.md +6 -6
  81. package/lib/host-packages/openai/openplanr/skills/design/references/team-review.md +3 -4
  82. package/lib/host-packages/openai/openplanr/skills/design/references/utilities.md +78 -38
  83. package/lib/host-packages/openai/openplanr/skills/design-loop/SKILL.md +15 -7
  84. package/lib/host-packages/openai/openplanr/skills/design-loop/openplanr.skill.json +0 -11
  85. package/lib/host-packages/openai/openplanr/skills/design-loop/references/design-system.md +2 -2
  86. package/lib/host-packages/openai/openplanr/skills/design-loop/references/generation.md +3 -3
  87. package/lib/host-packages/openai/openplanr/skills/design-loop/references/review.md +6 -6
  88. package/lib/host-packages/openai/openplanr/skills/design-loop/references/team-review.md +3 -4
  89. package/lib/host-packages/openai/openplanr/skills/design-loop/references/utilities.md +78 -38
  90. package/lib/host-packages/openai/openplanr/skills/design-review/SKILL.md +25 -5
  91. package/lib/host-packages/openai/openplanr/skills/design-review/openplanr.skill.json +0 -11
  92. package/lib/host-packages/openai/openplanr/skills/design-review/references/design-system.md +2 -2
  93. package/lib/host-packages/openai/openplanr/skills/design-review/references/generation.md +3 -3
  94. package/lib/host-packages/openai/openplanr/skills/design-review/references/review.md +6 -6
  95. package/lib/host-packages/openai/openplanr/skills/design-review/references/team-review.md +3 -4
  96. package/lib/host-packages/openai/openplanr/skills/design-review/references/utilities.md +78 -38
  97. package/lib/host-packages/openai/openplanr/skills/plan/SKILL.md +7 -3
  98. package/lib/host-packages/openai/openplanr/skills/plan/openplanr.skill.json +0 -11
  99. package/package.json +2 -2
  100. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-artifact-shell.mjs +0 -1772
  101. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-document.mjs +0 -2937
  102. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-entities.mjs +0 -439
  103. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-handoff.mjs +0 -2370
  104. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-loopback-server.mjs +0 -359
  105. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-parse5-parser.mjs +0 -3970
  106. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-parse5-tokenizer.mjs +0 -3501
  107. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review-state-store.mjs +0 -45
  108. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review.mjs +0 -3376
  109. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -38
  110. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-sandbox-guards.mjs +0 -1102
  111. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +0 -321
  112. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs +0 -1652
  113. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs +0 -1037
  114. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +0 -137
  115. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design.mjs +0 -1500
  116. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -2023
  117. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/lib/artifact/ui/studio-shell.css +0 -2
  118. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/01-module-runtime.js +0 -18
  119. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +0 -814
  120. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +0 -10
  121. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +0 -2568
  122. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +0 -60
  123. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/package.json +0 -65
  124. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/enhancements.css +0 -388
  125. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/handoff-center.css +0 -81
  126. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.css +0 -1
  127. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +0 -139
  128. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +0 -1159
  129. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +0 -30
  130. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +0 -900
  131. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +0 -2261
  132. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +0 -1595
  133. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +0 -1467
  134. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +0 -1860
  135. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/07-react-dom-client-work-loop-scheduling.js +0 -914
  136. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/08-react-dom-client-dom-events-properties-hydration.js +0 -2578
  137. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +0 -582
  138. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +0 -17
  139. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +0 -12
  140. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +0 -285
  141. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +0 -2773
  142. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-studio-shell.js +0 -134
  143. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +0 -2207
  144. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-design-review-experience.js +0 -16
  145. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-studio-shell.js +0 -8
  146. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/18-interface-primitives.js +0 -3883
  147. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/19-interface-primitives.js +0 -966
  148. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/20-studio-shell.js +0 -520
  149. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +0 -1597
  150. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +0 -311
  151. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +0 -163
  152. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/registries/artifact-theme.json +0 -64
  153. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.0.0/design-manifest.schema.json +0 -90
  154. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-envelope.schema.json +0 -142
  155. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  156. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-bundle.schema.json +0 -584
  157. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-context.schema.json +0 -191
  158. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-handoff.schema.json +0 -407
  159. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-metadata-payload.schema.json +0 -106
  160. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +0 -398
  161. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +0 -518
  162. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +0 -106
  163. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-review-metadata-payload.schema.json +0 -107
  164. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.14.0/artifact-theme.schema.json +0 -316
  165. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/artifact-envelope.schema.json +0 -408
  166. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/design-review-bundle.schema.json +0 -590
  167. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-document.schema.json +0 -506
  168. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-review-bundle.schema.json +0 -822
  169. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-bundle.schema.json +0 -346
  170. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +0 -1232
  171. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design.mjs +0 -2611
  172. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -21
  173. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +0 -163
  174. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  175. package/lib/host-packages/claude/openplanr/skills/design/scripts/design.mjs +0 -12
  176. package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design.mjs +0 -12
  177. package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design.mjs +0 -12
  178. package/lib/host-packages/claude/openplanr/skills/plan/scripts/design.mjs +0 -12
  179. package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design.mjs +0 -12
  180. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design.mjs +0 -12
  181. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design.mjs +0 -12
  182. package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design.mjs +0 -12
  183. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-artifact-shell.mjs +0 -1772
  184. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-document.mjs +0 -2937
  185. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-entities.mjs +0 -439
  186. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-handoff.mjs +0 -2370
  187. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-loopback-server.mjs +0 -359
  188. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-parse5-parser.mjs +0 -3970
  189. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-parse5-tokenizer.mjs +0 -3501
  190. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review-state-store.mjs +0 -45
  191. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review.mjs +0 -3376
  192. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -38
  193. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-sandbox-guards.mjs +0 -1102
  194. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +0 -321
  195. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs +0 -1652
  196. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs +0 -1037
  197. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +0 -137
  198. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design.mjs +0 -1500
  199. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -2023
  200. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/lib/artifact/ui/studio-shell.css +0 -2
  201. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/01-module-runtime.js +0 -18
  202. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +0 -814
  203. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +0 -10
  204. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +0 -2568
  205. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +0 -60
  206. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/package.json +0 -65
  207. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/enhancements.css +0 -388
  208. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/handoff-center.css +0 -81
  209. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.css +0 -1
  210. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +0 -139
  211. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +0 -1159
  212. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +0 -30
  213. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +0 -900
  214. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +0 -2261
  215. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +0 -1595
  216. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +0 -1467
  217. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +0 -1860
  218. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/07-react-dom-client-work-loop-scheduling.js +0 -914
  219. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/08-react-dom-client-dom-events-properties-hydration.js +0 -2578
  220. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +0 -582
  221. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +0 -17
  222. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +0 -12
  223. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +0 -285
  224. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +0 -2773
  225. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-studio-shell.js +0 -134
  226. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +0 -2207
  227. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-design-review-experience.js +0 -16
  228. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-studio-shell.js +0 -8
  229. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/18-interface-primitives.js +0 -3883
  230. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/19-interface-primitives.js +0 -966
  231. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/20-studio-shell.js +0 -520
  232. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +0 -1597
  233. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +0 -311
  234. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +0 -163
  235. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/registries/artifact-theme.json +0 -64
  236. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.0.0/design-manifest.schema.json +0 -90
  237. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-envelope.schema.json +0 -142
  238. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  239. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-bundle.schema.json +0 -584
  240. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-context.schema.json +0 -191
  241. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-handoff.schema.json +0 -407
  242. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-metadata-payload.schema.json +0 -106
  243. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +0 -398
  244. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +0 -518
  245. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +0 -106
  246. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-review-metadata-payload.schema.json +0 -107
  247. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.14.0/artifact-theme.schema.json +0 -316
  248. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/artifact-envelope.schema.json +0 -408
  249. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/design-review-bundle.schema.json +0 -590
  250. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-document.schema.json +0 -506
  251. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-review-bundle.schema.json +0 -822
  252. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-bundle.schema.json +0 -346
  253. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +0 -1232
  254. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design.mjs +0 -2611
  255. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -21
  256. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +0 -163
  257. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  258. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-artifact-shell.mjs +0 -1772
  259. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-document.mjs +0 -2937
  260. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-entities.mjs +0 -439
  261. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-handoff.mjs +0 -2370
  262. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-loopback-server.mjs +0 -359
  263. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-parse5-parser.mjs +0 -3970
  264. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-parse5-tokenizer.mjs +0 -3501
  265. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review-state-store.mjs +0 -45
  266. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review.mjs +0 -3376
  267. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -38
  268. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-sandbox-guards.mjs +0 -1102
  269. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +0 -321
  270. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs +0 -1652
  271. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs +0 -1037
  272. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +0 -137
  273. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design.mjs +0 -1500
  274. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -2023
  275. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/lib/artifact/ui/studio-shell.css +0 -2
  276. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/01-module-runtime.js +0 -18
  277. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +0 -814
  278. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +0 -10
  279. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +0 -2568
  280. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +0 -60
  281. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/package.json +0 -65
  282. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/enhancements.css +0 -388
  283. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/handoff-center.css +0 -81
  284. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.css +0 -1
  285. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +0 -139
  286. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +0 -1159
  287. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +0 -30
  288. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +0 -900
  289. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +0 -2261
  290. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +0 -1595
  291. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +0 -1467
  292. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +0 -1860
  293. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/07-react-dom-client-work-loop-scheduling.js +0 -914
  294. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/08-react-dom-client-dom-events-properties-hydration.js +0 -2578
  295. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +0 -582
  296. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +0 -17
  297. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +0 -12
  298. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +0 -285
  299. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +0 -2773
  300. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-studio-shell.js +0 -134
  301. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +0 -2207
  302. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-design-review-experience.js +0 -16
  303. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-studio-shell.js +0 -8
  304. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/18-interface-primitives.js +0 -3883
  305. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/19-interface-primitives.js +0 -966
  306. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/20-studio-shell.js +0 -520
  307. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +0 -1597
  308. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +0 -311
  309. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +0 -163
  310. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/registries/artifact-theme.json +0 -64
  311. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.0.0/design-manifest.schema.json +0 -90
  312. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-envelope.schema.json +0 -142
  313. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  314. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-bundle.schema.json +0 -584
  315. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-context.schema.json +0 -191
  316. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-handoff.schema.json +0 -407
  317. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-metadata-payload.schema.json +0 -106
  318. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +0 -398
  319. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +0 -518
  320. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +0 -106
  321. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-review-metadata-payload.schema.json +0 -107
  322. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.14.0/artifact-theme.schema.json +0 -316
  323. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/artifact-envelope.schema.json +0 -408
  324. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/design-review-bundle.schema.json +0 -590
  325. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-document.schema.json +0 -506
  326. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-review-bundle.schema.json +0 -822
  327. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-bundle.schema.json +0 -346
  328. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +0 -1232
  329. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design.mjs +0 -2611
  330. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -21
  331. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +0 -163
  332. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  333. package/lib/host-packages/openai/openplanr/skills/design/scripts/design.mjs +0 -12
  334. package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design.mjs +0 -12
  335. package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design.mjs +0 -12
  336. package/lib/host-packages/openai/openplanr/skills/plan/scripts/design.mjs +0 -12
@@ -1,2937 +0,0 @@
1
- import {
2
- DESIGN_DOCUMENT_SCHEMA,
3
- assertDesignDocument,
4
- bundleLocalDocument,
5
- initialFrameHeight,
6
- inspectLocalDocumentBody,
7
- loadReviewContext,
8
- parse,
9
- parseFragment,
10
- resolveLocalDocumentFile,
11
- reviewDigest,
12
- reviewFingerprints,
13
- serialize,
14
- validateDesignProps
15
- } from "./design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs";
16
- import {
17
- AA_NORMAL,
18
- contrastRatio,
19
- embedJson,
20
- escapeHtml,
21
- renderArtifactShellDocument,
22
- renderPlanrMark
23
- } from "./design-artifact-shell.mjs";
24
- import {
25
- ARTIFACT_ERROR_CODES,
26
- ARTIFACT_MAX_SOURCES,
27
- ARTIFACT_MAX_VIEWS,
28
- PipelineError,
29
- createSharedArtifactEnvelope,
30
- digestArtifactEnvelope,
31
- resolveArtifactHtml
32
- } from "./design-shared-artifact-support-protocol-contracts-9f6918b0.mjs";
33
- import {
34
- ARTIFACT_FRAME_GUARD_TEMPLATE,
35
- ARTIFACT_HOST_GUARD_TEMPLATE,
36
- ARTIFACT_WORKER_GUARD_SOURCE
37
- } from "./design-sandbox-guards.mjs";
38
- import {
39
- atomicBytes,
40
- atomicJson,
41
- currentDesign,
42
- designSpecPath,
43
- hash,
44
- json,
45
- readJson,
46
- recoverDesignPublication
47
- } from "./design-shared-design-support-0bac378d.mjs";
48
- import {
49
- acquireStartLock
50
- } from "./design-loopback-server.mjs";
51
-
52
- // packages/design/lib/design/frame-size.mjs
53
- function designFrameLabel(frame) {
54
- const dimensions = `${frame.width} \xD7 ${frame.height}`;
55
- return frame.label.includes(dimensions) ? frame.label : `${frame.label} \xB7 ${dimensions}`;
56
- }
57
- function designFrameViewport(document2, frame) {
58
- return {
59
- width: frame.width,
60
- height: frame.height === "auto" ? initialFrameHeight(document2) : frame.height
61
- };
62
- }
63
- function matchesDesignFrame(frame, viewport) {
64
- return Boolean(
65
- frame && viewport?.width === frame.width && (frame.height === "auto" ? Number.isInteger(viewport.height) && viewport.height > 0 && viewport.height <= 16384 : viewport.height === frame.height)
66
- );
67
- }
68
- function observeDesignFrameSize() {
69
- let pending = false;
70
- const measure = async () => {
71
- if (pending) return;
72
- pending = true;
73
- try {
74
- await document.fonts.ready;
75
- await new Promise((resolve3) => requestAnimationFrame(() => requestAnimationFrame(resolve3)));
76
- parent.postMessage(
77
- {
78
- type: "openplanr:design-size",
79
- screenId: document.body.getAttribute("data-planr-screen"),
80
- width: innerWidth,
81
- height: Math.min(16384, Math.max(1, document.documentElement.scrollHeight))
82
- },
83
- "*"
84
- );
85
- } finally {
86
- pending = false;
87
- }
88
- };
89
- const schedule = () => {
90
- void measure().catch((error) => {
91
- parent.postMessage({ type: "openplanr:design-size-error", message: error.message }, "*");
92
- });
93
- };
94
- addEventListener("message", (event) => {
95
- if (event.source === parent && event.data?.type === "openplanr:design-measure") schedule();
96
- });
97
- addEventListener(
98
- "load",
99
- () => {
100
- new ResizeObserver(schedule).observe(document.body);
101
- schedule();
102
- },
103
- { once: true }
104
- );
105
- }
106
-
107
- // packages/design/lib/design/layout-save.mjs
108
- import { readFileSync as readFileSync6, statSync } from "node:fs";
109
- import { join as join3 } from "node:path";
110
-
111
- // packages/design/lib/design/document.mjs
112
- import { randomUUID } from "node:crypto";
113
- import {
114
- existsSync as existsSync2,
115
- mkdirSync,
116
- readFileSync as readFileSync5,
117
- realpathSync,
118
- renameSync,
119
- rmSync,
120
- writeFileSync
121
- } from "node:fs";
122
- import { dirname, join as join2, resolve as resolve2 } from "node:path";
123
-
124
- // packages/artifact/lib/artifact/bridge.mjs
125
- import { randomBytes as randomBytes2 } from "node:crypto";
126
-
127
- // packages/artifact/lib/artifact/internal/board-token.mjs
128
- import { randomBytes, timingSafeEqual } from "node:crypto";
129
- var BASE64URL_TOKEN_RE = /^[A-Za-z0-9_-]+$/;
130
- function mintCapabilityToken({
131
- bytes = 32,
132
- encoding = "base64url",
133
- randomBytesImpl = randomBytes
134
- } = {}) {
135
- if (!Number.isInteger(bytes) || bytes < 12 || bytes > 64) {
136
- throw new RangeError("Capability tokens require 12 through 64 random bytes.");
137
- }
138
- if (!["base64url", "hex"].includes(encoding)) {
139
- throw new TypeError(`Unsupported capability token encoding: ${String(encoding)}`);
140
- }
141
- const value = randomBytesImpl(bytes);
142
- if (!Buffer.isBuffer(value) || value.byteLength !== bytes) {
143
- throw new TypeError("Capability token random source returned the wrong byte length.");
144
- }
145
- return value.toString(encoding);
146
- }
147
- function isCapabilityToken(value, { bytes = 32, encoding = "base64url" } = {}) {
148
- if (typeof value !== "string") return false;
149
- if (encoding === "hex") return value.length === bytes * 2 && /^[a-f0-9]+$/.test(value);
150
- if (encoding !== "base64url" || !BASE64URL_TOKEN_RE.test(value)) return false;
151
- try {
152
- return Buffer.from(value, "base64url").byteLength === bytes && Buffer.from(value, "base64url").toString("base64url") === value;
153
- } catch {
154
- return false;
155
- }
156
- }
157
- function timingSafeTokenEqual(left, right) {
158
- if (typeof left !== "string" || typeof right !== "string") return false;
159
- const leftBytes = Buffer.from(left, "utf8");
160
- const rightBytes = Buffer.from(right, "utf8");
161
- return leftBytes.byteLength === rightBytes.byteLength && timingSafeEqual(leftBytes, rightBytes);
162
- }
163
-
164
- // packages/artifact/lib/artifact/ui/bridge-tools.mjs
165
- var ARTIFACT_THUMBNAIL_MAX_EDGE = 320;
166
- var ARTIFACT_THUMBNAIL_MAX_DATA_URL = 256 * 1024;
167
- var ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA = 1e3;
168
- var ARTIFACT_VIEWPORT_PAN_MAX_DELTA = 1e3;
169
- function normalizeArtifactViewportZoom(value, viewport) {
170
- if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
171
- return null;
172
- const keys = ["x", "y", "deltaY"];
173
- if (Object.keys(value).length !== keys.length || !Object.keys(value).every((key) => keys.includes(key)))
174
- return null;
175
- const descriptors = Object.getOwnPropertyDescriptors(value);
176
- if (!keys.every(
177
- (key) => Object.hasOwn(descriptors[key], "value") && typeof descriptors[key].value === "number" && Number.isFinite(descriptors[key].value)
178
- ))
179
- return null;
180
- const { x, y, deltaY } = value;
181
- if (!Number.isFinite(viewport?.width) || !Number.isFinite(viewport?.height) || viewport.width < 1 || viewport.height < 1 || viewport.width > 16384 || viewport.height > 16384 || x < 0 || y < 0 || x > viewport.width || y > viewport.height || deltaY === 0 || Math.abs(deltaY) > ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA)
182
- return null;
183
- return Object.freeze({ x, y, deltaY });
184
- }
185
- function normalizeArtifactViewportPan(value) {
186
- if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
187
- return null;
188
- const keys = ["deltaX", "deltaY"];
189
- if (Object.keys(value).length !== keys.length || !Object.keys(value).every((key) => keys.includes(key)))
190
- return null;
191
- const descriptors = Object.getOwnPropertyDescriptors(value);
192
- if (!keys.every(
193
- (key) => Object.hasOwn(descriptors[key], "value") && typeof descriptors[key].value === "number" && Number.isFinite(descriptors[key].value)
194
- ))
195
- return null;
196
- const { deltaX, deltaY } = value;
197
- if (!deltaX && !deltaY || Math.abs(deltaX) > ARTIFACT_VIEWPORT_PAN_MAX_DELTA || Math.abs(deltaY) > ARTIFACT_VIEWPORT_PAN_MAX_DELTA)
198
- return null;
199
- return Object.freeze({ deltaX, deltaY });
200
- }
201
- function createArtifactViewportGestures(window2, emit) {
202
- const add = window2.addEventListener.bind(window2), remove = window2.removeEventListener.bind(window2);
203
- const schedule = window2.setTimeout.bind(window2), cancel = window2.clearTimeout.bind(window2);
204
- const prevent = window2.Event.prototype.preventDefault, stop = window2.Event.prototype.stopImmediatePropagation;
205
- const getter = (prototype, name) => Object.getOwnPropertyDescriptor(prototype, name)?.get;
206
- const native = {
207
- x: getter(window2.MouseEvent.prototype, "clientX"),
208
- y: getter(window2.MouseEvent.prototype, "clientY"),
209
- ctrl: getter(window2.MouseEvent.prototype, "ctrlKey"),
210
- meta: getter(window2.MouseEvent.prototype, "metaKey"),
211
- shift: getter(window2.MouseEvent.prototype, "shiftKey"),
212
- deltaX: getter(window2.WheelEvent.prototype, "deltaX"),
213
- deltaY: getter(window2.WheelEvent.prototype, "deltaY"),
214
- mode: getter(window2.WheelEvent.prototype, "deltaMode")
215
- };
216
- let enabled = false, disposed = false, timer = 0, pending = null, pendingType = "";
217
- let gestureScale = 1;
218
- const clear = () => {
219
- gestureScale = 1;
220
- if (timer) cancel(timer);
221
- timer = 0;
222
- pending = null;
223
- pendingType = "";
224
- };
225
- const flush = () => {
226
- timer = 0;
227
- const value = pending;
228
- const type = pendingType;
229
- pending = null;
230
- pendingType = "";
231
- if (enabled && !disposed && value) emit(type, value);
232
- };
233
- const queue = (type, value) => {
234
- const deltaX = value.deltaX ?? 0;
235
- const deltaY = value.deltaY ?? 0;
236
- if (pendingType && pendingType !== type) flush();
237
- pendingType = type;
238
- pending = type === "viewport.zoom" ? {
239
- ...value,
240
- deltaY: Math.max(
241
- -ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA,
242
- Math.min(ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA, (pending?.deltaY || 0) + deltaY)
243
- )
244
- } : {
245
- deltaX: Math.max(
246
- -ARTIFACT_VIEWPORT_PAN_MAX_DELTA,
247
- Math.min(ARTIFACT_VIEWPORT_PAN_MAX_DELTA, (pending?.deltaX || 0) + deltaX)
248
- ),
249
- deltaY: Math.max(
250
- -ARTIFACT_VIEWPORT_PAN_MAX_DELTA,
251
- Math.min(ARTIFACT_VIEWPORT_PAN_MAX_DELTA, (pending?.deltaY || 0) + deltaY)
252
- )
253
- };
254
- if (!timer) timer = schedule(flush, 16);
255
- };
256
- const wheel = (event) => {
257
- if (!enabled || disposed || !event.isTrusted || !event.cancelable) return;
258
- let x, y, deltaX, deltaY, mode, zooming, shift;
259
- try {
260
- x = native.x.call(event);
261
- y = native.y.call(event);
262
- mode = native.mode.call(event);
263
- zooming = native.ctrl.call(event) || native.meta.call(event);
264
- shift = native.shift.call(event);
265
- deltaX = native.deltaX.call(event);
266
- deltaY = native.deltaY.call(event);
267
- } catch {
268
- return;
269
- }
270
- if (!Number.isFinite(deltaX) || !Number.isFinite(deltaY)) return;
271
- const scaleX = mode === 1 ? 16 : mode === 2 ? window2.innerWidth : 1;
272
- const scaleY = mode === 1 ? 16 : mode === 2 ? window2.innerHeight : 1;
273
- deltaX *= scaleX;
274
- deltaY *= scaleY;
275
- let type;
276
- let value;
277
- if (zooming) {
278
- type = "viewport.zoom";
279
- deltaY = Math.max(
280
- -ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA,
281
- Math.min(ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA, deltaY)
282
- );
283
- value = normalizeArtifactViewportZoom(
284
- { x, y, deltaY },
285
- { width: window2.innerWidth, height: window2.innerHeight }
286
- );
287
- } else {
288
- type = "viewport.pan";
289
- if (shift && !deltaX) {
290
- deltaX = deltaY;
291
- deltaY = 0;
292
- }
293
- deltaX = Math.max(
294
- -ARTIFACT_VIEWPORT_PAN_MAX_DELTA,
295
- Math.min(ARTIFACT_VIEWPORT_PAN_MAX_DELTA, deltaX)
296
- );
297
- deltaY = Math.max(
298
- -ARTIFACT_VIEWPORT_PAN_MAX_DELTA,
299
- Math.min(ARTIFACT_VIEWPORT_PAN_MAX_DELTA, deltaY)
300
- );
301
- value = normalizeArtifactViewportPan({ deltaX, deltaY });
302
- }
303
- if (!value) return;
304
- prevent.call(event);
305
- stop.call(event);
306
- queue(type, value);
307
- };
308
- const consumeGesture = (event) => {
309
- if (!enabled || disposed || !event.isTrusted || !event.cancelable) return false;
310
- prevent.call(event);
311
- stop.call(event);
312
- return true;
313
- };
314
- const gestureStart = (event) => {
315
- if (consumeGesture(event)) gestureScale = 1;
316
- };
317
- const gestureChange = (raw) => {
318
- if (!consumeGesture(raw)) return;
319
- const event = raw;
320
- if (!Number.isFinite(event.scale) || event.scale <= 0) return;
321
- const deltaY = Math.max(
322
- -ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA,
323
- Math.min(ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA, -Math.log(event.scale / gestureScale) / 0.01)
324
- );
325
- gestureScale = event.scale;
326
- const value = normalizeArtifactViewportZoom(
327
- { x: event.clientX, y: event.clientY, deltaY },
328
- { width: window2.innerWidth, height: window2.innerHeight }
329
- );
330
- if (value) queue("viewport.zoom", value);
331
- };
332
- const gestureEnd = (event) => {
333
- if (consumeGesture(event)) gestureScale = 1;
334
- };
335
- const destroy = () => {
336
- disposed = true;
337
- enabled = false;
338
- clear();
339
- remove("wheel", wheel, true);
340
- remove("gesturestart", gestureStart, true);
341
- remove("gesturechange", gestureChange, true);
342
- remove("gestureend", gestureEnd, true);
343
- remove("pagehide", destroy);
344
- };
345
- add("wheel", wheel, { capture: true, passive: false });
346
- add("gesturestart", gestureStart, { capture: true, passive: false });
347
- add("gesturechange", gestureChange, { capture: true, passive: false });
348
- add("gestureend", gestureEnd, { capture: true, passive: false });
349
- add("pagehide", destroy, { once: true });
350
- return Object.freeze({
351
- setEnabled(value) {
352
- if (disposed || typeof value !== "boolean") return false;
353
- enabled = value;
354
- if (!value) clear();
355
- return true;
356
- },
357
- destroy
358
- });
359
- }
360
- var ARTIFACT_BRIDGE_OPERATION_TIMEOUTS = Object.freeze({
361
- "inspect.point": 1200,
362
- "inspect.anchor": 1200,
363
- "thumbnail.request": 3e3,
364
- "export.request": 8e3
365
- });
366
- var ARTIFACT_INSPECTION_PROPERTIES = Object.freeze([
367
- "font-family",
368
- "font-size",
369
- "font-weight",
370
- "line-height",
371
- "letter-spacing",
372
- "color",
373
- "background-color",
374
- "display",
375
- "position",
376
- "gap",
377
- "padding-top",
378
- "padding-right",
379
- "padding-bottom",
380
- "padding-left",
381
- "margin-top",
382
- "margin-right",
383
- "margin-bottom",
384
- "margin-left",
385
- "border-radius",
386
- "border-width",
387
- "border-color",
388
- "box-sizing"
389
- ]);
390
- function normalizeArtifactInspection(value, viewport) {
391
- const plain = (entry) => entry && typeof entry === "object" && !Array.isArray(entry) && [Object.prototype, null].includes(Object.getPrototypeOf(entry));
392
- const own = (entry, key) => {
393
- const descriptor = plain(entry) ? Object.getOwnPropertyDescriptor(entry, key) : null;
394
- return descriptor && Object.hasOwn(descriptor, "value") ? descriptor.value : void 0;
395
- };
396
- const exact2 = (entry, keys) => plain(entry) && Object.keys(entry).length === keys.length && Object.keys(entry).every((key) => keys.includes(key));
397
- if (!exact2(value, ["tagName", "anchor", "rect", "viewport", "styles", "accessibility"]))
398
- return null;
399
- const tagName = own(value, "tagName");
400
- const anchor = own(value, "anchor");
401
- const rect = own(value, "rect");
402
- const size = own(value, "viewport");
403
- const styles = own(value, "styles");
404
- const accessibility = own(value, "accessibility");
405
- if (typeof tagName !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(tagName)) return null;
406
- if (!exact2(size, ["width", "height"]) || !exact2(rect, ["x", "y", "width", "height"])) return null;
407
- if (!["width", "height"].every(
408
- (key) => Number.isInteger(own(size, key)) && own(size, key) >= 1 && own(size, key) <= 16384 && own(size, key) === viewport?.[key]
409
- ))
410
- return null;
411
- if (!["x", "y", "width", "height"].every(
412
- (key) => typeof own(rect, key) === "number" && Number.isFinite(own(rect, key)) && own(rect, key) >= 0
413
- ))
414
- return null;
415
- if (rect.x + rect.width > size.width || rect.y + rect.height > size.height) return null;
416
- if (anchor !== null && (!exact2(anchor, own(anchor, "screen") === void 0 ? ["planrId"] : ["planrId", "screen"]) || typeof own(anchor, "planrId") !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,511}$/.test(anchor.planrId) || own(anchor, "screen") !== void 0 && (typeof anchor.screen !== "string" || !/^[^\u0000-\u001f\u007f]{1,128}$/.test(anchor.screen))))
417
- return null;
418
- if (!exact2(styles, ARTIFACT_INSPECTION_PROPERTIES) || !ARTIFACT_INSPECTION_PROPERTIES.every((key) => {
419
- const text = own(styles, key);
420
- return typeof text === "string" && text.length <= 256 && !/[\u0000-\u001f\u007f]/.test(text) && !/(?:url\s*\(|https?:|file:)/i.test(text);
421
- }))
422
- return null;
423
- if (!exact2(accessibility, ["role", "ariaLabel", "alt", "tabIndex", "disabled"]) || !["role", "ariaLabel", "alt"].every(
424
- (key) => typeof own(accessibility, key) === "string" && own(accessibility, key).length <= 256 && !/[\u0000-\u001f\u007f]/.test(own(accessibility, key))
425
- ) || !Number.isInteger(own(accessibility, "tabIndex")) || accessibility.tabIndex < -1 || accessibility.tabIndex > 32767 || typeof own(accessibility, "disabled") !== "boolean")
426
- return null;
427
- return Object.freeze({
428
- tagName,
429
- anchor: anchor === null ? null : Object.freeze({ ...anchor }),
430
- rect: Object.freeze({ ...rect }),
431
- viewport: Object.freeze({ ...size }),
432
- styles: Object.freeze({ ...styles }),
433
- accessibility: Object.freeze({ ...accessibility })
434
- });
435
- }
436
- function normalizeArtifactThumbnail(value) {
437
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== 4 || !Object.keys(value).every((key) => ["dataUrl", "width", "height", "label"].includes(key)) || !Object.values(Object.getOwnPropertyDescriptors(value)).every(
438
- (descriptor) => Object.hasOwn(descriptor, "value")
439
- ))
440
- return null;
441
- const { dataUrl, width, height, label } = value;
442
- if (typeof dataUrl !== "string" || dataUrl.length > ARTIFACT_THUMBNAIL_MAX_DATA_URL || !/^data:image\/png;base64,[A-Za-z0-9+/]+=*$/.test(dataUrl) || !Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1 || width > ARTIFACT_THUMBNAIL_MAX_EDGE || height > ARTIFACT_THUMBNAIL_MAX_EDGE || typeof label !== "string" || label.length < 1 || label.length > 128)
443
- return null;
444
- return Object.freeze({ dataUrl, width, height, label });
445
- }
446
- function normalizeArtifactBridgeToolResult(requestType, message, viewport) {
447
- const base = ["channel", "schemaVersion", "type", "nonce", "artifactId", "requestId"];
448
- const exact2 = (keys) => message && typeof message === "object" && !Array.isArray(message) && Object.keys(message).length === keys.length && Object.keys(message).every((key) => keys.includes(key)) && Object.values(Object.getOwnPropertyDescriptors(message)).every(
449
- (descriptor) => Object.hasOwn(descriptor, "value")
450
- );
451
- if (requestType === "inspect.point" || requestType === "inspect.anchor") {
452
- if (exact2(base) && message.type === "inspect.miss") return { valid: true, value: null };
453
- if (!exact2([...base, "inspection"]) || message.type !== "inspect.result")
454
- return { valid: false };
455
- const value = normalizeArtifactInspection(message.inspection, viewport);
456
- return value ? { valid: true, value } : { valid: false };
457
- }
458
- if (requestType === "thumbnail.request") {
459
- if (exact2([...base, "reason"]) && message.type === "thumbnail.error" && typeof message.reason === "string" && message.reason.length <= 256)
460
- return { valid: true, value: null };
461
- if (!exact2([...base, "dataUrl", "width", "height", "label"]) || message.type !== "thumbnail.result")
462
- return { valid: false };
463
- const value = normalizeArtifactThumbnail({
464
- dataUrl: message.dataUrl,
465
- width: message.width,
466
- height: message.height,
467
- label: message.label
468
- });
469
- return value ? { valid: true, value } : { valid: false };
470
- }
471
- return { valid: false };
472
- }
473
- function createArtifactBridgeTools(document2, window2) {
474
- const fromPoint = document2.elementFromPoint.bind(document2);
475
- const query = document2.querySelectorAll.bind(document2);
476
- const attr = window2.Element.prototype.getAttribute;
477
- const closest = window2.Element.prototype.closest;
478
- const bounds = window2.Element.prototype.getBoundingClientRect;
479
- const computed = window2.getComputedStyle.bind(window2);
480
- const cloneNode = window2.Node.prototype.cloneNode;
481
- const append = window2.Node.prototype.appendChild;
482
- const create = document2.createElement.bind(document2);
483
- const setAttribute = window2.Element.prototype.setAttribute;
484
- const serialize2 = window2.XMLSerializer.prototype.serializeToString;
485
- const Image = window2.Image;
486
- const Serializer = window2.XMLSerializer;
487
- const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
488
- const get = (element, key) => attr.call(element, key);
489
- const validId = (id) => typeof id === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,511}$/.test(id);
490
- const screen = (element) => {
491
- const owner = closest.call(element, "[data-planr-screen]");
492
- const value = owner && get(owner, "data-planr-screen");
493
- return typeof value === "string" && /^[^\u0000-\u001f\u007f]{1,128}$/.test(value) ? value : void 0;
494
- };
495
- const clean = (value) => String(value || "").replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, 256);
496
- const inspectElement = (element) => {
497
- if (!(element instanceof window2.Element)) return null;
498
- const tagName = element.localName;
499
- if (typeof tagName !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(tagName)) return null;
500
- const rect = bounds.call(element), width = window2.innerWidth, height = window2.innerHeight;
501
- const x = clamp(rect.left, 0, width), y = clamp(rect.top, 0, height);
502
- const owner = closest.call(element, "[data-planr-id]");
503
- const planrId = owner && get(owner, "data-planr-id");
504
- const anchorScreen = owner && screen(owner);
505
- const style = computed(element);
506
- const styles = Object.fromEntries(
507
- ARTIFACT_INSPECTION_PROPERTIES.map((key) => {
508
- const value = clean(style.getPropertyValue(key));
509
- return [key, /(?:url\s*\(|https?:|file:)/i.test(value) ? "" : value];
510
- })
511
- );
512
- return {
513
- tagName,
514
- anchor: validId(planrId) ? { planrId, ...anchorScreen ? { screen: anchorScreen } : {} } : null,
515
- rect: {
516
- x,
517
- y,
518
- width: Math.max(0, clamp(rect.right, 0, width) - x),
519
- height: Math.max(0, clamp(rect.bottom, 0, height) - y)
520
- },
521
- viewport: { width, height },
522
- styles,
523
- accessibility: {
524
- role: clean(get(element, "role")),
525
- ariaLabel: clean(get(element, "aria-label")),
526
- alt: clean(get(element, "alt")),
527
- tabIndex: clamp(Number(element.tabIndex) || 0, -1, 32767),
528
- disabled: get(element, "disabled") !== null || get(element, "aria-disabled") === "true"
529
- }
530
- };
531
- };
532
- let capturing = false;
533
- return Object.freeze({
534
- inspectAt(x, y) {
535
- if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0 || x > window2.innerWidth || y > window2.innerHeight)
536
- return null;
537
- return inspectElement(fromPoint(x, y));
538
- },
539
- inspect(anchor) {
540
- if (!anchor || !validId(anchor.planrId) || Object.keys(anchor).some((key) => !["planrId", "screen"].includes(key)) || anchor.screen !== void 0 && (typeof anchor.screen !== "string" || !/^[^\u0000-\u001f\u007f]{1,128}$/.test(anchor.screen)))
541
- return null;
542
- let count = 0;
543
- for (const element of query("[data-planr-id]")) {
544
- if (++count > 1e4) return null;
545
- if (get(element, "data-planr-id") === anchor.planrId && (anchor.screen === void 0 || screen(element) === anchor.screen))
546
- return inspectElement(element);
547
- }
548
- return null;
549
- },
550
- async thumbnail() {
551
- if (capturing) throw new Error("Thumbnail capture is busy.");
552
- capturing = true;
553
- let image;
554
- try {
555
- const deadline = Date.now() + 2400;
556
- const width = window2.innerWidth, height = window2.innerHeight;
557
- if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1 || width > 16384 || height > 16384)
558
- throw new Error("Thumbnail dimensions are unavailable.");
559
- let count = 0, contentSize = 0;
560
- const cloneStyled = (source) => {
561
- if (++count > 4e3 || Date.now() > deadline)
562
- throw new Error("Thumbnail capture limit exceeded.");
563
- if (source.nodeType === 8 || source.nodeType === 1 && ["SCRIPT", "STYLE", "LINK", "META"].includes(source.tagName))
564
- return document2.createTextNode("");
565
- const target = cloneNode.call(source, false);
566
- if (source.nodeType === 1) {
567
- const styles = computed(source);
568
- let css = "";
569
- for (let index = 0; index < styles.length; index++) {
570
- if (index >= 2048 || Date.now() > deadline)
571
- throw new Error("Thumbnail style limit exceeded.");
572
- const name = styles[index];
573
- if (!name.startsWith("--")) css += name + ":" + styles.getPropertyValue(name) + ";";
574
- }
575
- contentSize += css.length + (source.textContent?.length || 0);
576
- if (contentSize > 4 * 1024 * 1024) throw new Error("Thumbnail markup limit exceeded.");
577
- setAttribute.call(target, "style", css + "animation:none;transition:none;");
578
- for (const attribute of [...target.attributes])
579
- if (/^on/i.test(attribute.name) || ["value", "srcdoc"].includes(attribute.name))
580
- target.removeAttribute(attribute.name);
581
- if (source.tagName === "INPUT" || source.tagName === "TEXTAREA") {
582
- target.value = "";
583
- target.textContent = "";
584
- }
585
- if (source.tagName === "CANVAS") {
586
- try {
587
- const replacement = create("img");
588
- replacement.src = source.toDataURL("image/png");
589
- setAttribute.call(replacement, "style", css);
590
- return replacement;
591
- } catch {
592
- }
593
- }
594
- }
595
- if (source.nodeType !== 1 || source.tagName !== "TEXTAREA")
596
- for (let child = source.firstChild; child; child = child.nextSibling)
597
- append.call(target, cloneStyled(child));
598
- return target;
599
- };
600
- const clone = cloneStyled(document2.documentElement);
601
- setAttribute.call(clone, "xmlns", "http://www.w3.org/1999/xhtml");
602
- const markup = serialize2.call(new Serializer(), clone);
603
- if (markup.length > 4 * 1024 * 1024) throw new Error("Thumbnail markup limit exceeded.");
604
- const scale = Math.min(1, ARTIFACT_THUMBNAIL_MAX_EDGE / Math.max(width, height));
605
- const outputWidth = Math.max(1, Math.round(width * scale)), outputHeight = Math.max(1, Math.round(height * scale));
606
- const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + outputWidth + '" height="' + outputHeight + '" viewBox="0 0 ' + width + " " + height + '"><foreignObject width="' + width + '" height="' + height + '">' + markup + "</foreignObject></svg>";
607
- image = new Image();
608
- await new Promise((resolve3, reject) => {
609
- const timer = setTimeout(
610
- () => reject(new Error("Thumbnail capture timed out.")),
611
- Math.max(1, deadline - Date.now())
612
- );
613
- image.onload = () => {
614
- clearTimeout(timer);
615
- resolve3();
616
- };
617
- image.onerror = () => {
618
- clearTimeout(timer);
619
- reject(new Error("Thumbnail rendering unavailable."));
620
- };
621
- image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
622
- });
623
- const canvas = create("canvas");
624
- canvas.width = outputWidth;
625
- canvas.height = outputHeight;
626
- canvas.getContext("2d").drawImage(image, 0, 0, outputWidth, outputHeight);
627
- const dataUrl = canvas.toDataURL("image/png");
628
- if (dataUrl.length > ARTIFACT_THUMBNAIL_MAX_DATA_URL)
629
- throw new Error("Thumbnail output limit exceeded.");
630
- return { dataUrl, width: outputWidth, height: outputHeight, label: "screen" };
631
- } finally {
632
- if (image) {
633
- image.onload = null;
634
- image.onerror = null;
635
- }
636
- capturing = false;
637
- }
638
- }
639
- });
640
- }
641
- function renderArtifactBridgeToolsSource() {
642
- return `const ARTIFACT_THUMBNAIL_MAX_EDGE=${ARTIFACT_THUMBNAIL_MAX_EDGE};
643
- const ARTIFACT_THUMBNAIL_MAX_DATA_URL=${ARTIFACT_THUMBNAIL_MAX_DATA_URL};
644
- const ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA=${ARTIFACT_VIEWPORT_ZOOM_MAX_DELTA};
645
- const ARTIFACT_VIEWPORT_PAN_MAX_DELTA=${ARTIFACT_VIEWPORT_PAN_MAX_DELTA};
646
- const ARTIFACT_INSPECTION_PROPERTIES=${JSON.stringify(ARTIFACT_INSPECTION_PROPERTIES)};
647
- const ARTIFACT_BRIDGE_OPERATION_TIMEOUTS=${JSON.stringify(ARTIFACT_BRIDGE_OPERATION_TIMEOUTS)};
648
- ${normalizeArtifactInspection.toString()}
649
- ${normalizeArtifactThumbnail.toString()}
650
- ${normalizeArtifactBridgeToolResult.toString()}
651
- ${normalizeArtifactViewportZoom.toString()}
652
- ${normalizeArtifactViewportPan.toString()}
653
- ${createArtifactViewportGestures.toString()}
654
- ${createArtifactBridgeTools.toString()}`;
655
- }
656
-
657
- // packages/artifact/lib/artifact/ui/prototype-state.mjs
658
- function installPrototypeState(screenId, viewId, parentOrigin, nonce) {
659
- if (!/^[A-Za-z0-9_-]{43}$/.test(nonce)) return;
660
- const win = window;
661
- let snapshot = { session: {}, forms: {} }, restoring = false;
662
- let aliasFields = [];
663
- let aliasRestoreType = "";
664
- let aliasesConfigured = false;
665
- const earlyFields = /* @__PURE__ */ new Map();
666
- let sequence = 0, hostRevision = -1, acknowledgedSequence = 0, pendingReset = 0;
667
- const documentId = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
668
- let generation = null;
669
- const pendingSession = /* @__PURE__ */ new Map();
670
- let pendingForm = null;
671
- function valid(value) {
672
- let keys = 0;
673
- function visit(node, depth) {
674
- if (depth > 6) return false;
675
- if (node === null || typeof node === "boolean") return true;
676
- if (typeof node === "number") return Number.isFinite(node);
677
- if (typeof node === "string") return node.length <= 8192;
678
- if (Array.isArray(node))
679
- return node.length <= 128 && node.every((item) => visit(item, depth + 1));
680
- if (!node || typeof node !== "object" || Object.getPrototypeOf(node) !== Object.prototype)
681
- return false;
682
- return Object.entries(node).every(
683
- ([key, item]) => ++keys <= 128 && key.length <= 256 && !["__proto__", "constructor", "prototype"].includes(key) && visit(item, depth + 1)
684
- );
685
- }
686
- try {
687
- return !!value && Object.keys(value).every((key) => key === "session" || key === "forms") && !!value.session && !Array.isArray(value.session) && typeof value.session === "object" && !!value.forms && !Array.isArray(value.forms) && typeof value.forms === "object" && visit(value, 0) && new TextEncoder().encode(JSON.stringify(value)).byteLength <= 16384;
688
- } catch {
689
- return false;
690
- }
691
- }
692
- const post = (type, update) => win.parent.postMessage(
693
- {
694
- type,
695
- version: 1,
696
- nonce,
697
- screenId,
698
- viewId,
699
- documentId,
700
- generation,
701
- ...type === "openplanr:prototype-state" ? { state: snapshot, sequence, ...update } : {}
702
- },
703
- parentOrigin
704
- );
705
- const fields = () => [
706
- ...document.querySelectorAll(
707
- "input,textarea,select"
708
- )
709
- ].filter(
710
- (field) => !["password", "file", "hidden", "submit", "button", "reset"].includes(field.type) && !field.hasAttribute("data-planr-state-private")
711
- );
712
- const fieldKey = (field, index) => (field.id || field.name || `field-${index}`).slice(0, 256) + (field.type === "radio" ? `:${field.value}` : "");
713
- function capture(sessionValue = snapshot.session) {
714
- if (restoring) return;
715
- const form = {};
716
- fields().forEach((field, index) => {
717
- const input = field;
718
- form[fieldKey(field, index)] = ["checkbox", "radio"].includes(field.type) ? input.checked : field instanceof HTMLSelectElement && field.multiple ? [...field.selectedOptions].map((option) => option.value) : field.value;
719
- });
720
- const session = { ...sessionValue };
721
- for (const field of fields()) {
722
- if (aliasesConfigured && aliasFields.includes(field.id) && typeof field.value === "string" && field.value.length <= 4e3)
723
- session[field.id] = field.value;
724
- }
725
- const next = { session, forms: { ...snapshot.forms, [viewId]: form } };
726
- return commit(next);
727
- }
728
- function commit(next, reset = false) {
729
- if (!valid(next) || sequence === Number.MAX_SAFE_INTEGER) return false;
730
- const keys = [
731
- .../* @__PURE__ */ new Set([...Object.keys(snapshot.session), ...Object.keys(next.session)])
732
- ].filter(
733
- (key) => Object.hasOwn(snapshot.session, key) !== Object.hasOwn(next.session, key) || JSON.stringify(snapshot.session[key]) !== JSON.stringify(next.session[key])
734
- );
735
- const pending = new Map(pendingSession);
736
- if (reset) pending.clear();
737
- for (const key of keys)
738
- pending.set(key, {
739
- sequence: sequence + 1,
740
- present: Object.hasOwn(next.session, key),
741
- value: next.session[key]
742
- });
743
- if (pending.size > 256 || new TextEncoder().encode(JSON.stringify([...pending])).byteLength > 16384)
744
- return false;
745
- sequence++;
746
- pendingSession.clear();
747
- for (const [key, value] of pending) pendingSession.set(key, value);
748
- if (reset) {
749
- earlyFields.clear();
750
- pendingReset = sequence;
751
- pendingForm = null;
752
- } else if (Object.hasOwn(next.forms, viewId))
753
- pendingForm = { sequence, value: structuredClone(next.forms[viewId]) };
754
- snapshot = structuredClone(next);
755
- postPending();
756
- return true;
757
- }
758
- function postPending() {
759
- post("openplanr:prototype-state", {
760
- sessionKeys: [...pendingSession.keys()],
761
- sessionSequences: Object.fromEntries(
762
- [...pendingSession].map(([key, update]) => [key, update.sequence])
763
- ),
764
- ...pendingReset ? { reset: true, resetSequence: pendingReset } : {}
765
- });
766
- }
767
- function apply() {
768
- const form = snapshot.forms[viewId];
769
- if (!form) return;
770
- restoring = true;
771
- fields().forEach((field, index) => {
772
- const value = form[fieldKey(field, index)];
773
- if (value === void 0) return;
774
- if (["checkbox", "radio"].includes(field.type) && typeof value === "boolean")
775
- field.checked = value;
776
- else if (field instanceof HTMLSelectElement && field.multiple && Array.isArray(value))
777
- for (const option of field.options) option.selected = value.includes(option.value);
778
- else if (typeof value === "string") field.value = value;
779
- });
780
- restoring = false;
781
- }
782
- win.__OPENPLANR_PROTOTYPE_STATE__ = Object.freeze({
783
- get: () => structuredClone(snapshot.session),
784
- set(value) {
785
- const next = { session: value, forms: snapshot.forms };
786
- if (!valid(next)) throw new TypeError("Prototype state exceeds its bounded JSON contract.");
787
- if (!capture(structuredClone(value)))
788
- throw new TypeError("Pending prototype edits exceed their bounded JSON contract.");
789
- for (const key of Object.keys(value)) earlyFields.delete(key);
790
- },
791
- reset() {
792
- if (!commit({ session: {}, forms: {} }, true))
793
- throw new TypeError("Pending prototype edits exceed their bounded JSON contract.");
794
- }
795
- });
796
- win.addEventListener("message", (event) => {
797
- const data = event.data;
798
- if (event.source === win.parent && (parentOrigin === "*" || event.origin === parentOrigin) && data?.nonce === nonce && aliasRestoreType && data.type === aliasRestoreType) {
799
- data.state = Object.fromEntries(
800
- aliasFields.flatMap((field) => {
801
- const early = !aliasesConfigured ? earlyFields.get(field) : void 0;
802
- const value = early?.value ?? snapshot.session[field];
803
- return typeof value === "string" && value.length <= 4e3 ? [[field, value]] : [];
804
- })
805
- );
806
- return;
807
- }
808
- if (event.source !== win.parent || parentOrigin !== "*" && event.origin !== parentOrigin || data?.type !== "openplanr:prototype-state:restore" || data.version !== 1 || data.nonce !== nonce || data.screenId !== screenId || data.viewId !== viewId || !valid(data.state))
809
- return;
810
- const configuring = generation === null && data.generation !== void 0;
811
- if (data.generation !== void 0) {
812
- if (typeof data.generation !== "string" || !/^[A-Za-z0-9_-][A-Za-z0-9._:-]{7,127}$/.test(data.generation))
813
- return;
814
- if (data.documentId === void 0) {
815
- post("openplanr:prototype-state:ready");
816
- return;
817
- }
818
- if (data.documentId !== documentId || generation !== null && data.generation !== generation)
819
- return;
820
- } else if (generation !== null) return;
821
- if (data.revision !== void 0) {
822
- if (!Number.isSafeInteger(data.revision) || data.revision < 0 || data.revision < hostRevision || !Number.isSafeInteger(data.acknowledgedSequence) || data.acknowledgedSequence < acknowledgedSequence || data.acknowledgedSequence > sequence)
823
- return;
824
- } else if (hostRevision >= 0) return;
825
- const aliases = data.aliases;
826
- aliasRestoreType = aliases?.version === 1 && typeof aliases.restoreType === "string" && /^[a-zA-Z][a-zA-Z0-9:_-]{0,127}$/.test(aliases.restoreType) && !aliases.restoreType.startsWith("openplanr:") ? aliases.restoreType : "";
827
- aliasFields = aliases?.version === 1 && Array.isArray(aliases.fields) && aliases.fields.length <= 32 && aliases.fields.every(
828
- (field) => typeof field === "string" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(field) && !["constructor", "prototype", "__proto__"].includes(field)
829
- ) ? aliases.fields : [];
830
- const ack = data.revision === void 0 ? sequence : data.acknowledgedSequence;
831
- const next = structuredClone(data.state);
832
- if (pendingReset > ack) {
833
- next.session = {};
834
- next.forms = {};
835
- }
836
- for (const [key, update] of pendingSession) {
837
- if (update.sequence <= ack) continue;
838
- if (update.present && update.value !== void 0) next.session[key] = update.value;
839
- else delete next.session[key];
840
- }
841
- if (pendingForm && pendingForm.sequence > ack) next.forms[viewId] = pendingForm.value;
842
- if (!valid(next)) return;
843
- for (const [field, early] of earlyFields)
844
- if (early.sequence <= ack && JSON.stringify(data.state.forms[viewId]?.[early.fieldKey]) !== JSON.stringify(early.formValue))
845
- earlyFields.delete(field);
846
- hostRevision = data.revision ?? hostRevision;
847
- if (data.generation !== void 0) generation = data.generation;
848
- acknowledgedSequence = data.revision === void 0 ? 0 : ack;
849
- if (pendingReset <= ack) pendingReset = 0;
850
- for (const [key, update] of pendingSession)
851
- if (update.sequence <= ack) pendingSession.delete(key);
852
- if (pendingForm && pendingForm.sequence <= ack) pendingForm = null;
853
- snapshot = next;
854
- if (!aliasesConfigured && aliasRestoreType) {
855
- const promoted = structuredClone(snapshot);
856
- for (const field of aliasFields) {
857
- const early = earlyFields.get(field);
858
- if (!early || (pendingSession.get(field)?.sequence ?? 0) > early.sequence) continue;
859
- promoted.session[field] = early.value;
860
- promoted.forms[viewId] ??= {};
861
- promoted.forms[viewId][early.fieldKey] = early.formValue;
862
- }
863
- if (!aliasFields.some((field) => earlyFields.has(field)) || commit(promoted)) {
864
- aliasesConfigured = true;
865
- earlyFields.clear();
866
- }
867
- }
868
- apply();
869
- win.dispatchEvent(
870
- new CustomEvent("openplanr:prototype-state-restored", {
871
- detail: structuredClone(snapshot.session)
872
- })
873
- );
874
- if (data.revision !== void 0 && (pendingSession.size || pendingReset || pendingForm))
875
- postPending();
876
- else if (configuring) post("openplanr:prototype-state:ready");
877
- });
878
- const captureField = (event) => {
879
- if (!capture() || aliasesConfigured) return;
880
- const field = fields().find((candidate) => candidate === event.target);
881
- if (!field || !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(field.id) || ["constructor", "prototype", "__proto__"].includes(field.id) || field.value.length > 4e3)
882
- return;
883
- const key = fieldKey(field, fields().indexOf(field));
884
- const next = new Map(earlyFields);
885
- next.set(field.id, {
886
- value: field.value,
887
- fieldKey: key,
888
- formValue: snapshot.forms[viewId][key],
889
- sequence
890
- });
891
- if (next.size > 128 || new TextEncoder().encode(JSON.stringify([...next])).byteLength > 16384)
892
- return;
893
- earlyFields.clear();
894
- for (const [id, value] of next) earlyFields.set(id, value);
895
- };
896
- document.addEventListener("input", captureField, true);
897
- document.addEventListener("change", captureField, true);
898
- document.addEventListener("submit", () => capture(), true);
899
- if (document.readyState === "loading")
900
- document.addEventListener(
901
- "DOMContentLoaded",
902
- () => {
903
- apply();
904
- post("openplanr:prototype-state:ready");
905
- },
906
- { once: true }
907
- );
908
- else {
909
- apply();
910
- post("openplanr:prototype-state:ready");
911
- }
912
- post("openplanr:prototype-state:ready");
913
- win.setTimeout(() => post("openplanr:prototype-state:ready"), 500);
914
- }
915
- function renderPrototypeStateInstaller() {
916
- return installPrototypeState.toString();
917
- }
918
-
919
- // packages/artifact/lib/artifact/browser-sandbox.mjs
920
- function pipelineError(code, message, details) {
921
- return new PipelineError(code, message, "", details);
922
- }
923
- function browserCapabilityToken({ bytes = 32 } = {}) {
924
- const data = crypto.getRandomValues(new Uint8Array(bytes));
925
- return btoa(String.fromCharCode(...data)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
926
- }
927
- function isCapabilityToken2(value) {
928
- if (typeof value !== "string" || !/^[A-Za-z0-9_-]{43}$/u.test(value)) return false;
929
- try {
930
- const decoded = atob(value.replaceAll("-", "+").replaceAll("_", "/") + "=");
931
- return decoded.length === 32 && btoa(decoded).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "") === value;
932
- } catch {
933
- return false;
934
- }
935
- }
936
- function validHostedOrigin(origin) {
937
- if (typeof origin !== "string") return false;
938
- try {
939
- const parsed = new URL(origin);
940
- return parsed.protocol === "https:" && parsed.origin === origin && !parsed.username && !parsed.password;
941
- } catch {
942
- return false;
943
- }
944
- }
945
- var ARTIFACT_BRIDGE_CHANNEL = "openplanr.artifact-anchor";
946
- var ARTIFACT_BRIDGE_VERSION = "1.0.0";
947
- var ARTIFACT_BRIDGE_EVENT = "planr:artifact-anchor";
948
- var ARTIFACT_BRIDGE_READY_EVENT = "planr:artifact-bridge-ready";
949
- var ARTIFACT_BRIDGE_LAYOUT_EVENT = "planr:artifact-layout";
950
- var ARTIFACT_VIEWPORT_ZOOM_EVENT = "planr:artifact-viewport-zoom";
951
- var ARTIFACT_VIEWPORT_PAN_EVENT = "planr:artifact-viewport-pan";
952
- var ARTIFACT_EXPORT_MAX_EDGE = 11e3;
953
- var ARTIFACT_EXPORT_MAX_DATA_URL = 24 * 1024 * 1024;
954
- var ARTIFACT_LAYOUT_MAX_WIDTH = 16384;
955
- var ARTIFACT_LAYOUT_MAX_HEIGHT = 262144;
956
- var ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,511}$/;
957
- var SCRIPT_NONCE_RE = /^[A-Za-z0-9_-]{24}$/;
958
- var FORBIDDEN_TAGS = /* @__PURE__ */ new Set([
959
- "applet",
960
- "base",
961
- "embed",
962
- "fencedframe",
963
- "form",
964
- "frame",
965
- "frameset",
966
- "iframe",
967
- "noembed",
968
- "noframes",
969
- "noscript",
970
- "object",
971
- "portal"
972
- ]);
973
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
974
- "action",
975
- "archive",
976
- "background",
977
- "classid",
978
- "codebase",
979
- "formaction",
980
- "href",
981
- "longdesc",
982
- "manifest",
983
- "ping",
984
- "profile",
985
- "src",
986
- "srcdoc",
987
- "target",
988
- "xlink:href"
989
- ]);
990
- var REMOTE_URL_RE = /^(?:https?:|file:|ftp:|wss?:|\/\/)/i;
991
- function getAttr(node, name) {
992
- return node.attrs?.find((attribute) => attribute.name.toLowerCase() === name)?.value;
993
- }
994
- function setAttr(node, name, value) {
995
- const existing = node.attrs?.find((attribute) => attribute.name.toLowerCase() === name);
996
- if (existing) existing.value = value;
997
- else {
998
- node.attrs ??= [];
999
- node.attrs.push({ name, value });
1000
- }
1001
- }
1002
- function createElement(tagName) {
1003
- return parseFragment(`<${tagName}></${tagName}>`).childNodes[0];
1004
- }
1005
- function createText(value, parentNode) {
1006
- return { nodeName: "#text", value, parentNode };
1007
- }
1008
- function descendants(node) {
1009
- return [...node?.childNodes ?? [], ...node?.content?.childNodes ?? []];
1010
- }
1011
- function removeNode(node) {
1012
- const parent2 = node.parentNode;
1013
- const index = parent2?.childNodes?.indexOf(node) ?? -1;
1014
- if (index >= 0) parent2.childNodes.splice(index, 1);
1015
- }
1016
- function findElement(document2, tagName) {
1017
- const queue = descendants(document2);
1018
- while (queue.length > 0) {
1019
- const node = queue.shift();
1020
- if (node?.tagName?.toLowerCase() === tagName) return node;
1021
- queue.push(...descendants(node));
1022
- }
1023
- return null;
1024
- }
1025
- function artifactContentSecurityPolicy(scriptNonce) {
1026
- if (typeof scriptNonce !== "string" || !SCRIPT_NONCE_RE.test(scriptNonce)) {
1027
- throw pipelineError(ARTIFACT_ERROR_CODES.SANDBOX_POLICY, "Artifact script nonce is invalid.");
1028
- }
1029
- return [
1030
- "default-src 'none'",
1031
- `script-src 'nonce-${scriptNonce}' data: blob:`,
1032
- `script-src-elem 'nonce-${scriptNonce}' data: blob:`,
1033
- "script-src-attr 'unsafe-inline'",
1034
- "style-src 'unsafe-inline' data: blob:",
1035
- "img-src data: blob:",
1036
- "media-src data: blob:",
1037
- "font-src data:",
1038
- "worker-src data: blob:",
1039
- "connect-src 'none'",
1040
- "frame-src 'none'",
1041
- "child-src 'none'",
1042
- "object-src 'none'",
1043
- "manifest-src 'none'",
1044
- "form-action 'none'",
1045
- "base-uri 'none'"
1046
- ].join("; ");
1047
- }
1048
- function assertSandboxableTree(document2, { allowLocalForms = false } = {}) {
1049
- const queue = descendants(document2);
1050
- while (queue.length > 0) {
1051
- const node = queue.shift();
1052
- if (!node?.tagName) {
1053
- queue.push(...descendants(node));
1054
- continue;
1055
- }
1056
- const tag = node.tagName.toLowerCase();
1057
- if (FORBIDDEN_TAGS.has(tag) && !(tag === "form" && allowLocalForms)) {
1058
- throw pipelineError(
1059
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1060
- `Unsafe <${tag}> cannot enter an artifact review sandbox.`
1061
- );
1062
- }
1063
- if (tag === "meta" && getAttr(node, "http-equiv")?.trim().toLowerCase() === "refresh") {
1064
- throw pipelineError(
1065
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1066
- "Meta refresh navigation is forbidden."
1067
- );
1068
- }
1069
- for (const attribute of node.attrs ?? []) {
1070
- const name = attribute.name.toLowerCase();
1071
- const value = attribute.value.trim();
1072
- if (["srcdoc", "target", "action", "formaction"].includes(name)) {
1073
- throw pipelineError(
1074
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1075
- `Artifact navigation attribute ${name} is forbidden.`
1076
- );
1077
- }
1078
- if (URL_ATTRIBUTES.has(name) && REMOTE_URL_RE.test(value)) {
1079
- throw pipelineError(
1080
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1081
- `Remote artifact resource is forbidden: ${value.slice(0, 80)}`
1082
- );
1083
- }
1084
- if (name === "style" && /(?:@import|url\s*\(\s*['"]?(?:https?:|file:|\/\/))/i.test(value)) {
1085
- throw pipelineError(ARTIFACT_ERROR_CODES.SANDBOX_POLICY, "Remote inline CSS is forbidden.");
1086
- }
1087
- }
1088
- if (tag === "style") {
1089
- const css = (node.childNodes ?? []).map((child) => child.value ?? "").join("");
1090
- if (/(?:@import|url\s*\(\s*['"]?(?:https?:|file:|\/\/))/i.test(css)) {
1091
- throw pipelineError(
1092
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1093
- "Remote stylesheet resources are forbidden."
1094
- );
1095
- }
1096
- }
1097
- queue.push(...descendants(node));
1098
- }
1099
- }
1100
- var SANDBOX_GUARD_LIMITS = Object.freeze({
1101
- __PLANR_SANDBOX_EXPORT_MAX_EDGE__: String(ARTIFACT_EXPORT_MAX_EDGE),
1102
- __PLANR_SANDBOX_EXPORT_MAX_DATA_URL__: String(ARTIFACT_EXPORT_MAX_DATA_URL),
1103
- __PLANR_SANDBOX_LAYOUT_MAX_WIDTH__: String(ARTIFACT_LAYOUT_MAX_WIDTH),
1104
- __PLANR_SANDBOX_LAYOUT_MAX_HEIGHT__: String(ARTIFACT_LAYOUT_MAX_HEIGHT)
1105
- });
1106
- function sandboxGuardFiller(name, template, keys) {
1107
- const pattern = new RegExp(keys.join("|"), "gu");
1108
- const unfilled = [...new Set(template.replace(pattern, "").match(/__PLANR_SANDBOX_[A-Z_]+__/gu))];
1109
- const missing = keys.filter((key) => !template.includes(key));
1110
- if (unfilled.length > 0 || missing.length > 0) {
1111
- throw pipelineError(
1112
- ARTIFACT_ERROR_CODES.BRIDGE_INVALID,
1113
- `The generated ${name} does not match bridge.mjs (unfilled: ${unfilled.join(", ") || "none"}; missing: ${missing.join(", ") || "none"}). Run npm run generate.`
1114
- );
1115
- }
1116
- return (values) => template.replace(pattern, (key) => {
1117
- if (typeof values[key] !== "string") {
1118
- throw pipelineError(
1119
- ARTIFACT_ERROR_CODES.BRIDGE_INVALID,
1120
- `The ${name} needs a string for ${key}; received ${typeof values[key]}.`
1121
- );
1122
- }
1123
- return values[key];
1124
- });
1125
- }
1126
- var fillFrameGuard = sandboxGuardFiller("frame guard", ARTIFACT_FRAME_GUARD_TEMPLATE, [
1127
- "__PLANR_SANDBOX_CONTRACT__",
1128
- "__PLANR_SANDBOX_BRIDGE_TOOLS__;",
1129
- "__PLANR_SANDBOX_WORKER_GUARD__",
1130
- ...Object.keys(SANDBOX_GUARD_LIMITS)
1131
- ]);
1132
- function artifactGuardAndBridgeSource({
1133
- artifactId,
1134
- nonce,
1135
- parentOrigin,
1136
- prototypeState = false,
1137
- reviewSelection = false,
1138
- screenId = artifactId
1139
- }) {
1140
- const contract = JSON.stringify({
1141
- channel: ARTIFACT_BRIDGE_CHANNEL,
1142
- schemaVersion: ARTIFACT_BRIDGE_VERSION,
1143
- artifactId,
1144
- nonce,
1145
- parentOrigin,
1146
- ...prototypeState ? { screenId } : {},
1147
- ...reviewSelection ? { reviewSelection: true } : {}
1148
- }).replace(/</gu, "\\u003c");
1149
- const guard = fillFrameGuard({
1150
- __PLANR_SANDBOX_CONTRACT__: prototypeState || reviewSelection ? "__openplanrPrototypeContract" : contract,
1151
- "__PLANR_SANDBOX_BRIDGE_TOOLS__;": renderArtifactBridgeToolsSource(),
1152
- __PLANR_SANDBOX_WORKER_GUARD__: JSON.stringify(ARTIFACT_WORKER_GUARD_SOURCE),
1153
- ...SANDBOX_GUARD_LIMITS
1154
- });
1155
- if (!prototypeState && !reviewSelection) return guard;
1156
- const prototypeInstaller = prototypeState ? `(${renderPrototypeStateInstaller()})(__openplanrPrototypeContract.screenId,__openplanrPrototypeContract.artifactId,__openplanrPrototypeContract.parentOrigin === 'null' ? '*' : __openplanrPrototypeContract.parentOrigin,__openplanrPrototypeContract.nonce);` : "";
1157
- return `(function(__openplanrPrototypeContract){${guard};${prototypeInstaller}(${installPreviewNavigation.toString()})(__openplanrPrototypeContract);})(${contract});`;
1158
- }
1159
- function installPreviewNavigation(contract) {
1160
- const trustedParent = parent, post = trustedParent.postMessage.bind(trustedParent), NativeElement = Element, svgRoots = [], svgClick = () => {
1161
- }, addListener = EventTarget.prototype.addEventListener, removeListener = EventTarget.prototype.removeEventListener, queryAll = Document.prototype.querySelectorAll, query = NativeElement.prototype.querySelector, closest = NativeElement.prototype.closest, attribute = NativeElement.prototype.getAttribute, preventDefault = Event.prototype.preventDefault, stopImmediatePropagation = Event.prototype.stopImmediatePropagation, descriptors = Object.getOwnPropertyDescriptors, prototype = Object.getPrototypeOf, objectPrototype = Object.prototype, keys = Reflect.ownKeys;
1162
- let selectionEnabled = false;
1163
- if (contract.reviewSelection === true)
1164
- addEventListener("message", (event) => {
1165
- if (event.source !== trustedParent || event.origin !== contract.parentOrigin) return;
1166
- const data = event.data;
1167
- if (!data || typeof data !== "object") return;
1168
- const dataPrototype = prototype(data);
1169
- if (dataPrototype !== objectPrototype && dataPrototype !== null) return;
1170
- const fields = descriptors(data), allowed = ["schemaVersion", "type", "channel", "viewId", "enabled"];
1171
- if (keys(fields).length !== allowed.length || allowed.some((key) => !fields[key]?.enumerable || !("value" in fields[key])))
1172
- return;
1173
- if (fields.schemaVersion.value !== "1.0.0" || fields.type.value !== "openplanr:review-selection" || fields.channel.value !== contract.nonce || fields.viewId.value !== contract.artifactId || typeof fields.enabled.value !== "boolean")
1174
- return;
1175
- selectionEnabled = fields.enabled.value;
1176
- for (const svg of svgRoots) removeListener.call(svg, "click", svgClick);
1177
- svgRoots.length = 0;
1178
- if (selectionEnabled)
1179
- for (const svg of queryAll.call(document, "svg")) {
1180
- if (!attribute.call(svg, "data-planr-id") && !attribute.call(svg, "data-element-id") && !query.call(svg, "[data-planr-id],[data-element-id]"))
1181
- continue;
1182
- addListener.call(svg, "click", svgClick);
1183
- svgRoots.push(svg);
1184
- }
1185
- });
1186
- document.addEventListener(
1187
- "click",
1188
- (event) => {
1189
- if (!(event.target instanceof NativeElement)) return;
1190
- if (selectionEnabled) {
1191
- const target2 = closest.call(event.target, "[data-planr-id],[data-element-id]"), elementId = target2 ? attribute.call(target2, "data-planr-id") ?? attribute.call(target2, "data-element-id") : null;
1192
- if (!elementId || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(elementId)) return;
1193
- preventDefault.call(event);
1194
- stopImmediatePropagation.call(event);
1195
- post(
1196
- {
1197
- schemaVersion: "1.0.0",
1198
- channel: contract.nonce,
1199
- type: "select",
1200
- viewId: contract.artifactId,
1201
- elementId
1202
- },
1203
- contract.parentOrigin === "null" ? "*" : contract.parentOrigin
1204
- );
1205
- return;
1206
- }
1207
- if (!contract.screenId) return;
1208
- const target = closest.call(
1209
- event.target,
1210
- "[data-design-target],[data-planr-navigate],[data-design-navigate]"
1211
- );
1212
- if (!target) return;
1213
- const screenId = target.getAttribute("data-design-target") || target.getAttribute("data-planr-navigate") || target.getAttribute("data-design-navigate");
1214
- if (!screenId || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(screenId)) return;
1215
- event.preventDefault();
1216
- post(
1217
- {
1218
- schemaVersion: "1.0.0",
1219
- channel: contract.nonce,
1220
- type: "navigate",
1221
- viewId: contract.artifactId,
1222
- screenId
1223
- },
1224
- contract.parentOrigin === "null" ? "*" : contract.parentOrigin
1225
- );
1226
- },
1227
- true
1228
- );
1229
- }
1230
- function prepareArtifactDocument({
1231
- html,
1232
- artifactId,
1233
- nonce,
1234
- parentOrigin,
1235
- scriptNonce = browserCapabilityToken({ bytes: 18 }),
1236
- allowLocalForms = false,
1237
- portable = false,
1238
- trustedParentOrigin,
1239
- screenId = artifactId,
1240
- prototypeState = false,
1241
- reviewSelection = false
1242
- } = {}) {
1243
- if (typeof html !== "string" || html.length === 0) {
1244
- throw pipelineError(ARTIFACT_ERROR_CODES.SANDBOX_POLICY, "Artifact HTML is required.");
1245
- }
1246
- if (typeof artifactId !== "string" || !ID_RE.test(artifactId)) {
1247
- throw pipelineError(ARTIFACT_ERROR_CODES.SANDBOX_POLICY, "Artifact id is invalid.");
1248
- }
1249
- if (!isCapabilityToken2(nonce)) {
1250
- throw pipelineError(ARTIFACT_ERROR_CODES.SANDBOX_POLICY, "Artifact bridge nonce is invalid.");
1251
- }
1252
- if (typeof reviewSelection !== "boolean")
1253
- throw pipelineError(
1254
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1255
- "Artifact review selection option must be a boolean."
1256
- );
1257
- if (reviewSelection && artifactId.length > 128)
1258
- throw pipelineError(
1259
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1260
- "Review selection requires an artifact view id of at most 128 characters."
1261
- );
1262
- if (prototypeState && (typeof screenId !== "string" || !ID_RE.test(screenId)))
1263
- throw pipelineError(
1264
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1265
- "Prototype screen identity is invalid."
1266
- );
1267
- const originMatch = /^http:\/\/127\.0\.0\.1:(\d{1,5})$/.exec(parentOrigin ?? "");
1268
- const originPort = Number(originMatch?.[1]);
1269
- if (!(portable && parentOrigin === "null") && !(portable && parentOrigin === trustedParentOrigin && validHostedOrigin(parentOrigin)) && (!originMatch || !Number.isInteger(originPort) || originPort < 1 || originPort > 65535 || String(originPort) !== originMatch[1])) {
1270
- throw pipelineError(
1271
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1272
- "Artifact parent origin must be IPv4 loopback or an explicitly trusted HTTPS origin."
1273
- );
1274
- }
1275
- const document2 = parse(html, { sourceCodeLocationInfo: false });
1276
- assertSandboxableTree(document2, { allowLocalForms });
1277
- const head = findElement(document2, "head");
1278
- if (!head)
1279
- throw pipelineError(
1280
- ARTIFACT_ERROR_CODES.SANDBOX_POLICY,
1281
- "Artifact document has no head element."
1282
- );
1283
- for (const node of [...descendants(head)]) {
1284
- if (node?.tagName?.toLowerCase() === "meta") {
1285
- const httpEquiv = getAttr(node, "http-equiv")?.trim().toLowerCase();
1286
- if (httpEquiv === "content-security-policy") removeNode(node);
1287
- }
1288
- }
1289
- const csp = artifactContentSecurityPolicy(scriptNonce);
1290
- const hasViewport = descendants(head).some(
1291
- (node) => node?.tagName?.toLowerCase() === "meta" && getAttr(node, "name")?.trim().toLowerCase() === "viewport"
1292
- );
1293
- const viewportMeta = hasViewport ? null : createElement("meta");
1294
- if (viewportMeta) {
1295
- setAttr(viewportMeta, "name", "viewport");
1296
- setAttr(viewportMeta, "content", "width=device-width,initial-scale=1,viewport-fit=cover");
1297
- viewportMeta.parentNode = head;
1298
- }
1299
- const cspMeta = createElement("meta");
1300
- setAttr(cspMeta, "http-equiv", "Content-Security-Policy");
1301
- setAttr(cspMeta, "content", csp);
1302
- cspMeta.parentNode = head;
1303
- const referrerMeta = createElement("meta");
1304
- setAttr(referrerMeta, "name", "referrer");
1305
- setAttr(referrerMeta, "content", "no-referrer");
1306
- referrerMeta.parentNode = head;
1307
- const bridge = createElement("script");
1308
- setAttr(bridge, "nonce", scriptNonce);
1309
- bridge.childNodes = [
1310
- createText(
1311
- artifactGuardAndBridgeSource({
1312
- artifactId,
1313
- nonce,
1314
- parentOrigin,
1315
- prototypeState,
1316
- reviewSelection,
1317
- screenId
1318
- }),
1319
- bridge
1320
- )
1321
- ];
1322
- bridge.parentNode = head;
1323
- const queue = descendants(document2);
1324
- while (queue.length > 0) {
1325
- const node = queue.shift();
1326
- if (node?.tagName?.toLowerCase() === "script") setAttr(node, "nonce", scriptNonce);
1327
- queue.push(...descendants(node));
1328
- }
1329
- head.childNodes = [
1330
- cspMeta,
1331
- referrerMeta,
1332
- ...viewportMeta ? [viewportMeta] : [],
1333
- bridge,
1334
- ...head.childNodes ?? []
1335
- ];
1336
- return Object.freeze({ html: serialize(document2), csp, scriptNonce });
1337
- }
1338
- function prepareArtifactSourceTemplate(options = {}) {
1339
- const artifactId = `template-${browserCapabilityToken()}`;
1340
- const prepared = prepareArtifactDocument({
1341
- ...options,
1342
- artifactId,
1343
- portable: true,
1344
- parentOrigin: options.parentOrigin ?? "null"
1345
- });
1346
- const artifactIdToken = `"artifactId":${JSON.stringify(artifactId)}`;
1347
- if (prepared.html.split(artifactIdToken).length !== 2)
1348
- throw pipelineError(
1349
- ARTIFACT_ERROR_CODES.BRIDGE_INVALID,
1350
- "Shared source bridge identity is not unique."
1351
- );
1352
- return Object.freeze({ html: prepared.html, artifactIdToken });
1353
- }
1354
-
1355
- // packages/artifact/lib/artifact/bridge.mjs
1356
- var ID_RE2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,511}$/;
1357
- function pipelineError2(code, message, details) {
1358
- return new PipelineError(code, message, "", details);
1359
- }
1360
- function createArtifactBridgeNonce({ randomBytesImpl = randomBytes2 } = {}) {
1361
- return mintCapabilityToken({ bytes: 32, randomBytesImpl });
1362
- }
1363
- var fillHostGuard = sandboxGuardFiller("host guard", ARTIFACT_HOST_GUARD_TEMPLATE, [
1364
- "__PLANR_SANDBOX_CONFIG__",
1365
- "__PLANR_SANDBOX_BRIDGE_TOOLS__;",
1366
- ...Object.keys(SANDBOX_GUARD_LIMITS)
1367
- ]);
1368
- function validInlineSourcePool(sources, references) {
1369
- if (!sources || typeof sources !== "object" || Array.isArray(sources) || !references || typeof references !== "object" || Array.isArray(references))
1370
- return false;
1371
- const entries = Object.entries(sources), views = Object.entries(references);
1372
- if (!entries.length || entries.length > ARTIFACT_MAX_SOURCES || !views.length || views.length > ARTIFACT_MAX_VIEWS)
1373
- return false;
1374
- let bytes = 0;
1375
- for (const [id, source] of entries) {
1376
- if (!ID_RE2.test(id) || !source || Object.keys(source).length !== 2 || typeof source.html !== "string" || !/^"artifactId":"template-[A-Za-z0-9_-]{43}"$/u.test(source.artifactIdToken) || source.html.split(source.artifactIdToken).length !== 2)
1377
- return false;
1378
- bytes += Buffer.byteLength(source.html);
1379
- if (bytes > 100 * 1024 * 1024) return false;
1380
- }
1381
- return views.every(
1382
- ([id, sourceId]) => ID_RE2.test(id) && typeof sourceId === "string" && Object.hasOwn(sources, sourceId)
1383
- ) && new Set(Object.values(references)).size === entries.length;
1384
- }
1385
- function renderArtifactParentRuntime({
1386
- artifactBaseUrl,
1387
- stageRuntimeUrl,
1388
- adapterRuntimeUrl,
1389
- nonce,
1390
- inlineArtifacts,
1391
- inlineSources,
1392
- inlineArtifactSources,
1393
- sourceTransport = "blob",
1394
- frameBudget
1395
- } = {}) {
1396
- const canonicalPath = (value, { trailingSlash = false } = {}) => {
1397
- if (typeof value !== "string" || !value.startsWith("/") || value.startsWith("//") || value.includes("\\") || value.includes("?") || value.includes("#") || /[\u0000-\u001f]/.test(value) || /%(?:00|2f|5c)/i.test(value) || (trailingSlash ? !value.endsWith("/") : value.endsWith("/")))
1398
- return false;
1399
- try {
1400
- return value.split("/").filter(Boolean).every((segment) => {
1401
- const decoded = decodeURIComponent(segment);
1402
- return decoded !== "." && decoded !== ".." && !decoded.includes("/") && !decoded.includes("\\");
1403
- });
1404
- } catch {
1405
- return false;
1406
- }
1407
- };
1408
- const legacyPortable = inlineArtifacts && typeof inlineArtifacts === "object" && !Array.isArray(inlineArtifacts) && Object.values(inlineArtifacts).every((html) => typeof html === "string");
1409
- const pooledPortable = validInlineSourcePool(inlineSources, inlineArtifactSources);
1410
- const portable = legacyPortable || pooledPortable;
1411
- if ((inlineSources !== void 0 || inlineArtifactSources !== void 0) && (!pooledPortable || inlineArtifacts !== void 0) || !portable && !canonicalPath(artifactBaseUrl, { trailingSlash: true }) || !(canonicalPath(stageRuntimeUrl) || portable && /^data:text\/javascript;base64,[A-Za-z0-9+/=]+$/u.test(stageRuntimeUrl)) || adapterRuntimeUrl !== void 0 && !canonicalPath(adapterRuntimeUrl) || !isCapabilityToken(nonce) || !["blob", "srcdoc"].includes(sourceTransport) || frameBudget !== void 0 && (!Number.isInteger(frameBudget) || frameBudget < 1 || frameBudget > 8)) {
1412
- throw pipelineError2(
1413
- ARTIFACT_ERROR_CODES.BRIDGE_INVALID,
1414
- "Artifact parent runtime configuration is invalid."
1415
- );
1416
- }
1417
- const config = JSON.stringify({
1418
- artifactBaseUrl,
1419
- stageRuntimeUrl,
1420
- adapterRuntimeUrl: adapterRuntimeUrl ?? null,
1421
- nonce,
1422
- inlineArtifacts: legacyPortable ? inlineArtifacts : null,
1423
- ...pooledPortable ? { inlineSources, inlineArtifactSources } : {},
1424
- sourceTransport,
1425
- ...frameBudget === void 0 ? {} : { frameBudget },
1426
- channel: ARTIFACT_BRIDGE_CHANNEL,
1427
- schemaVersion: ARTIFACT_BRIDGE_VERSION,
1428
- anchorEvent: ARTIFACT_BRIDGE_EVENT,
1429
- readyEvent: ARTIFACT_BRIDGE_READY_EVENT,
1430
- layoutEvent: ARTIFACT_BRIDGE_LAYOUT_EVENT,
1431
- viewportZoomEvent: ARTIFACT_VIEWPORT_ZOOM_EVENT,
1432
- viewportPanEvent: ARTIFACT_VIEWPORT_PAN_EVENT,
1433
- navigationEvent: "planr:artifact-navigation-blocked",
1434
- frameCsp: [
1435
- "default-src 'none'",
1436
- "script-src 'unsafe-inline' data: blob:",
1437
- "script-src-attr 'unsafe-inline'",
1438
- "style-src 'unsafe-inline' data: blob:",
1439
- "img-src data: blob:",
1440
- "media-src data: blob:",
1441
- "font-src data:",
1442
- "worker-src data: blob:",
1443
- "connect-src 'none'",
1444
- "frame-src 'none'",
1445
- "child-src 'none'",
1446
- "object-src 'none'",
1447
- "manifest-src 'none'",
1448
- "form-action 'none'",
1449
- "base-uri 'none'"
1450
- ].join("; ")
1451
- });
1452
- return fillHostGuard({
1453
- __PLANR_SANDBOX_CONFIG__: config,
1454
- "__PLANR_SANDBOX_BRIDGE_TOOLS__;": renderArtifactBridgeToolsSource(),
1455
- ...SANDBOX_GUARD_LIMITS
1456
- });
1457
- }
1458
-
1459
- // packages/design/lib/design/lint.mjs
1460
- import { readFileSync } from "node:fs";
1461
- import { fileURLToPath } from "node:url";
1462
-
1463
- // packages/design/lib/design/tokens.mjs
1464
- var SPACING_STEP = 4;
1465
- var FRAMES = {
1466
- desktop: { w: 1440, h: 1024 },
1467
- tablet: { w: 834, h: 1194 },
1468
- mobile: { w: 390, h: 844 }
1469
- };
1470
- var DEFAULT_FRAME = FRAMES.desktop;
1471
- var RESPONSIVE_FRAMES = [
1472
- { name: "desktop", ...FRAMES.desktop },
1473
- { name: "tablet", ...FRAMES.tablet },
1474
- { name: "mobile", ...FRAMES.mobile }
1475
- ];
1476
- function isOnSpacingScale(px) {
1477
- const n = Math.abs(Number(px));
1478
- if (!Number.isInteger(n)) return false;
1479
- return n === 0 || n === 2 || n % SPACING_STEP === 0;
1480
- }
1481
- function nearestSpacing(px) {
1482
- const v = Number(px) || 0;
1483
- const n = Math.abs(v);
1484
- const sign = v < 0 ? -1 : 1;
1485
- const candidates = [.../* @__PURE__ */ new Set([0, 2, Math.round(n / SPACING_STEP) * SPACING_STEP])].sort(
1486
- (a, b) => a - b
1487
- );
1488
- const best = candidates.reduce(
1489
- (b, c) => Math.abs(c - n) <= Math.abs(b - n) ? c : b,
1490
- candidates[0]
1491
- );
1492
- return sign * best;
1493
- }
1494
- function isCanonicalFrame({ w, h } = {}) {
1495
- return Object.values(FRAMES).some((f) => f.w === Number(w) && f.h === Number(h));
1496
- }
1497
-
1498
- // packages/design/lib/design/lint.mjs
1499
- var SPACING_PROP = /^(padding|margin|gap|row-gap|column-gap|inset|top|right|bottom|left)(-(top|right|bottom|left|block|inline)(-(start|end))?)?$/;
1500
- var SIZING_PROP = /^(width|height|min-width|min-height|max-width|max-height|padding|margin|font-size|gap)/;
1501
- var COLOR_PROP = /^(color|background|background-color|border(-(top|right|bottom|left))?-color|outline-color|fill|stroke|caret-color|text-decoration-color)$/;
1502
- var COLOR_LITERAL = /#[0-9a-fA-F]{3,8}\b|rgba?\([^)]*\)|oklch\([^)]*\)/gi;
1503
- var SYSTEM_FONTS = /* @__PURE__ */ new Set([
1504
- "inherit",
1505
- "initial",
1506
- "sans-serif",
1507
- "serif",
1508
- "monospace",
1509
- "system-ui",
1510
- "ui-sans-serif",
1511
- "ui-monospace",
1512
- "ui-serif",
1513
- "-apple-system",
1514
- "blinkmacsystemfont",
1515
- "segoe ui",
1516
- "roboto",
1517
- "helvetica neue",
1518
- "helvetica",
1519
- "arial",
1520
- "sfmono-regular",
1521
- "sf mono",
1522
- "menlo",
1523
- "consolas",
1524
- "liberation mono",
1525
- "cursive"
1526
- ]);
1527
- function literalColors(value) {
1528
- return /gradient/i.test(value) ? [] : value.match(COLOR_LITERAL) || [];
1529
- }
1530
- function firstColor(value) {
1531
- if (/var\(|gradient/i.test(value)) return null;
1532
- const m = value.match(/#[0-9a-fA-F]{3,8}\b|rgba?\([^)]*\)|oklch\([^)]*\)|\b(?:white|black)\b/i);
1533
- return m ? m[0] : null;
1534
- }
1535
- function firstFontFamily(value) {
1536
- const first = value.split(",")[0].trim().replace(/^["']|["']$/g, "");
1537
- return first ? first.toLowerCase() : null;
1538
- }
1539
- function declBlocks(html) {
1540
- const blocks = [];
1541
- for (const m of html.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/gi))
1542
- for (const b of cssDeclBlocks(m[1])) blocks.push(declsFromBlock(b));
1543
- for (const m of html.matchAll(/style\s*=\s*(["'])([\s\S]*?)\1/gi))
1544
- blocks.push(declsFromBlock(m[2]));
1545
- return blocks;
1546
- }
1547
- function pxValues(value) {
1548
- if (/\bvar\(|\bcalc\(/.test(value)) return [];
1549
- const out = [];
1550
- for (const m of value.matchAll(/-?\d+(?:\.\d+)?(px|rem|em)\b/g)) {
1551
- const n = parseFloat(m[0]);
1552
- out.push(/r?em/.test(m[1]) ? Math.round(n * 16 * 1e3) / 1e3 : n);
1553
- }
1554
- return out;
1555
- }
1556
- function declsFromBlock(block) {
1557
- return block.split(";").map((s) => s.trim()).filter(Boolean).map((s) => {
1558
- const i = s.indexOf(":");
1559
- if (i < 0) return null;
1560
- return { prop: s.slice(0, i).trim().toLowerCase(), value: s.slice(i + 1).trim() };
1561
- }).filter(Boolean);
1562
- }
1563
- function cssDeclBlocks(css) {
1564
- const clean = css.replace(/\/\*[\s\S]*?\*\//g, "");
1565
- const blocks = [];
1566
- let depth = 0;
1567
- let buf = "";
1568
- for (const ch of clean) {
1569
- if (ch === "{") {
1570
- depth += 1;
1571
- buf = "";
1572
- continue;
1573
- }
1574
- if (ch === "}") {
1575
- if (depth >= 1) blocks.push(buf);
1576
- depth -= 1;
1577
- buf = "";
1578
- continue;
1579
- }
1580
- if (depth >= 1) buf += ch;
1581
- }
1582
- return blocks;
1583
- }
1584
- function styleBlockDecls(html) {
1585
- const out = [];
1586
- for (const m of html.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/gi)) {
1587
- for (const block of cssDeclBlocks(m[1])) {
1588
- for (const d of declsFromBlock(block)) out.push({ ...d, where: "style" });
1589
- }
1590
- }
1591
- return out;
1592
- }
1593
- function inlineStyleDecls(html) {
1594
- const out = [];
1595
- for (const m of html.matchAll(/style\s*=\s*(["'])([\s\S]*?)\1/gi)) {
1596
- for (const d of declsFromBlock(m[2])) out.push({ ...d, where: "inline" });
1597
- }
1598
- return out;
1599
- }
1600
- function inlineSizingDrift(html) {
1601
- const out = [];
1602
- for (const m of html.matchAll(/<([a-z][\w-]*)\b([^>]*)>/gi)) {
1603
- const attrs = m[2];
1604
- const cls = /\bclass\s*=\s*(["'])([\s\S]*?)\1/i.exec(attrs);
1605
- const style = /\bstyle\s*=\s*(["'])([\s\S]*?)\1/i.exec(attrs);
1606
- if (!cls || !style) continue;
1607
- const sized = declsFromBlock(style[2]).filter((d) => SIZING_PROP.test(d.prop));
1608
- if (sized.length) {
1609
- out.push({
1610
- rule: "inline-sizing-drift",
1611
- level: "warn",
1612
- tag: m[1],
1613
- class: cls[2].trim(),
1614
- props: sized.map((d) => d.prop).join(", "),
1615
- message: `<${m[1]} class="${cls[2].trim()}"> carries inline sizing (${sized.map((d) => d.prop).join(", ")}) \u2014 move it to the shared class so siblings can't drift`
1616
- });
1617
- }
1618
- }
1619
- return out;
1620
- }
1621
- function lintDesign(html, opts = {}) {
1622
- const { designSystem = null } = opts;
1623
- const errors = [];
1624
- const warnings = [];
1625
- if (opts.screen) {
1626
- const values = Object.hasOwn(opts.variant?.props ?? {}, opts.screen.id) ? opts.variant.props[opts.screen.id] : {};
1627
- errors.push(
1628
- ...validateDesignProps({
1629
- screens: [opts.screen],
1630
- variants: [{ props: { [opts.screen.id]: values } }]
1631
- }).map((message) => ({ rule: "invalid-instance-prop", level: "error", message }))
1632
- );
1633
- }
1634
- const decls = [...styleBlockDecls(html), ...inlineStyleDecls(html)];
1635
- for (const d of decls) {
1636
- if (SPACING_PROP.test(d.prop)) {
1637
- for (const px of pxValues(d.value)) {
1638
- const scale = opts.spacing ?? designSystem?.spacing;
1639
- const onScale = scale?.length ? scale.includes(Math.abs(px)) : isOnSpacingScale(px);
1640
- if (!onScale) {
1641
- errors.push({
1642
- rule: "spacing-off-grid",
1643
- level: "error",
1644
- prop: d.prop,
1645
- value: `${px}px`,
1646
- suggestion: `${nearestSpacing(px)}px`,
1647
- where: d.where,
1648
- message: `${d.prop}: ${px}px is off the 4-point grid (${d.where}) \u2192 use ${nearestSpacing(px)}px`
1649
- });
1650
- }
1651
- }
1652
- }
1653
- if (COLOR_PROP.test(d.prop)) {
1654
- for (const lit of literalColors(d.value)) {
1655
- warnings.push({
1656
- rule: "color-not-token",
1657
- level: "warn",
1658
- prop: d.prop,
1659
- value: lit,
1660
- where: d.where,
1661
- message: `${d.prop}: ${lit} is a raw color \u2014 use a design-system token via var(--\u2026)`
1662
- });
1663
- }
1664
- }
1665
- if ((d.prop === "font-family" || d.prop === "font") && designSystem?.fonts?.length) {
1666
- const fam = firstFontFamily(d.value);
1667
- const allowed = new Set(designSystem.fonts.map((f) => String(f.family || "").toLowerCase()));
1668
- if (fam && !allowed.has(fam) && !SYSTEM_FONTS.has(fam)) {
1669
- warnings.push({
1670
- rule: "font-not-token",
1671
- level: "warn",
1672
- value: fam,
1673
- where: d.where,
1674
- message: `font "${fam}" is not in the design system (${[...allowed].join(", ") || "none"})`
1675
- });
1676
- }
1677
- }
1678
- }
1679
- for (const block of declBlocks(html)) {
1680
- const c = block.find((d) => d.prop === "color");
1681
- const bg = block.find((d) => d.prop === "background" || d.prop === "background-color");
1682
- if (!c || !bg) continue;
1683
- const fg = firstColor(c.value);
1684
- const back = firstColor(bg.value);
1685
- if (!fg || !back) continue;
1686
- const ratio = contrastRatio(fg, back);
1687
- if (ratio != null && ratio < AA_NORMAL) {
1688
- errors.push({
1689
- rule: "contrast-below-aa",
1690
- level: "error",
1691
- value: `${ratio.toFixed(1)}:1`,
1692
- fg,
1693
- bg: back,
1694
- message: `text/background contrast ${ratio.toFixed(1)}:1 is below AA 4.5:1 (${fg} on ${back})`
1695
- });
1696
- }
1697
- }
1698
- warnings.push(...inlineSizingDrift(html));
1699
- return {
1700
- ok: errors.length === 0,
1701
- errors,
1702
- warnings,
1703
- declarations: decls.length,
1704
- summary: `${errors.length} error(s), ${warnings.length} warning(s)`
1705
- };
1706
- }
1707
- function withoutRootTheme(html) {
1708
- if (!/theme-(?:light|dark)|data-theme/iu.test(html)) return null;
1709
- const body = inspectLocalDocumentBody(html);
1710
- if (!body) return null;
1711
- const edits = [];
1712
- for (const { name, value } of body.attrs) {
1713
- const location = body.location.attrs?.[name];
1714
- if (!location) continue;
1715
- let replacement;
1716
- if (name === "data-theme" && ["light", "dark"].includes(value)) replacement = "";
1717
- if (name === "class") {
1718
- const raw = html.slice(location.startOffset, location.endOffset);
1719
- const quoted = /^(class\s*=\s*)(["'])([\s\S]*)\2$/iu.exec(raw);
1720
- if (quoted) {
1721
- const kept = quoted[3].replace(/(^|\s)theme-(?:light|dark)(?=\s|$)/gu, "$1");
1722
- if (kept !== quoted[3]) replacement = `${quoted[1]}${quoted[2]}${kept}${quoted[2]}`;
1723
- } else if (/^class\s*=\s*theme-(?:light|dark)$/iu.test(raw)) replacement = "";
1724
- }
1725
- if (replacement !== void 0) edits.push({ ...location, replacement });
1726
- }
1727
- if (!edits.length) return null;
1728
- let normalized = html;
1729
- for (const { startOffset, endOffset, replacement } of edits.sort(
1730
- (a, b) => b.startOffset - a.startOffset
1731
- ))
1732
- normalized = normalized.slice(0, startOffset) + replacement + normalized.slice(endOffset);
1733
- return normalized;
1734
- }
1735
- function lintThemeOnlySources(sources) {
1736
- const warnings = [];
1737
- const seen = /* @__PURE__ */ new Map();
1738
- for (const { screenId, source, html } of sources) {
1739
- const normalized = withoutRootTheme(html);
1740
- if (normalized === null) continue;
1741
- const key = JSON.stringify({
1742
- html: normalized,
1743
- styles: source.styles ?? [],
1744
- scripts: source.scripts ?? []
1745
- });
1746
- const previous = seen.get(key);
1747
- if (previous && previous.screenId !== screenId && previous.source !== source.html && previous.html !== html)
1748
- warnings.push({
1749
- rule: "theme-only-source",
1750
- level: "warn",
1751
- screenId,
1752
- message: `Screens ${previous.screenId} (${previous.source}) and ${screenId} (${source.html}) differ only by a root theme marker; declare a theme prop and share one source.`
1753
- });
1754
- else seen.set(key, { screenId, source: source.html, html });
1755
- }
1756
- return warnings;
1757
- }
1758
- function lintCanvasData(data, { frames } = {}) {
1759
- const errors = [];
1760
- for (const section of data?.sections || []) {
1761
- for (const a of section.artboards || []) {
1762
- const accepted = frames?.length ? frames.some(
1763
- (frame) => frame.width === a.width && (frame.height === "auto" ? a.height === "auto" || Number.isInteger(a.height) && a.height > 0 && a.height <= 16384 : frame.height === a.height)
1764
- ) : isCanonicalFrame({ w: a.width, h: a.height });
1765
- if (!accepted) {
1766
- errors.push({
1767
- rule: "frame-not-canonical",
1768
- level: "error",
1769
- artboard: a.id || a.label || "(unnamed)",
1770
- value: `${a.width}\xD7${a.height}`,
1771
- suggestion: `${FRAMES.desktop.w}\xD7${FRAMES.desktop.h}`,
1772
- message: `artboard "${a.id || a.label || "?"}" is ${a.width}\xD7${a.height} \u2014 use the canonical ${FRAMES.desktop.w}\xD7${FRAMES.desktop.h} desktop frame`
1773
- });
1774
- }
1775
- }
1776
- }
1777
- return { ok: errors.length === 0, errors };
1778
- }
1779
- function extractCanvasData(html) {
1780
- const m = /(?:var\s+DATA\s*=|__CANVAS_DATA\s*=)\s*(\{[\s\S]*?\})\s*;/.exec(html);
1781
- if (!m) return null;
1782
- try {
1783
- return JSON.parse(m[1]);
1784
- } catch {
1785
- return null;
1786
- }
1787
- }
1788
- var isMain = process.argv[1] && fileURLToPath(new URL("./runtime/packages/design/lib/design/lint.mjs", import.meta.url).href) === process.argv[1];
1789
- if (isMain) {
1790
- const expectStyles = process.argv.includes("--expect-styles");
1791
- const files = process.argv.slice(2).filter((a) => a !== "--expect-styles");
1792
- if (!files.length) {
1793
- console.error("usage: node lib/design/lint.mjs [--expect-styles] <file.html> [<file2.html> \u2026]");
1794
- process.exit(2);
1795
- }
1796
- let totalErrors = 0;
1797
- let emptyParsed = false;
1798
- for (const file of files) {
1799
- let html;
1800
- try {
1801
- html = readFileSync(file, "utf-8");
1802
- } catch (e) {
1803
- console.error(`\u2717 cannot read ${file}: ${e.message}`);
1804
- totalErrors += 1;
1805
- continue;
1806
- }
1807
- if (/\.s?css$/i.test(file) || !/<style[\s>]/i.test(html) && /\{[^}]*:[^}]*\}/.test(html)) {
1808
- html = `<style>${html}</style>`;
1809
- }
1810
- const res = lintDesign(html);
1811
- const data = extractCanvasData(html);
1812
- const frame = data ? lintCanvasData(data) : { ok: true, errors: [] };
1813
- const allErrors = [...res.errors, ...frame.errors];
1814
- totalErrors += allErrors.length;
1815
- console.log(
1816
- `
1817
- ${file} \u2014 ${res.declarations} declaration(s), ${allErrors.length} error(s), ${res.warnings.length} warning(s)`
1818
- );
1819
- for (const e of allErrors) console.log(` \u2717 [${e.rule}] ${e.message}`);
1820
- for (const w of res.warnings) console.log(` \u26A0 [${w.rule}] ${w.message}`);
1821
- if (res.declarations === 0) {
1822
- emptyParsed = true;
1823
- console.log(
1824
- ` \u26A0 [no-styles-parsed] 0 CSS declarations found \u2014 point at COMPILED CSS, or wrap raw CSS as <style>\u2026</style>${expectStyles ? " (fails --expect-styles)" : ""}`
1825
- );
1826
- } else if (!allErrors.length && !res.warnings.length) {
1827
- console.log(" \u2713 clean");
1828
- }
1829
- }
1830
- process.exit(totalErrors ? 1 : expectStyles && emptyParsed ? 3 : 0);
1831
- }
1832
-
1833
- // packages/design/lib/design/recommend-format.mjs
1834
- var DESIGN_FORMATS = Object.freeze(["prototype", "walkthrough", "canvas"]);
1835
- var EXPLORATORY_KEYWORDS = Object.freeze([
1836
- "option",
1837
- "options",
1838
- "concept",
1839
- "concepts",
1840
- "explore",
1841
- "exploration",
1842
- "variant",
1843
- "variants",
1844
- "moodboard",
1845
- "brainstorm",
1846
- "compare",
1847
- "comparison"
1848
- ]);
1849
-
1850
- // packages/design/lib/design/manifest.mjs
1851
- var DESIGN_SOURCES = Object.freeze(["spec", "png", "describe"]);
1852
- var CONTENT_PROVENANCE = Object.freeze(["spec", "inferred"]);
1853
- var FRAMEWORKS = Object.freeze(["vanilla", "react"]);
1854
- var NAV_MODES = Object.freeze(["anchor", "lazy"]);
1855
- var SCHEMA_VERSION = "1.0.0";
1856
- function defaultFramework(designFormat) {
1857
- return designFormat === "canvas" ? "react" : "vanilla";
1858
- }
1859
- function buildManifest({
1860
- designFormat,
1861
- source,
1862
- generatedAt,
1863
- screens = [],
1864
- contentProvenance = "spec",
1865
- framework,
1866
- navMode = null,
1867
- screenName = "",
1868
- iterations = 0,
1869
- branch = "",
1870
- specId = "",
1871
- htmlFile = "",
1872
- pretextTier = ""
1873
- } = {}) {
1874
- if (!designFormat) throw new Error("buildManifest: designFormat is required");
1875
- if (!source) throw new Error("buildManifest: source is required");
1876
- if (!generatedAt) throw new Error("buildManifest: generatedAt is required");
1877
- const manifest = {
1878
- schema_version: SCHEMA_VERSION,
1879
- design_format: designFormat,
1880
- source,
1881
- content_provenance: contentProvenance,
1882
- framework: framework ?? defaultFramework(designFormat),
1883
- screens: [...screens],
1884
- screen_count: screens.length,
1885
- iterations,
1886
- generated_at: generatedAt
1887
- };
1888
- if (navMode) manifest.nav_mode = navMode;
1889
- if (screenName) manifest.screen_name = screenName;
1890
- if (branch) manifest.branch = branch;
1891
- if (specId) manifest.spec_id = specId;
1892
- if (htmlFile) manifest.html_file = htmlFile;
1893
- if (pretextTier) manifest.pretext_tier = pretextTier;
1894
- return manifest;
1895
- }
1896
-
1897
- // packages/design/lib/design/studio.mjs
1898
- import { createHash as createHash2 } from "node:crypto";
1899
- import { readFileSync as readFileSync3 } from "node:fs";
1900
-
1901
- // packages/artifact/lib/artifact/internal/runtime-asset.mjs
1902
- import { createHash } from "node:crypto";
1903
- import {
1904
- closeSync,
1905
- constants,
1906
- existsSync,
1907
- fstatSync,
1908
- lstatSync,
1909
- openSync,
1910
- readFileSync as readFileSync2,
1911
- readSync
1912
- } from "node:fs";
1913
- import { resolve } from "node:path";
1914
- import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
1915
- var DIGEST = /^[a-f0-9]{64}$/u;
1916
- var MAX_FRAGMENT_BYTES = 128 * 1024;
1917
- var MAX_SOURCE_BYTES = 256 * 1024;
1918
- var MAX_ASSET_BYTES = 16 * 1024 * 1024;
1919
- var MAX_SOURCES = 256;
1920
- var MAX_SCOPES = 16;
1921
- var MAX_SCOPE_TEXT_BYTES = 4096;
1922
- var MAX_MANIFEST_BYTES = 1024 * 1024;
1923
- var SCOPE_ID = /^[a-z][a-z0-9-]{0,63}$/u;
1924
- function isRecord(value) {
1925
- return value !== null && typeof value === "object" && !Array.isArray(value);
1926
- }
1927
- var digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
1928
- function boundedRead(url, label, { limit, exact: exact2, links = false }) {
1929
- if (!links) {
1930
- const stat = lstatSync(url, { throwIfNoEntry: false });
1931
- if (!stat?.isFile()) throw new Error(`${label} must be a regular file, not a link.`);
1932
- }
1933
- const descriptor = openSync(
1934
- fileURLToPath2(url),
1935
- constants.O_RDONLY | (links ? 0 : constants.O_NOFOLLOW ?? 0)
1936
- );
1937
- try {
1938
- const stat = fstatSync(descriptor);
1939
- if (!stat.isFile()) throw new Error(`${label} must be a regular file, not a link.`);
1940
- if (exact2 !== void 0 ? stat.size !== exact2 : stat.size > limit)
1941
- throw new Error(`${label} is ${stat.size} bytes; expected ${exact2 ?? `at most ${limit}`}.`);
1942
- const capacity = (exact2 ?? Math.min(stat.size, limit)) + 1;
1943
- const buffer = Buffer.alloc(capacity);
1944
- let length = 0;
1945
- for (let read = -1; read !== 0 && length < capacity; length += read)
1946
- read = readSync(descriptor, buffer, length, capacity - length, null);
1947
- if (length === capacity) throw new Error(`${label} changed size while it was read.`);
1948
- if (exact2 !== void 0 && length !== exact2)
1949
- throw new Error(`${label} changed size while it was read.`);
1950
- return buffer.subarray(0, length);
1951
- } finally {
1952
- closeSync(descriptor);
1953
- }
1954
- }
1955
- function sourceManifest(value, basename) {
1956
- const path = new RegExp(
1957
- `^${basename.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}\\.sources/\\d{2,4}-[a-z0-9-]+\\.js$`,
1958
- "u"
1959
- );
1960
- if (!isRecord(value) || value.kind !== "openplanr-runtime-asset-sources" || value.schemaVersion !== "1.0.0" || value.asset !== basename || typeof value.sha256 !== "string" || !DIGEST.test(value.sha256) || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || value.bytes > MAX_ASSET_BYTES || !Array.isArray(value.scopes) || !value.scopes.length || value.scopes.length > MAX_SCOPES || !Array.isArray(value.sources) || !value.sources.length || value.sources.length > MAX_SOURCES)
1961
- throw new Error("Runtime source manifest is invalid.");
1962
- const ids = /* @__PURE__ */ new Set();
1963
- const scopes = value.scopes.map((scope, index) => {
1964
- if (!isRecord(scope) || typeof scope.id !== "string" || !SCOPE_ID.test(scope.id) || ids.has(scope.id) || (index === 0 ? scope.parent !== null : typeof scope.parent !== "string" || !ids.has(scope.parent)) || typeof scope.open !== "string" || typeof scope.close !== "string" || Buffer.byteLength(scope.open) > MAX_SCOPE_TEXT_BYTES || Buffer.byteLength(scope.close) > MAX_SCOPE_TEXT_BYTES)
1965
- throw new Error("Runtime source scope is invalid.");
1966
- ids.add(scope.id);
1967
- return scope;
1968
- });
1969
- const paths = /* @__PURE__ */ new Set();
1970
- const sources = value.sources.map((source) => {
1971
- if (!isRecord(source) || typeof source.path !== "string" || !path.test(source.path) || paths.has(source.path) || typeof source.scope !== "string" || !ids.has(source.scope) || !Number.isSafeInteger(source.bytes) || source.bytes <= 0 || source.bytes >= MAX_SOURCE_BYTES || typeof source.sha256 !== "string" || !DIGEST.test(source.sha256))
1972
- throw new Error("Runtime source identity is invalid.");
1973
- paths.add(source.path);
1974
- return source;
1975
- });
1976
- return { bytes: value.bytes, sha256: value.sha256, scopes, sources };
1977
- }
1978
- function readSourceUnits(url, basename) {
1979
- const manifestUrl = new URL(`${basename}.sources.json`, url);
1980
- const manifest = boundedRead(manifestUrl, "Runtime source manifest", {
1981
- limit: MAX_MANIFEST_BYTES
1982
- });
1983
- const expected = sourceManifest(JSON.parse(manifest.toString("utf8")), basename);
1984
- const directory = lstatSync(new URL(`${basename}.sources`, url), { throwIfNoEntry: false });
1985
- if (!directory?.isDirectory()) throw new Error("Runtime source directory must not be a link.");
1986
- const byId = new Map(expected.scopes.map((scope) => [scope.id, scope]));
1987
- const parts = [];
1988
- const open = [];
1989
- const closed = /* @__PURE__ */ new Set();
1990
- let total = 0;
1991
- const append = (bytes2) => {
1992
- total += bytes2.length;
1993
- if (total > expected.bytes) throw new Error("Runtime sources exceed their declared size.");
1994
- parts.push(bytes2);
1995
- };
1996
- const close = () => {
1997
- const scope = open.pop();
1998
- if (!scope) return;
1999
- closed.add(scope.id);
2000
- append(Buffer.from(scope.close, "utf8"));
2001
- };
2002
- for (const source of expected.sources) {
2003
- const chain = [];
2004
- for (let scope = byId.get(source.scope); scope; scope = byId.get(scope.parent ?? ""))
2005
- chain.unshift(scope);
2006
- while (open.length && !chain.includes(open[open.length - 1])) close();
2007
- for (const scope of chain.slice(open.length)) {
2008
- if (closed.has(scope.id)) throw new Error("Runtime source scopes are not contiguous.");
2009
- open.push(scope);
2010
- append(Buffer.from(scope.open, "utf8"));
2011
- }
2012
- const unitUrl = new URL(source.path, url);
2013
- const bytes2 = boundedRead(unitUrl, `Runtime source unit ${source.path}`, {
2014
- limit: MAX_SOURCE_BYTES,
2015
- exact: source.bytes
2016
- });
2017
- if (digest(bytes2) !== source.sha256)
2018
- throw new Error("Runtime source unit failed integrity verification.");
2019
- append(bytes2);
2020
- }
2021
- while (open.length) close();
2022
- const bytes = Buffer.concat(parts);
2023
- if (bytes.length !== expected.bytes || digest(bytes) !== expected.sha256)
2024
- throw new Error("Runtime sources failed integrity verification.");
2025
- return bytes;
2026
- }
2027
- function readRuntimeAsset(file) {
2028
- const url = typeof file === "string" ? pathToFileURL(resolve(file)) : file;
2029
- if (existsSync(url)) return readFileSync2(url);
2030
- const basename = url.pathname.split("/").at(-1);
2031
- if (!basename) throw new Error("Studio runtime asset identity is invalid.");
2032
- if (lstatSync(new URL(`${basename}.sources.json`, url), { throwIfNoEntry: false }))
2033
- return readSourceUnits(url, basename);
2034
- const manifest = JSON.parse(
2035
- boundedRead(new URL(`${basename}.parts.json`, url), "Studio runtime fragment manifest", {
2036
- limit: MAX_MANIFEST_BYTES,
2037
- links: true
2038
- }).toString("utf8")
2039
- );
2040
- if (!isRecord(manifest) || manifest.schemaVersion !== "1.0.0" || typeof manifest.sha256 !== "string" || !DIGEST.test(manifest.sha256) || !Array.isArray(manifest.parts) || !manifest.parts.length || manifest.parts.length > 64)
2041
- throw new Error("Studio runtime fragment manifest is invalid.");
2042
- const parts = manifest.parts.map((part, index) => {
2043
- if (!isRecord(part) || part.name !== `${basename}.part-${String(index + 1).padStart(3, "0")}` || typeof part.sha256 !== "string" || !DIGEST.test(part.sha256))
2044
- throw new Error("Studio runtime fragment identity is invalid.");
2045
- const bytes2 = boundedRead(new URL(part.name, url), "Studio runtime fragment", {
2046
- limit: MAX_FRAGMENT_BYTES,
2047
- links: true
2048
- });
2049
- if (createHash("sha256").update(bytes2).digest("hex") !== part.sha256)
2050
- throw new Error("Studio runtime fragment failed integrity verification.");
2051
- return bytes2;
2052
- });
2053
- const bytes = Buffer.concat(parts);
2054
- if (createHash("sha256").update(bytes).digest("hex") !== manifest.sha256)
2055
- throw new Error("Studio runtime failed integrity verification.");
2056
- return bytes;
2057
- }
2058
-
2059
- // packages/design/lib/design/studio-render.mjs
2060
- var DESIGN_STUDIO_VERSION = "1.9.0";
2061
- var DESIGN_STUDIO_ASSETS = Object.freeze({
2062
- style: "templates/studio/studio.css",
2063
- runtime: "templates/studio/studio.js",
2064
- enhancementsStyle: "templates/studio/enhancements.css",
2065
- handoffCenterStyle: "templates/studio/handoff-center.css"
2066
- });
2067
- function applyDesignStudioTheme(preference) {
2068
- if (!preference) {
2069
- try {
2070
- preference = localStorage.getItem("openplanr.design.theme");
2071
- } catch {
2072
- }
2073
- }
2074
- if (!["dark", "light", "system"].includes(preference)) preference = "dark";
2075
- const resolved = preference === "system" ? globalThis.matchMedia?.("(prefers-color-scheme: light)").matches ? "light" : "dark" : preference;
2076
- Object.assign(document.documentElement.dataset, {
2077
- planrTheme: resolved,
2078
- designTheme: resolved,
2079
- designThemePreference: preference
2080
- });
2081
- const modelNode = document.getElementById("planr-artifact-shell-model");
2082
- if (modelNode) {
2083
- try {
2084
- const model = JSON.parse(modelNode.textContent);
2085
- model.theme = resolved;
2086
- modelNode.textContent = JSON.stringify(model);
2087
- } catch {
2088
- }
2089
- }
2090
- return { preference, resolved };
2091
- }
2092
- function applyDesignStudioResourcePolicy() {
2093
- const root = document.querySelector(".planr-shell");
2094
- if (!root || !document.documentElement.hasAttribute("data-design-studio")) return;
2095
- if (globalThis.matchMedia?.("(pointer: coarse)").matches) {
2096
- root.dataset.planrFrameBudget = "3";
2097
- root.dataset.designResourceMode = "bounded";
2098
- }
2099
- }
2100
- function designStudioThemeBootstrapSource() {
2101
- return `(${applyDesignStudioTheme.toString()})();(${applyDesignStudioResourcePolicy.toString()})();`;
2102
- }
2103
- function button(label, attributes = "", className = "") {
2104
- return `<button type="button"${className ? ` class="${className}"` : ""} ${attributes}>${escapeHtml(label)}</button>`;
2105
- }
2106
- var icons = {
2107
- left: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16"/>',
2108
- right: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M15 4v16"/>',
2109
- canvas: '<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>',
2110
- prototype: '<path d="m8 4 12 8-12 8Z"/>',
2111
- walkthrough: '<rect x="3" y="4" width="18" height="14" rx="2"/><path d="m9 22 3-4 3 4M9 9h6M9 13h3"/>',
2112
- share: '<path d="M12 16V3m-4 4 4-4 4 4M5 13v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6"/>',
2113
- export: '<path d="M12 3v12m-4-4 4 4 4-4M5 16v4h14v-4"/>',
2114
- pointer: '<path d="m5 3 15 9-7 1-3 7Z"/>',
2115
- comment: '<path d="M21 11a8 8 0 0 1-8 8H7l-4 3V11a9 9 0 0 1 18 0Z"/><path d="M8 9h8M8 13h5"/>'
2116
- };
2117
- function icon(name) {
2118
- return `<svg class="design-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${icons[name]}</svg>`;
2119
- }
2120
- function iconButton(label, name, attributes = "", className = "") {
2121
- return `<button type="button" class="${className}" ${attributes} title="${escapeHtml(label)}" aria-label="${escapeHtml(label)}">${icon(name)}<span class="design-button-label">${escapeHtml(label)}</span></button>`;
2122
- }
2123
- function toolbar(document2) {
2124
- return `<header class="planr-toolbar design-toolbar">
2125
- <div class="design-toolbar-leading">
2126
- ${iconButton("Screens", "left", 'data-design-toggle-nav aria-controls="design-navigator" aria-expanded="true"', "planr-toolbar-action design-panel-toggle")}
2127
- <div class="planr-brand">${renderPlanrMark()}<span class="design-wordmark" aria-label="OpenPlanr">Open<span>Planr</span></span><span class="planr-title-block"><strong title="${escapeHtml(document2.title)}">${escapeHtml(document2.title)}</strong></span></div>
2128
- </div>
2129
- <div class="planr-segment design-view-picker" role="group" aria-label="Design view">${["canvas", "prototype", "walkthrough"].map((view) => iconButton(view[0].toUpperCase() + view.slice(1), view, `data-design-view="${view}" aria-pressed="${document2.defaultView === view}"`)).join("")}</div>
2130
- <div class="design-toolbar-trailing">
2131
- <span class="design-preview-state" role="status" data-design-preview-state>Loading preview</span>
2132
- <span class="design-save-state" role="status" aria-live="polite" data-design-save-state>Loading studio</span>
2133
- ${iconButton("Review", "right", 'data-planr-action="feedback" data-planr-review-label="Review" aria-controls="planr-review-rail" aria-expanded="true"', "planr-toolbar-action design-review-toggle")}
2134
- ${iconButton("Share design", "share", 'data-planr-action="share" aria-haspopup="dialog"', "planr-toolbar-action design-share")}
2135
- <details class="design-export"><summary aria-label="Export" title="Export">${icon("export")}</summary><div>${button("Portable HTML", 'data-design-export="html"')}${button("Screen PNG", 'data-design-export="png"')}</div></details>
2136
- </div>
2137
- </header>`;
2138
- }
2139
- function navigator(document2) {
2140
- const failed = document2.variants.filter(({ status }) => status === "failed");
2141
- return `<nav class="design-navigator" id="design-navigator" aria-label="Design screens">
2142
- <div class="design-nav-title"><strong>Screens <span>${document2.screenOrder.length}</span></strong>${iconButton("Close screens", "left", 'data-design-toggle-nav aria-controls="design-navigator" aria-expanded="true"', "design-nav-close")}</div>
2143
- <label class="design-field" for="design-variant">Direction<select id="design-variant" data-design-variant>${document2.variants.map((variant) => `<option value="${escapeHtml(variant.id)}"${variant.status !== "ready" ? " disabled" : ""}>${escapeHtml(variant.label)}${variant.status === "failed" ? " \u2014 unavailable" : ""}</option>`).join("")}</select></label>
2144
- ${document2.variants.filter(({ status }) => status === "ready").length > 1 ? `<label class="design-compare"><input type="checkbox" data-design-compare> Compare directions</label>` : ""}
2145
- <label class="design-field design-screen-search">Find a screen<input type="search" data-design-screen-search placeholder="Search screens" aria-label="Search screens"></label>
2146
- ${document2.flows?.length ? `<label class="design-field">Journey<select data-design-screen-group aria-label="Filter screens by journey"><option value="">All screens</option>${document2.flows.map((flow) => `<option value="${escapeHtml(flow.id)}">${escapeHtml(flow.title)}</option>`).join("")}</select></label>` : ""}
2147
- <p data-design-search-empty hidden>No screens match this search.</p>
2148
- <div class="design-screen-list">${document2.screenOrder.map((screenId, index) => {
2149
- const screen = document2.screens.find(({ id }) => id === screenId);
2150
- return `<button type="button" class="design-screen" data-design-screen="${escapeHtml(screen.id)}" aria-current="${index === 0 ? "page" : "false"}" title="${escapeHtml(screen.title)}"><span class="design-screen-number" aria-hidden="true">${String(index + 1).padStart(2, "0")}</span><span>${escapeHtml(screen.title)}</span></button>`;
2151
- }).join("")}</div>
2152
- <div class="design-nav-footer"><span data-design-verification>Browser inspection pending</span><span class="design-local">Local workspace</span></div>
2153
- ${failed.length ? `<details class="design-failed-variants"><summary>${failed.length} unavailable ${failed.length === 1 ? "direction" : "directions"}</summary>${failed.map((variant) => `<p><strong>${escapeHtml(variant.label)}</strong><br>${escapeHtml(variant.issue ?? "Generation did not complete. The previous design remains available.")}</p>`).join("")}</details>` : ""}
2154
- </nav>`;
2155
- }
2156
- function designNotes(document2) {
2157
- const screens = document2.screens.filter(({ description }) => description);
2158
- if (!screens.length) return "";
2159
- return `<details class="design-guidance" data-design-notes><summary aria-controls="design-guidance-panel" aria-expanded="false">Notes <span>${screens.length}</span></summary><div class="design-guidance-panel" id="design-guidance-panel" role="region" aria-labelledby="design-guidance-title"><header><div><strong id="design-guidance-title">Design notes</strong><p>Implementation context stays outside the product UI.</p></div><button type="button" data-design-close-notes aria-label="Close design notes" title="Close design notes">\xD7</button></header>${screens.map(
2160
- (screen, index) => `<details data-design-note-screen="${escapeHtml(screen.id)}"${index === 0 ? " open" : ""}><summary>${escapeHtml(screen.title)}</summary><p>${escapeHtml(screen.description)}</p></details>`
2161
- ).join("")}</div></details>`;
2162
- }
2163
- function stageDetails(document2) {
2164
- return `<div class="design-stage-context"><div><strong data-design-screen-title>${escapeHtml(document2.screens.find(({ id }) => id === document2.screenOrder[0]).title)}</strong><span data-design-stage-description>Every screen, one connected design</span></div><div class="design-stage-actions">${designNotes(document2)}<label class="design-frame-picker">Frame<select aria-label="Responsive frame" data-design-frame>${document2.frames.map((frame) => `<option value="${escapeHtml(frame.id)}">${escapeHtml(designFrameLabel(frame))}</option>`).join("")}</select></label></div></div>
2165
- <div class="design-canvas-tools" role="group" aria-label="Canvas controls"><div class="planr-segment design-interaction-picker" role="group" aria-label="Review mode">${iconButton("Interact", "pointer", 'data-planr-mode="interact" aria-pressed="true" aria-keyshortcuts="I"')}${iconButton("Annotate", "comment", 'data-planr-mode="comment" aria-pressed="false" aria-keyshortcuts="C"')}</div><span></span>${button("\u2212", 'data-design-zoom="out" aria-label="Zoom out"')}${button("100%", 'data-design-zoom="reset" aria-label="Reset zoom"')}${button("+", 'data-design-zoom="in" aria-label="Zoom in"')}<span></span>${button("100%", 'data-design-actual-size aria-label="Inspect screen at actual size" aria-pressed="false"')}${button("Focus", 'data-design-focus aria-label="Focus on the preview"')}${button("Fit", 'data-design-fit aria-label="Fit all visible artboards"')}${button("Pan", 'data-design-pan aria-pressed="false" aria-label="Pan canvas" aria-keyshortcuts="H Space" title="Pan canvas (H). Hold Space to pan temporarily; Escape returns to Interact."')}</div>
2166
- <div class="design-walkthrough-caption" hidden><div><span data-design-step></span><h2 data-design-narrative-title></h2><p data-design-narrative></p></div><div>${button("Previous", 'data-design-step-change="-1"')}${button("Next", 'data-design-step-change="1"')}</div></div>
2167
- <div class="design-notice" role="status" aria-live="polite" hidden><span data-design-notice-message></span><button type="button" data-design-dismiss-notice aria-label="Dismiss notification" title="Dismiss notification"><span aria-hidden="true">\xD7</span></button></div>`;
2168
- }
2169
- function directionDetails() {
2170
- return `<section class="planr-domain-rail design-direction-review" data-planr-slot="domain-rail" aria-label="Direction review"><div><strong data-design-direction-label>Direction</strong><span data-design-selected-direction></span></div><div class="design-rating" role="group" aria-label="Rate this direction">${[1, 2, 3, 4, 5].map((rating) => button("\u2606", `data-design-rating="${rating}" aria-label="Rate ${rating} out of 5" aria-pressed="false"`)).join("")}</div>${button("Use this direction", "data-design-select-direction")}<details class="design-refinement"><summary>Refine this direction</summary><div><label for="design-remix">Refinement note<textarea id="design-remix" maxlength="8192" data-design-remix placeholder="What should change in the next iteration?"></textarea></label>${button("Save refinement note", "data-design-save-remix")}<p data-design-review-hint>Saved with your review for the next iteration.</p></div></details></section>`;
2171
- }
2172
- function earlierFeedback(pins) {
2173
- if (!pins.length) return "";
2174
- return `<section class="design-stale-feedback" aria-label="Earlier feedback"><details open><summary>Earlier feedback <span>${pins.length}</span></summary><p>These comments refer to an earlier revision or a missing anchor. Review them before resolving.</p>${pins.map((pin) => `<article data-design-stale-pin="${escapeHtml(pin.id)}"><div><strong>${escapeHtml(pin.screenId ?? pin.anchor?.screen ?? "Earlier screen")}</strong><span>Stale</span></div><p>${escapeHtml(pin.comment)}</p>${pin.anchor?.planrId ? `<small>Anchor: ${escapeHtml(pin.anchor.planrId)}</small>` : ""}</article>`).join("")}</details></section>`;
2175
- }
2176
- function renderDesignStudioMarkup({
2177
- document: document2,
2178
- envelope,
2179
- entries,
2180
- state = null,
2181
- revision = null,
2182
- verification = null,
2183
- stalePins = [],
2184
- reviewContext = null,
2185
- contextDigest = null,
2186
- fingerprints = [],
2187
- contentIdentity = null
2188
- } = {}, {
2189
- stageRuntimeUrl = "./artifact-review-stage.js",
2190
- renderShell,
2191
- style = "",
2192
- runtime = "",
2193
- lazySources = false
2194
- } = {}) {
2195
- if (!document2 || !envelope)
2196
- throw new TypeError("Design studio requires a design document and artifact envelope.");
2197
- const artifactIds = new Set(envelope.artifacts.map(({ id }) => id));
2198
- for (const entry of entries) {
2199
- if (!artifactIds.has(entry.artifactId) || !document2.screens.some(({ id }) => id === entry.screenId) || !document2.variants.some(({ id, status }) => id === entry.variantId && status === "ready") || !document2.frames.some(({ id }) => id === entry.frameId)) {
2200
- throw new TypeError(`Invalid design studio entry: ${entry.artifactId}.`);
2201
- }
2202
- }
2203
- const activeEntry = entries.find(
2204
- ({ variantId, screenId, frameId }) => variantId === (state?.variantId ?? document2.selectedVariant) && screenId === (state?.screenId ?? document2.screenOrder[0]) && frameId === (state?.frameId ?? document2.frames[0].id)
2205
- );
2206
- if (!activeEntry)
2207
- throw new TypeError("Design studio requires the selected direction and first screen/frame.");
2208
- const payload = {
2209
- schemaVersion: DESIGN_STUDIO_VERSION,
2210
- document: document2,
2211
- entries,
2212
- state,
2213
- revision,
2214
- verification,
2215
- reviewContext,
2216
- contextDigest,
2217
- fingerprints,
2218
- contentIdentity,
2219
- artifactKinds: Object.fromEntries(
2220
- envelope.artifacts.map((artifact) => [artifact.id, artifact.kind])
2221
- ),
2222
- staticArtifacts: envelope.artifacts.filter(
2223
- (artifact) => artifact.kind !== "html" || !lazySources && /\bdata-(?:design|planr)-static(?:\s|=|>)/u.test(
2224
- resolveArtifactHtml(envelope, artifact)
2225
- )
2226
- ).map((artifact) => artifact.id)
2227
- };
2228
- let html = renderShell(
2229
- {
2230
- envelope,
2231
- viewer: {
2232
- mode: "single",
2233
- presentation: "canvas",
2234
- activeArtifactId: activeEntry.artifactId
2235
- },
2236
- shell: {
2237
- title: document2.title,
2238
- theme: "dark",
2239
- railOpen: true,
2240
- zoom: 100
2241
- }
2242
- },
2243
- { stageRuntimeUrl }
2244
- );
2245
- html = html.replace('class="planr-shell"', 'class="planr-shell" data-planr-frame-budget="3"');
2246
- html = html.replace(
2247
- /(<iframe\b[^>]*\bsandbox=")allow-scripts(")/g,
2248
- "$1allow-scripts allow-forms$2"
2249
- );
2250
- html = html.replace(/<header class="planr-toolbar">[\s\S]*?<\/header>/, toolbar(document2));
2251
- html = html.replace(
2252
- '<div class="planr-workspace">',
2253
- `<div class="planr-workspace">${navigator(document2)}`
2254
- );
2255
- html = html.replace(
2256
- '<main class="planr-stage" aria-label="Artifact review stage">',
2257
- `<main class="planr-stage" aria-label="Design canvas">${stageDetails(document2)}`
2258
- );
2259
- html = html.replace(/<section class="planr-domain-rail"[^>]*><\/section>/, directionDetails());
2260
- html = html.replace("<h2>Review comments</h2>", "<h2>Review</h2>");
2261
- html = html.replace(
2262
- 'aria-label="Close comments">\xD7',
2263
- `aria-label="Close review" title="Close review">${icon("right")}`
2264
- );
2265
- html = html.replace("Overall note for the coding agent\u2026", "Summarize your review\u2026");
2266
- html = html.replace(
2267
- '<div class="planr-feedback-slot"',
2268
- `${earlierFeedback(stalePins)}<div class="design-comment-action">${iconButton("Add comment", "comment", 'data-planr-action="add-comment" aria-pressed="false"')}</div><div class="planr-feedback-slot"`
2269
- );
2270
- html = html.replace(
2271
- '<html lang="en"',
2272
- `<html lang="en" data-design-studio="${DESIGN_STUDIO_VERSION}" data-design-opening="true"`
2273
- );
2274
- html = html.replace(
2275
- "</head>",
2276
- `<style>${style}</style><script>${designStudioThemeBootstrapSource()}</script></head>`
2277
- );
2278
- html = html.replace(
2279
- "</body>",
2280
- `<script type="application/json" id="planr-design-studio-payload">${embedJson(payload)}</script><script>${designStudioThemeBootstrapSource()}</script><script>${runtime}</script></body>`
2281
- );
2282
- return html;
2283
- }
2284
-
2285
- // packages/design/lib/design/studio.mjs
2286
- var templateRoot = new URL("../../templates/studio/", new URL("./runtime/packages/design/lib/design/studio.mjs", import.meta.url).href);
2287
- function readDesignStudioRuntime() {
2288
- return readRuntimeAsset(new URL("studio.js", templateRoot)).toString("utf8");
2289
- }
2290
- function designStudioArtifactId(variantId, screenId, frameId) {
2291
- const id = [
2292
- "design",
2293
- ...[variantId, screenId, frameId].map((value) => `${value.length}-${value}`)
2294
- ].join(".");
2295
- return id.length <= 128 ? id : `design.${createHash2("sha256").update(JSON.stringify([variantId, screenId, frameId])).digest("hex")}`;
2296
- }
2297
- function createDesignStudioEntries(document2, envelope) {
2298
- const ids = new Set(envelope.artifacts.map(({ id }) => id));
2299
- const entries = [];
2300
- for (const variant of document2.variants.filter(({ status }) => status === "ready")) {
2301
- for (const screenId of document2.screenOrder) {
2302
- for (const frame of document2.frames) {
2303
- const artifactId = designStudioArtifactId(variant.id, screenId, frame.id);
2304
- if (!ids.has(artifactId))
2305
- throw new Error(`Design studio is missing artifact ${artifactId}.`);
2306
- entries.push({
2307
- artifactId,
2308
- variantId: variant.id,
2309
- screenId,
2310
- frameId: frame.id,
2311
- ...frame.height === "auto" ? { height: "auto" } : {}
2312
- });
2313
- }
2314
- }
2315
- }
2316
- return entries;
2317
- }
2318
- function renderDesignStudio(input = {}, options = {}) {
2319
- return renderDesignStudioMarkup(
2320
- {
2321
- ...input,
2322
- entries: input.entries ?? createDesignStudioEntries(input.document, input.envelope)
2323
- },
2324
- {
2325
- ...options,
2326
- renderShell: renderArtifactShellDocument,
2327
- style: readFileSync3(
2328
- new URL(new URL("./runtime/packages/artifact/lib/artifact/ui/studio-shell.css", import.meta.url).href),
2329
- "utf8"
2330
- ) + "\n" + ["studio.css", "enhancements.css", "handoff-center.css"].map((file) => readFileSync3(new URL(file, templateRoot), "utf8")).join("\n"),
2331
- runtime: readDesignStudioRuntime()
2332
- }
2333
- );
2334
- }
2335
-
2336
- // packages/design/lib/design/view-context.mjs
2337
- import { readFileSync as readFileSync4 } from "node:fs";
2338
- import { join } from "node:path";
2339
- var anchorPattern = new RegExp(DESIGN_DOCUMENT_SCHEMA.$defs.id.pattern, "u");
2340
- var exact = (value, keys) => value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
2341
- var boardFields = ["screenId", "frameId", "variantId"];
2342
- function validateDesignViewContext(input, current) {
2343
- if (!exact(input, ["revision", "view", "visibleScreens", "selected", "dirty"]))
2344
- throw new Error(
2345
- "View context requires exactly revision, view, visibleScreens, selected and dirty."
2346
- );
2347
- if (input.revision !== current.revision)
2348
- throw Object.assign(
2349
- new Error(
2350
- `View context revision ${input.revision} is stale; current revision is ${current.revision}.`
2351
- ),
2352
- { statusCode: 409 }
2353
- );
2354
- if (!["canvas", "prototype", "walkthrough"].includes(input.view))
2355
- throw new Error(`Unknown view context view ${input.view}.`);
2356
- if (typeof input.dirty !== "boolean")
2357
- throw new Error(`View context dirty must be boolean; received ${input.dirty}.`);
2358
- const board = (value, selected = false) => {
2359
- if (!exact(value, selected ? [...boardFields, "planrId"] : boardFields))
2360
- throw new Error(
2361
- `View context ${selected ? "selected" : "visibleScreens entry"} has invalid fields.`
2362
- );
2363
- if (!current.entries.some((entry) => boardFields.every((key) => entry[key] === value[key])))
2364
- throw new Error(`Unknown view context board ${JSON.stringify(value)}.`);
2365
- return boardFields.map((key) => value[key]).join("/");
2366
- };
2367
- if (!Array.isArray(input.visibleScreens) || input.visibleScreens.length > current.entries.length)
2368
- throw new Error(
2369
- `View context visibleScreens must contain at most ${current.entries.length} boards.`
2370
- );
2371
- const keys = input.visibleScreens.map((value) => board(value));
2372
- if (new Set(keys).size !== keys.length)
2373
- throw new Error("View context visibleScreens contains duplicate boards.");
2374
- board(input.selected, true);
2375
- const { planrId } = input.selected;
2376
- if (planrId !== null && (typeof planrId !== "string" || planrId.length > 128 || !anchorPattern.test(planrId)))
2377
- throw new Error(
2378
- `View context planrId ${JSON.stringify(planrId)} must be null or a document anchor ID of at most 128 characters.`
2379
- );
2380
- return structuredClone(input);
2381
- }
2382
- function saveDesignViewContext(file, input, clock) {
2383
- const current = currentDesign(file);
2384
- const context = validateDesignViewContext(input, current);
2385
- const value = {
2386
- kind: "openplanr-design-view-context",
2387
- schemaVersion: "1.0.0",
2388
- ...context,
2389
- updatedAt: clock().toISOString()
2390
- };
2391
- atomicJson(join(current.root, ".design/view-context.json"), value);
2392
- return { ok: true };
2393
- }
2394
- function readDesignViewContext(file, { requestTime } = {}) {
2395
- try {
2396
- const current = currentDesign(file);
2397
- const path = join(current.root, ".design/view-context.json");
2398
- const bytes = readFileSync4(path);
2399
- if (bytes.length > 128 * 1024) throw new Error(`${path} exceeds 128 KB.`);
2400
- const value = JSON.parse(bytes.toString("utf8"));
2401
- if (!exact(value, [
2402
- "kind",
2403
- "schemaVersion",
2404
- "revision",
2405
- "updatedAt",
2406
- "view",
2407
- "visibleScreens",
2408
- "selected",
2409
- "dirty"
2410
- ]) || value.kind !== "openplanr-design-view-context" || value.schemaVersion !== "1.0.0")
2411
- throw new Error(`${path} is not a supported design view context.`);
2412
- if (typeof value.updatedAt !== "string" || !Number.isFinite(Date.parse(value.updatedAt)))
2413
- throw new Error(`${path} has an invalid updatedAt timestamp.`);
2414
- const { kind, schemaVersion, updatedAt, ...input } = value;
2415
- validateDesignViewContext(input, current);
2416
- if (requestTime !== void 0) {
2417
- const requestedAt = Date.parse(requestTime);
2418
- if (!Number.isFinite(requestedAt))
2419
- throw new Error(`Invalid view context request time ${requestTime}.`);
2420
- if (Date.parse(updatedAt) < requestedAt)
2421
- throw new Error(
2422
- `View context updatedAt ${updatedAt} is older than request ${requestTime}.`
2423
- );
2424
- }
2425
- return value;
2426
- } catch (error) {
2427
- return { viewContext: null, reason: error.message };
2428
- }
2429
- }
2430
-
2431
- // packages/design/lib/design/document.mjs
2432
- var DESIGN_VIEWS = Object.freeze(["canvas", "prototype", "walkthrough"]);
2433
- var DESIGN_RENDERER_VERSION = "1.3.0";
2434
- function loadDesignDocument(file, { readSource } = {}) {
2435
- const checked = readSource?.(file, process.cwd());
2436
- const path = checked?.file ?? realpathSync(resolve2(file));
2437
- const document2 = assertDesignDocument(
2438
- checked ? JSON.parse(checked.value.toString("utf8")) : readJson(path)
2439
- );
2440
- return { path, root: dirname(path), document: document2 };
2441
- }
2442
- function inspectDesignDocument(file, { readSource } = {}) {
2443
- const { path, root, document: document2 } = loadDesignDocument(file, { readSource });
2444
- const ready = document2.variants.filter((variant) => variant.status === "ready").length;
2445
- const sourceCount = document2.screenOrder.length * ready;
2446
- const viewCount = sourceCount * document2.frames.length;
2447
- if (sourceCount > 256 || viewCount > 4096)
2448
- throw new RangeError(
2449
- `This board needs ${sourceCount} screen sources and ${viewCount} viewport references. Split it into linked boards: one board supports 256 sources and 4096 views.`
2450
- );
2451
- const sources = new Set(document2.assets ?? []);
2452
- if (existsSync2(join2(root, "review-context.json"))) sources.add("review-context.json");
2453
- if (document2.designSystem?.tokens) sources.add(document2.designSystem.tokens);
2454
- for (const screen of document2.screens)
2455
- for (const source of [
2456
- screen.source,
2457
- ...document2.variants.filter((variant) => variant.status === "ready").map((variant) => variant.sources?.[screen.id]).filter(Boolean)
2458
- ]) {
2459
- sources.add(source.html);
2460
- for (const item of [...source.styles ?? [], ...source.scripts ?? []]) sources.add(item);
2461
- }
2462
- const missing = [];
2463
- for (const source of sources) {
2464
- try {
2465
- if (readSource) readSource(source, root);
2466
- else resolveLocalDocumentFile(root, source);
2467
- } catch (error) {
2468
- missing.push({ path: source, message: error.message });
2469
- }
2470
- }
2471
- return {
2472
- ok: missing.length === 0,
2473
- path,
2474
- root,
2475
- document: document2,
2476
- sources: [...sources],
2477
- missing,
2478
- specPath: designSpecPath(root),
2479
- // Guarded source preparation is independent of local render cache state.
2480
- current: readSource ? null : readJson(join2(root, ".design/current.json"), null)
2481
- };
2482
- }
2483
- function prepareDesignDocument(file, { readSource, passive = false, maxBytes } = {}) {
2484
- const inspected = inspectDesignDocument(file, { readSource });
2485
- if (!inspected.ok)
2486
- throw new Error(inspected.missing.map((item) => `${item.path}: ${item.message}`).join("\n"));
2487
- const { document: document2, root } = inspected;
2488
- const reviewContext = loadReviewContext(root, document2, { readSource });
2489
- const contextDigest = reviewDigest(reviewContext);
2490
- const fingerprints = [];
2491
- const artifacts = [], sources = [], entries = [], lint = [], sourceFiles = new Set(inspected.sources);
2492
- const sourceContents = new Map(
2493
- inspected.sources.map((source) => [
2494
- source,
2495
- readSource ? readSource(source, root).value : readFileSync5(resolveLocalDocumentFile(root, source))
2496
- ])
2497
- );
2498
- for (const variant of document2.variants.filter((item) => item.status === "ready")) {
2499
- const themeWarnings = lintThemeOnlySources(
2500
- document2.screens.map((screen) => {
2501
- const source = variant.sources?.[screen.id] ?? screen.source;
2502
- return {
2503
- screenId: screen.id,
2504
- source,
2505
- html: sourceContents.get(source.html).toString("utf8")
2506
- };
2507
- })
2508
- );
2509
- for (const screenId of document2.screenOrder) {
2510
- const screen = document2.screens.find((item) => item.id === screenId);
2511
- const values = Object.hasOwn(variant.props ?? {}, screenId) ? variant.props[screenId] : {};
2512
- const bundled = bundleLocalDocument({
2513
- root,
2514
- source: variant.sources?.[screenId] ?? screen.source,
2515
- sharedStyles: document2.designSystem?.tokens ? [document2.designSystem.tokens] : [],
2516
- screenId,
2517
- readSource,
2518
- passive,
2519
- ...screen.props ? {
2520
- screenProps: Object.fromEntries(
2521
- Object.entries(screen.props).map(([name, declaration]) => [
2522
- name,
2523
- Object.hasOwn(values, name) ? values[name] : declaration.default
2524
- ])
2525
- )
2526
- } : {},
2527
- ...maxBytes === void 0 ? {} : { maxBytes }
2528
- });
2529
- if (document2.frames.some(({ height }) => height === "auto"))
2530
- bundled.html = bundled.html.replace(
2531
- "</body>",
2532
- `<script>(${observeDesignFrameSize.toString()})()</script></body>`
2533
- );
2534
- const navigate = `<script>document.addEventListener('click',function(event){var link=event.target.closest('[data-design-target],[data-planr-navigate],[data-design-navigate]');if(!link)return;event.preventDefault();parent.postMessage({type:'openplanr:design-navigate',screenId:link.getAttribute('data-design-target')||link.getAttribute('data-planr-navigate')||link.getAttribute('data-design-navigate')},'*')});document.addEventListener('submit',function(event){event.preventDefault()});</script>`;
2535
- if (!passive) bundled.html = bundled.html.replace("</body>", `${navigate}</body>`);
2536
- for (const name of bundled.files) {
2537
- sourceFiles.add(name);
2538
- if (!sourceContents.has(name))
2539
- sourceContents.set(
2540
- name,
2541
- readSource ? readSource(name, root).value : readFileSync5(resolveLocalDocumentFile(root, name))
2542
- );
2543
- if (hash(sourceContents.get(name)) !== bundled.sourceDigests[name])
2544
- throw new Error(
2545
- `Source ${name} changed during rendering. Retry with the completed source revision.`
2546
- );
2547
- }
2548
- const report = lintDesign(bundled.html, {
2549
- designSystem: document2.designSystem,
2550
- screen,
2551
- variant
2552
- });
2553
- report.warnings.push(...themeWarnings.filter((item) => item.screenId === screenId));
2554
- report.summary = `${report.errors.length} error(s), ${report.warnings.length} warning(s)`;
2555
- lint.push({ variantId: variant.id, screenId, ...report });
2556
- for (const anchor of screen.anchors ?? []) {
2557
- if (!bundled.html.includes(`data-planr-id="${anchor}"`) && !bundled.html.includes(`id="${anchor}"`))
2558
- throw new Error(`Screen ${screenId} is missing its declared anchor ${anchor}.`);
2559
- }
2560
- const sourceId = `source-${hash(`${variant.id}:${screenId}`).slice(0, 32)}`;
2561
- sources.push({ id: sourceId, kind: "html", html: bundled.html });
2562
- for (const frame of document2.frames) {
2563
- fingerprints.push(
2564
- reviewFingerprints({
2565
- document: document2,
2566
- context: reviewContext,
2567
- screen,
2568
- variant,
2569
- frame,
2570
- sourceDigests: bundled.sourceDigests
2571
- })
2572
- );
2573
- const artifactId = designStudioArtifactId(
2574
- `${document2.id}-${variant.id}`,
2575
- screenId,
2576
- frame.id
2577
- );
2578
- artifacts.push({
2579
- id: artifactId,
2580
- kind: "html",
2581
- title: `${screen.title} \xB7 ${variant.label} \xB7 ${frame.label}`,
2582
- sourceId,
2583
- viewport: designFrameViewport(document2, frame),
2584
- colorScheme: "light"
2585
- });
2586
- entries.push({
2587
- artifactId,
2588
- screenId,
2589
- variantId: variant.id,
2590
- frameId: frame.id,
2591
- ...frame.height === "auto" ? { height: "auto" } : {}
2592
- });
2593
- }
2594
- }
2595
- }
2596
- const activeArtifactId = [...artifacts].sort((a, b) => a.id.localeCompare(b.id))[0].id;
2597
- const envelope = createSharedArtifactEnvelope({
2598
- sources,
2599
- artifacts,
2600
- viewer: {
2601
- mode: artifacts.length > 1 ? "variants" : "single",
2602
- activeArtifactId,
2603
- presentation: "canvas"
2604
- }
2605
- });
2606
- const revision = hash(
2607
- json({ document: document2, contextDigest, digest: digestArtifactEnvelope(envelope) })
2608
- );
2609
- return {
2610
- ...inspected,
2611
- reviewContext,
2612
- contextDigest,
2613
- fingerprints,
2614
- envelope,
2615
- entries,
2616
- revision,
2617
- lint,
2618
- sourceFiles: [...sourceFiles],
2619
- sourceContents
2620
- };
2621
- }
2622
- function stageRuntimeBytes() {
2623
- const stagePath = new URL(
2624
- "../../../artifact/templates/artifact-review-stage.js",
2625
- new URL("./runtime/packages/design/lib/design/document.mjs", import.meta.url).href
2626
- );
2627
- try {
2628
- return readRuntimeAsset(stagePath);
2629
- } catch {
2630
- return readRuntimeAsset(
2631
- new URL("../../templates/artifact-review-stage.js", new URL("./runtime/packages/design/lib/design/document.mjs", import.meta.url).href)
2632
- );
2633
- }
2634
- }
2635
- function designRendererRevision() {
2636
- return hash(
2637
- json({
2638
- version: DESIGN_RENDERER_VERSION,
2639
- stage: hash(stageRuntimeBytes()),
2640
- assets: ["studio.css", "studio.js", "enhancements.css", "handoff-center.css"].filter(
2641
- (name) => name === "studio.js" || existsSync2(new URL(`../../templates/studio/${name}`, new URL("./runtime/packages/design/lib/design/document.mjs", import.meta.url).href))
2642
- ).map(
2643
- (name) => hash(
2644
- name === "studio.js" ? readDesignStudioRuntime() : readFileSync5(new URL(`../../templates/studio/${name}`, new URL("./runtime/packages/design/lib/design/document.mjs", import.meta.url).href))
2645
- )
2646
- )
2647
- })
2648
- );
2649
- }
2650
- function standaloneDesignHtml(prepared, view = prepared.document.defaultView, {
2651
- parentOrigin = "null",
2652
- sourceTransport = parentOrigin === "null" ? "blob" : "srcdoc",
2653
- prototypeStateAliases
2654
- } = {}) {
2655
- const nonce = createArtifactBridgeNonce();
2656
- const pool = prepared.envelope.schemaVersion === "1.1.0";
2657
- const sources = pool ? Object.fromEntries(
2658
- prepared.envelope.sources.map((source) => [
2659
- source.id,
2660
- prepareArtifactSourceTemplate({
2661
- html: source.html,
2662
- nonce,
2663
- parentOrigin,
2664
- allowLocalForms: true,
2665
- prototypeState: true,
2666
- screenId: prepared.entries.find(
2667
- (entry) => prepared.envelope.artifacts.find((artifact) => artifact.id === entry.artifactId)?.sourceId === source.id
2668
- )?.screenId ?? source.id
2669
- })
2670
- ])
2671
- ) : null;
2672
- const artifacts = pool ? null : Object.fromEntries(
2673
- prepared.envelope.artifacts.map((artifact) => [
2674
- artifact.id,
2675
- prepareArtifactDocument({
2676
- html: resolveArtifactHtml(prepared.envelope, artifact),
2677
- artifactId: artifact.id,
2678
- nonce,
2679
- parentOrigin,
2680
- portable: true,
2681
- allowLocalForms: true,
2682
- prototypeState: true,
2683
- screenId: prepared.entries.find((entry) => entry.artifactId === artifact.id)?.screenId ?? artifact.id
2684
- }).html
2685
- ])
2686
- );
2687
- const stage = stageRuntimeBytes();
2688
- const stageRuntimeUrl = `data:text/javascript;base64,${Buffer.from(stage).toString("base64")}`;
2689
- const runtime = renderArtifactParentRuntime({
2690
- nonce,
2691
- ...pool ? {
2692
- inlineSources: sources,
2693
- inlineArtifactSources: Object.fromEntries(
2694
- prepared.envelope.artifacts.map((artifact) => [artifact.id, artifact.sourceId])
2695
- )
2696
- } : { inlineArtifacts: artifacts },
2697
- sourceTransport,
2698
- frameBudget: 3,
2699
- stageRuntimeUrl
2700
- });
2701
- const configuredRuntime = prototypeStateAliases ? `globalThis.__OPENPLANR_DESIGN_STUDIO_OPTIONS__=${JSON.stringify({ prototypeStateAliases }).replaceAll("<", "\\u003c")};
2702
- ${runtime}` : runtime;
2703
- const runtimeUrl = `data:text/javascript;base64,${Buffer.from(configuredRuntime).toString("base64")}`;
2704
- const state = { ...prepared.state, view };
2705
- if (state.selectedVariant) state.variantId = state.selectedVariant;
2706
- return renderDesignStudio(
2707
- {
2708
- ...prepared,
2709
- state,
2710
- verification: prepared.verification ?? { status: "unverified" }
2711
- },
2712
- { stageRuntimeUrl: runtimeUrl }
2713
- );
2714
- }
2715
- async function renderDesignDocument(file, options = {}) {
2716
- return withDesignRenderLock(file, (render) => render(options));
2717
- }
2718
- async function withDesignRenderLock(file, action) {
2719
- const { root } = loadDesignDocument(file);
2720
- const work = join2(root, ".design");
2721
- mkdirSync(work, { recursive: true });
2722
- const release = await acquireStartLock(join2(work, "render.lock"), {
2723
- timeout: 1e3,
2724
- stale: 3e4
2725
- });
2726
- try {
2727
- recoverDesignPublication(root, { ownsRenderLock: true });
2728
- return await action((options) => renderDesignDocumentUnlocked(file, options));
2729
- } finally {
2730
- release();
2731
- }
2732
- }
2733
- async function renderDesignDocumentUnlocked(file, {
2734
- beforeCommit,
2735
- writePointer = atomicJson,
2736
- rendererRevision = designRendererRevision(),
2737
- now = () => (/* @__PURE__ */ new Date()).toISOString()
2738
- } = {}) {
2739
- const { root } = loadDesignDocument(file);
2740
- const work = join2(root, ".design");
2741
- let temporary;
2742
- try {
2743
- const prepared = prepareDesignDocument(file);
2744
- const errors = prepared.lint.flatMap(
2745
- (item) => item.errors.map((error) => `${item.screenId}: ${error.message}`)
2746
- );
2747
- if (errors.length) throw new Error(`Design lint failed:
2748
- ${errors.join("\n")}`);
2749
- const specPath = designSpecPath(root);
2750
- const spec = readFileSync5(specPath, "utf8");
2751
- for (let section = 1; section <= 10; section++)
2752
- if (!new RegExp(`^## ${section}\\. `, "m").test(spec))
2753
- throw new Error(`design-spec.md is missing section ${section}.`);
2754
- prepared.revision = hash(json({ source: prepared.revision, spec, rendererRevision }));
2755
- const directory = join2(work, "revisions", prepared.revision);
2756
- const previous = readJson(join2(work, "current.json"), null);
2757
- const generatedAt = now();
2758
- const manifest = existsSync2(directory) ? readJson(join2(directory, "render.json")).manifest : buildManifest({
2759
- framework: "vanilla",
2760
- designFormat: prepared.document.defaultView,
2761
- source: prepared.document.brief.source,
2762
- contentProvenance: prepared.document.brief.provenance,
2763
- generatedAt,
2764
- screens: prepared.document.screenOrder.map(
2765
- (id) => prepared.document.screens.find((item) => item.id === id).title
2766
- ),
2767
- iterations: previous && previous.revision !== prepared.revision ? (previous.iterations ?? 0) + 1 : previous?.iterations ?? 0,
2768
- htmlFile: `.design/revisions/${prepared.revision}/${prepared.document.defaultView}.html`
2769
- });
2770
- const record = {
2771
- reviewContext: prepared.reviewContext,
2772
- contextDigest: prepared.contextDigest,
2773
- fingerprints: prepared.fingerprints,
2774
- document: prepared.document,
2775
- envelope: prepared.envelope,
2776
- entries: prepared.entries,
2777
- revision: prepared.revision,
2778
- rendererRevision,
2779
- lint: prepared.lint,
2780
- manifest
2781
- };
2782
- if (!existsSync2(directory)) {
2783
- temporary = join2(work, `pending-${randomUUID()}`);
2784
- mkdirSync(join2(temporary, "sources"), { recursive: true });
2785
- writeFileSync(join2(temporary, "render.json"), json(record));
2786
- writeFileSync(join2(temporary, "design-document.json"), json(prepared.document));
2787
- writeFileSync(join2(temporary, "design-spec.md"), spec);
2788
- for (const source of prepared.sourceFiles) {
2789
- const destination = join2(temporary, "sources", source);
2790
- mkdirSync(dirname(destination), { recursive: true });
2791
- writeFileSync(destination, prepared.sourceContents.get(source));
2792
- }
2793
- for (const view of DESIGN_VIEWS)
2794
- writeFileSync(join2(temporary, `${view}.html`), standaloneDesignHtml(record, view));
2795
- beforeCommit?.(record);
2796
- mkdirSync(dirname(directory), { recursive: true });
2797
- renameSync(temporary, directory);
2798
- temporary = null;
2799
- }
2800
- const manifestPath = join2(root, "finalized.json");
2801
- atomicJson(join2(work, "publication.json"), {
2802
- revision: prepared.revision,
2803
- previousRevision: previous?.revision ?? null,
2804
- manifest,
2805
- previousManifest: existsSync2(manifestPath) ? readFileSync5(manifestPath).toString("base64") : null
2806
- });
2807
- try {
2808
- atomicJson(manifestPath, manifest);
2809
- writePointer(join2(work, "current.json"), {
2810
- revision: prepared.revision,
2811
- previousRevision: previous?.revision === prepared.revision ? previous.previousRevision : previous?.revision ?? null,
2812
- iterations: manifest.iterations,
2813
- generatedAt: manifest.generated_at
2814
- });
2815
- rmSync(join2(work, "publication.json"), { force: true });
2816
- } catch (error) {
2817
- recoverDesignPublication(root, { ownsRenderLock: true });
2818
- throw error;
2819
- }
2820
- return {
2821
- ok: true,
2822
- revision: prepared.revision,
2823
- document: resolve2(file),
2824
- artifact: join2(directory, `${prepared.document.defaultView}.html`),
2825
- views: Object.fromEntries(
2826
- DESIGN_VIEWS.map((view) => [view, join2(directory, `${view}.html`)])
2827
- ),
2828
- manifest: join2(root, "finalized.json"),
2829
- spec: specPath,
2830
- verification: "unverified"
2831
- };
2832
- } finally {
2833
- if (temporary) rmSync(temporary, { recursive: true, force: true });
2834
- }
2835
- }
2836
-
2837
- // packages/design/lib/design/layout-save.mjs
2838
- async function saveDesignLayout(file, input) {
2839
- if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key) => !["revision", "boards", "restoreRevision"].includes(key)) || typeof input.revision !== "string" || Object.hasOwn(input, "boards") === Object.hasOwn(input, "restoreRevision"))
2840
- throw new Error("Design layout requires a revision and either boards or restoreRevision.");
2841
- return withDesignRenderLock(file, async (render) => {
2842
- const current = currentDesign(file);
2843
- if (input.revision !== current.revision)
2844
- throw Object.assign(new Error("The design changed. Reload before saving its layout."), {
2845
- statusCode: 409
2846
- });
2847
- const authored = prepareDesignDocument(file);
2848
- const authoredContent = { ...authored.document };
2849
- const committedContent = { ...current.document };
2850
- delete authoredContent.layout;
2851
- delete committedContent.layout;
2852
- if (digestArtifactEnvelope(authored.envelope) !== digestArtifactEnvelope(current.envelope) || JSON.stringify(authored.fingerprints) !== JSON.stringify(current.fingerprints) || JSON.stringify(authoredContent) !== JSON.stringify(committedContent) || readFileSync6(designSpecPath(current.root), "utf8") !== readFileSync6(join3(current.directory, "design-spec.md"), "utf8"))
2853
- throw Object.assign(
2854
- new Error(
2855
- "Design sources or content changed. Render the design and reload before saving its layout."
2856
- ),
2857
- { statusCode: 409 }
2858
- );
2859
- const previous = readFileSync6(file, "utf8");
2860
- const mode = statSync(file).mode & 4095;
2861
- const document2 = JSON.parse(previous);
2862
- if (Object.hasOwn(input, "restoreRevision")) {
2863
- if (typeof input.restoreRevision !== "string" || !/^[a-f0-9]{64}$/u.test(input.restoreRevision))
2864
- throw new Error(`Invalid layout restore revision ${input.restoreRevision}.`);
2865
- const restored = readJson(
2866
- join3(current.root, ".design/revisions", input.restoreRevision, "design-document.json")
2867
- );
2868
- if (restored.layout === void 0) delete document2.layout;
2869
- else document2.layout = structuredClone(restored.layout);
2870
- } else {
2871
- if (!input.boards || typeof input.boards !== "object" || Array.isArray(input.boards))
2872
- throw new Error("Design layout boards must be an object of exact board keys.");
2873
- const boards = { ...document2.layout?.boards };
2874
- for (const [key, point] of Object.entries(input.boards)) {
2875
- if (!current.entries.some(
2876
- (entry) => `${entry.screenId}/${entry.frameId}/${entry.variantId}` === key
2877
- ))
2878
- throw new Error(`Unknown exact design board key ${key}.`);
2879
- if (point === null) delete boards[key];
2880
- else {
2881
- if (!point || typeof point !== "object" || Array.isArray(point) || Object.keys(point).some((field) => !["x", "y"].includes(field)) || !Number.isFinite(point.x) || !Number.isFinite(point.y) || Math.abs(point.x) > 1e7 || Math.abs(point.y) > 1e7)
2882
- throw new Error(
2883
- `Design board ${key} requires finite x and y coordinates within \xB110000000.`
2884
- );
2885
- boards[key] = { ...boards[key], x: point.x, y: point.y };
2886
- }
2887
- }
2888
- document2.layout = { ...document2.layout, boards };
2889
- if (!Object.keys(boards).length) delete document2.layout.boards;
2890
- if (!Object.keys(document2.layout).length) delete document2.layout;
2891
- }
2892
- assertDesignDocument(document2);
2893
- const indent = previous.match(/^([\t ]+)\S/mu)?.[1] ?? "";
2894
- const formatted = indent ? JSON.stringify(document2, null, " ").replace(
2895
- /^\t+/gmu,
2896
- (tabs) => indent.repeat(tabs.length)
2897
- ) : JSON.stringify(document2);
2898
- const written = `${formatted}${previous.endsWith("\n") ? "\n" : ""}`;
2899
- atomicBytes(file, written, mode);
2900
- try {
2901
- const result = await render();
2902
- return { revision: result.revision };
2903
- } catch (error) {
2904
- if (hash(readFileSync6(file)) !== hash(written))
2905
- throw Object.assign(
2906
- new Error(
2907
- `The authored document ${file} changed during the save. Its newer contents were preserved; reload before saving its layout.`,
2908
- { cause: error }
2909
- ),
2910
- { statusCode: 409 }
2911
- );
2912
- atomicBytes(file, previous, mode);
2913
- throw error;
2914
- }
2915
- });
2916
- }
2917
-
2918
- export {
2919
- mintCapabilityToken,
2920
- isCapabilityToken,
2921
- timingSafeTokenEqual,
2922
- prepareArtifactDocument,
2923
- createArtifactBridgeNonce,
2924
- renderArtifactParentRuntime,
2925
- matchesDesignFrame,
2926
- readRuntimeAsset,
2927
- renderDesignStudio,
2928
- saveDesignLayout,
2929
- saveDesignViewContext,
2930
- readDesignViewContext,
2931
- inspectDesignDocument,
2932
- prepareDesignDocument,
2933
- designRendererRevision,
2934
- standaloneDesignHtml,
2935
- renderDesignDocument,
2936
- withDesignRenderLock
2937
- };