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
@@ -1,1844 +0,0 @@
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
- const design = payload.document;
14
- const entries = payload.entries;
15
- const options2 = globalThis.__OPENPLANR_DESIGN_STUDIO_OPTIONS__ ?? {};
16
- const storageKey = `openplanr.design-studio.${design.id}`;
17
- const q = (selector) => document.querySelector(selector);
18
- const qa = (selector) => [...document.querySelectorAll(selector)];
19
- const clone = (value) => JSON.parse(JSON.stringify(value));
20
- const clamp2 = (value, min2, max2) => Math.min(max2, Math.max(min2, value));
21
- const readyVariants = design.variants.filter(({ status: status2 }) => status2 === "ready");
22
- const views = ["canvas", "prototype", "walkthrough"];
23
- const validId = (items, id, fallback) => items.some((item) => (typeof item === "string" ? item : item.id) === id) ? id : fallback;
24
- const normalizedViewport = (value) => {
25
- if (!Number.isFinite(value?.x) || !Number.isFinite(value?.y) || !Number.isFinite(value?.zoom))
26
- return null;
27
- return {
28
- x: clamp2(value.x, -1e7, 1e7),
29
- y: clamp2(value.y, -1e7, 1e7),
30
- zoom: clamp2(value.zoom, 0.05, 2)
31
- };
32
- };
33
- function normalizeState(input = {}) {
34
- const view = views.includes(input.view) ? input.view : design.defaultView;
35
- const viewports = {};
36
- for (const name of views) {
37
- const viewport = normalizedViewport(input.viewports?.[name]);
38
- if (viewport) viewports[name] = viewport;
39
- }
40
- const legacyViewport = Number.isFinite(input.zoom) ? normalizedViewport({
41
- x: Number.isFinite(input.camera?.x) ? input.camera.x : 40,
42
- y: Number.isFinite(input.camera?.y) ? input.camera.y : 40,
43
- zoom: input.zoom
44
- }) : null;
45
- const activeViewport = viewports[view] ?? legacyViewport ?? { x: 40, y: 40, zoom: 0.5 };
46
- const state2 = {
47
- schemaVersion: "1.1.0",
48
- view,
49
- screenId: validId(design.screenOrder, input.screenId, design.screenOrder[0]),
50
- frameId: validId(design.frames, input.frameId, design.frames[0].id),
51
- variantId: validId(readyVariants, input.variantId, design.selectedVariant),
52
- selectedVariant: validId(readyVariants, input.selectedVariant, design.selectedVariant),
53
- compare: Boolean(input.compare),
54
- navOpen: input.navOpen === void 0 ? true : Boolean(input.navOpen),
55
- reviewOpen: input.reviewOpen === void 0 ? true : Boolean(input.reviewOpen),
56
- inspectionScale: input.inspectionScale === "actual" ? "actual" : "fit",
57
- zoom: activeViewport.zoom,
58
- camera: { x: activeViewport.x, y: activeViewport.y },
59
- viewports,
60
- positions: {},
61
- ratings: {},
62
- remix: {},
63
- preferences: { selected: [], rejected: [] }
64
- };
65
- for (const entry of entries) {
66
- const point = input.positions?.[entry.artifactId];
67
- if (Number.isFinite(point?.x) && Number.isFinite(point?.y)) {
68
- state2.positions[entry.artifactId] = {
69
- x: clamp2(point.x, -1e7, 1e7),
70
- y: clamp2(point.y, -1e7, 1e7)
71
- };
72
- }
73
- }
74
- for (const variant of readyVariants) {
75
- const rating = input.ratings?.[variant.id];
76
- if (Number.isInteger(rating) && rating >= 1 && rating <= 5)
77
- state2.ratings[variant.id] = rating;
78
- if (typeof input.remix?.[variant.id] === "string")
79
- state2.remix[variant.id] = input.remix[variant.id].slice(0, 8192);
80
- }
81
- for (const kind of ["selected", "rejected"]) {
82
- state2.preferences[kind] = [
83
- ...new Set(
84
- (Array.isArray(input.preferences?.[kind]) ? input.preferences[kind] : []).filter(
85
- (id) => readyVariants.some((variant) => variant.id === id)
86
- )
87
- )
88
- ];
89
- }
90
- return state2;
91
- }
92
- let savedDraft = null;
93
- try {
94
- savedDraft = JSON.parse(localStorage.getItem(storageKey) ?? "null");
95
- } catch {
96
- }
97
- let state = normalizeState({
98
- ...savedDraft?.state ?? {},
99
- ...payload.state ?? {}
100
- });
101
- function linkedState() {
102
- const params = new URLSearchParams(location.hash.slice(1));
103
- const linked = {};
104
- for (const [key, query] of [
105
- ["screenId", "screen"],
106
- ["frameId", "frame"],
107
- ["variantId", "direction"],
108
- ["view", "view"]
109
- ])
110
- if (params.has(query)) linked[key] = params.get(query);
111
- return linked;
112
- }
113
- state = normalizeState({ ...state, ...linkedState() });
114
- function recordLocation() {
115
- const params = new URLSearchParams({
116
- screen: state.screenId,
117
- frame: state.frameId,
118
- direction: state.variantId,
119
- view: state.view
120
- });
121
- const next = `#${params}`;
122
- if (location.hash !== next) history.pushState(null, "", next);
123
- }
124
- let revision = payload.revision;
125
- let stateVersion = null;
126
- let stage = null;
127
- let root = null;
128
- let chrome = null;
129
- let scroll = null;
130
- let surface = null;
131
- let grid = null;
132
- let canvasBackground = null;
133
- let cameraFrame = null;
134
- let selectionOutline = null;
135
- let dirty = false;
136
- let contentDirty = false;
137
- let contentSerial = 0;
138
- let reviewDirty = false;
139
- let reviewSerial = 0;
140
- let reviewSaving = Promise.resolve();
141
- let reviewTimer = null;
142
- let latestReview = null;
143
- let serial = 0;
144
- let saveTimer = null;
145
- let saving = Promise.resolve();
146
- let pollTimer = null;
147
- let noticeTimer = null;
148
- let activeNotice = null;
149
- const dismissedNotices = /* @__PURE__ */ new Map();
150
- let readyReported = false;
151
- let readyReporting = false;
152
- let destroyed = false;
153
- let panEnabled = false;
154
- let spacePan = false;
155
- let spaceReturnTool = null;
156
- let activeTool = "interact";
157
- let changingTool = false;
158
- let previousReviewMode = "interact";
159
- let gesture = null;
160
- let rendering = false;
161
- let previousStageZoom = 100;
162
- let compactViewport = false;
163
- let activeArtifactId = "";
164
- let walkthroughTransition = null;
165
- let pendingScreen = null, screenRequest = 0, frameDemandTimer = null;
166
- let failedSelection = null;
167
- let frameDemandKey = "";
168
- let frameDemandGeneration = 0;
169
- let queuedSelection = null;
170
- let noteScreenId = null;
171
- let notesReturnFocus = null;
172
- const panels = /* @__PURE__ */ new Map();
173
- const cleanup = [];
174
- function listen(target, event, handler, settings) {
175
- target?.addEventListener(event, handler, settings);
176
- cleanup.push(() => target?.removeEventListener(event, handler, settings));
177
- }
178
- function status(message, phase = "saved") {
179
- if (destroyed) return;
180
- if (chrome) chrome.update({ saveLabel: message, savePhase: phase });
181
- else {
182
- const node = q("[data-design-save-state]");
183
- node.textContent = message;
184
- node.dataset.status = phase;
185
- }
186
- }
187
- function hideNotice() {
188
- clearTimeout(noticeTimer);
189
- const node = q(".design-notice");
190
- const restoreFocus = node.contains(document.activeElement);
191
- node.hidden = true;
192
- activeNotice = null;
193
- if (restoreFocus) q(`button[data-design-view="${state.view}"]`)?.focus();
194
- }
195
- function clearNotice(key) {
196
- dismissedNotices.delete(key);
197
- if (activeNotice?.key === key) hideNotice();
198
- }
199
- function clearFailedSelection() {
200
- if (failedSelection) clearNotice(`preview-load:${failedSelection.artifactId}`);
201
- failedSelection = null;
202
- }
203
- function notice(message, persistent = false, key = message) {
204
- if (destroyed || dismissedNotices.get(key) === message) return;
205
- const node = q(".design-notice");
206
- clearTimeout(noticeTimer);
207
- activeNotice = { key, message, persistent };
208
- q("[data-design-notice-message]").textContent = message;
209
- node.hidden = false;
210
- if (!persistent) noticeTimer = setTimeout(hideNotice, 6e3);
211
- }
212
- function storeDraft() {
213
- try {
214
- localStorage.setItem(
215
- storageKey,
216
- JSON.stringify({
217
- revision,
218
- stateVersion,
219
- state,
220
- unsaved: contentDirty,
221
- personalPending: dirty && !contentDirty
222
- })
223
- );
224
- } catch {
225
- }
226
- }
227
- async function jsonRequest(url, init = {}) {
228
- const response = await fetch(url, { credentials: "same-origin", ...init });
229
- const result = await response.json().catch(() => ({}));
230
- if (!response.ok) {
231
- const error = new Error(
232
- result.error?.message ?? result.error ?? result.message ?? `Save failed (${response.status}).`
233
- );
234
- error.conflict = response.status === 409;
235
- throw error;
236
- }
237
- return result;
238
- }
239
- function persist({ content = false } = {}) {
240
- dirty = true;
241
- serial += 1;
242
- if (content) {
243
- contentDirty = true;
244
- contentSerial += 1;
245
- status("Saving changes", "saving");
246
- }
247
- storeDraft();
248
- root.dispatchEvent(
249
- new CustomEvent("planr:design-state-change", {
250
- bubbles: true,
251
- detail: clone(state)
252
- })
253
- );
254
- clearTimeout(saveTimer);
255
- saveTimer = setTimeout(flush, 180);
256
- }
257
- function flushState() {
258
- clearTimeout(saveTimer);
259
- if (!dirty) return saving;
260
- const captured = clone(state);
261
- const capturedSerial = serial;
262
- const capturedContent = contentDirty;
263
- const capturedContentSerial = contentSerial;
264
- saving = saving.catch(() => {
265
- }).then(async () => {
266
- if (destroyed) return;
267
- try {
268
- let result;
269
- if (!capturedContent && typeof options2.savePersonalState === "function") {
270
- result = await options2.savePersonalState(captured, { revision, stateVersion });
271
- } else if (typeof options2.saveState === "function") {
272
- result = await options2.saveState(captured, {
273
- revision,
274
- stateVersion,
275
- personal: !capturedContent
276
- });
277
- } else if (options2.stateUrl) {
278
- result = await jsonRequest(options2.stateUrl, {
279
- method: "PUT",
280
- headers: { "Content-Type": "application/json" },
281
- body: JSON.stringify({ state: captured, revision, stateVersion })
282
- });
283
- }
284
- if (result?.stateVersion !== void 0) stateVersion = result.stateVersion;
285
- if (result?.revision !== void 0) revision = result.revision;
286
- clearNotice("personal-save");
287
- if (capturedContent) clearNotice("content-save");
288
- if (capturedSerial === serial) {
289
- dirty = false;
290
- }
291
- if (capturedContent && capturedContentSerial === contentSerial) {
292
- contentDirty = false;
293
- if (!reviewDirty)
294
- status(
295
- options2.stateUrl || options2.saveState ? "All changes saved" : "Saved in this browser"
296
- );
297
- }
298
- storeDraft();
299
- } catch (error) {
300
- if (capturedContent)
301
- status(
302
- error.conflict ? "Save conflict" : "Disconnected · unsaved",
303
- error.conflict ? "failed" : "disconnected"
304
- );
305
- notice(
306
- !capturedContent ? "Canvas preferences could not be synchronized. Your arrangement remains saved in this browser." : 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.",
307
- true,
308
- capturedContent ? "content-save" : "personal-save"
309
- );
310
- storeDraft();
311
- }
312
- });
313
- return saving;
314
- }
315
- function reviewUrl() {
316
- return options2.reviewUrl ?? (options2.stateUrl ? options2.stateUrl.replace(/design-state$/, "review") : null);
317
- }
318
- function storeReviewDraft() {
319
- if (!latestReview) return;
320
- try {
321
- localStorage.setItem(
322
- `${storageKey}.review`,
323
- JSON.stringify({ review: latestReview, unsaved: reviewDirty })
324
- );
325
- } catch {
326
- }
327
- }
328
- function flushReview() {
329
- clearTimeout(reviewTimer);
330
- if (!reviewDirty || !latestReview) return reviewSaving;
331
- const captured = clone(latestReview);
332
- const capturedSerial = reviewSerial;
333
- reviewSaving = reviewSaving.catch(() => {
334
- }).then(async () => {
335
- try {
336
- if (reviewUrl())
337
- await jsonRequest(reviewUrl(), {
338
- method: "PUT",
339
- headers: { "Content-Type": "application/json" },
340
- body: JSON.stringify({ review: captured })
341
- });
342
- clearNotice("review-save");
343
- if (capturedSerial === reviewSerial) {
344
- reviewDirty = false;
345
- if (!contentDirty) status(reviewUrl() ? "All changes saved" : "Saved in this browser");
346
- }
347
- storeReviewDraft();
348
- } catch (error) {
349
- status("Comments unsaved", "disconnected");
350
- notice(
351
- `Comments could not be saved: ${error.message} Your draft remains in this browser.`,
352
- true,
353
- "review-save"
354
- );
355
- storeReviewDraft();
356
- }
357
- });
358
- return reviewSaving;
359
- }
360
- function flush() {
361
- return Promise.all([flushState(), flushReview()]);
362
- }
363
- function entryFor(screenId = state.screenId, variantId = state.variantId, frameId = state.frameId) {
364
- return entries.find(
365
- (entry) => entry.screenId === screenId && entry.variantId === variantId && entry.frameId === frameId
366
- );
367
- }
368
- function visibleEntries() {
369
- return entries.filter(
370
- (entry) => (state.view === "canvas" && state.compare || entry.variantId === state.variantId) && (state.view === "canvas" || entry.screenId === state.screenId && entry.frameId === state.frameId)
371
- );
372
- }
373
- function updatePan() {
374
- root.dataset.designPanActive = String(state.view === "canvas" && (panEnabled || spacePan));
375
- root.dataset.designTool = activeTool;
376
- root.dataset.designDragging = String(gesture?.mode === "pan");
377
- q("[data-design-pan]").setAttribute("aria-pressed", String(activeTool === "pan"));
378
- q('[data-planr-mode="interact"]').setAttribute(
379
- "aria-pressed",
380
- String(activeTool === "interact")
381
- );
382
- q('[data-planr-mode="comment"]').setAttribute(
383
- "aria-pressed",
384
- String(activeTool === "annotate")
385
- );
386
- q("[data-design-inspect-mode]")?.setAttribute("aria-pressed", String(activeTool === "inspect"));
387
- for (const [id, panel] of panels) {
388
- const frame = stage.getFrame(id);
389
- if (frame) {
390
- const tabIndex = activeTool === "interact" && !panel.hidden && stage.getState().status === "ready" ? 0 : -1;
391
- if (frame.tabIndex !== tabIndex) frame.tabIndex = tabIndex;
392
- frame.__openPlanrBridge?.setViewportGestures?.(state.view === "canvas" && !panel.hidden);
393
- }
394
- }
395
- }
396
- function finishGesture() {
397
- if (!gesture) return;
398
- const pointerId = gesture.pointerId;
399
- gesture = null;
400
- try {
401
- if (scroll.hasPointerCapture?.(pointerId)) scroll.releasePointerCapture(pointerId);
402
- } catch {
403
- }
404
- updatePan();
405
- persist();
406
- }
407
- function setTool(tool) {
408
- if (!["interact", "annotate", "pan", "inspect"].includes(tool)) return;
409
- if (tool === "pan" && state.view !== "canvas") return;
410
- if (tool === "annotate" && root.hasAttribute("data-planr-room-comments-paused")) return;
411
- finishWalkthroughTransition();
412
- finishGesture();
413
- spacePan = false;
414
- spaceReturnTool = null;
415
- activeTool = tool;
416
- panEnabled = tool === "pan";
417
- previousReviewMode = tool === "annotate" ? "comment" : "interact";
418
- changingTool = true;
419
- try {
420
- stage.dispatch({ type: "set-review-mode", reviewMode: previousReviewMode });
421
- } finally {
422
- changingTool = false;
423
- }
424
- root.dispatchEvent(new CustomEvent("planr:design-tool-change", { detail: { tool } }));
425
- updatePan();
426
- }
427
- function releaseTemporaryPan() {
428
- const previous = spaceReturnTool;
429
- finishGesture();
430
- if (spacePan) setTool(previous || "interact");
431
- }
432
- function saveViewport() {
433
- state.viewports[state.view] = {
434
- x: state.camera.x,
435
- y: state.camera.y,
436
- zoom: state.zoom
437
- };
438
- }
439
- function restoreViewport(view) {
440
- const viewport = state.viewports[view];
441
- if (!viewport) return false;
442
- state.camera = { x: viewport.x, y: viewport.y };
443
- state.zoom = viewport.zoom;
444
- return true;
445
- }
446
- function switchView(view, { save = true } = {}) {
447
- if (!views.includes(view)) throw new TypeError(`Unknown design view: ${view}`);
448
- if (view === state.view) return;
449
- const queued = pendingScreen ? null : queuedSelection;
450
- finishWalkthroughTransition();
451
- closeDesignNotes();
452
- saveViewport();
453
- state.view = view;
454
- setTool(view === "canvas" && activeTool === "annotate" ? "annotate" : "interact");
455
- root.dispatchEvent(new CustomEvent("planr:design-view-change", { detail: { view } }));
456
- const restored = view === "canvas" && restoreViewport(view);
457
- render();
458
- if (!restored) fit({ save: false });
459
- if (queued) selectEntry(queued.entry, { ...queued.settings, save: false });
460
- if (save) {
461
- persist();
462
- recordLocation();
463
- }
464
- }
465
- function defaultPosition(entry, variant) {
466
- const gap = 96;
467
- const top = 72;
468
- const frame = design.frames.find(({ id }) => id === entry.frameId);
469
- const variants = state.compare ? readyVariants : [variant];
470
- const variantIndex = variants.findIndex(({ id }) => id === entry.variantId);
471
- const groupWidth = design.frames.reduce((sum, item) => sum + item.width, 0) + gap * Math.max(0, design.frames.length - 1) + 160;
472
- return {
473
- x: variantIndex * groupWidth + design.frames.slice(0, design.frames.indexOf(frame)).reduce((sum, item) => sum + item.width + gap, 0),
474
- y: top + design.screenOrder.indexOf(entry.screenId) * (Math.max(...design.frames.map(({ height }) => height)) + 176)
475
- };
476
- }
477
- function contentBounds(selection = null) {
478
- const visible = selection ? [selection] : visibleEntries();
479
- if (!visible.length) return { minX: 0, minY: 0, maxX: 1, maxY: 1 };
480
- return visible.reduce(
481
- (bounds, entry) => {
482
- const panel = panels.get(entry.artifactId);
483
- const frame = design.frames.find(({ id }) => id === entry.frameId);
484
- const x = Number.parseFloat(panel.style.left) || 0;
485
- const y = Number.parseFloat(panel.style.top) || 0;
486
- bounds.minX = Math.min(bounds.minX, x);
487
- bounds.minY = Math.min(bounds.minY, y - 64);
488
- bounds.maxX = Math.max(bounds.maxX, x + frame.width);
489
- bounds.maxY = Math.max(bounds.maxY, y + frame.height);
490
- return bounds;
491
- },
492
- { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }
493
- );
494
- }
495
- function centerEntry(entry) {
496
- const panel = panels.get(entry.artifactId);
497
- const frame = design.frames.find(({ id }) => id === entry.frameId);
498
- const x = Number.parseFloat(panel.style.left) || 0;
499
- const y = Number.parseFloat(panel.style.top) || 0;
500
- state.camera = {
501
- x: scroll.clientWidth / 2 - (x + frame.width / 2) * state.zoom,
502
- y: scroll.clientHeight / 2 - (y + frame.height / 2) * state.zoom
503
- };
504
- saveViewport();
505
- render();
506
- }
507
- function updateDesignNotes() {
508
- const notes = q("[data-design-notes]");
509
- if (!notes) return;
510
- notes.querySelector(":scope > summary").setAttribute("aria-expanded", String(notes.open));
511
- for (const button of qa("[data-design-note]"))
512
- button.setAttribute(
513
- "aria-expanded",
514
- String(notes.open && button.dataset.designNote === noteScreenId)
515
- );
516
- if (!notes.open) return;
517
- const panel = q(".design-guidance-panel");
518
- const anchor = notes.querySelector(":scope > summary").getBoundingClientRect();
519
- const width = panel.getBoundingClientRect().width || Math.min(340, window.innerWidth - 24);
520
- const top = clamp2(anchor.bottom + 7, 12, Math.max(12, window.innerHeight - 100));
521
- panel.style.left = `${clamp2(anchor.right - width, 12, Math.max(12, window.innerWidth - width - 12))}px`;
522
- panel.style.top = `${top}px`;
523
- panel.style.maxHeight = `${Math.min(520, Math.max(60, window.innerHeight - top - 12))}px`;
524
- }
525
- function closeDesignNotes({ restoreFocus = false } = {}) {
526
- const notes = q("[data-design-notes]");
527
- if (!notes?.open) return;
528
- notes.open = false;
529
- noteScreenId = null;
530
- updateDesignNotes();
531
- if (restoreFocus) {
532
- const target = notesReturnFocus?.isConnected ? notesReturnFocus : notes.querySelector(":scope > summary");
533
- target?.focus({ preventScroll: true });
534
- }
535
- notesReturnFocus = null;
536
- }
537
- function openDesignNote(screenId, trigger) {
538
- const notes = q("[data-design-notes]");
539
- if (!notes) return;
540
- if (notes.open && noteScreenId === screenId) {
541
- closeDesignNotes();
542
- return;
543
- }
544
- notesReturnFocus = trigger;
545
- noteScreenId = screenId;
546
- notes.open = true;
547
- let target = null;
548
- for (const item of qa("[data-design-note-screen]")) {
549
- const selected = item.dataset.designNoteScreen === screenId;
550
- item.open = selected;
551
- item.dataset.active = String(selected);
552
- if (selected) target = item;
553
- }
554
- updateDesignNotes();
555
- const panel = q(".design-guidance-panel");
556
- if (target)
557
- panel.scrollTop = Math.max(0, target.offsetTop - panel.querySelector("header").offsetHeight);
558
- }
559
- function render() {
560
- if (!stage || rendering || destroyed) return;
561
- rendering = true;
562
- try {
563
- if (window.innerWidth <= 680)
564
- root.style.setProperty(
565
- "--planr-toolbar-height",
566
- `${q(".design-toolbar").getBoundingClientRect().height}px`
567
- );
568
- else root.style.removeProperty("--planr-toolbar-height");
569
- const screen = design.screens.find(({ id }) => id === state.screenId);
570
- const variant = design.variants.find(({ id }) => id === state.variantId);
571
- const stageState = stage.getState();
572
- root.dataset.designView = state.view;
573
- root.dataset.designNavOpen = String(state.navOpen);
574
- const navigator2 = q(".design-navigator");
575
- navigator2.inert = !state.navOpen;
576
- navigator2.setAttribute("aria-hidden", String(!state.navOpen));
577
- for (const toggle of qa("[data-design-toggle-nav]"))
578
- toggle.setAttribute("aria-expanded", String(state.navOpen));
579
- for (const button of qa("[data-design-view]")) {
580
- if (button.tagName === "BUTTON")
581
- button.setAttribute("aria-pressed", String(button.dataset.designView === state.view));
582
- }
583
- for (const button of qa("[data-design-screen]"))
584
- button.setAttribute(
585
- "aria-current",
586
- button.dataset.designScreen === state.screenId ? "page" : "false"
587
- );
588
- q("[data-design-variant]").value = failedSelection?.variantId ?? state.variantId;
589
- q("[data-design-frame]").value = failedSelection?.frameId ?? state.frameId;
590
- if (q("[data-design-compare]")) q("[data-design-compare]").checked = state.compare;
591
- q("[data-design-screen-title]").textContent = state.view === "canvas" ? design.title : screen.title;
592
- 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";
593
- q("[data-design-direction-label]").textContent = variant.label;
594
- const chosen = state.selectedVariant === state.variantId && state.preferences.selected.includes(state.variantId);
595
- q("[data-design-selected-direction]").textContent = chosen ? "Selected" : "";
596
- q("[data-design-select-direction]").disabled = chosen;
597
- q("[data-design-select-direction]").textContent = chosen ? "Selected direction" : "Use this direction";
598
- for (const button of qa("[data-design-rating]")) {
599
- const selected = Number(button.dataset.designRating) <= (state.ratings[state.variantId] ?? 0);
600
- button.setAttribute("aria-pressed", String(selected));
601
- button.textContent = selected ? "★" : "☆";
602
- }
603
- if (document.activeElement !== q("[data-design-remix]"))
604
- q("[data-design-remix]").value = state.remix[state.variantId] ?? "";
605
- q("[data-design-pan]").hidden = state.view !== "canvas";
606
- for (const control of qa("[data-design-zoom]")) control.hidden = state.view !== "canvas";
607
- q("[data-design-actual-size]").hidden = state.view === "canvas";
608
- q("[data-design-actual-size]").setAttribute(
609
- "aria-pressed",
610
- String(state.inspectionScale === "actual")
611
- );
612
- root.dataset.designInspectionScale = state.inspectionScale;
613
- q(".design-canvas-tools").setAttribute(
614
- "aria-label",
615
- state.view === "canvas" ? "Canvas controls" : "Screen controls"
616
- );
617
- const fitControl = q("[data-design-fit]");
618
- fitControl.textContent = state.view === "canvas" ? "Fit" : "Fit screen";
619
- fitControl.setAttribute(
620
- "aria-label",
621
- state.view === "canvas" ? "Fit all visible artboards" : "Fit screen"
622
- );
623
- const comparison = q("[data-design-compare]");
624
- if (comparison) comparison.closest("label").hidden = state.view !== "canvas";
625
- const caption = q(".design-walkthrough-caption");
626
- caption.hidden = state.view !== "walkthrough";
627
- q("[data-design-step]").textContent = `Step ${design.screenOrder.indexOf(state.screenId) + 1} of ${design.screenOrder.length}`;
628
- q("[data-design-narrative-title]").textContent = screen.title;
629
- q("[data-design-narrative]").textContent = screen.description ?? design.flows?.find((flow) => flow.screens.includes(screen.id))?.title ?? design.brief?.text ?? "";
630
- q('[data-design-step-change="-1"]').disabled = requestedScreen() === design.screenOrder[0];
631
- q('[data-design-step-change="1"]').disabled = requestedScreen() === design.screenOrder.at(-1);
632
- for (const item of qa("[data-design-note-screen]"))
633
- item.dataset.active = String(item.dataset.designNoteScreen === state.screenId);
634
- const visible = visibleEntries();
635
- const visibleIds = new Set(visible.map(({ artifactId }) => artifactId));
636
- for (const entry of entries) {
637
- const panel = panels.get(entry.artifactId);
638
- const isVisible = visibleIds.has(entry.artifactId);
639
- const outgoing = walkthroughTransition?.from === entry.artifactId;
640
- panel.hidden = !isVisible && !outgoing;
641
- panel.dataset.designActive = String(entry.artifactId === activeArtifactId);
642
- const frameElement = stage.getFrame(entry.artifactId);
643
- const layer = panel.querySelector("[data-planr-annotation-layer]");
644
- const ready = stageState.status === "ready" && (!stage.frameBudget || frameElement.dataset.planrFrameState === "ready");
645
- frameElement.tabIndex = isVisible && ready && stageState.reviewMode === "interact" ? 0 : -1;
646
- layer.tabIndex = isVisible && ready && stageState.reviewMode === "comment" ? 0 : -1;
647
- layer.setAttribute("aria-disabled", String(layer.tabIndex < 0));
648
- if (!isVisible && !outgoing) continue;
649
- let { x, y } = defaultPosition(entry, variant);
650
- if (state.view === "canvas") ({ x, y } = state.positions[entry.artifactId] ?? { x, y });
651
- else {
652
- x = 0;
653
- y = 0;
654
- }
655
- panel.style.left = `${x}px`;
656
- panel.style.top = `${y}px`;
657
- }
658
- grid.style.width = "1px";
659
- grid.style.height = "1px";
660
- surface.style.width = "100%";
661
- surface.style.height = "100%";
662
- updatePan();
663
- renderCamera();
664
- chrome?.update({ view: state.view, navOpen: state.navOpen, reviewOpen: stageState.railOpen });
665
- root.dispatchEvent(new CustomEvent("planr:design-render", { detail: clone(state) }));
666
- } finally {
667
- rendering = false;
668
- }
669
- }
670
- function scheduleCamera() {
671
- if (cameraFrame !== null || destroyed) return;
672
- cameraFrame = requestAnimationFrame(() => {
673
- cameraFrame = null;
674
- renderCamera();
675
- });
676
- }
677
- function renderCamera() {
678
- if (!stage || destroyed) return;
679
- if (cameraFrame !== null) {
680
- cancelAnimationFrame(cameraFrame);
681
- cameraFrame = null;
682
- }
683
- if (state.view !== "canvas") {
684
- const frame = design.frames.find((item) => item.id === state.frameId);
685
- const inset = window.innerWidth <= 680 ? 12 : 24;
686
- state.zoom = state.inspectionScale === "actual" ? 1 : Math.max(
687
- 1e-3,
688
- Math.min(
689
- 1,
690
- Math.max(1, scroll.clientWidth - inset * 2) / frame.width,
691
- Math.max(1, scroll.clientHeight - inset * 2) / frame.height
692
- )
693
- );
694
- state.camera = {
695
- x: state.inspectionScale === "actual" ? inset : (scroll.clientWidth - frame.width * state.zoom) / 2,
696
- y: state.inspectionScale === "actual" ? inset : (scroll.clientHeight - frame.height * state.zoom) / 2
697
- };
698
- if (state.inspectionScale === "actual") {
699
- surface.style.width = `${Math.max(scroll.clientWidth, frame.width + inset * 2)}px`;
700
- surface.style.height = `${Math.max(scroll.clientHeight, frame.height + inset * 2)}px`;
701
- }
702
- saveViewport();
703
- }
704
- const zoom = String(state.zoom);
705
- if (grid.style.getPropertyValue("--design-zoom") !== zoom)
706
- grid.style.setProperty("--design-zoom", zoom);
707
- grid.style.transform = `translate(${state.camera.x}px, ${state.camera.y}px) scale(${state.zoom})`;
708
- const pixelRatio = window.devicePixelRatio || 1;
709
- const backgroundOffset = (value) => Math.round(value % 24 * pixelRatio) / pixelRatio;
710
- canvasBackground.style.transform = `translate3d(${backgroundOffset(state.camera.x)}px, ${backgroundOffset(state.camera.y)}px, 0)`;
711
- const active = entries.find((entry) => entry.artifactId === activeArtifactId);
712
- const panel = panels.get(activeArtifactId);
713
- if (selectionOutline) {
714
- selectionOutline.hidden = state.view !== "canvas" || !active || !panel || panel.hidden;
715
- if (!selectionOutline.hidden) {
716
- const frame = design.frames.find((item) => item.id === active.frameId);
717
- const ratio = window.devicePixelRatio || 1;
718
- const pixel = (value) => Math.round(value * ratio) / ratio;
719
- const x = pixel(state.camera.x + Number.parseFloat(panel.style.left) * state.zoom);
720
- const y = pixel(state.camera.y + Number.parseFloat(panel.style.top) * state.zoom);
721
- selectionOutline.style.transform = `translate3d(${x - 3}px, ${y - 3}px, 0)`;
722
- selectionOutline.style.width = `${pixel(frame.width * state.zoom) + 6}px`;
723
- selectionOutline.style.height = `${pixel(frame.height * state.zoom) + 6}px`;
724
- }
725
- }
726
- const label = `${Math.round(state.zoom * 100)}%`;
727
- const reset = q('[data-design-zoom="reset"]');
728
- if (reset.textContent !== label) reset.textContent = label;
729
- scheduleFrameDemand();
730
- root.dispatchEvent(
731
- new CustomEvent("planr:design-camera", {
732
- detail: { view: state.view, zoom: state.zoom, camera: { ...state.camera } }
733
- })
734
- );
735
- }
736
- function updateFramePlaceholders() {
737
- if (!stage?.frameBudget || destroyed) return;
738
- for (const [id, panel] of panels) {
739
- const frame = stage.getFrame(id), placeholder = panel.querySelector("[data-design-load-frame]");
740
- if (!placeholder) continue;
741
- const status2 = frame.dataset.planrFrameState || "unloaded";
742
- placeholder.hidden = status2 === "ready";
743
- placeholder.disabled = status2 === "loading";
744
- const copy = status2 === "loading" ? "Loading screen…" : status2 === "error" ? "Retry screen" : "Open screen";
745
- if (placeholder.textContent !== copy) placeholder.textContent = copy;
746
- frame.style.visibility = status2 === "ready" ? "" : "hidden";
747
- }
748
- }
749
- function scheduleFrameDemand() {
750
- if (!stage?.frameBudget || destroyed) return;
751
- clearTimeout(frameDemandTimer);
752
- const generation = frameDemandGeneration;
753
- frameDemandTimer = setTimeout(() => {
754
- if (destroyed || generation !== frameDemandGeneration || !readyReported || pendingScreen || walkthroughTransition || state.view !== "canvas" || root.dataset.sharedOpening || document.querySelector(".design-welcome[open]"))
755
- return;
756
- const box = scroll.getBoundingClientRect();
757
- const nearby = visibleEntries().map((entry) => {
758
- const rect = panels.get(entry.artifactId).getBoundingClientRect();
759
- return {
760
- id: entry.artifactId,
761
- visible: rect.right > box.left && rect.left < box.right && rect.bottom > box.top && rect.top < box.bottom,
762
- distance: Math.hypot(
763
- (rect.left + rect.right - box.left - box.right) / 2,
764
- (rect.top + rect.bottom - box.top - box.bottom) / 2
765
- )
766
- };
767
- }).filter((item) => item.visible).sort((a, b) => a.distance - b.distance);
768
- const ids = [
769
- ...new Set([activeArtifactId, ...nearby.map((item) => item.id)].filter(Boolean))
770
- ].slice(0, stage.frameBudget);
771
- const key = ids.join("|");
772
- if (key === frameDemandKey) return;
773
- frameDemandKey = key;
774
- void stage.ensureFrames(ids, () => !destroyed && generation === frameDemandGeneration).then(updateFramePlaceholders).catch((error) => {
775
- if (!destroyed && error.name !== "AbortError") {
776
- frameDemandKey = "";
777
- updateFramePlaceholders();
778
- }
779
- });
780
- }, 120);
781
- }
782
- function finishWalkthroughTransition({ continueQueued = false } = {}) {
783
- const transition = walkthroughTransition;
784
- const queued = continueQueued ? queuedSelection : null;
785
- queuedSelection = null;
786
- if (!transition) return;
787
- walkthroughTransition = null;
788
- cancelAnimationFrame(transition.frame);
789
- clearTimeout(transition.timeout);
790
- for (const animation of transition.animations ?? []) animation.cancel();
791
- for (const visual of transition.visuals ?? []) delete visual.dataset.designTransitionVisual;
792
- for (const id of [transition.from, transition.to]) {
793
- const panel = panels.get(id);
794
- panel.inert = false;
795
- panel.removeAttribute("aria-hidden");
796
- delete panel.dataset.designTransitionOutgoing;
797
- delete panel.dataset.designTransitionIncoming;
798
- }
799
- delete root.dataset.designTransition;
800
- render();
801
- if (queued && !destroyed) selectEntry(queued.entry, queued.settings);
802
- }
803
- function prepareWalkthroughTransition(entry) {
804
- const from = activeArtifactId;
805
- const outgoing = panels.get(from);
806
- const incoming = panels.get(entry.artifactId);
807
- const direction = design.screenOrder.indexOf(entry.screenId) < design.screenOrder.indexOf(state.screenId) ? -1 : 1;
808
- const transition = {
809
- from,
810
- to: entry.artifactId,
811
- direction,
812
- frame: null,
813
- timeout: null,
814
- animations: [],
815
- visuals: []
816
- };
817
- walkthroughTransition = transition;
818
- root.dataset.designTransition = "preparing";
819
- outgoing.dataset.designTransitionOutgoing = "";
820
- incoming.dataset.designTransitionIncoming = "";
821
- if (outgoing.contains(document.activeElement))
822
- q(`[data-design-step-change="${direction}"]`).focus({ preventScroll: true });
823
- outgoing.inert = incoming.inert = true;
824
- outgoing.setAttribute("aria-hidden", "true");
825
- const finish = () => {
826
- if (walkthroughTransition === transition)
827
- finishWalkthroughTransition({ continueQueued: true });
828
- };
829
- transition.timeout = setTimeout(finish, 1e3);
830
- transition.frame = requestAnimationFrame(() => {
831
- transition.frame = requestAnimationFrame(() => {
832
- if (walkthroughTransition !== transition || destroyed) return;
833
- if (document.hidden || window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches || typeof incoming.animate !== "function") {
834
- finish();
835
- return;
836
- }
837
- root.dataset.designTransition = "running";
838
- const frameVisual = incoming.querySelector(".planr-frame iframe");
839
- const annotationVisual = incoming.querySelector("[data-planr-annotation-layer]");
840
- transition.visuals = [frameVisual, annotationVisual].filter(Boolean);
841
- for (const visual of transition.visuals) visual.dataset.designTransitionVisual = "";
842
- const options3 = { duration: 220, easing: "cubic-bezier(0.22, 1, 0.36, 1)", fill: "both" };
843
- transition.animations = [
844
- frameVisual.animate(
845
- [
846
- { opacity: 0, transform: `translate3d(${direction * 12 / state.zoom}px, 0, 0)` },
847
- { opacity: 1, transform: "translate3d(0, 0, 0)" }
848
- ],
849
- options3
850
- )
851
- ];
852
- if (annotationVisual)
853
- transition.animations.push(
854
- annotationVisual.animate([{ opacity: 0 }, { opacity: 1 }], options3)
855
- );
856
- Promise.all(transition.animations.map((animation) => animation.finished)).then(
857
- finish,
858
- () => {
859
- }
860
- );
861
- });
862
- });
863
- }
864
- function requestedSelection() {
865
- return pendingScreen?.entry ?? queuedSelection?.entry ?? failedSelection ?? state;
866
- }
867
- function requestedEntry(changes = {}) {
868
- const requested = requestedSelection();
869
- return entryFor(
870
- changes.screenId ?? requested.screenId,
871
- changes.variantId ?? requested.variantId,
872
- changes.frameId ?? requested.frameId
873
- );
874
- }
875
- function requestedScreen() {
876
- return requestedSelection().screenId;
877
- }
878
- function selectEntry(entry, { save = true, reveal = false, loaded = false } = {}) {
879
- if (!entry) return false;
880
- if (stage.frameBudget && !loaded && (pendingScreen || !stage.getLoadedArtifactIds().includes(entry.artifactId))) {
881
- queuedSelection = null;
882
- if (pendingScreen?.entry.artifactId === entry.artifactId) return true;
883
- clearFailedSelection();
884
- const request = ++screenRequest;
885
- pendingScreen = { entry };
886
- root.dataset.designScreenLoading = "true";
887
- const ids = [...new Set([activeArtifactId, entry.artifactId].filter(Boolean))];
888
- void stage.ensureFrames(ids).then(() => {
889
- if (destroyed || request !== screenRequest) return;
890
- pendingScreen = null;
891
- delete root.dataset.designScreenLoading;
892
- selectEntry(entry, { save, reveal, loaded: true });
893
- updateFramePlaceholders();
894
- }).catch((error) => {
895
- if (destroyed || request !== screenRequest || error.name === "AbortError") return;
896
- pendingScreen = null;
897
- failedSelection = entry;
898
- delete root.dataset.designScreenLoading;
899
- notice(
900
- "This screen could not load. Select it again to retry; your review is preserved.",
901
- true,
902
- `preview-load:${entry.artifactId}`
903
- );
904
- render();
905
- updateFramePlaceholders();
906
- });
907
- return true;
908
- }
909
- clearFailedSelection();
910
- const sameFrame = entry.frameId === state.frameId && entry.variantId === state.variantId;
911
- if (walkthroughTransition && state.view === "walkthrough" && sameFrame) {
912
- queuedSelection = entry.artifactId === activeArtifactId ? null : { entry, settings: { save, reveal } };
913
- q('[data-design-step-change="-1"]').disabled = requestedScreen() === design.screenOrder[0];
914
- q('[data-design-step-change="1"]').disabled = requestedScreen() === design.screenOrder.at(-1);
915
- return true;
916
- }
917
- finishWalkthroughTransition();
918
- if (state.view === "walkthrough" && sameFrame && readyReported && entry.artifactId !== activeArtifactId && !document.hidden)
919
- prepareWalkthroughTransition(entry);
920
- state.screenId = entry.screenId;
921
- state.frameId = entry.frameId;
922
- state.variantId = entry.variantId;
923
- activeArtifactId = entry.artifactId;
924
- stage.dispatch({ type: "set-active", artifactId: entry.artifactId });
925
- render();
926
- if (reveal) centerEntry(entry);
927
- if (save) {
928
- persist();
929
- recordLocation();
930
- }
931
- return true;
932
- }
933
- function fit({ save = true, selection = null } = {}) {
934
- state.inspectionScale = "fit";
935
- render();
936
- if (state.view !== "canvas") {
937
- if (save) persist();
938
- return;
939
- }
940
- const bounds = contentBounds(selection);
941
- const width = Math.max(1, bounds.maxX - bounds.minX);
942
- const height = Math.max(1, bounds.maxY - bounds.minY);
943
- const availableWidth = Math.max(100, scroll.clientWidth - 96);
944
- const availableHeight = Math.max(100, scroll.clientHeight - 96);
945
- state.zoom = clamp2(
946
- Math.min(availableWidth / Math.max(1, width), availableHeight / Math.max(1, height)),
947
- 0.05,
948
- 1
949
- );
950
- state.camera = {
951
- x: (scroll.clientWidth - width * state.zoom) / 2 - bounds.minX * state.zoom,
952
- y: (scroll.clientHeight - height * state.zoom) / 2 - bounds.minY * state.zoom
953
- };
954
- saveViewport();
955
- renderCamera();
956
- if (save) persist();
957
- }
958
- function zoomTo(value, point = null, { scheduled = false } = {}) {
959
- if (state.view !== "canvas") return;
960
- const previous = state.zoom;
961
- const next = clamp2(value, 0.05, 2);
962
- const rect = scroll.getBoundingClientRect();
963
- const x = point ? point.x - rect.left : scroll.clientWidth / 2;
964
- const y = point ? point.y - rect.top : scroll.clientHeight / 2;
965
- state.camera = {
966
- x: x - (x - state.camera.x) * next / previous,
967
- y: y - (y - state.camera.y) * next / previous
968
- };
969
- state.zoom = next;
970
- saveViewport();
971
- if (scheduled) scheduleCamera();
972
- else renderCamera();
973
- persist();
974
- }
975
- async function exportArtifact(kind) {
976
- const artifactId = activeArtifactId;
977
- try {
978
- let blob;
979
- if (kind === "png") {
980
- const result = typeof options2.exportPng === "function" ? await options2.exportPng({ artifactId }) : await stage.getFrame(artifactId)?.__openPlanrBridge?.exportPng("screen");
981
- const dataUrl = typeof result === "string" ? result : result?.dataUrl;
982
- if (!dataUrl?.startsWith("data:image/png;base64,"))
983
- throw new Error(
984
- "PNG capture is unavailable. Open the local review with the artifact bridge and retry."
985
- );
986
- const bytes = Uint8Array.from(
987
- atob(dataUrl.split(",")[1]),
988
- (character) => character.charCodeAt(0)
989
- );
990
- blob = new Blob([bytes], { type: "image/png" });
991
- } else {
992
- const result = typeof options2.exportHtml === "function" ? await options2.exportHtml({ artifactId }) : null;
993
- if (result instanceof Blob) blob = result;
994
- else if (typeof result === "string")
995
- blob = new Blob([result], { type: "text/html;charset=utf-8" });
996
- else
997
- throw new Error(
998
- "Portable export is unavailable in this viewer. Use the design export utility from the project."
999
- );
1000
- }
1001
- const url = URL.createObjectURL(blob);
1002
- const anchor = document.createElement("a");
1003
- anchor.href = url;
1004
- anchor.download = `${design.id}-${state.screenId}.${kind === "png" ? "png" : "html"}`;
1005
- anchor.click();
1006
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
1007
- q(".design-export").open = false;
1008
- notice(kind === "png" ? "Screen PNG exported." : "Portable HTML exported.");
1009
- } catch (error) {
1010
- notice(error.message, true);
1011
- }
1012
- }
1013
- function editable(target) {
1014
- return Boolean(target?.closest?.('input,textarea,select,[contenteditable="true"]'));
1015
- }
1016
- function installEvents() {
1017
- listen(root, "planr:artifact-viewport-zoom", (event) => {
1018
- if (state.view !== "canvas") return;
1019
- const entry = entries.find((item) => stage.getFrame(item.artifactId) === event.target);
1020
- if (!entry || panels.get(entry.artifactId)?.hidden) return;
1021
- const { x, y, deltaY } = event.detail;
1022
- const frame = design.frames.find((item) => item.id === entry.frameId);
1023
- const rect = event.target.getBoundingClientRect();
1024
- chrome?.update({ exportMenuOpen: false });
1025
- zoomTo(
1026
- state.zoom * Math.exp(-deltaY * 4e-3),
1027
- {
1028
- x: rect.left + x * rect.width / frame.width,
1029
- y: rect.top + y * rect.height / frame.height
1030
- },
1031
- { scheduled: true }
1032
- );
1033
- });
1034
- listen(root, "planr:artifact-viewport-pan", (event) => {
1035
- if (state.view !== "canvas") return;
1036
- const entry = entries.find((item) => stage.getFrame(item.artifactId) === event.target);
1037
- if (!entry || panels.get(entry.artifactId)?.hidden) return;
1038
- chrome?.update({ exportMenuOpen: false });
1039
- state.camera = {
1040
- x: clamp2(state.camera.x - event.detail.deltaX, -1e7, 1e7),
1041
- y: clamp2(state.camera.y - event.detail.deltaY, -1e7, 1e7)
1042
- };
1043
- saveViewport();
1044
- scheduleCamera();
1045
- persist();
1046
- });
1047
- listen(
1048
- root,
1049
- "click",
1050
- (event) => {
1051
- const button = event.target.closest('[data-planr-mode],[data-planr-action="add-comment"]');
1052
- if (!button || button.disabled) return;
1053
- setTool(button.dataset.planrMode === "interact" ? "interact" : "annotate");
1054
- },
1055
- true
1056
- );
1057
- listen(
1058
- document,
1059
- "keydown",
1060
- (event) => {
1061
- if (editable(event.target) || event.altKey || event.ctrlKey || event.metaKey || document.querySelector("dialog[open],[role=dialog][data-state=open]"))
1062
- return;
1063
- const key = event.key.toLowerCase();
1064
- if (["i", "c", "h"].includes(key)) {
1065
- if (key === "h" && state.view !== "canvas") return;
1066
- event.preventDefault();
1067
- setTool({ i: "interact", c: "annotate", h: "pan" }[key]);
1068
- } else if (event.key === "Escape" && (panEnabled || spacePan || gesture || activeTool === "inspect") && !q("[data-design-notes][open]") && !q("[data-planr-annotation-composer]")) {
1069
- event.preventDefault();
1070
- setTool("interact");
1071
- q('[data-planr-mode="interact"]').focus();
1072
- }
1073
- },
1074
- true
1075
- );
1076
- if (typeof ResizeObserver === "function") {
1077
- const observer = new ResizeObserver(() => {
1078
- if (state.view !== "canvas" && root.dataset.designRestoring !== "true")
1079
- fit({ save: false });
1080
- updateDesignNotes();
1081
- });
1082
- observer.observe(scroll);
1083
- cleanup.push(() => observer.disconnect());
1084
- }
1085
- const notes = q("[data-design-notes]");
1086
- listen(notes?.querySelector(":scope > summary"), "click", (event) => {
1087
- event.preventDefault();
1088
- if (notes.open) closeDesignNotes();
1089
- else {
1090
- notesReturnFocus = event.currentTarget;
1091
- notes.open = true;
1092
- updateDesignNotes();
1093
- }
1094
- });
1095
- listen(notes, "toggle", updateDesignNotes);
1096
- listen(
1097
- document,
1098
- "pointerdown",
1099
- (event) => {
1100
- if (notes?.open && !notes.contains(event.target) && !event.target.closest?.("[data-design-note]"))
1101
- closeDesignNotes();
1102
- },
1103
- true
1104
- );
1105
- listen(
1106
- document,
1107
- "keydown",
1108
- (event) => {
1109
- if (event.key === "Escape" && notes?.open) {
1110
- event.preventDefault();
1111
- event.stopPropagation();
1112
- closeDesignNotes({ restoreFocus: true });
1113
- }
1114
- },
1115
- true
1116
- );
1117
- listen(root, "click", (event) => {
1118
- const button = event.target.closest("button");
1119
- if (!button) return;
1120
- if (button.dataset.designLoadFrame) {
1121
- selectEntry(
1122
- entries.find((item) => item.artifactId === button.dataset.designLoadFrame),
1123
- { reveal: true }
1124
- );
1125
- } else if (button.hasAttribute("data-design-actual-size")) {
1126
- state.inspectionScale = "actual";
1127
- render();
1128
- persist();
1129
- } else if (button.hasAttribute("data-design-focus")) {
1130
- state.navOpen = false;
1131
- state.reviewOpen = false;
1132
- stage.dispatch({ type: "set-rail-open", railOpen: false });
1133
- if (state.view === "canvas") switchView("prototype");
1134
- render();
1135
- persist();
1136
- } else if (button.dataset.designView) {
1137
- switchView(button.dataset.designView);
1138
- } else if (button.hasAttribute("data-design-toggle-nav")) {
1139
- state.navOpen = !state.navOpen;
1140
- if (!state.navOpen && button.closest(".design-navigator"))
1141
- q(".design-toolbar [data-design-toggle-nav]").focus();
1142
- render();
1143
- persist();
1144
- } else if (button.dataset.designScreen) {
1145
- selectEntry(requestedEntry({ screenId: button.dataset.designScreen }), {
1146
- reveal: state.view === "canvas"
1147
- });
1148
- } else if (button.dataset.designNote) {
1149
- openDesignNote(button.dataset.designNote, button);
1150
- } else if (button.hasAttribute("data-design-close-notes")) {
1151
- closeDesignNotes({ restoreFocus: true });
1152
- } else if (button.hasAttribute("data-design-fit")) fit();
1153
- else if (button.dataset.designZoom)
1154
- zoomTo(
1155
- button.dataset.designZoom === "reset" ? 1 : state.zoom * (button.dataset.designZoom === "in" ? 1.2 : 1 / 1.2)
1156
- );
1157
- else if (button.hasAttribute("data-design-pan")) {
1158
- if (state.view !== "canvas") return;
1159
- setTool(activeTool === "pan" ? "interact" : "pan");
1160
- } else if (button.dataset.designStepChange) {
1161
- const index2 = clamp2(
1162
- design.screenOrder.indexOf(requestedScreen()) + Number(button.dataset.designStepChange),
1163
- 0,
1164
- design.screenOrder.length - 1
1165
- );
1166
- selectEntry(requestedEntry({ screenId: design.screenOrder[index2] }));
1167
- } else if (button.dataset.designRating) {
1168
- const rating = Number(button.dataset.designRating);
1169
- state.ratings[state.variantId] = rating;
1170
- if (rating <= 2)
1171
- state.preferences.rejected = [
1172
- .../* @__PURE__ */ new Set([...state.preferences.rejected, state.variantId])
1173
- ];
1174
- else
1175
- state.preferences.rejected = state.preferences.rejected.filter(
1176
- (id) => id !== state.variantId
1177
- );
1178
- render();
1179
- persist({ content: true });
1180
- } else if (button.hasAttribute("data-design-select-direction")) {
1181
- state.selectedVariant = state.variantId;
1182
- state.preferences.selected = [.../* @__PURE__ */ new Set([...state.preferences.selected, state.variantId])];
1183
- state.preferences.rejected = state.preferences.rejected.filter(
1184
- (id) => id !== state.variantId
1185
- );
1186
- render();
1187
- persist({ content: true });
1188
- } else if (button.hasAttribute("data-design-save-remix")) {
1189
- state.remix[state.variantId] = q("[data-design-remix]").value.trim();
1190
- persist({ content: true });
1191
- notice("Refinement note saved for the next design iteration.");
1192
- } else if (button.dataset.designExport) void exportArtifact(button.dataset.designExport);
1193
- });
1194
- listen(
1195
- q("[data-design-variant]"),
1196
- "change",
1197
- (event) => selectEntry(requestedEntry({ variantId: event.target.value }))
1198
- );
1199
- listen(q("[data-design-frame]"), "change", (event) => {
1200
- selectEntry(requestedEntry({ frameId: event.target.value }), {
1201
- reveal: state.view === "canvas"
1202
- });
1203
- });
1204
- listen(q("[data-design-compare]"), "change", (event) => {
1205
- state.compare = event.target.checked;
1206
- render();
1207
- persist();
1208
- });
1209
- listen(root, "planr:stage-change", (event) => {
1210
- const next = event.detail;
1211
- if (!changingTool && next.reviewMode !== previousReviewMode)
1212
- setTool(next.reviewMode === "comment" ? "annotate" : "interact");
1213
- const reviewChanged = typeof next.railOpen === "boolean" && next.railOpen !== state.reviewOpen;
1214
- if (reviewChanged) state.reviewOpen = next.railOpen;
1215
- const entry = entries.find(({ artifactId }) => artifactId === next.activeArtifactId);
1216
- if (entry && entry.artifactId !== activeArtifactId) {
1217
- if (stage.frameBudget && !stage.getLoadedArtifactIds().includes(entry.artifactId)) {
1218
- selectEntry(entry, { save: false });
1219
- } else {
1220
- finishWalkthroughTransition();
1221
- activeArtifactId = entry.artifactId;
1222
- state.screenId = entry.screenId;
1223
- state.frameId = entry.frameId;
1224
- state.variantId = entry.variantId;
1225
- }
1226
- }
1227
- if (next.zoom !== previousStageZoom) {
1228
- state.zoom = clamp2(next.zoom / 100, 0.05, 2);
1229
- previousStageZoom = next.zoom;
1230
- saveViewport();
1231
- }
1232
- render();
1233
- if (reviewChanged) persist();
1234
- });
1235
- listen(root, "planr:artifact-review-change", (event) => {
1236
- latestReview = clone(event.detail);
1237
- reviewDirty = true;
1238
- reviewSerial += 1;
1239
- status("Saving comments", "saving");
1240
- storeReviewDraft();
1241
- clearTimeout(reviewTimer);
1242
- reviewTimer = setTimeout(flushReview, 180);
1243
- });
1244
- listen(
1245
- root,
1246
- "pointerdown",
1247
- (event) => {
1248
- const panel = event.target.closest(".planr-artifact-panel");
1249
- if (event.button === 0 && panel && !panEnabled && !spacePan && !event.target.closest("[data-design-note]")) {
1250
- selectEntry(
1251
- entries.find(({ artifactId }) => artifactId === panel.dataset.artifactId),
1252
- { save: false }
1253
- );
1254
- }
1255
- },
1256
- true
1257
- );
1258
- listen(
1259
- scroll,
1260
- "wheel",
1261
- (event) => {
1262
- if (state.view !== "canvas") return;
1263
- event.preventDefault();
1264
- if (event.ctrlKey || event.metaKey) {
1265
- zoomTo(
1266
- state.zoom * Math.exp(-event.deltaY * 4e-3),
1267
- {
1268
- x: event.clientX,
1269
- y: event.clientY
1270
- },
1271
- { scheduled: true }
1272
- );
1273
- return;
1274
- }
1275
- state.camera = {
1276
- x: state.camera.x - (event.shiftKey ? event.deltaY : event.deltaX),
1277
- y: state.camera.y - (event.shiftKey ? 0 : event.deltaY)
1278
- };
1279
- saveViewport();
1280
- scheduleCamera();
1281
- persist();
1282
- },
1283
- { passive: false }
1284
- );
1285
- listen(scroll, "pointerdown", (event) => {
1286
- if (state.view !== "canvas") return;
1287
- if (root.dataset.designInspect === "true") return;
1288
- if (event.button !== 0 && event.button !== 1) return;
1289
- const label = event.target.closest("[data-design-drag]");
1290
- const dragging = event.button === 0 && state.view === "canvas" && label && !panEnabled && !spacePan;
1291
- const panning = event.button === 1 || panEnabled || spacePan || event.target === scroll || event.target === grid || event.target === surface;
1292
- if (!dragging && !panning) return;
1293
- event.preventDefault();
1294
- const panel = label?.closest(".planr-artifact-panel");
1295
- gesture = {
1296
- pointerId: event.pointerId,
1297
- x: event.clientX,
1298
- y: event.clientY,
1299
- mode: dragging ? "artboard" : "pan",
1300
- artifactId: dragging ? panel.dataset.artifactId : null,
1301
- left: dragging ? parseFloat(panel.style.left) : state.camera.x,
1302
- top: dragging ? parseFloat(panel.style.top) : state.camera.y
1303
- };
1304
- scroll.setPointerCapture?.(event.pointerId);
1305
- updatePan();
1306
- });
1307
- listen(scroll, "pointermove", (event) => {
1308
- if (!gesture || gesture.pointerId !== event.pointerId) return;
1309
- const dx = event.clientX - gesture.x;
1310
- const dy = event.clientY - gesture.y;
1311
- if (gesture.mode === "pan") {
1312
- state.camera = {
1313
- x: clamp2(gesture.left + dx, -1e7, 1e7),
1314
- y: clamp2(gesture.top + dy, -1e7, 1e7)
1315
- };
1316
- saveViewport();
1317
- scheduleCamera();
1318
- } else {
1319
- state.positions[gesture.artifactId] = {
1320
- x: clamp2(gesture.left + dx / state.zoom, -1e7, 1e7),
1321
- y: clamp2(gesture.top + dy / state.zoom, -1e7, 1e7)
1322
- };
1323
- const panel = panels.get(gesture.artifactId);
1324
- panel.style.left = `${state.positions[gesture.artifactId].x}px`;
1325
- panel.style.top = `${state.positions[gesture.artifactId].y}px`;
1326
- scheduleCamera();
1327
- }
1328
- });
1329
- for (const eventName of ["pointerup", "pointercancel", "lostpointercapture"])
1330
- listen(scroll, eventName, (event) => {
1331
- if (!gesture || gesture.pointerId !== event.pointerId) return;
1332
- if (eventName === "pointerup") finishGesture();
1333
- else releaseTemporaryPan();
1334
- });
1335
- listen(document, "keydown", (event) => {
1336
- if (editable(event.target)) return;
1337
- if (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]")) {
1338
- event.preventDefault();
1339
- if (event.repeat) return;
1340
- const previous = activeTool;
1341
- setTool("pan");
1342
- panEnabled = false;
1343
- spacePan = true;
1344
- spaceReturnTool = previous;
1345
- updatePan();
1346
- }
1347
- const label = event.target.closest("[data-design-drag]");
1348
- if (label && state.view === "canvas" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) {
1349
- event.preventDefault();
1350
- const panel = label.closest(".planr-artifact-panel");
1351
- const distance = event.shiftKey ? 40 : 8;
1352
- state.positions[panel.dataset.artifactId] = {
1353
- x: clamp2(
1354
- parseFloat(panel.style.left) + (event.key === "ArrowRight" ? distance : event.key === "ArrowLeft" ? -distance : 0),
1355
- -1e7,
1356
- 1e7
1357
- ),
1358
- y: clamp2(
1359
- parseFloat(panel.style.top) + (event.key === "ArrowDown" ? distance : event.key === "ArrowUp" ? -distance : 0),
1360
- -1e7,
1361
- 1e7
1362
- )
1363
- };
1364
- render();
1365
- persist();
1366
- } else if (state.view === "walkthrough" && ["ArrowLeft", "ArrowRight"].includes(event.key) && !event.target.closest('button,[role="radio"]')) {
1367
- event.preventDefault();
1368
- const index2 = clamp2(
1369
- design.screenOrder.indexOf(requestedScreen()) + (event.key === "ArrowRight" ? 1 : -1),
1370
- 0,
1371
- design.screenOrder.length - 1
1372
- );
1373
- selectEntry(requestedEntry({ screenId: design.screenOrder[index2] }));
1374
- }
1375
- });
1376
- listen(document, "keyup", (event) => {
1377
- if (event.code === "Space") {
1378
- releaseTemporaryPan();
1379
- }
1380
- });
1381
- listen(window, "blur", () => {
1382
- releaseTemporaryPan();
1383
- if (document.activeElement?.tagName === "IFRAME") closeDesignNotes();
1384
- });
1385
- listen(document, "visibilitychange", () => {
1386
- if (document.hidden) {
1387
- releaseTemporaryPan();
1388
- finishWalkthroughTransition();
1389
- }
1390
- });
1391
- listen(
1392
- window.matchMedia?.("(prefers-reduced-motion: reduce)"),
1393
- "change",
1394
- () => finishWalkthroughTransition()
1395
- );
1396
- listen(window, "resize", () => {
1397
- const compact = window.innerWidth <= 960;
1398
- if (compact && !compactViewport) {
1399
- state.navOpen = false;
1400
- state.reviewOpen = false;
1401
- stage.dispatch({ type: "set-rail-open", railOpen: false });
1402
- persist();
1403
- }
1404
- compactViewport = compact;
1405
- render();
1406
- if (state.view !== "canvas" && root.dataset.designRestoring !== "true") fit({ save: false });
1407
- updateDesignNotes();
1408
- });
1409
- listen(window, "online", () => {
1410
- if (dirty || reviewDirty) void flush();
1411
- });
1412
- listen(window, "beforeunload", (event) => {
1413
- clearTimeout(frameDemandTimer);
1414
- frameDemandGeneration += 1;
1415
- frameDemandKey = "";
1416
- storeDraft();
1417
- storeReviewDraft();
1418
- if ((contentDirty || reviewDirty) && (options2.stateUrl || options2.saveState || reviewUrl())) {
1419
- event.preventDefault();
1420
- event.returnValue = "";
1421
- }
1422
- });
1423
- listen(window, "message", (event) => {
1424
- if (event.origin !== "null" || !["openplanr:design-navigate", "navigate"].includes(event.data?.type) || typeof event.data.screenId !== "string")
1425
- return;
1426
- const source = entries.find(
1427
- ({ artifactId }) => stage.getFrame(artifactId)?.contentWindow === event.source
1428
- );
1429
- if (!source || !design.screenOrder.includes(event.data.screenId)) return;
1430
- const nonce = stage.getFrame(source.artifactId)?.__openPlanrBridge?.getPrototypeNonce?.();
1431
- if (!nonce) return;
1432
- if (event.data.type === "navigate" && (event.data.channel !== nonce || event.data.viewId !== source.artifactId || event.data.schemaVersion !== "1.0.0"))
1433
- return;
1434
- if (state.view !== "canvas" && (source.artifactId !== activeArtifactId || walkthroughTransition))
1435
- return;
1436
- chrome?.update({ exportMenuOpen: false });
1437
- selectEntry(entryFor(event.data.screenId, source.variantId, source.frameId));
1438
- });
1439
- }
1440
- async function refreshSavedState() {
1441
- if (!options2.stateUrl) return;
1442
- try {
1443
- const result = await jsonRequest(options2.stateUrl);
1444
- if (destroyed) return;
1445
- if (result.revision !== void 0) revision = result.revision;
1446
- if (result.stateVersion !== void 0) stateVersion = result.stateVersion;
1447
- if (result.state && !dirty) {
1448
- state = normalizeState({ ...result.state, ...linkedState() });
1449
- if (compactViewport) {
1450
- state.navOpen = false;
1451
- state.reviewOpen = false;
1452
- }
1453
- stage.dispatch({ type: "set-rail-open", railOpen: state.reviewOpen });
1454
- }
1455
- if (savedDraft?.unsaved)
1456
- notice(
1457
- "A previous unsaved draft is available in this browser. The saved review is shown; your draft has not overwritten it.",
1458
- true
1459
- );
1460
- selectEntry(entryFor(), { save: false });
1461
- status("All changes saved");
1462
- } catch {
1463
- status("Disconnected · local draft", "disconnected");
1464
- }
1465
- }
1466
- async function pollRevision() {
1467
- if (!options2.statusUrl || destroyed) return;
1468
- try {
1469
- const result = await jsonRequest(options2.statusUrl);
1470
- if (destroyed) return;
1471
- if (typeof result.verification === "string")
1472
- q("[data-design-verification]").textContent = result.verification === "verified" ? "Browser inspection complete" : "Browser inspection pending";
1473
- if (result.revision !== void 0 && revision !== null && result.revision !== revision) {
1474
- if (contentDirty || reviewDirty)
1475
- notice(
1476
- "A new design revision is ready. Save or recover this browser’s unsaved changes before reloading.",
1477
- true
1478
- );
1479
- else {
1480
- storeDraft();
1481
- window.location.reload();
1482
- return;
1483
- }
1484
- }
1485
- if (result.status === "failed")
1486
- notice(
1487
- result.message ?? "The new revision failed. This working design remains available.",
1488
- true
1489
- );
1490
- } catch {
1491
- if (!dirty) status("Disconnected", "disconnected");
1492
- }
1493
- pollTimer = setTimeout(pollRevision, 4e3);
1494
- }
1495
- async function reportReady() {
1496
- if (readyReported || readyReporting || destroyed) return;
1497
- readyReporting = true;
1498
- try {
1499
- const requiredEntries = [entryFor()];
1500
- await stage.ensureFrames(requiredEntries.map((entry) => entry.artifactId));
1501
- if (destroyed) return;
1502
- const loaded = requiredEntries.filter(({ artifactId }) => {
1503
- const frame = stage.getFrame(artifactId);
1504
- return (frame?.src?.startsWith("blob:") || Boolean(frame?.srcdoc)) && frame.dataset.planrFrameState === "ready" && frame.dataset.planrArtifactDigest && (!frame.__openPlanrBridge || frame.dataset.planrBridgeTrusted === "true");
1505
- });
1506
- if (loaded.length !== requiredEntries.length) throw new Error("Preview bridge is not ready.");
1507
- readyReported = true;
1508
- render();
1509
- await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
1510
- if (destroyed) return;
1511
- root.dataset.designReady = "true";
1512
- if (!root.dataset.sharedOpening) delete document.documentElement.dataset.designOpening;
1513
- chrome?.update({ previewLabel: "Preview ready" });
1514
- if (!chrome) q("[data-design-preview-state]").textContent = "Preview ready";
1515
- if (!contentDirty && !reviewDirty && (!options2.stateUrl || stateVersion !== null))
1516
- status(options2.stateUrl ? "All changes saved" : "Local preview");
1517
- const detail = {
1518
- revision,
1519
- status: "ready",
1520
- artifacts: loaded.map((entry) => entry.artifactId)
1521
- };
1522
- root.dispatchEvent(new CustomEvent("planr:design-ready", { bubbles: true, detail }));
1523
- if (typeof options2.onReady === "function") await options2.onReady(detail);
1524
- if (options2.readyUrl) {
1525
- try {
1526
- await jsonRequest(options2.readyUrl, {
1527
- method: "POST",
1528
- headers: { "Content-Type": "application/json" },
1529
- body: JSON.stringify(detail)
1530
- });
1531
- } catch {
1532
- status("Server disconnected · local draft", "disconnected");
1533
- }
1534
- }
1535
- } catch {
1536
- if (destroyed) return;
1537
- delete document.documentElement.dataset.designOpening;
1538
- chrome?.update({ previewLabel: "Preview unavailable" });
1539
- if (!chrome) q("[data-design-preview-state]").textContent = "Preview unavailable";
1540
- notice(
1541
- "This preview could not load. Use Retry screen; your saved feedback remains available.",
1542
- true
1543
- );
1544
- } finally {
1545
- readyReporting = false;
1546
- }
1547
- }
1548
- function mount() {
1549
- stage = artifactStage;
1550
- if (!stage) {
1551
- status("Review runtime unavailable", "failed");
1552
- return null;
1553
- }
1554
- root = q(".planr-shell");
1555
- chrome = mountDesignChrome({ root, title: design.title, state });
1556
- if (chrome) cleanup.push(() => chrome.destroy());
1557
- previousReviewMode = stage.getState().reviewMode;
1558
- activeTool = previousReviewMode === "comment" ? "annotate" : "interact";
1559
- scroll = q(".planr-stage-scroll");
1560
- surface = q(".planr-stage-surface");
1561
- grid = q(".planr-frame-grid");
1562
- canvasBackground = document.createElement("div");
1563
- canvasBackground.className = "design-canvas-background";
1564
- canvasBackground.setAttribute("aria-hidden", "true");
1565
- scroll.prepend(canvasBackground);
1566
- selectionOutline = document.createElement("div");
1567
- selectionOutline.className = "design-selection-outline";
1568
- selectionOutline.setAttribute("aria-hidden", "true");
1569
- selectionOutline.hidden = true;
1570
- scroll.append(selectionOutline);
1571
- for (const entry of entries) {
1572
- const panel = stage.getPanel(entry.artifactId);
1573
- if (!panel) {
1574
- status("Missing design preview", "failed");
1575
- return null;
1576
- }
1577
- panels.set(entry.artifactId, panel);
1578
- const screen = design.screens.find(({ id }) => id === entry.screenId);
1579
- const variant = design.variants.find(({ id }) => id === entry.variantId);
1580
- const frame = design.frames.find(({ id }) => id === entry.frameId);
1581
- const label = document.createElement("div");
1582
- label.className = "design-artboard-label";
1583
- label.setAttribute("role", "group");
1584
- const drag = document.createElement("button");
1585
- drag.type = "button";
1586
- drag.className = "design-artboard-drag";
1587
- drag.dataset.designDrag = entry.artifactId;
1588
- drag.setAttribute(
1589
- "aria-label",
1590
- `Move ${screen.title}, ${variant.label}, ${frame.label}. Use arrow keys to arrange.`
1591
- );
1592
- const title = document.createElement("strong");
1593
- title.textContent = `${screen.title}${readyVariants.length > 1 ? ` / ${variant.label}` : ""}`;
1594
- const size4 = document.createElement("span");
1595
- size4.textContent = `${frame.label} · ${frame.width} × ${frame.height}`;
1596
- drag.append(title, size4);
1597
- label.append(drag);
1598
- if (screen.description) {
1599
- const note = document.createElement("button");
1600
- note.type = "button";
1601
- note.className = "design-artboard-note";
1602
- note.dataset.designNote = screen.id;
1603
- note.setAttribute("aria-controls", "design-guidance-panel");
1604
- note.setAttribute("aria-expanded", "false");
1605
- note.title = screen.description;
1606
- note.setAttribute("aria-label", `Open design notes for ${screen.title}`);
1607
- note.textContent = "i";
1608
- label.append(note);
1609
- }
1610
- panel.prepend(label);
1611
- if (stage.frameBudget) {
1612
- const load = document.createElement("button");
1613
- load.type = "button";
1614
- load.className = "design-frame-placeholder";
1615
- load.dataset.designLoadFrame = entry.artifactId;
1616
- load.setAttribute("aria-label", `Open screen: ${screen.title}, ${frame.label}`);
1617
- load.textContent = "Open screen";
1618
- panel.querySelector(".planr-frame").append(load);
1619
- }
1620
- }
1621
- const verified = payload.verification?.status === "verified";
1622
- q("[data-design-verification]").textContent = verified ? "Browser inspection complete" : "Browser inspection pending";
1623
- const prototypeState = createPrototypeStateRelay({
1624
- contextId: `${design.id}:${payload.revision ?? payload.reviewOf ?? "local"}`,
1625
- parentWindow: window,
1626
- frames: () => entries.map((entry) => ({
1627
- screenId: entry.screenId,
1628
- viewId: entry.artifactId,
1629
- window: stage.getFrame(entry.artifactId)?.contentWindow ?? null,
1630
- nonce: stage.getFrame(entry.artifactId)?.__openPlanrBridge?.getPrototypeNonce?.() ?? null,
1631
- generation: stage.getFrame(entry.artifactId)?.__openPlanrBridge?.getPrototypeGeneration?.(),
1632
- aliases: options2.prototypeStateAliases?.[entry.screenId]
1633
- }))
1634
- });
1635
- cleanup.push(() => prototypeState.dispose());
1636
- installEvents();
1637
- listen(q("[data-design-dismiss-notice]"), "click", () => {
1638
- if (activeNotice?.persistent) dismissedNotices.set(activeNotice.key, activeNotice.message);
1639
- hideNotice();
1640
- });
1641
- function previewHealth() {
1642
- for (const entry of entries) {
1643
- const frame = stage.getFrame(entry.artifactId);
1644
- const nonce = frame?.__openPlanrBridge?.getPrototypeNonce?.() ?? null;
1645
- if (nonce)
1646
- prototypeState.restore({
1647
- screenId: entry.screenId,
1648
- viewId: entry.artifactId,
1649
- window: frame.contentWindow,
1650
- nonce,
1651
- generation: frame.__openPlanrBridge?.getPrototypeGeneration?.(),
1652
- aliases: options2.prototypeStateAliases?.[entry.screenId]
1653
- });
1654
- }
1655
- const selected = stage.getFrameDiagnostics?.().find((item) => item.artifactId === activeArtifactId);
1656
- const node = q("[data-design-preview-state]");
1657
- const phase = selected?.status ?? stage.getState().status;
1658
- const previewLabel = phase === "ready" ? "Preview ready" : phase === "error" || phase === "invalid" ? "Preview unavailable" : "Loading preview";
1659
- if (chrome) chrome.update({ previewLabel });
1660
- else node.textContent = previewLabel;
1661
- node.dataset.status = phase;
1662
- if (selected)
1663
- node.title = `${selected.phase} · ${Math.round(selected.elapsedMs ?? 0)} ms${selected.code ? ` · ${selected.code}` : ""}`;
1664
- if (!readyReported && phase === "ready") void reportReady();
1665
- }
1666
- listen(root, "planr:artifact-frame-state", previewHealth);
1667
- listen(root, "planr:artifact-state", previewHealth);
1668
- previewHealth();
1669
- const search = q("[data-design-screen-search]"), group = q("[data-design-screen-group]");
1670
- function filterScreens() {
1671
- const term = search.value.trim().toLocaleLowerCase();
1672
- const flow = design.flows?.find((item) => item.id === group?.value);
1673
- let count3 = 0;
1674
- for (const button of qa("[data-design-screen]")) {
1675
- button.hidden = !(button.textContent.toLocaleLowerCase().includes(term) && (!flow || flow.screens.includes(button.dataset.designScreen)));
1676
- if (!button.hidden) count3++;
1677
- }
1678
- q("[data-design-search-empty]").hidden = count3 > 0;
1679
- }
1680
- listen(search, "input", filterScreens);
1681
- listen(group, "change", filterScreens);
1682
- function followLocation() {
1683
- const next = normalizeState({ ...state, ...linkedState() });
1684
- if (next.view !== state.view) switchView(next.view, { save: false });
1685
- selectEntry(entryFor(next.screenId, next.variantId, next.frameId), { save: false });
1686
- }
1687
- listen(window, "popstate", followLocation);
1688
- listen(window, "hashchange", followLocation);
1689
- if (stage.frameBudget) {
1690
- listen(root, "planr:artifact-frame-state", updateFramePlaceholders);
1691
- updateFramePlaceholders();
1692
- }
1693
- compactViewport = window.innerWidth <= 960;
1694
- if (compactViewport) {
1695
- state.navOpen = false;
1696
- state.reviewOpen = false;
1697
- stage.dispatch({ type: "set-rail-open", railOpen: false });
1698
- } else stage.dispatch({ type: "set-rail-open", railOpen: state.reviewOpen });
1699
- selectEntry(entryFor(), { save: false });
1700
- if (!reviewUrl() && !stage.review.getState().review) {
1701
- try {
1702
- const storedReview = JSON.parse(localStorage.getItem(`${storageKey}.review`) ?? "null");
1703
- if (storedReview?.review) stage.review.replaceReview(storedReview.review);
1704
- } catch {
1705
- }
1706
- }
1707
- const panelDialogs = mountDesignPanels({
1708
- root,
1709
- closePanel(side) {
1710
- if (side === "left") state.navOpen = false;
1711
- else {
1712
- state.reviewOpen = false;
1713
- stage.dispatch({ type: "set-rail-open", railOpen: false });
1714
- }
1715
- render();
1716
- persist();
1717
- q(
1718
- side === "left" ? ".design-toolbar [data-design-toggle-nav]" : ".design-toolbar [data-planr-action=feedback]"
1719
- )?.focus();
1720
- }
1721
- });
1722
- cleanup.push(() => panelDialogs.destroy());
1723
- const controller = Object.freeze({
1724
- setTool,
1725
- getTool: () => activeTool,
1726
- getState: () => clone(state),
1727
- getRevision: () => revision,
1728
- getSaveState: () => ({
1729
- dirty: contentDirty || reviewDirty,
1730
- personalPending: dirty && !contentDirty,
1731
- commentsPending: reviewDirty,
1732
- revision,
1733
- stateVersion
1734
- }),
1735
- selectScreen: (screenId) => selectEntry(requestedEntry({ screenId })),
1736
- selectEntry: (entry) => selectEntry(entry),
1737
- fitSelection: ({ save = true } = {}) => fit({ selection: entryFor(), save }),
1738
- setCamera(camera) {
1739
- if (state.view !== "canvas") return;
1740
- if (!Number.isFinite(camera?.x) || !Number.isFinite(camera?.y)) return;
1741
- state.camera = { x: clamp2(camera.x, -1e7, 1e7), y: clamp2(camera.y, -1e7, 1e7) };
1742
- if (Number.isFinite(camera.zoom)) state.zoom = clamp2(camera.zoom, 0.05, 2);
1743
- saveViewport();
1744
- renderCamera();
1745
- persist();
1746
- },
1747
- setPanels({ navOpen = state.navOpen, reviewOpen = state.reviewOpen }, { save = true } = {}) {
1748
- state.navOpen = Boolean(navOpen);
1749
- state.reviewOpen = Boolean(reviewOpen);
1750
- stage.dispatch({ type: "set-rail-open", railOpen: state.reviewOpen });
1751
- render();
1752
- if (save) persist();
1753
- },
1754
- restorePresentation(snapshot) {
1755
- finishWalkthroughTransition();
1756
- const personal = {};
1757
- for (const key of [
1758
- "view",
1759
- "screenId",
1760
- "frameId",
1761
- "variantId",
1762
- "compare",
1763
- "navOpen",
1764
- "reviewOpen",
1765
- "inspectionScale",
1766
- "zoom",
1767
- "camera",
1768
- "viewports",
1769
- "positions"
1770
- ])
1771
- if (snapshot[key] !== void 0) personal[key] = clone(snapshot[key]);
1772
- state = normalizeState({ ...state, ...personal });
1773
- stage.dispatch({ type: "set-rail-open", railOpen: state.reviewOpen });
1774
- selectEntry(entryFor(), { save: false });
1775
- render();
1776
- persist();
1777
- },
1778
- setView(view) {
1779
- switchView(view);
1780
- },
1781
- fit,
1782
- flush,
1783
- exportArtifact,
1784
- destroy() {
1785
- finishWalkthroughTransition();
1786
- root.dispatchEvent(new CustomEvent("planr:design-destroy"));
1787
- for (const { artifactId } of entries)
1788
- stage.getFrame(artifactId)?.__openPlanrBridge?.setViewportGestures?.(false);
1789
- destroyed = true;
1790
- ++screenRequest;
1791
- pendingScreen = null;
1792
- failedSelection = null;
1793
- clearTimeout(frameDemandTimer);
1794
- clearTimeout(saveTimer);
1795
- clearTimeout(reviewTimer);
1796
- clearTimeout(pollTimer);
1797
- clearTimeout(noticeTimer);
1798
- if (cameraFrame !== null) cancelAnimationFrame(cameraFrame);
1799
- canvasBackground?.remove();
1800
- selectionOutline?.remove();
1801
- for (const remove of cleanup.splice(0)) remove();
1802
- }
1803
- });
1804
- void refreshSavedState().then(reportReady);
1805
- pollTimer = setTimeout(pollRevision, 4e3);
1806
- return controller;
1807
- }
1808
- return mount();
1809
- }
1810
-
1811
- // lib/design/ui/studio-runtime.mjs
1812
- var PAYLOAD_ID = "planr-design-studio-payload";
1813
- var STAGE_TIMEOUT_MS = 15e3;
1814
- function documentReady() {
1815
- if (document.readyState !== "loading") return Promise.resolve();
1816
- return new Promise((resolve) => {
1817
- document.addEventListener("DOMContentLoaded", resolve, { once: true });
1818
- });
1819
- }
1820
- function stageBefore(deadline) {
1821
- let timer;
1822
- const timeout = new Promise((resolve) => {
1823
- timer = setTimeout(resolve, Math.max(0, deadline - Date.now()), null);
1824
- });
1825
- return Promise.race([whenArtifactStage(window), timeout]).finally(() => clearTimeout(timer));
1826
- }
1827
- async function start(payloadNode2, payload, deadline) {
1828
- const current = () => document.getElementById(PAYLOAD_ID) === payloadNode2;
1829
- await documentReady();
1830
- if (!current()) return;
1831
- const stage = await stageBefore(deadline);
1832
- if (!current()) return;
1833
- const studio = mountDesignStudio({ payload, stage });
1834
- if (!studio) return;
1835
- window.__openPlanrDesignStudio = studio;
1836
- const { experience, handoffCenter } = mountDesignEnhancements({ payload, studio, stage });
1837
- window.__openPlanrDesignExperience = experience;
1838
- if (handoffCenter) window.__openPlanrDesignHandoffCenter = handoffCenter;
1839
- }
1840
- var script = document.currentScript;
1841
- var payloadNode = document.getElementById(PAYLOAD_ID);
1842
- if ((!script || script.isConnected) && payloadNode) {
1843
- void start(payloadNode, JSON.parse(payloadNode.textContent), Date.now() + STAGE_TIMEOUT_MS);
1844
- }