openplanr 2.2640.8 → 2.2641.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 (192) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/dashboard/.vite/manifest.json +1 -1
  3. package/dist/dashboard/assets/{index-COZ36pFn.js → index-D7R6tnlX.js} +2 -2
  4. package/dist/dashboard/assets/{index-COZ36pFn.js.map → index-D7R6tnlX.js.map} +1 -1
  5. package/dist/dashboard/dashboard-manifest.json +8 -8
  6. package/dist/dashboard/index.html +1 -1
  7. package/lib/host-packages/adapter-registry.json +5 -5
  8. package/lib/host-packages/capability-map.json +1 -1
  9. package/lib/host-packages/claude/.claude-plugin/marketplace.json +2 -2
  10. package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +1 -1
  11. package/lib/host-packages/claude/openplanr/.openplanr-content.json +167 -35
  12. package/lib/host-packages/claude/openplanr/README.md +22 -1
  13. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-artifact-shell.mjs +3 -11
  14. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +555 -0
  15. package/lib/host-packages/claude/openplanr/runtime/design/scripts/{design-pako-esm.mjs → design-compression.mjs} +2 -1521
  16. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-document.mjs +133 -12
  17. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-encrypted-workspace-client.mjs +647 -0
  18. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-entities.mjs +17 -67
  19. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-handoff.mjs +14 -16
  20. package/lib/host-packages/claude/openplanr/runtime/design/scripts/{design-planr-home.mjs → design-loopback-server.mjs} +15 -36
  21. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-owner-custody.mjs +121 -0
  22. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-resource-pack.mjs +698 -0
  23. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review.mjs +798 -796
  24. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-runtime-home.mjs +39 -0
  25. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-runtime-support-5cb7531bede8.mjs +24 -0
  26. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-share.mjs +138 -765
  27. package/lib/host-packages/claude/openplanr/runtime/design/scripts/{design-escape.mjs → design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs} +158 -156
  28. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-artifact-sources.mjs → claude/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs} +1 -1
  29. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-bounded-json-data.mjs → claude/openplanr/runtime/design/scripts/design-shared-protocol-contracts-31a760fc.mjs} +241 -454
  30. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-protocol-contracts-75a938cc.mjs +222 -0
  31. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-upload-spool.mjs +198 -0
  32. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design.mjs +12 -5
  33. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/lib/artifact/ui/studio-shell.css +1 -1
  34. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/01-module-runtime.js +18 -0
  35. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +809 -0
  36. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +5193 -0
  37. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-compression.js +4175 -0
  38. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/05-artifact-sharing.js +65 -0
  39. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +1755 -0
  40. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/07-artifact-sharing.js +62 -0
  41. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +2600 -0
  42. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +123 -0
  43. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/package.json +3 -3
  44. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +3 -3
  45. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +34 -0
  46. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +992 -0
  47. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +2513 -0
  48. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +1919 -0
  49. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +1759 -0
  50. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +1551 -0
  51. 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 +1121 -0
  52. 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 +2183 -0
  53. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +705 -0
  54. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +27 -0
  55. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +13 -0
  56. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-design-review-experience.js +3561 -0
  57. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-studio-shell.js +184 -0
  58. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-interface-primitives.js +5045 -0
  59. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-interface-primitives.js +1698 -0
  60. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-studio-shell.js +455 -0
  61. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-design-studio.js +1844 -0
  62. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +257 -0
  63. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +1 -1
  64. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +4966 -0
  65. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design.mjs +240 -5192
  66. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +1 -1
  67. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/backend-diagnostics.mjs +6 -2
  68. package/lib/host-packages/cursor/openplanr/.openplanr-content.json +166 -34
  69. package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
  70. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/backend-diagnostics.mjs +6 -2
  71. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-artifact-shell.mjs +3 -11
  72. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +555 -0
  73. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-pako-esm.mjs → cursor/openplanr/runtime/design/scripts/design-compression.mjs} +2 -1521
  74. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-document.mjs +133 -12
  75. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-encrypted-workspace-client.mjs +647 -0
  76. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-entities.mjs +17 -67
  77. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-handoff.mjs +14 -16
  78. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-planr-home.mjs → cursor/openplanr/runtime/design/scripts/design-loopback-server.mjs} +15 -36
  79. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-owner-custody.mjs +121 -0
  80. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-resource-pack.mjs +698 -0
  81. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review.mjs +798 -796
  82. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-runtime-home.mjs +39 -0
  83. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-runtime-support-5cb7531bede8.mjs +24 -0
  84. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-share.mjs +138 -765
  85. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-escape.mjs → cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs} +158 -156
  86. package/lib/host-packages/{claude/openplanr/runtime/design/scripts/design-artifact-sources.mjs → cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs} +1 -1
  87. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/{design-bounded-json-data.mjs → design-shared-protocol-contracts-31a760fc.mjs} +241 -454
  88. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-protocol-contracts-75a938cc.mjs +222 -0
  89. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-upload-spool.mjs +198 -0
  90. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design.mjs +12 -5
  91. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/lib/artifact/ui/studio-shell.css +1 -1
  92. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/01-module-runtime.js +18 -0
  93. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +809 -0
  94. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +5193 -0
  95. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-compression.js +4175 -0
  96. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/05-artifact-sharing.js +65 -0
  97. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +1755 -0
  98. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/07-artifact-sharing.js +62 -0
  99. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +2600 -0
  100. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +123 -0
  101. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/package.json +3 -3
  102. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +3 -3
  103. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +34 -0
  104. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +992 -0
  105. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +2513 -0
  106. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +1919 -0
  107. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +1759 -0
  108. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +1551 -0
  109. 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 +1121 -0
  110. 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 +2183 -0
  111. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +705 -0
  112. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +27 -0
  113. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +13 -0
  114. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-design-review-experience.js +3561 -0
  115. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-studio-shell.js +184 -0
  116. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-interface-primitives.js +5045 -0
  117. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-interface-primitives.js +1698 -0
  118. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-studio-shell.js +455 -0
  119. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-design-studio.js +1844 -0
  120. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +257 -0
  121. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +1 -1
  122. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +4966 -0
  123. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design.mjs +240 -5192
  124. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +1 -1
  125. package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
  126. package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +1 -1
  127. package/lib/host-packages/openai/openplanr/.openplanr-content.json +166 -34
  128. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-artifact-shell.mjs +3 -11
  129. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +555 -0
  130. package/lib/host-packages/{cursor/openplanr/runtime/design/scripts/design-pako-esm.mjs → openai/openplanr/runtime/design/scripts/design-compression.mjs} +2 -1521
  131. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-document.mjs +133 -12
  132. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-encrypted-workspace-client.mjs +647 -0
  133. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-entities.mjs +17 -67
  134. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-handoff.mjs +14 -16
  135. package/lib/host-packages/{cursor/openplanr/runtime/design/scripts/design-planr-home.mjs → openai/openplanr/runtime/design/scripts/design-loopback-server.mjs} +15 -36
  136. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-owner-custody.mjs +121 -0
  137. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-resource-pack.mjs +698 -0
  138. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review.mjs +798 -796
  139. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-runtime-home.mjs +39 -0
  140. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-runtime-support-5cb7531bede8.mjs +24 -0
  141. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-share.mjs +138 -765
  142. package/lib/host-packages/{cursor/openplanr/runtime/design/scripts/design-escape.mjs → openai/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs} +158 -156
  143. package/lib/host-packages/{cursor/openplanr/runtime/design/scripts/design-artifact-sources.mjs → openai/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs} +1 -1
  144. package/lib/host-packages/{claude/openplanr/runtime/design/scripts/design-bounded-json-data.mjs → openai/openplanr/runtime/design/scripts/design-shared-protocol-contracts-31a760fc.mjs} +241 -454
  145. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-protocol-contracts-75a938cc.mjs +222 -0
  146. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-upload-spool.mjs +198 -0
  147. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design.mjs +12 -5
  148. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/lib/artifact/ui/studio-shell.css +1 -1
  149. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/01-module-runtime.js +18 -0
  150. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +809 -0
  151. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +5193 -0
  152. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-compression.js +4175 -0
  153. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/05-artifact-sharing.js +65 -0
  154. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +1755 -0
  155. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/07-artifact-sharing.js +62 -0
  156. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +2600 -0
  157. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +123 -0
  158. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/package.json +3 -3
  159. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +3 -3
  160. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +34 -0
  161. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +992 -0
  162. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +2513 -0
  163. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +1919 -0
  164. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +1759 -0
  165. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +1551 -0
  166. 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 +1121 -0
  167. 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 +2183 -0
  168. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +705 -0
  169. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +27 -0
  170. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +13 -0
  171. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-design-review-experience.js +3561 -0
  172. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-studio-shell.js +184 -0
  173. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-interface-primitives.js +5045 -0
  174. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-interface-primitives.js +1698 -0
  175. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-studio-shell.js +455 -0
  176. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-design-studio.js +1844 -0
  177. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +257 -0
  178. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +1 -1
  179. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +4966 -0
  180. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design.mjs +240 -5192
  181. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +1 -1
  182. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/backend-diagnostics.mjs +6 -2
  183. package/package.json +2 -2
  184. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-generated-b0798298053515fd.mjs +0 -18
  185. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js +0 -5
  186. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js +0 -25597
  187. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-generated-b0798298053515fd.mjs +0 -18
  188. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js +0 -5
  189. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js +0 -25597
  190. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-generated-b0798298053515fd.mjs +0 -18
  191. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js +0 -5
  192. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js +0 -25597
@@ -0,0 +1,1844 @@
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
+ }