openplanr 3.1.1 → 3.2.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 (363) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/cli/commands/groups/delivery.d.ts.map +1 -1
  3. package/dist/cli/commands/groups/delivery.js +2 -0
  4. package/dist/cli/commands/groups/delivery.js.map +1 -1
  5. package/dist/cli/commands/planning-artifacts.d.ts.map +1 -1
  6. package/dist/cli/commands/planning-artifacts.js +22 -12
  7. package/dist/cli/commands/planning-artifacts.js.map +1 -1
  8. package/dist/cli/commands/ship.d.ts +3 -0
  9. package/dist/cli/commands/ship.d.ts.map +1 -0
  10. package/dist/cli/commands/ship.js +65 -0
  11. package/dist/cli/commands/ship.js.map +1 -0
  12. package/dist/dashboard/.vite/manifest.json +1 -1
  13. package/dist/dashboard/assets/{index-G7ygVDGg.js → index-L8qtwqv6.js} +2 -2
  14. package/dist/dashboard/assets/{index-G7ygVDGg.js.map → index-L8qtwqv6.js.map} +1 -1
  15. package/dist/dashboard/dashboard-manifest.json +8 -8
  16. package/dist/dashboard/index.html +1 -1
  17. package/dist/models/schema.d.ts +39 -23
  18. package/dist/models/schema.d.ts.map +1 -1
  19. package/dist/models/schema.js +19 -12
  20. package/dist/models/schema.js.map +1 -1
  21. package/dist/models/types.d.ts +6 -0
  22. package/dist/models/types.d.ts.map +1 -1
  23. package/dist/services/id-service.d.ts +1 -5
  24. package/dist/services/id-service.d.ts.map +1 -1
  25. package/dist/services/id-service.js +1 -34
  26. package/dist/services/id-service.js.map +1 -1
  27. package/dist/services/ship-checks/changes.d.ts +24 -0
  28. package/dist/services/ship-checks/changes.d.ts.map +1 -0
  29. package/dist/services/ship-checks/changes.js +135 -0
  30. package/dist/services/ship-checks/changes.js.map +1 -0
  31. package/dist/services/ship-checks/contract.d.ts +81 -0
  32. package/dist/services/ship-checks/contract.d.ts.map +1 -0
  33. package/dist/services/ship-checks/contract.js +7 -0
  34. package/dist/services/ship-checks/contract.js.map +1 -0
  35. package/dist/services/ship-checks/discover.d.ts +10 -0
  36. package/dist/services/ship-checks/discover.d.ts.map +1 -0
  37. package/dist/services/ship-checks/discover.js +250 -0
  38. package/dist/services/ship-checks/discover.js.map +1 -0
  39. package/dist/services/ship-checks/glob.d.ts +5 -0
  40. package/dist/services/ship-checks/glob.d.ts.map +1 -0
  41. package/dist/services/ship-checks/glob.js +39 -0
  42. package/dist/services/ship-checks/glob.js.map +1 -0
  43. package/dist/services/ship-checks/hooks.d.ts +7 -0
  44. package/dist/services/ship-checks/hooks.d.ts.map +1 -0
  45. package/dist/services/ship-checks/hooks.js +65 -0
  46. package/dist/services/ship-checks/hooks.js.map +1 -0
  47. package/dist/services/ship-checks/packages.d.ts +29 -0
  48. package/dist/services/ship-checks/packages.d.ts.map +1 -0
  49. package/dist/services/ship-checks/packages.js +197 -0
  50. package/dist/services/ship-checks/packages.js.map +1 -0
  51. package/dist/services/ship-checks/related-tests.d.ts +13 -0
  52. package/dist/services/ship-checks/related-tests.d.ts.map +1 -0
  53. package/dist/services/ship-checks/related-tests.js +73 -0
  54. package/dist/services/ship-checks/related-tests.js.map +1 -0
  55. package/dist/services/ship-checks/scope.d.ts +8 -0
  56. package/dist/services/ship-checks/scope.d.ts.map +1 -0
  57. package/dist/services/ship-checks/scope.js +66 -0
  58. package/dist/services/ship-checks/scope.js.map +1 -0
  59. package/dist/services/ship-checks/task-requirements.d.ts +14 -0
  60. package/dist/services/ship-checks/task-requirements.d.ts.map +1 -0
  61. package/dist/services/ship-checks/task-requirements.js +145 -0
  62. package/dist/services/ship-checks/task-requirements.js.map +1 -0
  63. package/dist/services/ship-checks/workflows.d.ts +21 -0
  64. package/dist/services/ship-checks/workflows.d.ts.map +1 -0
  65. package/dist/services/ship-checks/workflows.js +208 -0
  66. package/dist/services/ship-checks/workflows.js.map +1 -0
  67. package/dist/services/template-service.d.ts +2 -0
  68. package/dist/services/template-service.d.ts.map +1 -1
  69. package/dist/services/template-service.js +4 -0
  70. package/dist/services/template-service.js.map +1 -1
  71. package/dist/utils/slugify.d.ts +1 -8
  72. package/dist/utils/slugify.d.ts.map +1 -1
  73. package/dist/utils/slugify.js +1 -25
  74. package/dist/utils/slugify.js.map +1 -1
  75. package/docs/CLI.md +48 -1
  76. package/lib/backlog-item.d.mts +49 -0
  77. package/lib/backlog-item.mjs +339 -0
  78. package/lib/host-packages/adapter-registry.json +5 -5
  79. package/lib/host-packages/capability-map.json +1 -1
  80. package/lib/host-packages/claude/.claude-plugin/marketplace.json +2 -2
  81. package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +1 -1
  82. package/lib/host-packages/claude/openplanr/.openplanr-content.json +126 -90
  83. package/lib/host-packages/claude/openplanr/CONNECTORS.md +4 -6
  84. package/lib/host-packages/claude/openplanr/README.md +1 -1
  85. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-artifact-shell.mjs +1 -1
  86. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +1 -1
  87. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-document.mjs +385 -79
  88. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-handoff.mjs +1 -1
  89. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-resource-pack.mjs +1 -1
  90. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review.mjs +45 -4
  91. package/lib/host-packages/{cursor/openplanr/runtime/design/scripts/design-runtime-support-e34c388a8e59.mjs → claude/openplanr/runtime/design/scripts/design-runtime-support-6774fd3d5d02.mjs} +1 -1
  92. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-share.mjs +8 -5
  93. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs +40 -7
  94. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs +1 -1
  95. package/lib/host-packages/claude/openplanr/runtime/design/scripts/{design-shared-protocol-contracts-31a760fc.mjs → design-shared-protocol-contracts-b5f28f94.mjs} +284 -45
  96. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-upload-spool.mjs +1 -1
  97. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design.mjs +215 -55
  98. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +23 -23
  99. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +246 -45
  100. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +12 -12
  101. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +47 -14
  102. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +11 -10
  103. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/package.json +4 -3
  104. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +16 -11
  105. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +7 -11
  106. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +163 -274
  107. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +494 -967
  108. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +346 -678
  109. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +382 -810
  110. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +406 -730
  111. 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 +259 -522
  112. 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 +585 -937
  113. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +115 -240
  114. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +7 -17
  115. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +3 -4
  116. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +215 -0
  117. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +2766 -0
  118. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-studio-shell.js +134 -0
  119. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +2170 -0
  120. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-design-review-experience.js +16 -0
  121. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-studio-shell.js +8 -0
  122. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/18-interface-primitives.js +3883 -0
  123. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/19-interface-primitives.js +966 -0
  124. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/{16-studio-shell.js → 20-studio-shell.js} +122 -227
  125. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +1583 -0
  126. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +101 -47
  127. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +5 -1
  128. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0 → claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.20.0}/design-document.schema.json +78 -5
  129. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +247 -45
  130. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design.mjs +45 -12
  131. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +5 -1
  132. package/lib/host-packages/claude/openplanr/skills/connect/references/connectors.md +4 -6
  133. package/lib/host-packages/claude/openplanr/skills/design/SKILL.md +12 -0
  134. package/lib/host-packages/claude/openplanr/skills/design/references/craft.md +14 -3
  135. package/lib/host-packages/claude/openplanr/skills/design/references/design-system.md +33 -1
  136. package/lib/host-packages/claude/openplanr/skills/design/references/discovery.md +18 -4
  137. package/lib/host-packages/claude/openplanr/skills/design/references/generation.md +74 -1
  138. package/lib/host-packages/claude/openplanr/skills/design/references/hosted-canvas.md +62 -0
  139. package/lib/host-packages/claude/openplanr/skills/design/references/review.md +46 -1
  140. package/lib/host-packages/claude/openplanr/skills/design/references/utilities.md +6 -0
  141. package/lib/host-packages/claude/openplanr/skills/design/schemas/design-document.schema.json +78 -5
  142. package/lib/host-packages/claude/openplanr/skills/design-loop/references/craft.md +14 -3
  143. package/lib/host-packages/claude/openplanr/skills/design-loop/references/design-system.md +33 -1
  144. package/lib/host-packages/claude/openplanr/skills/design-loop/references/discovery.md +18 -4
  145. package/lib/host-packages/claude/openplanr/skills/design-loop/references/generation.md +74 -1
  146. package/lib/host-packages/claude/openplanr/skills/design-loop/references/review.md +46 -1
  147. package/lib/host-packages/claude/openplanr/skills/design-loop/references/utilities.md +6 -0
  148. package/lib/host-packages/claude/openplanr/skills/design-loop/schemas/design-document.schema.json +78 -5
  149. package/lib/host-packages/claude/openplanr/skills/design-review/references/craft.md +14 -3
  150. package/lib/host-packages/claude/openplanr/skills/design-review/references/design-system.md +33 -1
  151. package/lib/host-packages/claude/openplanr/skills/design-review/references/discovery.md +18 -4
  152. package/lib/host-packages/claude/openplanr/skills/design-review/references/generation.md +74 -1
  153. package/lib/host-packages/claude/openplanr/skills/design-review/references/review.md +46 -1
  154. package/lib/host-packages/claude/openplanr/skills/design-review/references/utilities.md +6 -0
  155. package/lib/host-packages/claude/openplanr/skills/design-review/schemas/design-document.schema.json +78 -5
  156. package/lib/host-packages/claude/openplanr/skills/investigate/SKILL.md +10 -1
  157. package/lib/host-packages/claude/openplanr/skills/investigate/references/findings.md +92 -0
  158. package/lib/host-packages/claude/openplanr/skills/investigate/scripts/backlog-item.mjs +339 -0
  159. package/lib/host-packages/claude/openplanr/skills/plan/SKILL.md +3 -0
  160. package/lib/host-packages/claude/openplanr/skills/plan/schemas/design-document.schema.json +78 -5
  161. package/lib/host-packages/claude/openplanr/skills/share/references/connectors.md +4 -6
  162. package/lib/host-packages/claude/openplanr/skills/ship/SKILL.md +15 -4
  163. package/lib/host-packages/claude/openplanr/skills/ship/references/findings.md +92 -0
  164. package/lib/host-packages/claude/openplanr/skills/ship/references/result-contract.md +3 -1
  165. package/lib/host-packages/claude/openplanr/skills/ship/scripts/backlog-item.mjs +339 -0
  166. package/lib/host-packages/claude/openplanr/skills/sprint/references/connectors.md +4 -6
  167. package/lib/host-packages/claude/openplanr/skills/status/references/connectors.md +4 -6
  168. package/lib/host-packages/claude/openplanr/skills/sync/references/connectors.md +4 -6
  169. package/lib/host-packages/cursor/openplanr/.openplanr-content.json +125 -89
  170. package/lib/host-packages/cursor/openplanr/CONNECTORS.md +4 -6
  171. package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
  172. package/lib/host-packages/cursor/openplanr/rules/planr-connect/references/connectors.md +4 -6
  173. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/craft.md +14 -3
  174. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/design-system.md +33 -1
  175. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/discovery.md +18 -4
  176. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/generation.md +74 -1
  177. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/hosted-canvas.md +62 -0
  178. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/review.md +46 -1
  179. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/utilities.md +6 -0
  180. package/lib/host-packages/cursor/openplanr/rules/planr-design/schemas/design-document.schema.json +78 -5
  181. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/craft.md +14 -3
  182. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/design-system.md +33 -1
  183. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/discovery.md +18 -4
  184. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/generation.md +74 -1
  185. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/review.md +46 -1
  186. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/utilities.md +6 -0
  187. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/schemas/design-document.schema.json +78 -5
  188. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/craft.md +14 -3
  189. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/design-system.md +33 -1
  190. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/discovery.md +18 -4
  191. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/generation.md +74 -1
  192. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/review.md +46 -1
  193. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/utilities.md +6 -0
  194. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/schemas/design-document.schema.json +78 -5
  195. package/lib/host-packages/cursor/openplanr/rules/planr-design.mdc +12 -0
  196. package/lib/host-packages/cursor/openplanr/rules/planr-investigate/references/findings.md +92 -0
  197. package/lib/host-packages/cursor/openplanr/rules/planr-investigate/scripts/backlog-item.mjs +339 -0
  198. package/lib/host-packages/cursor/openplanr/rules/planr-investigate.mdc +10 -1
  199. package/lib/host-packages/cursor/openplanr/rules/planr-plan/schemas/design-document.schema.json +78 -5
  200. package/lib/host-packages/cursor/openplanr/rules/planr-plan.mdc +3 -0
  201. package/lib/host-packages/cursor/openplanr/rules/planr-share/references/connectors.md +4 -6
  202. package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/findings.md +92 -0
  203. package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/result-contract.md +3 -1
  204. package/lib/host-packages/cursor/openplanr/rules/planr-ship/scripts/backlog-item.mjs +339 -0
  205. package/lib/host-packages/cursor/openplanr/rules/planr-ship.mdc +15 -4
  206. package/lib/host-packages/cursor/openplanr/rules/planr-sprint/references/connectors.md +4 -6
  207. package/lib/host-packages/cursor/openplanr/rules/planr-status/references/connectors.md +4 -6
  208. package/lib/host-packages/cursor/openplanr/rules/planr-sync/references/connectors.md +4 -6
  209. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-artifact-shell.mjs +1 -1
  210. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +1 -1
  211. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-document.mjs +385 -79
  212. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-handoff.mjs +1 -1
  213. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-resource-pack.mjs +1 -1
  214. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review.mjs +45 -4
  215. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-runtime-support-e34c388a8e59.mjs → cursor/openplanr/runtime/design/scripts/design-runtime-support-6774fd3d5d02.mjs} +1 -1
  216. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-share.mjs +8 -5
  217. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs +40 -7
  218. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs +1 -1
  219. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-shared-protocol-contracts-31a760fc.mjs → cursor/openplanr/runtime/design/scripts/design-shared-protocol-contracts-b5f28f94.mjs} +284 -45
  220. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-upload-spool.mjs +1 -1
  221. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design.mjs +215 -55
  222. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +23 -23
  223. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +246 -45
  224. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +12 -12
  225. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +47 -14
  226. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +11 -10
  227. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/package.json +4 -3
  228. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +16 -11
  229. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +7 -11
  230. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +163 -274
  231. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +494 -967
  232. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +346 -678
  233. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +382 -810
  234. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +406 -730
  235. 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 +259 -522
  236. 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 +585 -937
  237. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +115 -240
  238. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +7 -17
  239. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +3 -4
  240. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +215 -0
  241. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +2766 -0
  242. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-studio-shell.js +134 -0
  243. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +2170 -0
  244. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-design-review-experience.js +16 -0
  245. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-studio-shell.js +8 -0
  246. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/18-interface-primitives.js +3883 -0
  247. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/19-interface-primitives.js +966 -0
  248. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/{16-studio-shell.js → 20-studio-shell.js} +122 -227
  249. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +1583 -0
  250. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +101 -47
  251. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +5 -1
  252. package/lib/host-packages/{claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0 → cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.20.0}/design-document.schema.json +78 -5
  253. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +247 -45
  254. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design.mjs +45 -12
  255. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +5 -1
  256. package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
  257. package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +1 -1
  258. package/lib/host-packages/openai/openplanr/.openplanr-content.json +128 -92
  259. package/lib/host-packages/openai/openplanr/CONNECTORS.md +4 -6
  260. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-artifact-shell.mjs +1 -1
  261. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +1 -1
  262. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-document.mjs +385 -79
  263. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-handoff.mjs +1 -1
  264. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-resource-pack.mjs +1 -1
  265. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review.mjs +45 -4
  266. package/lib/host-packages/{claude/openplanr/runtime/design/scripts/design-runtime-support-e34c388a8e59.mjs → openai/openplanr/runtime/design/scripts/design-runtime-support-6774fd3d5d02.mjs} +1 -1
  267. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-share.mjs +8 -5
  268. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs +40 -7
  269. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs +1 -1
  270. package/lib/host-packages/{cursor/openplanr/runtime/design/scripts/design-shared-protocol-contracts-31a760fc.mjs → openai/openplanr/runtime/design/scripts/design-shared-protocol-contracts-b5f28f94.mjs} +284 -45
  271. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-upload-spool.mjs +1 -1
  272. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design.mjs +215 -55
  273. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +23 -23
  274. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +246 -45
  275. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +12 -12
  276. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +47 -14
  277. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +11 -10
  278. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/package.json +4 -3
  279. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +16 -11
  280. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +7 -11
  281. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +163 -274
  282. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +494 -967
  283. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +346 -678
  284. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +382 -810
  285. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +406 -730
  286. 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 +259 -522
  287. 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 +585 -937
  288. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +115 -240
  289. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +7 -17
  290. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +3 -4
  291. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +215 -0
  292. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +2766 -0
  293. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-studio-shell.js +134 -0
  294. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +2170 -0
  295. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-design-review-experience.js +16 -0
  296. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-studio-shell.js +8 -0
  297. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/18-interface-primitives.js +3883 -0
  298. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/19-interface-primitives.js +966 -0
  299. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/{16-studio-shell.js → 20-studio-shell.js} +122 -227
  300. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +1583 -0
  301. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +101 -47
  302. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +5 -1
  303. package/lib/host-packages/{cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0 → openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.20.0}/design-document.schema.json +78 -5
  304. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +247 -45
  305. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design.mjs +45 -12
  306. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +5 -1
  307. package/lib/host-packages/openai/openplanr/skills/connect/references/connectors.md +4 -6
  308. package/lib/host-packages/openai/openplanr/skills/design/SKILL.md +12 -0
  309. package/lib/host-packages/openai/openplanr/skills/design/openplanr.skill.json +11 -0
  310. package/lib/host-packages/openai/openplanr/skills/design/references/craft.md +14 -3
  311. package/lib/host-packages/openai/openplanr/skills/design/references/design-system.md +33 -1
  312. package/lib/host-packages/openai/openplanr/skills/design/references/discovery.md +18 -4
  313. package/lib/host-packages/openai/openplanr/skills/design/references/generation.md +74 -1
  314. package/lib/host-packages/openai/openplanr/skills/design/references/hosted-canvas.md +62 -0
  315. package/lib/host-packages/openai/openplanr/skills/design/references/review.md +46 -1
  316. package/lib/host-packages/openai/openplanr/skills/design/references/utilities.md +6 -0
  317. package/lib/host-packages/openai/openplanr/skills/design/schemas/design-document.schema.json +78 -5
  318. package/lib/host-packages/openai/openplanr/skills/design-loop/references/craft.md +14 -3
  319. package/lib/host-packages/openai/openplanr/skills/design-loop/references/design-system.md +33 -1
  320. package/lib/host-packages/openai/openplanr/skills/design-loop/references/discovery.md +18 -4
  321. package/lib/host-packages/openai/openplanr/skills/design-loop/references/generation.md +74 -1
  322. package/lib/host-packages/openai/openplanr/skills/design-loop/references/review.md +46 -1
  323. package/lib/host-packages/openai/openplanr/skills/design-loop/references/utilities.md +6 -0
  324. package/lib/host-packages/openai/openplanr/skills/design-loop/schemas/design-document.schema.json +78 -5
  325. package/lib/host-packages/openai/openplanr/skills/design-review/references/craft.md +14 -3
  326. package/lib/host-packages/openai/openplanr/skills/design-review/references/design-system.md +33 -1
  327. package/lib/host-packages/openai/openplanr/skills/design-review/references/discovery.md +18 -4
  328. package/lib/host-packages/openai/openplanr/skills/design-review/references/generation.md +74 -1
  329. package/lib/host-packages/openai/openplanr/skills/design-review/references/review.md +46 -1
  330. package/lib/host-packages/openai/openplanr/skills/design-review/references/utilities.md +6 -0
  331. package/lib/host-packages/openai/openplanr/skills/design-review/schemas/design-document.schema.json +78 -5
  332. package/lib/host-packages/openai/openplanr/skills/investigate/SKILL.md +10 -1
  333. package/lib/host-packages/openai/openplanr/skills/investigate/openplanr.skill.json +22 -0
  334. package/lib/host-packages/openai/openplanr/skills/investigate/references/findings.md +92 -0
  335. package/lib/host-packages/openai/openplanr/skills/investigate/scripts/backlog-item.mjs +339 -0
  336. package/lib/host-packages/openai/openplanr/skills/plan/SKILL.md +3 -0
  337. package/lib/host-packages/openai/openplanr/skills/plan/schemas/design-document.schema.json +78 -5
  338. package/lib/host-packages/openai/openplanr/skills/share/references/connectors.md +4 -6
  339. package/lib/host-packages/openai/openplanr/skills/ship/SKILL.md +15 -4
  340. package/lib/host-packages/openai/openplanr/skills/ship/openplanr.skill.json +22 -0
  341. package/lib/host-packages/openai/openplanr/skills/ship/references/findings.md +92 -0
  342. package/lib/host-packages/openai/openplanr/skills/ship/references/result-contract.md +3 -1
  343. package/lib/host-packages/openai/openplanr/skills/ship/scripts/backlog-item.mjs +339 -0
  344. package/lib/host-packages/openai/openplanr/skills/sprint/references/connectors.md +4 -6
  345. package/lib/host-packages/openai/openplanr/skills/status/references/connectors.md +4 -6
  346. package/lib/host-packages/openai/openplanr/skills/sync/references/connectors.md +4 -6
  347. package/package.json +3 -3
  348. package/dist/templates/backlog/backlog-item.md.hbs +0 -40
  349. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-design-review-experience.js +0 -3589
  350. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-studio-shell.js +0 -184
  351. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-interface-primitives.js +0 -5229
  352. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-interface-primitives.js +0 -1733
  353. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-design-studio.js +0 -1844
  354. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-design-review-experience.js +0 -3589
  355. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-studio-shell.js +0 -184
  356. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-interface-primitives.js +0 -5229
  357. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-interface-primitives.js +0 -1733
  358. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-design-studio.js +0 -1844
  359. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-design-review-experience.js +0 -3589
  360. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-studio-shell.js +0 -184
  361. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-interface-primitives.js +0 -5229
  362. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-interface-primitives.js +0 -1733
  363. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-design-studio.js +0 -1844
@@ -0,0 +1,1583 @@
1
+
2
+
3
+ // lib/design/ui/studio-chrome.mjs
4
+ function mountDesignChrome(input) {
5
+ return mountDesignStudioChrome(input);
6
+ }
7
+ function mountDesignPanels(input) {
8
+ return mountStudioPanelDialogs(input);
9
+ }
10
+
11
+ // lib/design/ui/studio.mjs
12
+ function mountDesignStudio({ payload, stage: artifactStage }) {
13
+ let design = payload.document, entries = payload.entries, options2 = globalThis.__OPENPLANR_DESIGN_STUDIO_OPTIONS__ ?? {}, storageKey = `openplanr.design-studio.${design.id}`, q = (selector) => document.querySelector(selector), qa = (selector) => [...document.querySelectorAll(selector)], clone = (value) => JSON.parse(JSON.stringify(value)), clamp2 = (value, min2, max2) => Math.min(max2, Math.max(min2, value)), readyVariants = design.variants.filter(({ status: status2 }) => status2 === "ready"), views = ["canvas", "prototype", "walkthrough"], validId = (items, id, fallback) => items.some((item) => (typeof item == "string" ? item : item.id) === id) ? id : fallback, normalizedViewport = (value) => !Number.isFinite(value?.x) || !Number.isFinite(value?.y) || !Number.isFinite(value?.zoom) ? null : {
14
+ x: clamp2(value.x, -1e7, 1e7),
15
+ y: clamp2(value.y, -1e7, 1e7),
16
+ zoom: clamp2(value.zoom, 0.05, 2)
17
+ };
18
+ function normalizeState(input = {}) {
19
+ let view = views.includes(input.view) ? input.view : design.defaultView, viewports = {};
20
+ for (let name of views) {
21
+ let viewport = normalizedViewport(input.viewports?.[name]);
22
+ viewport && (viewports[name] = viewport);
23
+ }
24
+ let legacyViewport = Number.isFinite(input.zoom) ? normalizedViewport({
25
+ x: Number.isFinite(input.camera?.x) ? input.camera.x : 40,
26
+ y: Number.isFinite(input.camera?.y) ? input.camera.y : 40,
27
+ zoom: input.zoom
28
+ }) : null, activeViewport = viewports[view] ?? legacyViewport ?? { x: 40, y: 40, zoom: 0.5 }, state2 = {
29
+ schemaVersion: "1.1.0",
30
+ view,
31
+ screenId: validId(design.screenOrder, input.screenId, design.screenOrder[0]),
32
+ frameId: validId(design.frames, input.frameId, design.frames[0].id),
33
+ variantId: validId(readyVariants, input.variantId, design.selectedVariant),
34
+ selectedVariant: validId(readyVariants, input.selectedVariant, design.selectedVariant),
35
+ compare: !!input.compare,
36
+ navOpen: input.navOpen === void 0 ? !0 : !!input.navOpen,
37
+ reviewOpen: input.reviewOpen === void 0 ? !0 : !!input.reviewOpen,
38
+ inspectionScale: input.inspectionScale === "actual" ? "actual" : "fit",
39
+ zoom: activeViewport.zoom,
40
+ camera: { x: activeViewport.x, y: activeViewport.y },
41
+ viewports,
42
+ ratings: {},
43
+ remix: {},
44
+ preferences: { selected: [], rejected: [] }
45
+ };
46
+ for (let variant of readyVariants) {
47
+ let rating = input.ratings?.[variant.id];
48
+ Number.isInteger(rating) && rating >= 1 && rating <= 5 && (state2.ratings[variant.id] = rating), typeof input.remix?.[variant.id] == "string" && (state2.remix[variant.id] = input.remix[variant.id].slice(0, 8192));
49
+ }
50
+ for (let kind of ["selected", "rejected"])
51
+ state2.preferences[kind] = [
52
+ ...new Set(
53
+ (Array.isArray(input.preferences?.[kind]) ? input.preferences[kind] : []).filter(
54
+ (id) => readyVariants.some((variant) => variant.id === id)
55
+ )
56
+ )
57
+ ];
58
+ return state2;
59
+ }
60
+ let savedDraft = null;
61
+ try {
62
+ savedDraft = JSON.parse(localStorage.getItem(storageKey) ?? "null");
63
+ } catch {
64
+ }
65
+ let state = normalizeState({
66
+ ...savedDraft?.state ?? {},
67
+ ...payload.state ?? {}
68
+ });
69
+ function linkedState() {
70
+ let params = new URLSearchParams(location.hash.slice(1)), linked = {};
71
+ for (let [key, query] of [
72
+ ["screenId", "screen"],
73
+ ["frameId", "frame"],
74
+ ["variantId", "direction"],
75
+ ["view", "view"]
76
+ ])
77
+ params.has(query) && (linked[key] = params.get(query));
78
+ return linked;
79
+ }
80
+ state = normalizeState({ ...state, ...linkedState() });
81
+ function recordLocation() {
82
+ let next = `#${new URLSearchParams({
83
+ screen: state.screenId,
84
+ frame: state.frameId,
85
+ direction: state.variantId,
86
+ view: state.view
87
+ })}`;
88
+ location.hash !== next && history.pushState(null, "", next);
89
+ }
90
+ let revision = payload.revision, layout = clone(design.layout ?? {}), undoMoves = [], redoMoves = [], layoutOperations = [], pendingLayoutDraft = savedDraft?.pendingLayout ?? null, layoutSaving = !1, layoutFailed = !1, sessionLost = !1, startupSettled = !1, layoutDragDirty = !1, activeLayoutOperation = null, layoutBlocked = !1, legacyMigrated = !1, legacyServerPositions = payload.state?.positions, stateVersion = null, stage = null, root = null, chrome = null, scroll = null, surface = null, grid = null, canvasAttached = !1, canvas = createCanvas({
91
+ label: "Design canvas",
92
+ zoom: { min: 0.05, max: 2 },
93
+ lod: !1,
94
+ motion: "none",
95
+ keyboard: { arrows: "none" },
96
+ shortcutsEnabled: () => !1
97
+ }), cameraFrame = null, selectionOutline = null, dirty = !1, contentDirty = !1, contentSerial = 0, reviewDirty = !1, reviewSerial = 0, reviewSaving = Promise.resolve(), reviewTimer = null, latestReview = null, serial = 0, saveTimer = null, saving = Promise.resolve(), pollTimer = null, noticeTimer = null, activeNotice = null, dismissedNotices = /* @__PURE__ */ new Map(), readyReported = !1, readyReporting = !1, destroyed = !1, panEnabled = !1, spacePan = !1, spaceReturnTool = null, activeTool = "interact", changingTool = !1, previousReviewMode = "interact", gesture = null, rendering = !1, previousStageZoom = 100, compactViewport = !1, activeArtifactId = "", inspectedSelection = null, viewContextTimer = null, viewContextPending = null, viewContextSignature = null, viewContextConflictRevision = null, viewContextSaving = !1, framesUsedWithComments = /* @__PURE__ */ new Set(), frameReload = 0, walkthroughTransition = null, pendingScreen = null, screenRequest = 0, frameDemandTimer = null, failedSelection = null, frameDemandKey = "", frameDemandGeneration = 0, queuedSelection = null, noteScreenId = null, notesReturnFocus = null, panels = /* @__PURE__ */ new Map(), sectionHeadings = [], sectionZoom = null, canvasPointers = /* @__PURE__ */ new Set(), cleanup = [];
98
+ function listen(target, event, handler, settings) {
99
+ target?.addEventListener(event, handler, settings), cleanup.push(() => target?.removeEventListener(event, handler, settings));
100
+ }
101
+ function status(message, phase = "saved") {
102
+ if (!destroyed && !(phase === "saved" && hasPendingLayout())) {
103
+ if (chrome) chrome.update({ saveLabel: message, savePhase: phase });
104
+ else {
105
+ let node = q("[data-design-save-state]");
106
+ node.textContent = message, node.dataset.status = phase;
107
+ }
108
+ q("[data-design-save-state]").title = message;
109
+ }
110
+ }
111
+ function hideNotice() {
112
+ clearTimeout(noticeTimer);
113
+ let node = q(".design-notice"), restoreFocus = node.contains(document.activeElement);
114
+ node.hidden = !0, activeNotice = null, q("[data-design-retry-layout]")?.remove(), q("[data-design-reload-studio]")?.remove(), restoreFocus && q(`button[data-design-view="${state.view}"]`)?.focus();
115
+ }
116
+ function clearNotice(key) {
117
+ dismissedNotices.delete(key), activeNotice?.key === key && hideNotice();
118
+ }
119
+ function clearFailedSelection() {
120
+ failedSelection && clearNotice(`preview-load:${failedSelection.artifactId}`), failedSelection = null;
121
+ }
122
+ function notice(message, persistent = !1, key = message) {
123
+ if (destroyed || dismissedNotices.get(key) === message || sessionLost && key !== "studio-restart" || layoutFailed && hasPendingLayout() && !["layout-save", "studio-restart"].includes(key))
124
+ return;
125
+ let node = q(".design-notice");
126
+ clearTimeout(noticeTimer), q("[data-design-retry-layout]")?.remove(), q("[data-design-reload-studio]")?.remove(), activeNotice = { key, message, persistent }, q("[data-design-notice-message]").textContent = message, node.hidden = !1, persistent || (noticeTimer = setTimeout(hideNotice, 6e3));
127
+ }
128
+ function storeDraft() {
129
+ try {
130
+ localStorage.setItem(
131
+ storageKey,
132
+ JSON.stringify({
133
+ revision,
134
+ stateVersion,
135
+ state: !legacyMigrated && (legacyServerPositions || savedDraft?.state?.positions) ? {
136
+ ...state,
137
+ positions: { ...savedDraft?.state?.positions, ...legacyServerPositions }
138
+ } : state,
139
+ unsaved: contentDirty,
140
+ personalPending: dirty && !contentDirty,
141
+ pendingLayout: pendingLayoutDraft ?? (layoutOperations.length ? {
142
+ contentIdentity: payload.contentIdentity,
143
+ operations: layoutOperations.map((operation) => ({
144
+ revision: operation.revision ?? revision,
145
+ boards: operation.replayBoards ?? operation.input().boards
146
+ }))
147
+ } : void 0)
148
+ })
149
+ );
150
+ } catch (error) {
151
+ notice(`The browser draft could not be stored: ${error.message}`, !0, "draft-storage");
152
+ }
153
+ }
154
+ async function jsonRequest(url, init = {}) {
155
+ let response = await fetch(url, { credentials: "same-origin", ...init }), result = await response.json().catch(() => ({}));
156
+ if (!response.ok) {
157
+ let error = new Error(
158
+ result.error?.message ?? result.error ?? result.message ?? `Save failed (${response.status}).`
159
+ );
160
+ throw error.conflict = response.status === 409, error.statusCode = response.status, error;
161
+ }
162
+ return result;
163
+ }
164
+ function scheduleViewContext() {
165
+ if (!options2.viewContextUrl || !stage || !scroll || destroyed || state.view === "canvas" && !canvasAttached || viewContextConflictRevision !== null && revision === viewContextConflictRevision) return;
166
+ viewContextConflictRevision = null, inspectedSelection?.artifactId !== activeArtifactId && (inspectedSelection = null);
167
+ let board = ({ screenId, frameId, variantId }) => ({ screenId, frameId, variantId }), visible = state.view === "canvas" ? visibleEntries().filter((entry) => {
168
+ let bounds = placedBoardBounds(entry);
169
+ if (!bounds) return !1;
170
+ let { width, height } = bounds, { x, y } = canvas.worldToScreen(bounds);
171
+ return x < scroll.clientWidth && y < scroll.clientHeight && x + width * state.zoom > 0 && y + height * state.zoom > 0;
172
+ }) : [entryFor()];
173
+ visible.sort(
174
+ (a, b) => design.screenOrder.indexOf(a.screenId) - design.screenOrder.indexOf(b.screenId)
175
+ );
176
+ let context = {
177
+ revision,
178
+ view: state.view,
179
+ visibleScreens: visible.map(board),
180
+ selected: { ...board(state), planrId: inspectedSelection?.planrId ?? null },
181
+ dirty: contentDirty || reviewDirty || layoutDragDirty || layoutOperations.length > 0
182
+ }, signature = JSON.stringify(context);
183
+ signature !== viewContextSignature && (viewContextSignature = signature, viewContextPending = signature, clearTimeout(viewContextTimer), viewContextTimer = setTimeout(() => {
184
+ viewContextTimer = null, flushViewContext();
185
+ }, 250));
186
+ }
187
+ async function flushViewContext() {
188
+ if (viewContextSaving || !viewContextPending || destroyed) return;
189
+ let body = viewContextPending;
190
+ viewContextPending = null, viewContextSaving = !0;
191
+ try {
192
+ await jsonRequest(options2.viewContextUrl, {
193
+ method: "PUT",
194
+ headers: { "Content-Type": "application/json", "x-openplanr-design": "1" },
195
+ body
196
+ }), clearNotice("view-context");
197
+ } catch (error) {
198
+ viewContextSignature = null, error.conflict ? (viewContextConflictRevision = JSON.parse(body).revision, viewContextPending = null, clearTimeout(viewContextTimer), viewContextTimer = null, clearNotice("view-context"), revision !== viewContextConflictRevision && scheduleViewContext()) : notice(`View context could not be synchronized: ${error.message}`, !1, "view-context");
199
+ } finally {
200
+ viewContextSaving = !1, viewContextPending && viewContextTimer === null && flushViewContext();
201
+ }
202
+ }
203
+ function persist({ content = !1 } = {}) {
204
+ dirty = !0, serial += 1, content && (contentDirty = !0, contentSerial += 1, status("Saving changes", "saving")), storeDraft(), scheduleViewContext(), root.dispatchEvent(
205
+ new CustomEvent("planr:design-state-change", {
206
+ bubbles: !0,
207
+ detail: clone(state)
208
+ })
209
+ ), clearTimeout(saveTimer), saveTimer = setTimeout(flush, 180);
210
+ }
211
+ function flushState() {
212
+ if (clearTimeout(saveTimer), !dirty) return saving;
213
+ let captured = clone(state), capturedSerial = serial, capturedContent = contentDirty, capturedContentSerial = contentSerial;
214
+ return saving = saving.catch(() => {
215
+ }).then(async () => {
216
+ if (!destroyed)
217
+ try {
218
+ !legacyMigrated && (legacyServerPositions || savedDraft?.state?.positions) && (captured.positions = { ...savedDraft?.state?.positions, ...legacyServerPositions });
219
+ let result;
220
+ !capturedContent && typeof options2.savePersonalState == "function" ? result = await options2.savePersonalState(captured, { revision, stateVersion }) : typeof options2.saveState == "function" ? result = await options2.saveState(captured, {
221
+ revision,
222
+ stateVersion,
223
+ personal: !capturedContent
224
+ }) : options2.stateUrl && (result = await jsonRequest(options2.stateUrl, {
225
+ method: "PUT",
226
+ headers: { "Content-Type": "application/json" },
227
+ body: JSON.stringify({ state: captured, revision, stateVersion })
228
+ })), result?.stateVersion !== void 0 && (stateVersion = result.stateVersion), result?.revision !== void 0 && (revision = result.revision), clearNotice("personal-save"), capturedContent && clearNotice("content-save"), capturedSerial === serial && (dirty = !1), capturedContent && capturedContentSerial === contentSerial && (contentDirty = !1, !reviewDirty && !layoutOperations.length && status(
229
+ options2.stateUrl || options2.saveState ? "All changes saved" : "Saved in this browser"
230
+ )), storeDraft(), scheduleViewContext();
231
+ } catch (error) {
232
+ capturedContent && status(
233
+ error.conflict ? "Save conflict" : "Disconnected · unsaved",
234
+ error.conflict ? "failed" : "disconnected"
235
+ ), notice(
236
+ capturedContent ? error.conflict ? "This review changed in another window. Your unsaved edits remain in this browser. Reload to read the saved review before applying them again." : "Changes could not be saved. Your draft remains in this browser; reconnect to retry." : "Canvas preferences could not be synchronized. Your arrangement remains saved in this browser.",
237
+ !0,
238
+ capturedContent ? "content-save" : "personal-save"
239
+ ), storeDraft();
240
+ }
241
+ }), saving;
242
+ }
243
+ function layoutUrl() {
244
+ return options2.layoutUrl ?? options2.stateUrl?.replace(/design-state$/, "design-layout");
245
+ }
246
+ function canSaveLayout() {
247
+ return !!(options2.saveLayout || layoutUrl());
248
+ }
249
+ function hasPendingLayout() {
250
+ return !!(pendingLayoutDraft || layoutOperations.length || layoutSaving || layoutDragDirty);
251
+ }
252
+ function studioRestarted() {
253
+ sessionLost = !0, storeDraft(), status(hasPendingLayout() ? "Layout unsaved · reload required" : "Reload required", "failed"), notice(
254
+ "Studio restarted. Reload to reconnect; pending board moves remain in this browser.",
255
+ !0,
256
+ "studio-restart"
257
+ );
258
+ let reload = document.createElement("button");
259
+ reload.type = "button", reload.dataset.designReloadStudio = "", reload.textContent = "Reload", reload.addEventListener("click", () => {
260
+ storeDraft(), window.location.reload();
261
+ }), q(".design-notice").append(reload);
262
+ }
263
+ function applyBoardMoves(boards) {
264
+ for (let [key, point2] of Object.entries(boards))
265
+ layout.boards ??= {}, point2 === null ? delete layout.boards[key] : layout.boards[key] = { ...layout.boards[key], ...point2 };
266
+ }
267
+ function restorePendingLayout() {
268
+ if (!pendingLayoutDraft) return;
269
+ let pending = pendingLayoutDraft;
270
+ if (pendingLayoutDraft = null, layoutBlocked || !payload.contentIdentity || pending.contentIdentity !== payload.contentIdentity) {
271
+ storeDraft(), notice(
272
+ "Pending board moves were dropped because authored content changed. Review the current design before moving boards again.",
273
+ !0,
274
+ "layout-dropped"
275
+ );
276
+ return;
277
+ }
278
+ if (!(Array.isArray(pending.operations) && pending.operations.every(
279
+ (operation) => typeof operation?.revision == "string" && operation.boards && typeof operation.boards == "object" && !Array.isArray(operation.boards) && Object.entries(operation.boards).every(
280
+ ([key, point2]) => entries.some((entry) => boardKey(entry) === key) && (point2 === null || Number.isFinite(point2?.x) && Number.isFinite(point2?.y) && Math.abs(point2.x) <= 1e7 && Math.abs(point2.y) <= 1e7)
281
+ )
282
+ ))) {
283
+ storeDraft(), notice(
284
+ "Pending board moves could not be recovered because the browser draft contains invalid positions.",
285
+ !0,
286
+ "layout-dropped"
287
+ );
288
+ return;
289
+ }
290
+ for (let operation of pending.operations) {
291
+ let boards = clone(operation.boards);
292
+ applyBoardMoves(boards), layoutOperations.push({ revision, input: () => ({ boards }) });
293
+ }
294
+ storeDraft(), render(), layoutOperations.length && status("Recovering layout", "saving");
295
+ }
296
+ function newRevisionNotice() {
297
+ notice(
298
+ "A new design revision is ready. Save or recover this browser’s unsaved changes before reloading.",
299
+ !0,
300
+ "new-revision"
301
+ );
302
+ }
303
+ async function rebaseLayout({ announce = !0, expectedRevision = revision } = {}) {
304
+ let url = options2.statusUrl ?? options2.stateUrl?.replace(/design-state$/, "design-status");
305
+ if (!url) throw new Error("Layout conflict recovery requires a design status URL.");
306
+ let current = await jsonRequest(url);
307
+ if (revision !== expectedRevision) return !0;
308
+ if (typeof current.revision != "string" || typeof current.contentIdentity != "string")
309
+ throw new Error("Design status did not return a revision and authored content identity.");
310
+ if (current.contentIdentity !== payload.contentIdentity)
311
+ return layoutBlocked = !0, layoutOperations.length = 0, layoutFailed = !1, storeDraft(), status("Design changed · reload required", "failed"), notice(
312
+ "A new design revision is ready. Pending board moves were dropped because authored content changed. Reload to read the current design.",
313
+ !0,
314
+ "new-revision"
315
+ ), !1;
316
+ revision = current.revision, layout = clone(current.layout ?? {});
317
+ let discardedRestores = layoutOperations.some((operation) => operation.restore);
318
+ for (let index2 = layoutOperations.length - 1; index2 >= 0; index2--)
319
+ layoutOperations[index2].restore && layoutOperations.splice(index2, 1);
320
+ for (let moves of [undoMoves, redoMoves])
321
+ for (let index2 = moves.length - 1; index2 >= 0; index2--)
322
+ moves[index2].afterRevision !== null && moves.splice(index2, 1);
323
+ for (let operation of layoutOperations) {
324
+ operation.revision = revision;
325
+ let boards = operation.input().boards;
326
+ if (operation.move?.applied && (operation.move.before = clone(layout)), applyBoardMoves(boards), operation.move?.applied) operation.move.after = clone(layout);
327
+ else if (operation.move) {
328
+ let move = operation.move, movedBoard = move.after.boards?.[move.key];
329
+ move.before = clone(layout), move.after = clone(layout), movedBoard ? move.after.boards = { ...move.after.boards, [move.key]: movedBoard } : move.after.boards && delete move.after.boards[move.key];
330
+ }
331
+ }
332
+ return storeDraft(), render(), announce && notice(
333
+ `The layout changed in another window. Pending board moves were rebased onto the current revision.${discardedRestores ? " Stale undo and redo operations were dropped." : ""}`,
334
+ !0,
335
+ "layout-rebase"
336
+ ), !0;
337
+ }
338
+ function flushLayout() {
339
+ return layoutSaving || layoutBlocked || sessionLost || !layoutOperations.length || (layoutSaving = !0, status("Saving layout", "saving"), saving = saving.then(async () => {
340
+ let rebased = !1;
341
+ try {
342
+ for (; layoutOperations.length; ) {
343
+ let operation = layoutOperations[0], input = { revision, ...operation.input() };
344
+ activeLayoutOperation = operation, operation.attempted = !0, operation.appliedAtSave = operation.move?.applied, status("Saving layout", "saving");
345
+ let result;
346
+ try {
347
+ if (typeof options2.saveLayout == "function") result = await options2.saveLayout(input);
348
+ else if (layoutUrl())
349
+ result = await jsonRequest(layoutUrl(), {
350
+ method: "PUT",
351
+ headers: { "Content-Type": "application/json", "x-openplanr-design": "1" },
352
+ body: JSON.stringify(input)
353
+ });
354
+ else
355
+ throw new Error("Layout save requires a studio layout URL or saveLayout callback.");
356
+ } catch (error) {
357
+ if (activeLayoutOperation = null, operation.canceled = !1, !error.conflict || rebased) throw error;
358
+ if (rebased = !0, !await rebaseLayout()) return;
359
+ continue;
360
+ }
361
+ if (typeof result.revision != "string")
362
+ throw new Error("Layout save did not return a design revision.");
363
+ operation.saved?.(revision, result.revision), revision = result.revision, layoutOperations.shift(), activeLayoutOperation = null, storeDraft(), scheduleViewContext();
364
+ }
365
+ layoutFailed = !1, clearNotice("layout-save");
366
+ } catch (error) {
367
+ if (layoutFailed = !0, storeDraft(), error.statusCode === 404) {
368
+ studioRestarted();
369
+ return;
370
+ }
371
+ status(error.conflict ? "Layout conflict" : "Layout unsaved", "failed"), notice(`Board layout could not be saved: ${error.message}`, !0, "layout-save");
372
+ let retry = document.createElement("button");
373
+ retry.type = "button", retry.dataset.designRetryLayout = "", retry.textContent = "Retry", retry.addEventListener("click", () => {
374
+ flushLayout();
375
+ }), q(".design-notice").append(retry);
376
+ } finally {
377
+ activeLayoutOperation = null, layoutSaving = !1, !hasPendingLayout() && !contentDirty && !reviewDirty && !layoutBlocked && !sessionLost && status("All changes saved");
378
+ }
379
+ })), saving;
380
+ }
381
+ function boardKey(entry) {
382
+ return `${entry.screenId}/${entry.frameId}/${entry.variantId}`;
383
+ }
384
+ function queueMove(move) {
385
+ let point2 = (value) => {
386
+ let board = value.boards?.[move.key];
387
+ return board ? { x: board.x, y: board.y } : null;
388
+ }, operation = {
389
+ revision,
390
+ move,
391
+ boards: { [move.key]: point2(move.after) },
392
+ input: () => ({ boards: operation.boards }),
393
+ saved(previous, next) {
394
+ if (operation.appliedAtSave && (move.beforeRevision = previous, move.afterRevision = next), operation.canceled) {
395
+ let boards = { [move.key]: point2(layout) };
396
+ layoutOperations.push({ input: () => ({ boards }) });
397
+ }
398
+ }
399
+ };
400
+ layoutOperations.push(operation), storeDraft(), scheduleViewContext(), flushLayout();
401
+ }
402
+ function completeMove(entry, before) {
403
+ let after = clone(layout);
404
+ if (JSON.stringify(before) === JSON.stringify(after)) return;
405
+ let move = {
406
+ key: boardKey(entry),
407
+ before,
408
+ after,
409
+ beforeRevision: null,
410
+ afterRevision: null,
411
+ applied: !0
412
+ };
413
+ undoMoves.push(move), redoMoves.length = 0, storeDraft(), canSaveLayout() && queueMove(move);
414
+ }
415
+ function restoreMove(redo) {
416
+ let source = redo ? redoMoves : undoMoves, target = redo ? undoMoves : redoMoves, move = source.pop();
417
+ if (move && (move.applied = redo, layout = clone(redo ? move.after : move.before), target.push(move), render(), !!canSaveLayout())) {
418
+ if (move.beforeRevision === null || move.afterRevision === null) {
419
+ let pending = layoutOperations.find((operation) => operation.move === move);
420
+ if (pending) {
421
+ let board = layout.boards?.[move.key];
422
+ pending.boards = { [move.key]: board ? { x: board.x, y: board.y } : null };
423
+ }
424
+ pending === activeLayoutOperation && pending ? pending.canceled = !redo : pending?.attempted || (pending && layoutOperations.splice(layoutOperations.indexOf(pending), 1), redo && queueMove(move)), !layoutOperations.length && !layoutSaving && (layoutFailed = !1, clearNotice("layout-save"), !contentDirty && !reviewDirty && status("All changes saved")), storeDraft(), scheduleViewContext();
425
+ return;
426
+ }
427
+ layoutOperations.push({
428
+ revision,
429
+ restore: !0,
430
+ replayBoards: {
431
+ [move.key]: layout.boards?.[move.key] ? {
432
+ x: layout.boards[move.key].x,
433
+ y: layout.boards[move.key].y
434
+ } : null
435
+ },
436
+ input: () => ({ restoreRevision: redo ? move.afterRevision : move.beforeRevision })
437
+ }), storeDraft(), scheduleViewContext(), flushLayout();
438
+ }
439
+ }
440
+ function migrateLegacyPositions() {
441
+ if (layoutBlocked) return;
442
+ if (!legacyServerPositions && !savedDraft?.state?.positions) {
443
+ legacyMigrated = !0;
444
+ return;
445
+ }
446
+ let boards = {};
447
+ for (let positions of [legacyServerPositions, savedDraft?.state?.positions])
448
+ for (let entry of entries) {
449
+ let key = boardKey(entry), point2 = positions?.[entry.artifactId];
450
+ layout.boards?.[key] !== void 0 || boards[key] !== void 0 || !point2 || !Number.isFinite(point2.x) || !Number.isFinite(point2.y) || Math.abs(point2.x) > 1e7 || Math.abs(point2.y) > 1e7 || (boards[key] = { x: Math.round(point2.x), y: Math.round(point2.y) });
451
+ }
452
+ Object.keys(boards).length && (layout.boards = { ...layout.boards, ...boards }), render();
453
+ let migrated = () => {
454
+ legacyMigrated = !0, legacyServerPositions = void 0, storeDraft(), options2.stateUrl && persist();
455
+ };
456
+ Object.keys(boards).length && canSaveLayout() ? (layoutOperations.push({ revision, input: () => ({ boards }), saved: migrated }), storeDraft()) : canSaveLayout() && migrated();
457
+ }
458
+ function reviewUrl() {
459
+ return options2.reviewUrl ?? (options2.stateUrl ? options2.stateUrl.replace(/design-state$/, "review") : null);
460
+ }
461
+ function storeReviewDraft() {
462
+ if (latestReview)
463
+ try {
464
+ localStorage.setItem(
465
+ `${storageKey}.review`,
466
+ JSON.stringify({ review: latestReview, unsaved: reviewDirty })
467
+ );
468
+ } catch {
469
+ }
470
+ }
471
+ function flushReview() {
472
+ if (clearTimeout(reviewTimer), !reviewDirty || !latestReview) return reviewSaving;
473
+ let captured = clone(latestReview), capturedSerial = reviewSerial;
474
+ return reviewSaving = reviewSaving.catch(() => {
475
+ }).then(async () => {
476
+ try {
477
+ reviewUrl() && await jsonRequest(reviewUrl(), {
478
+ method: "PUT",
479
+ headers: { "Content-Type": "application/json" },
480
+ body: JSON.stringify({ review: captured })
481
+ }), clearNotice("review-save"), capturedSerial === reviewSerial && (reviewDirty = !1, !contentDirty && !layoutOperations.length && status(reviewUrl() ? "All changes saved" : "Saved in this browser")), storeReviewDraft(), scheduleViewContext();
482
+ } catch (error) {
483
+ status("Comments unsaved", "disconnected"), notice(
484
+ `Comments could not be saved: ${error.message} Your draft remains in this browser.`,
485
+ !0,
486
+ "review-save"
487
+ ), storeReviewDraft();
488
+ }
489
+ }), reviewSaving;
490
+ }
491
+ function flush() {
492
+ return Promise.all([flushState(), flushLayout(), flushReview()]);
493
+ }
494
+ function entryFor(screenId = state.screenId, variantId = state.variantId, frameId = state.frameId) {
495
+ return entries.find(
496
+ (entry) => entry.screenId === screenId && entry.variantId === variantId && entry.frameId === frameId
497
+ );
498
+ }
499
+ function viewportFor(entry) {
500
+ let frame = design.frames.find(({ id }) => id === entry.frameId);
501
+ return stage.getState().artifacts.find(({ id }) => id === entry.artifactId)?.viewport ?? designFrameViewport(design, frame);
502
+ }
503
+ function visibleEntries() {
504
+ return entries.filter(
505
+ (entry) => (state.view === "canvas" && state.compare || entry.variantId === state.variantId) && (state.view === "canvas" || entry.screenId === state.screenId && entry.frameId === state.frameId)
506
+ );
507
+ }
508
+ function syncFrameGestures(frame, panel) {
509
+ frame?.__openPlanrBridge?.setViewportGestures?.(state.view === "canvas" && !panel.hidden);
510
+ }
511
+ function updatePan() {
512
+ root.dataset.designPanActive = String(state.view === "canvas" && (panEnabled || spacePan)), root.dataset.designTool = activeTool, canvas.setPointerMode(root.dataset.designInspect === "true" ? "none" : "pan"), q("[data-design-pan]").setAttribute("aria-pressed", String(activeTool === "pan")), q('[data-planr-mode="interact"]').setAttribute(
513
+ "aria-pressed",
514
+ String(activeTool === "interact")
515
+ ), q('[data-planr-mode="comment"]').setAttribute(
516
+ "aria-pressed",
517
+ String(activeTool === "annotate")
518
+ ), q("[data-design-inspect-mode]")?.setAttribute("aria-pressed", String(activeTool === "inspect"));
519
+ for (let [id, panel] of panels) {
520
+ let frame = stage.getFrame(id);
521
+ if (frame) {
522
+ let tabIndex = activeTool === "interact" && !panel.hidden && stage.getState().status === "ready" ? 0 : -1;
523
+ frame.tabIndex !== tabIndex && (frame.tabIndex = tabIndex), syncFrameGestures(frame, panel);
524
+ }
525
+ }
526
+ }
527
+ function finishGesture() {
528
+ if (!gesture) return;
529
+ let completed = gesture, pointerId = completed.pointerId;
530
+ gesture = null, layoutDragDirty = !1;
531
+ try {
532
+ scroll.hasPointerCapture?.(pointerId) && scroll.releasePointerCapture(pointerId);
533
+ } catch {
534
+ }
535
+ updatePan(), completed.mode === "artboard" ? completeMove(
536
+ entries.find((entry) => entry.artifactId === completed.artifactId),
537
+ completed.before
538
+ ) : persist(), scheduleViewContext();
539
+ }
540
+ function noteFramesUsedWithComments() {
541
+ if (!(state.view !== "canvas" || activeTool !== "annotate"))
542
+ for (let id of stage.getLoadedArtifactIds()) framesUsedWithComments.add(id);
543
+ }
544
+ function reloadFrameUsedWithComments(artifactId) {
545
+ if (!framesUsedWithComments.delete(artifactId) || !stage.getLoadedArtifactIds().includes(artifactId)) return;
546
+ let reload = ++frameReload;
547
+ root.dataset.designScreenLoading = "true";
548
+ let settle = () => destroyed || reload !== frameReload ? !1 : (pendingScreen || delete root.dataset.designScreenLoading, updateFramePlaceholders(), !0);
549
+ stage.retryFrames([artifactId]).then(settle, (error) => {
550
+ !settle() || error.name === "AbortError" || notice(
551
+ "This screen could not reload. Select it again to retry; your review is preserved.",
552
+ !0,
553
+ `preview-load:${artifactId}`
554
+ );
555
+ });
556
+ }
557
+ function setTool(tool) {
558
+ if (["interact", "annotate", "pan", "inspect"].includes(tool) && !(tool === "pan" && state.view !== "canvas") && !(tool === "annotate" && root.hasAttribute("data-planr-room-comments-paused"))) {
559
+ if (finishWalkthroughTransition(), finishGesture(), canvasAttached && canvasPointers.size) {
560
+ saveViewport();
561
+ for (let pointerId of canvasPointers)
562
+ scroll.hasPointerCapture?.(pointerId) && scroll.releasePointerCapture(pointerId);
563
+ canvasPointers.clear();
564
+ }
565
+ spacePan = !1, spaceReturnTool = null, activeTool = tool, panEnabled = tool === "pan", previousReviewMode = tool === "annotate" ? "comment" : "interact", changingTool = !0;
566
+ try {
567
+ stage.dispatch({ type: "set-review-mode", reviewMode: previousReviewMode });
568
+ } finally {
569
+ changingTool = !1;
570
+ }
571
+ noteFramesUsedWithComments(), root.dispatchEvent(new CustomEvent("planr:design-tool-change", { detail: { tool } })), updatePan();
572
+ }
573
+ }
574
+ function releaseTemporaryPan() {
575
+ let previous = spaceReturnTool;
576
+ finishGesture(), spacePan && setTool(previous || "interact");
577
+ }
578
+ function readCanvasCamera() {
579
+ if (!canvasAttached || state.view !== "canvas") return;
580
+ let { x, y, scale } = cameraToScreenTranslate(canvas.getCamera());
581
+ state.camera = { x, y }, state.zoom = scale;
582
+ }
583
+ function applyCanvasCamera() {
584
+ canvas.setCamera(cameraFromScreenTranslate({ ...state.camera, scale: state.zoom })), saveViewport();
585
+ }
586
+ function saveViewport() {
587
+ readCanvasCamera(), state.viewports[state.view] = {
588
+ x: state.camera.x,
589
+ y: state.camera.y,
590
+ zoom: state.zoom
591
+ };
592
+ }
593
+ function restoreViewport(view) {
594
+ let viewport = state.viewports[view];
595
+ return viewport ? (state.camera = { x: viewport.x, y: viewport.y }, state.zoom = viewport.zoom, canvasAttached && view === "canvas" && applyCanvasCamera(), !0) : !1;
596
+ }
597
+ function switchView(view, { save = !0 } = {}) {
598
+ if (!views.includes(view)) throw new TypeError(`Unknown design view: ${view}`);
599
+ if (view === state.view) return;
600
+ let queued = pendingScreen ? null : queuedSelection;
601
+ finishWalkthroughTransition(), closeDesignNotes(), saveViewport(), state.view = view;
602
+ let restored = view === "canvas" && restoreViewport(view);
603
+ setTool(view === "canvas" && activeTool === "annotate" ? "annotate" : "interact"), root.dispatchEvent(new CustomEvent("planr:design-view-change", { detail: { view } })), render(), restored || fit({ save: !1 }), queued && selectEntry(queued.entry, { ...queued.settings, save: !1 }), save && (persist(), recordLocation());
604
+ }
605
+ function syncCanvasChrome() {
606
+ if (!canvasAttached) return;
607
+ let stageRect = scroll.getBoundingClientRect();
608
+ for (let [id, selector, open] of [
609
+ ["toolbar", ".design-toolbar", !0],
610
+ ["tools", ".design-canvas-tools", !0],
611
+ ["context", ".design-stage-context", !0],
612
+ ["navigator", ".design-navigator", state.navOpen],
613
+ ["review", ".planr-review-rail", stage.getState().railOpen]
614
+ ]) {
615
+ let rect2 = q(selector).getBoundingClientRect(), x = Math.max(0, rect2.left - stageRect.left), y = Math.max(0, rect2.top - stageRect.top), width = Math.max(0, Math.min(stageRect.width, rect2.right - stageRect.left) - x), height = Math.max(0, Math.min(stageRect.height, rect2.bottom - stageRect.top) - y), edge = width >= height ? y + height / 2 < stageRect.height / 2 ? "top" : "bottom" : x + width / 2 < stageRect.width / 2 ? "left" : "right";
616
+ canvas.setChrome({
617
+ id,
618
+ edge,
619
+ rect: { x, y, width, height },
620
+ open: open && width > 0 && height > 0
621
+ });
622
+ }
623
+ }
624
+ function placedBoardBounds(entry) {
625
+ let panel = panels.get(entry.artifactId);
626
+ if (!panel || panel.hidden) return null;
627
+ let bounds = {
628
+ ...viewportFor(entry),
629
+ x: parseFloat(panel.style.left),
630
+ y: parseFloat(panel.style.top)
631
+ };
632
+ return Object.values(bounds).every(Number.isFinite) ? bounds : null;
633
+ }
634
+ function syncCanvasNodes() {
635
+ if (!canvasAttached) return;
636
+ grid.style.setProperty("--design-zoom", String(state.zoom));
637
+ let placed = visibleEntries().flatMap((entry) => {
638
+ let bounds = placedBoardBounds(entry);
639
+ return bounds ? [{ entry, bounds }] : [];
640
+ }), nodes = placed.flatMap(({ entry, bounds }) => [
641
+ { id: entry.artifactId, bounds },
642
+ {
643
+ id: `label:${entry.artifactId}`,
644
+ bounds: { ...bounds, y: bounds.y - 34 / state.zoom, height: 28 / state.zoom }
645
+ }
646
+ ]);
647
+ sectionHeadings.forEach((heading, index2) => {
648
+ let section = layout.sections[index2];
649
+ heading.hidden = !1, heading.style.fontSize = `${clamp2(28 * state.zoom, 14, 28) / state.zoom}px`, section.maxW && (heading.style.maxWidth = `${section.maxW / state.zoom}px`);
650
+ let below = placed.filter(
651
+ ({ bounds: board }) => board.y >= section.y && section.x < board.x + board.width && section.x + heading.offsetWidth > board.x
652
+ ), bottom = Math.min(section.y, ...below.map(({ bounds: bounds2 }) => bounds2.y - 42 / state.zoom));
653
+ heading.style.top = `${bottom - heading.offsetHeight}px`;
654
+ let bounds = {
655
+ x: parseFloat(heading.style.left),
656
+ y: parseFloat(heading.style.top),
657
+ width: heading.offsetWidth,
658
+ height: heading.offsetHeight
659
+ };
660
+ heading.hidden = nodes.some(
661
+ ({ bounds: other }) => bounds.x < other.x + other.width && bounds.x + bounds.width > other.x && bounds.y < other.y + other.height && bounds.y + bounds.height > other.y
662
+ ), !heading.hidden && Object.values(bounds).every(Number.isFinite) && nodes.push({ id: `section:${section.id}`, bounds });
663
+ }), sectionZoom = state.zoom, canvas.setNodes(nodes);
664
+ for (let { entry } of placed) {
665
+ canvas.registerNodeElement(entry.artifactId, panels.get(entry.artifactId));
666
+ let label = panels.get(entry.artifactId).querySelector(".design-artboard-label"), title = label.querySelector("strong"), size4 = label.querySelector("span");
667
+ label.title = `${title.textContent} · ${size4.textContent}`, size4.hidden = !1, size4.hidden = title.scrollWidth > title.clientWidth || size4.scrollWidth > size4.clientWidth;
668
+ }
669
+ canvas.setSelection(
670
+ placed.some(({ entry }) => entry.artifactId === activeArtifactId) ? [activeArtifactId] : []
671
+ );
672
+ }
673
+ function syncCanvas() {
674
+ if (state.view !== "canvas") {
675
+ canvasAttached && canvas.detach(), canvasPointers.clear(), canvasAttached = !1;
676
+ return;
677
+ }
678
+ canvasAttached || (canvas.attach({ stage: scroll, world: grid }), canvasAttached = !0, applyCanvasCamera()), syncCanvasNodes(), syncCanvasChrome();
679
+ }
680
+ function centerEntry(entry) {
681
+ state.view !== "canvas" || !placedBoardBounds(entry) || fit({ selection: entry, save: !1, maxZoom: state.zoom });
682
+ }
683
+ function updateDesignNotes() {
684
+ let notes = q("[data-design-notes]");
685
+ if (!notes) return;
686
+ notes.querySelector(":scope > summary").setAttribute("aria-expanded", String(notes.open));
687
+ for (let button of qa("[data-design-note]"))
688
+ button.setAttribute(
689
+ "aria-expanded",
690
+ String(notes.open && button.dataset.designNote === noteScreenId)
691
+ );
692
+ if (!notes.open) return;
693
+ let panel = q(".design-guidance-panel"), anchor = notes.querySelector(":scope > summary").getBoundingClientRect(), width = panel.getBoundingClientRect().width || Math.min(340, window.innerWidth - 24), top = clamp2(anchor.bottom + 7, 12, Math.max(12, window.innerHeight - 100));
694
+ panel.style.left = `${clamp2(anchor.right - width, 12, Math.max(12, window.innerWidth - width - 12))}px`, panel.style.top = `${top}px`, panel.style.maxHeight = `${Math.min(520, Math.max(60, window.innerHeight - top - 12))}px`;
695
+ }
696
+ function closeDesignNotes({ restoreFocus = !1 } = {}) {
697
+ let notes = q("[data-design-notes]");
698
+ notes?.open && (notes.open = !1, noteScreenId = null, updateDesignNotes(), restoreFocus && (notesReturnFocus?.isConnected ? notesReturnFocus : notes.querySelector(":scope > summary"))?.focus({ preventScroll: !0 }), notesReturnFocus = null);
699
+ }
700
+ function openDesignNote(screenId, trigger) {
701
+ let notes = q("[data-design-notes]");
702
+ if (!notes) return;
703
+ if (notes.open && noteScreenId === screenId) {
704
+ closeDesignNotes();
705
+ return;
706
+ }
707
+ notesReturnFocus = trigger, noteScreenId = screenId, notes.open = !0;
708
+ let target = null;
709
+ for (let item of qa("[data-design-note-screen]")) {
710
+ let selected = item.dataset.designNoteScreen === screenId;
711
+ item.open = selected, item.dataset.active = String(selected), selected && (target = item);
712
+ }
713
+ updateDesignNotes();
714
+ let panel = q(".design-guidance-panel");
715
+ target && (panel.scrollTop = Math.max(0, target.offsetTop - panel.querySelector("header").offsetHeight));
716
+ }
717
+ function render() {
718
+ if (!(!stage || rendering || destroyed)) {
719
+ rendering = !0;
720
+ try {
721
+ window.innerWidth <= 680 ? root.style.setProperty(
722
+ "--planr-toolbar-height",
723
+ `${q(".design-toolbar").getBoundingClientRect().height}px`
724
+ ) : root.style.removeProperty("--planr-toolbar-height");
725
+ let screen = design.screens.find(({ id }) => id === state.screenId), variant = design.variants.find(({ id }) => id === state.variantId), stageState = stage.getState();
726
+ root.dataset.designView = state.view, root.dataset.designNavOpen = String(state.navOpen);
727
+ let navigator2 = q(".design-navigator");
728
+ navigator2.inert = !state.navOpen, navigator2.setAttribute("aria-hidden", String(!state.navOpen));
729
+ for (let toggle of qa("[data-design-toggle-nav]"))
730
+ toggle.setAttribute("aria-expanded", String(state.navOpen));
731
+ for (let button of qa("[data-design-view]"))
732
+ button.tagName === "BUTTON" && button.setAttribute("aria-pressed", String(button.dataset.designView === state.view));
733
+ for (let button of qa("[data-design-screen]"))
734
+ button.setAttribute(
735
+ "aria-current",
736
+ button.dataset.designScreen === state.screenId ? "page" : "false"
737
+ );
738
+ q("[data-design-variant]").value = failedSelection?.variantId ?? state.variantId, q("[data-design-frame]").value = failedSelection?.frameId ?? state.frameId, q("[data-design-compare]") && (q("[data-design-compare]").checked = state.compare), q("[data-design-screen-title]").textContent = state.view === "canvas" ? design.title : screen.title, q("[data-design-stage-description]").textContent = state.view === "canvas" ? `${design.screenOrder.length} screens · ${design.frames.length} responsive ${design.frames.length === 1 ? "frame" : "frames"}${state.compare ? ` · ${readyVariants.length} directions` : ""}` : state.view === "prototype" ? "Explore the working journey" : "A guided journey through the design", q("[data-design-direction-label]").textContent = variant.label;
739
+ let chosen = state.selectedVariant === state.variantId && state.preferences.selected.includes(state.variantId);
740
+ q("[data-design-selected-direction]").textContent = chosen ? "Selected" : "", q("[data-design-select-direction]").disabled = chosen, q("[data-design-select-direction]").textContent = chosen ? "Selected direction" : "Use this direction";
741
+ for (let button of qa("[data-design-rating]")) {
742
+ let selected = Number(button.dataset.designRating) <= (state.ratings[state.variantId] ?? 0);
743
+ button.setAttribute("aria-pressed", String(selected)), button.textContent = selected ? "★" : "☆";
744
+ }
745
+ document.activeElement !== q("[data-design-remix]") && (q("[data-design-remix]").value = state.remix[state.variantId] ?? ""), q("[data-design-pan]").hidden = state.view !== "canvas";
746
+ for (let control of qa("[data-design-zoom]")) control.hidden = state.view !== "canvas";
747
+ q("[data-design-actual-size]").hidden = state.view === "canvas", q("[data-design-actual-size]").setAttribute(
748
+ "aria-pressed",
749
+ String(state.inspectionScale === "actual")
750
+ ), root.dataset.designInspectionScale = state.inspectionScale, q(".design-canvas-tools").setAttribute(
751
+ "aria-label",
752
+ state.view === "canvas" ? "Canvas controls" : "Screen controls"
753
+ );
754
+ let fitControl = q("[data-design-fit]");
755
+ fitControl.textContent = state.view === "canvas" ? "Fit" : "Fit screen", fitControl.setAttribute(
756
+ "aria-label",
757
+ state.view === "canvas" ? "Fit all visible artboards" : "Fit screen"
758
+ );
759
+ let comparison = q("[data-design-compare]");
760
+ comparison && (comparison.closest("label").hidden = state.view !== "canvas");
761
+ let caption = q(".design-walkthrough-caption");
762
+ caption.hidden = state.view !== "walkthrough", q("[data-design-step]").textContent = `Step ${design.screenOrder.indexOf(state.screenId) + 1} of ${design.screenOrder.length}`, q("[data-design-narrative-title]").textContent = screen.title, q("[data-design-narrative]").textContent = screen.description ?? design.flows?.find((flow) => flow.screens.includes(screen.id))?.title ?? design.brief?.text ?? "", q('[data-design-step-change="-1"]').disabled = requestedScreen() === design.screenOrder[0], q('[data-design-step-change="1"]').disabled = requestedScreen() === design.screenOrder.at(-1);
763
+ for (let item of qa("[data-design-note-screen]"))
764
+ item.dataset.active = String(item.dataset.designNoteScreen === state.screenId);
765
+ for (let heading of sectionHeadings) heading.hidden = state.view !== "canvas";
766
+ let visible = visibleEntries(), visibleIds = new Set(visible.map(({ artifactId }) => artifactId));
767
+ for (let entry of entries) {
768
+ let panel = panels.get(entry.artifactId), isVisible = visibleIds.has(entry.artifactId), outgoing = walkthroughTransition?.from === entry.artifactId;
769
+ panel.hidden = !isVisible && !outgoing, panel.dataset.designActive = String(entry.artifactId === activeArtifactId);
770
+ let frameElement = stage.getFrame(entry.artifactId), layer = panel.querySelector("[data-planr-annotation-layer]"), ready = stageState.status === "ready" && (!stage.frameBudget || frameElement.dataset.planrFrameState === "ready");
771
+ if (frameElement.tabIndex = isVisible && ready && stageState.reviewMode === "interact" ? 0 : -1, layer.tabIndex = isVisible && ready && stageState.reviewMode === "comment" ? 0 : -1, layer.setAttribute("aria-disabled", String(layer.tabIndex < 0)), !isVisible && !outgoing) continue;
772
+ let placement = boardPlacement(design, entry, {
773
+ layout,
774
+ compare: state.compare,
775
+ variantId: state.variantId
776
+ }), { x, y } = placement;
777
+ state.view !== "canvas" && (x = 0, y = 0), panel.style.left = `${x}px`, panel.style.top = `${y}px`, panel.querySelector(".design-artboard-drag strong").textContent = placement.title;
778
+ }
779
+ grid.style.width = "1px", grid.style.height = "1px", surface.style.width = "100%", surface.style.height = "100%", syncCanvas(), updatePan(), renderCamera(), chrome?.update({ view: state.view, navOpen: state.navOpen, reviewOpen: stageState.railOpen }), state.view !== "canvas" && activeArtifactId && reloadFrameUsedWithComments(activeArtifactId), root.dispatchEvent(new CustomEvent("planr:design-render", { detail: clone(state) }));
780
+ } finally {
781
+ rendering = !1;
782
+ }
783
+ }
784
+ }
785
+ function scheduleCamera() {
786
+ cameraFrame !== null || destroyed || (cameraFrame = requestAnimationFrame(() => {
787
+ cameraFrame = null, renderCamera();
788
+ }));
789
+ }
790
+ function renderCamera() {
791
+ if (!stage || destroyed) return;
792
+ if (cameraFrame !== null && (cancelAnimationFrame(cameraFrame), cameraFrame = null), readCanvasCamera(), state.view !== "canvas") {
793
+ let frame = viewportFor(entryFor()), inset = window.innerWidth <= 680 ? 12 : 24;
794
+ state.zoom = state.inspectionScale === "actual" ? 1 : Math.max(
795
+ 1e-3,
796
+ Math.min(
797
+ 1,
798
+ Math.max(1, scroll.clientWidth - inset * 2) / frame.width,
799
+ Math.max(1, scroll.clientHeight - inset * 2) / frame.height
800
+ )
801
+ ), state.camera = {
802
+ x: state.inspectionScale === "actual" ? inset : (scroll.clientWidth - frame.width * state.zoom) / 2,
803
+ y: state.inspectionScale === "actual" ? inset : (scroll.clientHeight - frame.height * state.zoom) / 2
804
+ }, state.inspectionScale === "actual" && (surface.style.width = `${Math.max(scroll.clientWidth, frame.width + inset * 2)}px`, surface.style.height = `${Math.max(scroll.clientHeight, frame.height + inset * 2)}px`), saveViewport();
805
+ }
806
+ let zoom = String(state.zoom);
807
+ grid.style.getPropertyValue("--design-zoom") !== zoom && grid.style.setProperty("--design-zoom", zoom), state.view === "canvas" && sectionZoom !== state.zoom && syncCanvasNodes(), state.view !== "canvas" && (grid.style.transform = `translate(${state.camera.x}px, ${state.camera.y}px) scale(${state.zoom})`);
808
+ let active = entries.find((entry) => entry.artifactId === activeArtifactId), bounds = active ? placedBoardBounds(active) : null;
809
+ if (selectionOutline && (selectionOutline.hidden = state.view !== "canvas" || !bounds, !selectionOutline.hidden)) {
810
+ let frame = design.frames.find((item) => item.id === active.frameId), ratio = window.devicePixelRatio || 1, pixel = (value) => Math.round(value * ratio) / ratio, x = pixel(state.camera.x + bounds.x * state.zoom), y = pixel(state.camera.y + bounds.y * state.zoom);
811
+ selectionOutline.style.transform = `translate3d(${x - 3}px, ${y - 3}px, 0)`, selectionOutline.style.width = `${pixel(frame.width * state.zoom) + 6}px`, selectionOutline.style.height = `${pixel(viewportFor(active).height * state.zoom) + 6}px`;
812
+ }
813
+ let label = `${Math.round(state.zoom * 100)}%`, reset = q('[data-design-zoom="reset"]');
814
+ reset.textContent !== label && (reset.textContent = label), scheduleFrameDemand(), root.dispatchEvent(
815
+ new CustomEvent("planr:design-camera", {
816
+ detail: { view: state.view, zoom: state.zoom, camera: { ...state.camera } }
817
+ })
818
+ );
819
+ }
820
+ function updateFramePlaceholders() {
821
+ if (!(!stage?.frameBudget || destroyed))
822
+ for (let [id, panel] of panels) {
823
+ let frame = stage.getFrame(id), placeholder = panel.querySelector("[data-design-load-frame]");
824
+ if (syncFrameGestures(frame, panel), !placeholder) continue;
825
+ let status2 = frame.dataset.planrFrameState || "unloaded";
826
+ placeholder.hidden = status2 === "ready", placeholder.disabled = status2 === "loading";
827
+ let copy = status2 === "loading" ? "Loading screen…" : status2 === "error" ? "Retry screen" : "Open screen";
828
+ placeholder.textContent !== copy && (placeholder.textContent = copy), frame.style.visibility = status2 === "ready" ? "" : "hidden";
829
+ }
830
+ }
831
+ function scheduleFrameDemand() {
832
+ if (!stage?.frameBudget || destroyed) return;
833
+ clearTimeout(frameDemandTimer);
834
+ let generation = frameDemandGeneration;
835
+ frameDemandTimer = setTimeout(() => {
836
+ if (destroyed || generation !== frameDemandGeneration || !readyReported || pendingScreen || walkthroughTransition || state.view !== "canvas" || root.dataset.sharedOpening || document.querySelector(".design-welcome[open]"))
837
+ return;
838
+ let box = scroll.getBoundingClientRect(), nearby = visibleEntries().map((entry) => {
839
+ let rect2 = panels.get(entry.artifactId).getBoundingClientRect();
840
+ return {
841
+ id: entry.artifactId,
842
+ visible: rect2.right > box.left && rect2.left < box.right && rect2.bottom > box.top && rect2.top < box.bottom,
843
+ distance: Math.hypot(
844
+ (rect2.left + rect2.right - box.left - box.right) / 2,
845
+ (rect2.top + rect2.bottom - box.top - box.bottom) / 2
846
+ )
847
+ };
848
+ }).filter((item) => item.visible).sort((a, b) => a.distance - b.distance), ids = [
849
+ ...new Set([activeArtifactId, ...nearby.map((item) => item.id)].filter(Boolean))
850
+ ].slice(0, stage.frameBudget), key = ids.join("|");
851
+ key !== frameDemandKey && (frameDemandKey = key, stage.ensureFrames(ids, () => !destroyed && generation === frameDemandGeneration).then(() => {
852
+ noteFramesUsedWithComments(), updateFramePlaceholders();
853
+ }).catch((error) => {
854
+ !destroyed && error.name !== "AbortError" && (frameDemandKey = "", updateFramePlaceholders());
855
+ }));
856
+ }, 120);
857
+ }
858
+ function finishWalkthroughTransition({ continueQueued = !1 } = {}) {
859
+ let transition = walkthroughTransition, queued = continueQueued ? queuedSelection : null;
860
+ if (queuedSelection = null, !!transition) {
861
+ walkthroughTransition = null, cancelAnimationFrame(transition.frame), clearTimeout(transition.timeout);
862
+ for (let animation of transition.animations ?? []) animation.cancel();
863
+ for (let visual of transition.visuals ?? []) delete visual.dataset.designTransitionVisual;
864
+ for (let id of [transition.from, transition.to]) {
865
+ let panel = panels.get(id);
866
+ panel.inert = !1, panel.removeAttribute("aria-hidden"), delete panel.dataset.designTransitionOutgoing, delete panel.dataset.designTransitionIncoming;
867
+ }
868
+ delete root.dataset.designTransition, render(), queued && !destroyed && selectEntry(queued.entry, queued.settings);
869
+ }
870
+ }
871
+ function prepareWalkthroughTransition(entry) {
872
+ let from = activeArtifactId, outgoing = panels.get(from), incoming = panels.get(entry.artifactId), direction = design.screenOrder.indexOf(entry.screenId) < design.screenOrder.indexOf(state.screenId) ? -1 : 1, transition = {
873
+ from,
874
+ to: entry.artifactId,
875
+ direction,
876
+ frame: null,
877
+ timeout: null,
878
+ animations: [],
879
+ visuals: []
880
+ };
881
+ walkthroughTransition = transition, root.dataset.designTransition = "preparing", outgoing.dataset.designTransitionOutgoing = "", incoming.dataset.designTransitionIncoming = "", outgoing.contains(document.activeElement) && q(`[data-design-step-change="${direction}"]`).focus({ preventScroll: !0 }), outgoing.inert = incoming.inert = !0, outgoing.setAttribute("aria-hidden", "true");
882
+ let finish = () => {
883
+ walkthroughTransition === transition && finishWalkthroughTransition({ continueQueued: !0 });
884
+ };
885
+ transition.timeout = setTimeout(finish, 1e3), transition.frame = requestAnimationFrame(() => {
886
+ transition.frame = requestAnimationFrame(() => {
887
+ if (walkthroughTransition !== transition || destroyed) return;
888
+ if (document.hidden || window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches || typeof incoming.animate != "function") {
889
+ finish();
890
+ return;
891
+ }
892
+ root.dataset.designTransition = "running";
893
+ let frameVisual = incoming.querySelector(".planr-frame iframe"), annotationVisual = incoming.querySelector("[data-planr-annotation-layer]");
894
+ transition.visuals = [frameVisual, annotationVisual].filter(Boolean);
895
+ for (let visual of transition.visuals) visual.dataset.designTransitionVisual = "";
896
+ let options3 = { duration: 220, easing: "cubic-bezier(0.22, 1, 0.36, 1)", fill: "both" };
897
+ transition.animations = [
898
+ frameVisual.animate(
899
+ [
900
+ { opacity: 0, transform: `translate3d(${direction * 12 / state.zoom}px, 0, 0)` },
901
+ { opacity: 1, transform: "translate3d(0, 0, 0)" }
902
+ ],
903
+ options3
904
+ )
905
+ ], annotationVisual && transition.animations.push(
906
+ annotationVisual.animate([{ opacity: 0 }, { opacity: 1 }], options3)
907
+ ), Promise.all(transition.animations.map((animation) => animation.finished)).then(
908
+ finish,
909
+ () => {
910
+ }
911
+ );
912
+ });
913
+ });
914
+ }
915
+ function requestedSelection() {
916
+ return pendingScreen?.entry ?? queuedSelection?.entry ?? failedSelection ?? state;
917
+ }
918
+ function requestedEntry(changes = {}) {
919
+ let requested = requestedSelection();
920
+ return entryFor(
921
+ changes.screenId ?? requested.screenId,
922
+ changes.variantId ?? requested.variantId,
923
+ changes.frameId ?? requested.frameId
924
+ );
925
+ }
926
+ function requestedScreen() {
927
+ return requestedSelection().screenId;
928
+ }
929
+ function selectEntry(entry, { save = !0, reveal = !1, loaded = !1 } = {}) {
930
+ if (!entry) return !1;
931
+ if (stage.frameBudget && !loaded && (pendingScreen || !stage.getLoadedArtifactIds().includes(entry.artifactId))) {
932
+ if (queuedSelection = null, pendingScreen?.entry.artifactId === entry.artifactId) return !0;
933
+ clearFailedSelection();
934
+ let request = ++screenRequest;
935
+ pendingScreen = { entry }, root.dataset.designScreenLoading = "true";
936
+ let ids = [...new Set([activeArtifactId, entry.artifactId].filter(Boolean))];
937
+ return stage.ensureFrames(ids).then(() => {
938
+ destroyed || request !== screenRequest || (pendingScreen = null, delete root.dataset.designScreenLoading, selectEntry(entry, { save, reveal, loaded: !0 }), updateFramePlaceholders());
939
+ }).catch((error) => {
940
+ destroyed || request !== screenRequest || error.name === "AbortError" || (pendingScreen = null, failedSelection = entry, delete root.dataset.designScreenLoading, notice(
941
+ "This screen could not load. Select it again to retry; your review is preserved.",
942
+ !0,
943
+ `preview-load:${entry.artifactId}`
944
+ ), render(), updateFramePlaceholders());
945
+ }), !0;
946
+ }
947
+ clearFailedSelection();
948
+ let sameFrame = entry.frameId === state.frameId && entry.variantId === state.variantId;
949
+ return walkthroughTransition && state.view === "walkthrough" && sameFrame ? (queuedSelection = entry.artifactId === activeArtifactId ? null : { entry, settings: { save, reveal } }, q('[data-design-step-change="-1"]').disabled = requestedScreen() === design.screenOrder[0], q('[data-design-step-change="1"]').disabled = requestedScreen() === design.screenOrder.at(-1), !0) : (finishWalkthroughTransition(), state.view === "walkthrough" && sameFrame && readyReported && entry.artifactId !== activeArtifactId && !document.hidden && prepareWalkthroughTransition(entry), state.screenId = entry.screenId, state.frameId = entry.frameId, state.variantId = entry.variantId, activeArtifactId = entry.artifactId, stage.dispatch({ type: "set-active", artifactId: entry.artifactId }), render(), reveal && centerEntry(entry), save && (persist(), recordLocation()), !0);
950
+ }
951
+ function fit({ save = !0, selection = null, maxZoom = 1 } = {}) {
952
+ if (state.inspectionScale = "fit", render(), state.view !== "canvas") {
953
+ save && persist();
954
+ return;
955
+ }
956
+ syncCanvasChrome();
957
+ for (let attempt = 0; attempt < 12; attempt++) {
958
+ let previousZoom = state.zoom;
959
+ if (canvas.fit(selection ? [selection.artifactId, `label:${selection.artifactId}`] : "all", {
960
+ padding: 36,
961
+ maxZoom
962
+ }), saveViewport(), renderCamera(), Math.abs(state.zoom - previousZoom) < 1e-6) break;
963
+ }
964
+ save && persist();
965
+ }
966
+ function zoomTo(value, point2 = null) {
967
+ if (state.view !== "canvas") return;
968
+ let rect2 = scroll.getBoundingClientRect();
969
+ canvas.zoomAt(
970
+ value / canvas.getCamera().zoom,
971
+ point2 ? { x: point2.x - rect2.left, y: point2.y - rect2.top } : void 0
972
+ ), saveViewport(), renderCamera(), persist();
973
+ }
974
+ async function exportArtifact(kind) {
975
+ let artifactId = activeArtifactId;
976
+ try {
977
+ let blob;
978
+ if (kind === "png") {
979
+ let result = typeof options2.exportPng == "function" ? await options2.exportPng({ artifactId }) : await stage.getFrame(artifactId)?.__openPlanrBridge?.exportPng("screen"), dataUrl = typeof result == "string" ? result : result?.dataUrl;
980
+ if (!dataUrl?.startsWith("data:image/png;base64,"))
981
+ throw new Error(
982
+ "PNG capture is unavailable. Open the local review with the artifact bridge and retry."
983
+ );
984
+ let bytes = Uint8Array.from(
985
+ atob(dataUrl.split(",")[1]),
986
+ (character) => character.charCodeAt(0)
987
+ );
988
+ blob = new Blob([bytes], { type: "image/png" });
989
+ } else {
990
+ let result = typeof options2.exportHtml == "function" ? await options2.exportHtml({ artifactId }) : null;
991
+ if (result instanceof Blob) blob = result;
992
+ else if (typeof result == "string")
993
+ blob = new Blob([result], { type: "text/html;charset=utf-8" });
994
+ else
995
+ throw new Error(
996
+ "Portable export is unavailable in this viewer. Use the design export utility from the project."
997
+ );
998
+ }
999
+ let url = URL.createObjectURL(blob), anchor = document.createElement("a");
1000
+ anchor.href = url, anchor.download = `${design.id}-${state.screenId}.${kind === "png" ? "png" : "html"}`, anchor.click(), setTimeout(() => URL.revokeObjectURL(url), 1e3), q(".design-export").open = !1, notice(kind === "png" ? "Screen PNG exported." : "Portable HTML exported.");
1001
+ } catch (error) {
1002
+ notice(error.message, !0);
1003
+ }
1004
+ }
1005
+ function editable2(target) {
1006
+ return !!target?.closest?.('input,textarea,select,[contenteditable="true"]');
1007
+ }
1008
+ function installEvents() {
1009
+ if (listen(scroll, "focusin", (event) => {
1010
+ event.target.closest("[data-design-note]") && event.stopImmediatePropagation();
1011
+ }), listen(scroll, "keydown", (event) => {
1012
+ if (state.view === "canvas") {
1013
+ if (["Escape", " ", "Enter", "Home", "End"].includes(event.key) || event.code === "Space") {
1014
+ if (event.stopImmediatePropagation(), event.key !== "Escape" || event.defaultPrevented || event.isComposing || editable2(event.target) || event.ctrlKey || event.metaKey || event.altKey)
1015
+ return;
1016
+ if (q(".planr-more-menu:not([hidden])")) {
1017
+ q('[data-planr-action="more"]')?.click();
1018
+ return;
1019
+ }
1020
+ if (!root.dispatchEvent(new CustomEvent("planr:design-escape", { cancelable: !0 })))
1021
+ return;
1022
+ stage.getState().railOpen && (stage.dispatch({ type: "set-rail-open", railOpen: !1 }), q('[data-planr-action="feedback"]')?.focus());
1023
+ return;
1024
+ }
1025
+ canvas.handleKey(event) && event.stopPropagation();
1026
+ }
1027
+ }), listen(scroll, "gotpointercapture", (event) => {
1028
+ canvasAttached && gesture?.pointerId !== event.pointerId && canvasPointers.add(event.pointerId);
1029
+ }), listen(root, "planr:design-camera", scheduleViewContext), listen(root, "planr:design-inspection-change", (event) => {
1030
+ let { screenId, frameId, variantId, planrId } = event.detail;
1031
+ screenId !== state.screenId || frameId !== state.frameId || variantId !== state.variantId || (inspectedSelection = { artifactId: activeArtifactId, planrId }, scheduleViewContext());
1032
+ }), listen(window, "message", (event) => {
1033
+ if (event.origin !== "null" || !["openplanr:design-size", "openplanr:design-size-error"].includes(event.data?.type))
1034
+ return;
1035
+ let entry = entries.find(
1036
+ ({ artifactId }) => stage.getFrame(artifactId)?.contentWindow === event.source
1037
+ );
1038
+ if (!entry) return;
1039
+ let frame = design.frames.find(({ id }) => id === entry.frameId);
1040
+ if (frame.height !== "auto") return;
1041
+ if (event.data.type === "openplanr:design-size-error") {
1042
+ notice(`Could not measure ${entry.screenId}: ${event.data.message}`, !0);
1043
+ return;
1044
+ }
1045
+ let { screenId, width, height } = event.data;
1046
+ if (screenId !== entry.screenId || width !== frame.width || !Number.isInteger(height) || height < 1 || height > 16384)
1047
+ return;
1048
+ let viewport = viewportFor(entry);
1049
+ viewport.height !== height && (viewport.height = height, panels.get(entry.artifactId).style.setProperty("--planr-artifact-height", `${height}px`), render());
1050
+ }), listen(root, "planr:artifact-viewport-zoom", (event) => {
1051
+ if (state.view !== "canvas") return;
1052
+ let entry = entries.find((item) => stage.getFrame(item.artifactId) === event.target);
1053
+ if (!entry || panels.get(entry.artifactId)?.hidden) return;
1054
+ let { x, y, deltaY } = event.detail, frame = design.frames.find((item) => item.id === entry.frameId), rect2 = event.target.getBoundingClientRect();
1055
+ chrome?.update({ exportMenuOpen: !1 }), zoomTo(
1056
+ canvas.getCamera().zoom * clamp2(Math.exp(-deltaY * 0.01), 0.8, 1.25),
1057
+ {
1058
+ x: rect2.left + x * rect2.width / frame.width,
1059
+ y: rect2.top + y * rect2.height / viewportFor(entry).height
1060
+ },
1061
+ { scheduled: !0 }
1062
+ );
1063
+ }), listen(root, "planr:artifact-viewport-pan", (event) => {
1064
+ if (state.view !== "canvas") return;
1065
+ let entry = entries.find((item) => stage.getFrame(item.artifactId) === event.target);
1066
+ !entry || panels.get(entry.artifactId)?.hidden || (chrome?.update({ exportMenuOpen: !1 }), canvas.panBy(event.detail.deltaX, event.detail.deltaY), saveViewport(), scheduleCamera(), persist());
1067
+ }), listen(
1068
+ root,
1069
+ "click",
1070
+ (event) => {
1071
+ let button = event.target.closest('[data-planr-mode],[data-planr-action="add-comment"]');
1072
+ !button || button.disabled || setTool(button.dataset.planrMode === "interact" ? "interact" : "annotate");
1073
+ },
1074
+ !0
1075
+ ), listen(
1076
+ document,
1077
+ "keydown",
1078
+ (event) => {
1079
+ if (editable2(event.target) || event.altKey || event.ctrlKey || event.metaKey || document.querySelector("dialog[open],[role=dialog][data-state=open]"))
1080
+ return;
1081
+ let key = event.key.toLowerCase();
1082
+ if (["i", "c", "h"].includes(key)) {
1083
+ if (key === "h" && state.view !== "canvas") return;
1084
+ event.preventDefault(), setTool({ i: "interact", c: "annotate", h: "pan" }[key]);
1085
+ } else event.key === "Escape" && (panEnabled || spacePan || gesture || activeTool === "inspect") && !q("[data-design-notes][open]") && !q("[data-planr-annotation-composer]") && (event.preventDefault(), setTool("interact"), event.stopPropagation(), q('[data-planr-mode="interact"]').focus());
1086
+ },
1087
+ !0
1088
+ ), typeof ResizeObserver == "function") {
1089
+ let observer = new ResizeObserver(() => {
1090
+ state.view !== "canvas" && root.dataset.designRestoring !== "true" && fit({ save: !1 }), syncCanvasChrome(), updateDesignNotes(), scheduleViewContext();
1091
+ });
1092
+ for (let selector of [
1093
+ ".design-toolbar",
1094
+ ".design-canvas-tools",
1095
+ ".design-stage-context",
1096
+ ".design-navigator",
1097
+ ".planr-review-rail"
1098
+ ])
1099
+ observer.observe(q(selector));
1100
+ observer.observe(scroll), cleanup.push(() => observer.disconnect());
1101
+ }
1102
+ let notes = q("[data-design-notes]");
1103
+ listen(notes?.querySelector(":scope > summary"), "click", (event) => {
1104
+ event.preventDefault(), notes.open ? closeDesignNotes() : (notesReturnFocus = event.currentTarget, notes.open = !0, updateDesignNotes());
1105
+ }), listen(notes, "toggle", updateDesignNotes), listen(
1106
+ document,
1107
+ "pointerdown",
1108
+ (event) => {
1109
+ notes?.open && !notes.contains(event.target) && !event.target.closest?.("[data-design-note]") && closeDesignNotes();
1110
+ },
1111
+ !0
1112
+ ), listen(
1113
+ document,
1114
+ "keydown",
1115
+ (event) => {
1116
+ event.key === "Escape" && notes?.open && (event.preventDefault(), event.stopPropagation(), closeDesignNotes({ restoreFocus: !0 }));
1117
+ },
1118
+ !0
1119
+ ), listen(root, "click", (event) => {
1120
+ let button = event.target.closest("button");
1121
+ if (button)
1122
+ if (button.dataset.designLoadFrame)
1123
+ selectEntry(
1124
+ entries.find((item) => item.artifactId === button.dataset.designLoadFrame),
1125
+ { reveal: !0 }
1126
+ );
1127
+ else if (button.hasAttribute("data-design-actual-size"))
1128
+ state.inspectionScale = "actual", render(), persist();
1129
+ else if (button.hasAttribute("data-design-focus"))
1130
+ state.navOpen = !1, state.reviewOpen = !1, stage.dispatch({ type: "set-rail-open", railOpen: !1 }), state.view === "canvas" && switchView("prototype"), render(), persist();
1131
+ else if (button.dataset.designView)
1132
+ switchView(button.dataset.designView);
1133
+ else if (button.hasAttribute("data-design-toggle-nav"))
1134
+ state.navOpen = !state.navOpen, !state.navOpen && button.closest(".design-navigator") && q(".design-toolbar [data-design-toggle-nav]").focus(), render(), persist();
1135
+ else if (button.dataset.designScreen)
1136
+ selectEntry(requestedEntry({ screenId: button.dataset.designScreen }), {
1137
+ reveal: state.view === "canvas"
1138
+ });
1139
+ else if (button.dataset.designNote)
1140
+ openDesignNote(button.dataset.designNote, button);
1141
+ else if (button.hasAttribute("data-design-close-notes"))
1142
+ closeDesignNotes({ restoreFocus: !0 });
1143
+ else if (button.hasAttribute("data-design-fit")) fit();
1144
+ else if (button.dataset.designZoom)
1145
+ zoomTo(
1146
+ button.dataset.designZoom === "reset" ? 1 : state.zoom * (button.dataset.designZoom === "in" ? 1.2 : 1 / 1.2)
1147
+ );
1148
+ else if (button.hasAttribute("data-design-pan")) {
1149
+ if (state.view !== "canvas") return;
1150
+ setTool(activeTool === "pan" ? "interact" : "pan");
1151
+ } else if (button.dataset.designStepChange) {
1152
+ let index2 = clamp2(
1153
+ design.screenOrder.indexOf(requestedScreen()) + Number(button.dataset.designStepChange),
1154
+ 0,
1155
+ design.screenOrder.length - 1
1156
+ );
1157
+ selectEntry(requestedEntry({ screenId: design.screenOrder[index2] }));
1158
+ } else if (button.dataset.designRating) {
1159
+ let rating = Number(button.dataset.designRating);
1160
+ state.ratings[state.variantId] = rating, rating <= 2 ? state.preferences.rejected = [
1161
+ .../* @__PURE__ */ new Set([...state.preferences.rejected, state.variantId])
1162
+ ] : state.preferences.rejected = state.preferences.rejected.filter(
1163
+ (id) => id !== state.variantId
1164
+ ), render(), persist({ content: !0 });
1165
+ } else button.hasAttribute("data-design-select-direction") ? (state.selectedVariant = state.variantId, state.preferences.selected = [.../* @__PURE__ */ new Set([...state.preferences.selected, state.variantId])], state.preferences.rejected = state.preferences.rejected.filter(
1166
+ (id) => id !== state.variantId
1167
+ ), render(), persist({ content: !0 })) : button.hasAttribute("data-design-save-remix") ? (state.remix[state.variantId] = q("[data-design-remix]").value.trim(), persist({ content: !0 }), notice("Refinement note saved for the next design iteration.")) : button.dataset.designExport && exportArtifact(button.dataset.designExport);
1168
+ }), listen(
1169
+ q("[data-design-variant]"),
1170
+ "change",
1171
+ (event) => selectEntry(requestedEntry({ variantId: event.target.value }))
1172
+ ), listen(q("[data-design-frame]"), "change", (event) => {
1173
+ selectEntry(requestedEntry({ frameId: event.target.value }), {
1174
+ reveal: state.view === "canvas"
1175
+ });
1176
+ }), listen(q("[data-design-compare]"), "change", (event) => {
1177
+ state.compare = event.target.checked, render(), persist();
1178
+ }), listen(root, "planr:stage-change", (event) => {
1179
+ let next = event.detail;
1180
+ !changingTool && next.reviewMode !== previousReviewMode && setTool(next.reviewMode === "comment" ? "annotate" : "interact");
1181
+ let reviewChanged = typeof next.railOpen == "boolean" && next.railOpen !== state.reviewOpen;
1182
+ reviewChanged && (state.reviewOpen = next.railOpen);
1183
+ let entry = entries.find(({ artifactId }) => artifactId === next.activeArtifactId);
1184
+ entry && entry.artifactId !== activeArtifactId && (stage.frameBudget && !stage.getLoadedArtifactIds().includes(entry.artifactId) ? selectEntry(entry, { save: !1 }) : (finishWalkthroughTransition(), activeArtifactId = entry.artifactId, state.screenId = entry.screenId, state.frameId = entry.frameId, state.variantId = entry.variantId)), next.zoom !== previousStageZoom && (state.zoom = clamp2(next.zoom / 100, 0.05, 2), canvasAttached && applyCanvasCamera(), previousStageZoom = next.zoom, saveViewport()), render(), reviewChanged && persist();
1185
+ }), listen(root, "planr:artifact-review-change", (event) => {
1186
+ latestReview = clone(event.detail), reviewDirty = !0, scheduleViewContext(), reviewSerial += 1, status("Saving comments", "saving"), storeReviewDraft(), clearTimeout(reviewTimer), reviewTimer = setTimeout(flushReview, 180);
1187
+ }), listen(
1188
+ root,
1189
+ "pointerdown",
1190
+ (event) => {
1191
+ let panel = event.target.closest(".planr-artifact-panel");
1192
+ event.button === 0 && panel && !panEnabled && !spacePan && !event.target.closest("[data-design-note]") && selectEntry(
1193
+ entries.find(({ artifactId }) => artifactId === panel.dataset.artifactId),
1194
+ { save: !1 }
1195
+ );
1196
+ },
1197
+ !0
1198
+ ), listen(scroll, "pointerdown", (event) => {
1199
+ if (state.view !== "canvas" || root.dataset.designInspect === "true" || event.button !== 0 && event.button !== 1) return;
1200
+ let label = event.target.closest("[data-design-drag]");
1201
+ if (!(event.button === 0 && state.view === "canvas" && label && !panEnabled && !spacePan)) return;
1202
+ event.preventDefault();
1203
+ let panel = label?.closest(".planr-artifact-panel"), entry = entries.find((item) => item.artifactId === panel.dataset.artifactId), bounds = placedBoardBounds(entry);
1204
+ bounds && (gesture = {
1205
+ pointerId: event.pointerId,
1206
+ x: event.clientX,
1207
+ y: event.clientY,
1208
+ mode: "artboard",
1209
+ artifactId: panel.dataset.artifactId,
1210
+ left: bounds.x,
1211
+ top: bounds.y,
1212
+ before: clone(layout)
1213
+ }, scroll.setPointerCapture?.(event.pointerId), updatePan());
1214
+ }), listen(scroll, "pointermove", (event) => {
1215
+ if (!gesture || gesture.pointerId !== event.pointerId) return;
1216
+ let dx = event.clientX - gesture.x, dy = event.clientY - gesture.y, entry = entries.find((item) => item.artifactId === gesture.artifactId), key = boardKey(entry), point2 = {
1217
+ x: Math.round(clamp2(gesture.left + dx / state.zoom, -1e7, 1e7)),
1218
+ y: Math.round(clamp2(gesture.top + dy / state.zoom, -1e7, 1e7))
1219
+ };
1220
+ layout.boards = { ...layout.boards, [key]: { ...layout.boards?.[key], ...point2 } };
1221
+ let panel = panels.get(gesture.artifactId);
1222
+ layoutDragDirty = point2.x !== gesture.left || point2.y !== gesture.top, layoutDragDirty && status("Moving board", "saving"), panel.style.left = `${point2.x}px`, panel.style.top = `${point2.y}px`, syncCanvasNodes(), scheduleCamera();
1223
+ });
1224
+ for (let eventName of ["pointerup", "pointercancel", "lostpointercapture"])
1225
+ listen(scroll, eventName, (event) => {
1226
+ canvasPointers.delete(event.pointerId), !(!gesture || gesture.pointerId !== event.pointerId) && (eventName === "pointerup" ? finishGesture() : releaseTemporaryPan());
1227
+ });
1228
+ listen(
1229
+ document,
1230
+ "keydown",
1231
+ (event) => {
1232
+ if (!editable2(event.target) && state.view === "canvas" && event.code === "Space" && !event.defaultPrevented && !event.ctrlKey && !event.metaKey && !event.altKey && !document.querySelector("dialog[open],[role=dialog][data-state=open]") && !event.target.closest("button,[data-planr-annotation-layer]")) {
1233
+ if (event.preventDefault(), event.stopPropagation(), event.repeat) return;
1234
+ let previous = activeTool;
1235
+ setTool("pan"), panEnabled = !1, spacePan = !0, spaceReturnTool = previous, updatePan();
1236
+ }
1237
+ },
1238
+ !0
1239
+ ), listen(document, "keydown", (event) => {
1240
+ if (editable2(event.target)) return;
1241
+ if (state.view === "canvas" && !event.defaultPrevented && !event.altKey && (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "z" && !document.querySelector("dialog[open],[role=dialog][data-state=open]")) {
1242
+ event.preventDefault(), restoreMove(event.shiftKey);
1243
+ return;
1244
+ }
1245
+ let label = event.target.closest("[data-design-drag]");
1246
+ if (label && state.view === "canvas" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) {
1247
+ event.preventDefault();
1248
+ let panel = label.closest(".planr-artifact-panel"), distance = event.shiftKey ? 40 : 8, entry = entries.find((item) => item.artifactId === panel.dataset.artifactId), bounds = placedBoardBounds(entry);
1249
+ if (!bounds) return;
1250
+ let key = boardKey(entry), before = clone(layout), point2 = {
1251
+ x: Math.round(
1252
+ clamp2(
1253
+ bounds.x + (event.key === "ArrowRight" ? distance : event.key === "ArrowLeft" ? -distance : 0),
1254
+ -1e7,
1255
+ 1e7
1256
+ )
1257
+ ),
1258
+ y: Math.round(
1259
+ clamp2(
1260
+ bounds.y + (event.key === "ArrowDown" ? distance : event.key === "ArrowUp" ? -distance : 0),
1261
+ -1e7,
1262
+ 1e7
1263
+ )
1264
+ )
1265
+ };
1266
+ layout.boards = { ...layout.boards, [key]: { ...layout.boards?.[key], ...point2 } }, render(), completeMove(entry, before);
1267
+ } else if (state.view === "walkthrough" && ["ArrowLeft", "ArrowRight"].includes(event.key) && !event.target.closest('button,[role="radio"]')) {
1268
+ event.preventDefault();
1269
+ let index2 = clamp2(
1270
+ design.screenOrder.indexOf(requestedScreen()) + (event.key === "ArrowRight" ? 1 : -1),
1271
+ 0,
1272
+ design.screenOrder.length - 1
1273
+ );
1274
+ selectEntry(requestedEntry({ screenId: design.screenOrder[index2] }));
1275
+ }
1276
+ }), listen(document, "keyup", (event) => {
1277
+ event.code === "Space" && releaseTemporaryPan();
1278
+ }), listen(window, "blur", () => {
1279
+ releaseTemporaryPan(), document.activeElement?.tagName === "IFRAME" && closeDesignNotes();
1280
+ }), listen(document, "visibilitychange", () => {
1281
+ document.hidden && (releaseTemporaryPan(), finishWalkthroughTransition());
1282
+ }), listen(
1283
+ window.matchMedia?.("(prefers-reduced-motion: reduce)"),
1284
+ "change",
1285
+ () => finishWalkthroughTransition()
1286
+ ), listen(window, "resize", () => {
1287
+ let compact = window.innerWidth <= 960;
1288
+ compact && !compactViewport && (state.navOpen = !1, state.reviewOpen = !1, stage.dispatch({ type: "set-rail-open", railOpen: !1 }), persist()), compactViewport = compact, render(), state.view !== "canvas" && root.dataset.designRestoring !== "true" && fit({ save: !1 }), updateDesignNotes();
1289
+ }), listen(window, "online", () => {
1290
+ (dirty || reviewDirty || layoutOperations.length) && flush(), viewContextSignature = null, scheduleViewContext();
1291
+ }), listen(window, "beforeunload", (event) => {
1292
+ clearTimeout(frameDemandTimer), frameDemandGeneration += 1, frameDemandKey = "", storeDraft(), storeReviewDraft(), (contentDirty || reviewDirty || layoutOperations.length) && (options2.stateUrl || options2.saveState || options2.layoutUrl || options2.saveLayout || reviewUrl()) && (event.preventDefault(), event.returnValue = "");
1293
+ }), listen(window, "message", (event) => {
1294
+ if (event.origin !== "null" || !["openplanr:design-navigate", "navigate"].includes(event.data?.type) || typeof event.data.screenId != "string")
1295
+ return;
1296
+ let source = entries.find(
1297
+ ({ artifactId }) => stage.getFrame(artifactId)?.contentWindow === event.source
1298
+ );
1299
+ if (!source || !design.screenOrder.includes(event.data.screenId)) return;
1300
+ let nonce = stage.getFrame(source.artifactId)?.__openPlanrBridge?.getPrototypeNonce?.();
1301
+ nonce && (event.data.type === "navigate" && (event.data.channel !== nonce || event.data.viewId !== source.artifactId || event.data.schemaVersion !== "1.0.0") || state.view !== "canvas" && (source.artifactId !== activeArtifactId || walkthroughTransition) || (chrome?.update({ exportMenuOpen: !1 }), selectEntry(entryFor(event.data.screenId, source.variantId, source.frameId))));
1302
+ });
1303
+ }
1304
+ async function refreshSavedState() {
1305
+ if (options2.stateUrl)
1306
+ try {
1307
+ let observedRevision = revision, result = await jsonRequest(options2.stateUrl);
1308
+ if (destroyed) return;
1309
+ observedRevision === revision && result.revision !== void 0 && result.revision !== revision && await rebaseLayout({ announce: !1, expectedRevision: observedRevision }), result.stateVersion !== void 0 && (stateVersion = result.stateVersion), legacyServerPositions = result.state?.positions ?? legacyServerPositions, result.state && !dirty && (state = normalizeState({ ...result.state, ...linkedState() }), canvasAttached && state.view === "canvas" && applyCanvasCamera(), compactViewport && (state.navOpen = !1, state.reviewOpen = !1), stage.dispatch({ type: "set-rail-open", railOpen: state.reviewOpen })), savedDraft?.unsaved && notice(
1310
+ "A previous unsaved draft is available in this browser. The saved review is shown; your draft has not overwritten it.",
1311
+ !0
1312
+ ), selectEntry(entryFor(), { save: !1 }), layoutOperations.length || status("All changes saved");
1313
+ } catch {
1314
+ status("Disconnected · local draft", "disconnected");
1315
+ }
1316
+ }
1317
+ function renderVerification(verification) {
1318
+ q("[data-design-verification]").textContent = verification === "verified" ? "Browser inspection complete" : verification === "failed" ? "Browser inspection failed" : "Browser inspection pending";
1319
+ }
1320
+ async function pollRevision() {
1321
+ if (!(!options2.statusUrl || destroyed || sessionLost)) {
1322
+ try {
1323
+ let observedRevision = revision, result = await jsonRequest(options2.statusUrl);
1324
+ if (destroyed) return;
1325
+ if (typeof result.verification == "string" && renderVerification(result.verification), observedRevision === revision && result.revision !== void 0 && revision !== null && result.revision !== revision)
1326
+ if (contentDirty || reviewDirty || layoutOperations.length || layoutSaving)
1327
+ newRevisionNotice();
1328
+ else {
1329
+ storeDraft(), window.location.reload();
1330
+ return;
1331
+ }
1332
+ result.status === "failed" && notice(
1333
+ result.message ?? "The new revision failed. This working design remains available.",
1334
+ !0
1335
+ );
1336
+ } catch (error) {
1337
+ error.statusCode === 404 ? studioRestarted() : !dirty && !hasPendingLayout() && status("Disconnected", "disconnected");
1338
+ }
1339
+ pollTimer = setTimeout(pollRevision, 4e3);
1340
+ }
1341
+ }
1342
+ async function reportReady() {
1343
+ if (!(!startupSettled || readyReported || readyReporting || destroyed)) {
1344
+ readyReporting = !0;
1345
+ try {
1346
+ let requiredEntries = [entryFor()];
1347
+ if (await stage.ensureFrames(requiredEntries.map((entry) => entry.artifactId)), destroyed) return;
1348
+ let loaded = requiredEntries.filter(({ artifactId }) => {
1349
+ let frame = stage.getFrame(artifactId);
1350
+ return (frame?.src?.startsWith("blob:") || !!frame?.srcdoc) && frame.dataset.planrFrameState === "ready" && frame.dataset.planrArtifactDigest && (!frame.__openPlanrBridge || frame.dataset.planrBridgeTrusted === "true");
1351
+ });
1352
+ if (loaded.length !== requiredEntries.length) throw new Error("Preview bridge is not ready.");
1353
+ if (readyReported = !0, render(), await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))), destroyed) return;
1354
+ root.dataset.designReady = "true", root.dataset.sharedOpening || delete document.documentElement.dataset.designOpening, chrome?.update({ previewLabel: "Preview ready" }), chrome || (q("[data-design-preview-state]").textContent = "Preview ready"), !contentDirty && !reviewDirty && !layoutOperations.length && (!options2.stateUrl || stateVersion !== null) && status(options2.stateUrl ? "All changes saved" : "Local preview");
1355
+ let detail = {
1356
+ revision,
1357
+ status: "ready",
1358
+ artifacts: loaded.map((entry) => entry.artifactId)
1359
+ };
1360
+ if (root.dispatchEvent(new CustomEvent("planr:design-ready", { bubbles: !0, detail })), typeof options2.onReady == "function" && await options2.onReady(detail), options2.readyUrl)
1361
+ try {
1362
+ await jsonRequest(options2.readyUrl, {
1363
+ method: "POST",
1364
+ headers: { "Content-Type": "application/json" },
1365
+ body: JSON.stringify(detail)
1366
+ });
1367
+ } catch {
1368
+ status("Server disconnected · local draft", "disconnected");
1369
+ }
1370
+ } catch {
1371
+ if (destroyed) return;
1372
+ delete document.documentElement.dataset.designOpening, chrome?.update({ previewLabel: "Preview unavailable" }), chrome || (q("[data-design-preview-state]").textContent = "Preview unavailable"), notice(
1373
+ "This preview could not load. Use Retry screen; your saved feedback remains available.",
1374
+ !0
1375
+ );
1376
+ } finally {
1377
+ readyReporting = !1;
1378
+ }
1379
+ }
1380
+ }
1381
+ function mount() {
1382
+ if (stage = artifactStage, !stage)
1383
+ return status("Review runtime unavailable", "failed"), null;
1384
+ root = q(".planr-shell");
1385
+ let structuralStyle = document.createElement("style");
1386
+ structuralStyle.textContent = styles_default, document.head.prepend(structuralStyle), cleanup.push(() => structuralStyle.remove()), cleanup.push(
1387
+ canvas.on("camera", () => {
1388
+ if (!canvasAttached || state.view !== "canvas") return;
1389
+ let previous = state.viewports.canvas;
1390
+ saveViewport(), renderCamera();
1391
+ let next = state.viewports.canvas;
1392
+ (previous?.x !== next.x || previous?.y !== next.y || previous?.zoom !== next.zoom) && persist();
1393
+ })
1394
+ ), chrome = mountDesignChrome({ root, title: design.title, state }), hasPendingLayout() && status("Recovering layout", "saving"), chrome && cleanup.push(() => chrome.destroy()), previousReviewMode = stage.getState().reviewMode, activeTool = previousReviewMode === "comment" ? "annotate" : "interact", scroll = q(".planr-stage-scroll"), surface = q(".planr-stage-surface"), grid = q(".planr-frame-grid"), selectionOutline = document.createElement("div"), selectionOutline.className = "design-selection-outline", selectionOutline.setAttribute("aria-hidden", "true"), selectionOutline.hidden = !0, scroll.append(selectionOutline);
1395
+ for (let section of layout.sections ?? []) {
1396
+ let heading = document.createElement("div");
1397
+ heading.className = "design-section-heading", heading.setAttribute("role", "heading"), heading.setAttribute("aria-level", "2"), heading.textContent = section.text, heading.style.left = `${section.x}px`, heading.style.top = `${section.y}px`, section.maxW && (heading.style.maxWidth = `${section.maxW}px`), grid.append(heading), sectionHeadings.push(heading);
1398
+ }
1399
+ for (let entry of entries) {
1400
+ let panel = stage.getPanel(entry.artifactId);
1401
+ if (!panel)
1402
+ return status("Missing design preview", "failed"), null;
1403
+ panels.set(entry.artifactId, panel);
1404
+ let nodeId = panel.getAttribute("data-canvas-node-id");
1405
+ panel.dataset.canvasNodeId = entry.artifactId, cleanup.push(() => {
1406
+ nodeId === null ? panel.removeAttribute("data-canvas-node-id") : panel.setAttribute("data-canvas-node-id", nodeId);
1407
+ });
1408
+ let screen = design.screens.find(({ id }) => id === entry.screenId), variant = design.variants.find(({ id }) => id === entry.variantId), frame = design.frames.find(({ id }) => id === entry.frameId), iframe = stage.getFrame(entry.artifactId), syncGestures = () => syncFrameGestures(iframe, panel);
1409
+ if (listen(iframe, "planr:artifact-bridge-ready", syncGestures), listen(iframe, "load", syncGestures), frame.height === "auto") {
1410
+ let measure = () => iframe.contentWindow?.postMessage({ type: "openplanr:design-measure" }, "*");
1411
+ listen(iframe, "planr:artifact-bridge-ready", measure), listen(iframe, "load", measure), measure();
1412
+ }
1413
+ let label = document.createElement("div");
1414
+ label.className = "design-artboard-label", label.setAttribute("role", "group");
1415
+ let drag = document.createElement("button");
1416
+ drag.type = "button", drag.className = "design-artboard-drag", drag.dataset.designDrag = entry.artifactId, drag.setAttribute(
1417
+ "aria-label",
1418
+ `Move ${screen.title}, ${variant.label}, ${frame.label}. Use arrow keys to arrange.`
1419
+ );
1420
+ let title = document.createElement("strong");
1421
+ title.textContent = `${screen.title}${readyVariants.length > 1 ? ` / ${variant.label}` : ""}`;
1422
+ let size4 = document.createElement("span");
1423
+ if (size4.textContent = designFrameLabel(frame), drag.append(title, size4), label.append(drag), screen.description) {
1424
+ let note = document.createElement("button");
1425
+ note.type = "button", note.className = "design-artboard-note", note.dataset.designNote = screen.id, note.setAttribute("aria-controls", "design-guidance-panel"), note.setAttribute("aria-expanded", "false"), note.title = screen.description, note.setAttribute("aria-label", `Open design notes for ${screen.title}`), note.textContent = "i", label.append(note);
1426
+ }
1427
+ if (panel.prepend(label), stage.frameBudget) {
1428
+ let load = document.createElement("button");
1429
+ load.type = "button", load.className = "design-frame-placeholder", load.dataset.designLoadFrame = entry.artifactId, load.setAttribute("aria-label", `Open screen: ${screen.title}, ${frame.label}`), load.textContent = "Open screen", panel.querySelector(".planr-frame").append(load);
1430
+ }
1431
+ }
1432
+ renderVerification(payload.verification?.status);
1433
+ let prototypeState = createPrototypeStateRelay({
1434
+ contextId: `${design.id}:${payload.revision ?? payload.reviewOf ?? "local"}`,
1435
+ parentWindow: window,
1436
+ frames: () => entries.map((entry) => ({
1437
+ screenId: entry.screenId,
1438
+ viewId: entry.artifactId,
1439
+ window: stage.getFrame(entry.artifactId)?.contentWindow ?? null,
1440
+ nonce: stage.getFrame(entry.artifactId)?.__openPlanrBridge?.getPrototypeNonce?.() ?? null,
1441
+ generation: stage.getFrame(entry.artifactId)?.__openPlanrBridge?.getPrototypeGeneration?.(),
1442
+ aliases: options2.prototypeStateAliases?.[entry.screenId]
1443
+ }))
1444
+ });
1445
+ cleanup.push(() => prototypeState.dispose()), installEvents(), listen(q("[data-design-dismiss-notice]"), "click", () => {
1446
+ activeNotice?.persistent && dismissedNotices.set(activeNotice.key, activeNotice.message), hideNotice();
1447
+ });
1448
+ function previewHealth() {
1449
+ for (let entry of entries) {
1450
+ let frame = stage.getFrame(entry.artifactId), nonce = frame?.__openPlanrBridge?.getPrototypeNonce?.() ?? null;
1451
+ nonce && prototypeState.restore({
1452
+ screenId: entry.screenId,
1453
+ viewId: entry.artifactId,
1454
+ window: frame.contentWindow,
1455
+ nonce,
1456
+ generation: frame.__openPlanrBridge?.getPrototypeGeneration?.(),
1457
+ aliases: options2.prototypeStateAliases?.[entry.screenId]
1458
+ });
1459
+ }
1460
+ let selected = stage.getFrameDiagnostics?.().find((item) => item.artifactId === activeArtifactId), node = q("[data-design-preview-state]"), phase = selected?.status ?? stage.getState().status, previewLabel = phase === "ready" ? "Preview ready" : phase === "error" || phase === "invalid" ? "Preview unavailable" : "Loading preview";
1461
+ chrome ? chrome.update({ previewLabel }) : node.textContent = previewLabel, node.dataset.status = phase, selected && (node.title = `${selected.phase} · ${Math.round(selected.elapsedMs ?? 0)} ms${selected.code ? ` · ${selected.code}` : ""}`), !readyReported && phase === "ready" && reportReady();
1462
+ }
1463
+ listen(root, "planr:artifact-frame-state", previewHealth), listen(root, "planr:artifact-state", previewHealth), previewHealth();
1464
+ let search = q("[data-design-screen-search]"), group = q("[data-design-screen-group]");
1465
+ function filterScreens() {
1466
+ let term = search.value.trim().toLocaleLowerCase(), flow = design.flows?.find((item) => item.id === group?.value), count3 = 0;
1467
+ for (let button of qa("[data-design-screen]"))
1468
+ button.hidden = !(button.textContent.toLocaleLowerCase().includes(term) && (!flow || flow.screens.includes(button.dataset.designScreen))), button.hidden || count3++;
1469
+ q("[data-design-search-empty]").hidden = count3 > 0;
1470
+ }
1471
+ listen(search, "input", filterScreens), listen(group, "change", filterScreens);
1472
+ function followLocation() {
1473
+ let next = normalizeState({ ...state, ...linkedState() });
1474
+ next.view !== state.view && switchView(next.view, { save: !1 }), selectEntry(entryFor(next.screenId, next.variantId, next.frameId), { save: !1 });
1475
+ }
1476
+ if (listen(window, "popstate", followLocation), listen(window, "hashchange", followLocation), stage.frameBudget && (listen(root, "planr:artifact-frame-state", updateFramePlaceholders), updateFramePlaceholders()), compactViewport = window.innerWidth <= 960, compactViewport ? (state.navOpen = !1, state.reviewOpen = !1, stage.dispatch({ type: "set-rail-open", railOpen: !1 })) : stage.dispatch({ type: "set-rail-open", railOpen: state.reviewOpen }), selectEntry(entryFor(), { save: !1 }), !reviewUrl() && !stage.review.getState().review)
1477
+ try {
1478
+ let storedReview = JSON.parse(localStorage.getItem(`${storageKey}.review`) ?? "null");
1479
+ storedReview?.review && stage.review.replaceReview(storedReview.review);
1480
+ } catch {
1481
+ }
1482
+ let panelDialogs = mountDesignPanels({
1483
+ root,
1484
+ closePanel(side) {
1485
+ side === "left" ? state.navOpen = !1 : (state.reviewOpen = !1, stage.dispatch({ type: "set-rail-open", railOpen: !1 })), render(), persist(), q(
1486
+ side === "left" ? ".design-toolbar [data-design-toggle-nav]" : ".design-toolbar [data-planr-action=feedback]"
1487
+ )?.focus();
1488
+ }
1489
+ });
1490
+ cleanup.push(() => panelDialogs.destroy());
1491
+ let controller = Object.freeze({
1492
+ setTool,
1493
+ getTool: () => activeTool,
1494
+ getState: () => (readCanvasCamera(), clone(state)),
1495
+ getLayout: () => clone(layout),
1496
+ getViewport: (artifactId) => ({
1497
+ ...viewportFor(entries.find((entry) => entry.artifactId === artifactId))
1498
+ }),
1499
+ getRevision: () => revision,
1500
+ getSaveState: () => ({
1501
+ dirty: contentDirty || reviewDirty || hasPendingLayout(),
1502
+ personalPending: dirty && !contentDirty,
1503
+ commentsPending: reviewDirty,
1504
+ revision,
1505
+ stateVersion
1506
+ }),
1507
+ selectScreen: (screenId) => selectEntry(requestedEntry({ screenId })),
1508
+ selectEntry: (entry) => selectEntry(entry),
1509
+ fitSelection: ({ save = !0 } = {}) => fit({ selection: entryFor(), save }),
1510
+ setCamera(camera) {
1511
+ state.view === "canvas" && (!Number.isFinite(camera?.x) || !Number.isFinite(camera?.y) || (state.camera = { x: clamp2(camera.x, -1e7, 1e7), y: clamp2(camera.y, -1e7, 1e7) }, Number.isFinite(camera.zoom) && (state.zoom = clamp2(camera.zoom, 0.05, 2)), applyCanvasCamera(), saveViewport(), renderCamera(), persist()));
1512
+ },
1513
+ setPanels({ navOpen = state.navOpen, reviewOpen = state.reviewOpen }, { save = !0 } = {}) {
1514
+ state.navOpen = !!navOpen, state.reviewOpen = !!reviewOpen, stage.dispatch({ type: "set-rail-open", railOpen: state.reviewOpen }), render(), save && persist();
1515
+ },
1516
+ restorePresentation(snapshot) {
1517
+ finishWalkthroughTransition();
1518
+ let personal = {};
1519
+ for (let key of [
1520
+ "view",
1521
+ "screenId",
1522
+ "frameId",
1523
+ "variantId",
1524
+ "compare",
1525
+ "navOpen",
1526
+ "reviewOpen",
1527
+ "inspectionScale",
1528
+ "zoom",
1529
+ "camera",
1530
+ "viewports"
1531
+ ])
1532
+ snapshot[key] !== void 0 && (personal[key] = clone(snapshot[key]));
1533
+ state = normalizeState({ ...state, ...personal }), canvasAttached && state.view === "canvas" && applyCanvasCamera(), stage.dispatch({ type: "set-rail-open", railOpen: state.reviewOpen }), selectEntry(entryFor(), { save: !1 }), render(), persist();
1534
+ },
1535
+ setView(view) {
1536
+ switchView(view);
1537
+ },
1538
+ fit,
1539
+ flush,
1540
+ exportArtifact,
1541
+ destroy() {
1542
+ if (!destroyed) {
1543
+ finishWalkthroughTransition(), root.dispatchEvent(new CustomEvent("planr:design-destroy"));
1544
+ for (let { artifactId } of entries)
1545
+ stage.getFrame(artifactId)?.__openPlanrBridge?.setViewportGestures?.(!1);
1546
+ destroyed = !0, canvasAttached = !1, canvas.destroy(), ++screenRequest, pendingScreen = null, failedSelection = null, clearTimeout(frameDemandTimer), clearTimeout(saveTimer), clearTimeout(reviewTimer), clearTimeout(pollTimer), clearTimeout(noticeTimer), clearTimeout(viewContextTimer), cameraFrame !== null && cancelAnimationFrame(cameraFrame);
1547
+ for (let heading of sectionHeadings) heading.remove();
1548
+ selectionOutline?.remove();
1549
+ for (let remove of cleanup.splice(0)) remove();
1550
+ }
1551
+ }
1552
+ });
1553
+ return refreshSavedState().then(async () => (restorePendingLayout(), migrateLegacyPositions(), await flushLayout(), startupSettled = !0, reportReady())), pollTimer = setTimeout(pollRevision, 4e3), controller;
1554
+ }
1555
+ return mount();
1556
+ }
1557
+
1558
+ // lib/design/ui/studio-runtime.mjs
1559
+ var PAYLOAD_ID = "planr-design-studio-payload", STAGE_TIMEOUT_MS = 15e3;
1560
+ function documentReady() {
1561
+ return document.readyState !== "loading" ? Promise.resolve() : new Promise((resolve) => {
1562
+ document.addEventListener("DOMContentLoaded", resolve, { once: !0 });
1563
+ });
1564
+ }
1565
+ function stageBefore(deadline) {
1566
+ let timer, timeout = new Promise((resolve) => {
1567
+ timer = setTimeout(resolve, Math.max(0, deadline - Date.now()), null);
1568
+ });
1569
+ return Promise.race([whenArtifactStage(window), timeout]).finally(() => clearTimeout(timer));
1570
+ }
1571
+ async function start(payloadNode2, payload, deadline) {
1572
+ let current = () => document.getElementById(PAYLOAD_ID) === payloadNode2;
1573
+ if (await documentReady(), !current()) return;
1574
+ let stage = await stageBefore(deadline);
1575
+ if (!current()) return;
1576
+ let studio = mountDesignStudio({ payload, stage });
1577
+ if (!studio) return;
1578
+ window.__openPlanrDesignStudio = studio;
1579
+ let { experience, handoffCenter } = mountDesignEnhancements({ payload, studio, stage });
1580
+ window.__openPlanrDesignExperience = experience, handoffCenter && (window.__openPlanrDesignHandoffCenter = handoffCenter);
1581
+ }
1582
+ var script = document.currentScript, payloadNode = document.getElementById(PAYLOAD_ID);
1583
+ (!script || script.isConnected) && payloadNode && start(payloadNode, JSON.parse(payloadNode.textContent), Date.now() + STAGE_TIMEOUT_MS);