opendevbrowser 0.0.17 → 0.0.18

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 (529) hide show
  1. package/README.md +171 -73
  2. package/dist/annotate/agent-inbox-store.d.ts +58 -0
  3. package/dist/annotate/agent-inbox-store.d.ts.map +1 -0
  4. package/dist/annotate/agent-inbox.d.ts +25 -0
  5. package/dist/annotate/agent-inbox.d.ts.map +1 -0
  6. package/dist/annotate/direct-annotator.d.ts.map +1 -1
  7. package/dist/annotate/timeout-messages.d.ts +4 -0
  8. package/dist/annotate/timeout-messages.d.ts.map +1 -0
  9. package/dist/automation/coordinator.d.ts +55 -0
  10. package/dist/automation/coordinator.d.ts.map +1 -0
  11. package/dist/browser/annotation-manager.d.ts +4 -1
  12. package/dist/browser/annotation-manager.d.ts.map +1 -1
  13. package/dist/browser/browser-manager.d.ts +147 -47
  14. package/dist/browser/browser-manager.d.ts.map +1 -1
  15. package/dist/browser/canvas-client.d.ts +1 -0
  16. package/dist/browser/canvas-client.d.ts.map +1 -1
  17. package/dist/browser/canvas-code-sync-manager.d.ts +9 -1
  18. package/dist/browser/canvas-code-sync-manager.d.ts.map +1 -1
  19. package/dist/browser/canvas-manager.d.ts +29 -1
  20. package/dist/browser/canvas-manager.d.ts.map +1 -1
  21. package/dist/browser/global-challenge-coordinator.d.ts +27 -0
  22. package/dist/browser/global-challenge-coordinator.d.ts.map +1 -0
  23. package/dist/browser/manager-types.d.ts +167 -1
  24. package/dist/browser/manager-types.d.ts.map +1 -1
  25. package/dist/browser/ops-browser-manager.d.ts +103 -3
  26. package/dist/browser/ops-browser-manager.d.ts.map +1 -1
  27. package/dist/browser/ops-client.d.ts +17 -1
  28. package/dist/browser/ops-client.d.ts.map +1 -1
  29. package/dist/browser/playwright-runtime.d.ts +4 -0
  30. package/dist/browser/playwright-runtime.d.ts.map +1 -0
  31. package/dist/browser/review-surface.d.ts +9 -0
  32. package/dist/browser/review-surface.d.ts.map +1 -0
  33. package/dist/browser/screencast-recorder.d.ts +57 -0
  34. package/dist/browser/screencast-recorder.d.ts.map +1 -0
  35. package/dist/browser/session-inspector.d.ts +71 -0
  36. package/dist/browser/session-inspector.d.ts.map +1 -0
  37. package/dist/browser/session-store.d.ts +5 -1
  38. package/dist/browser/session-store.d.ts.map +1 -1
  39. package/dist/browser/system-chrome-cookies.d.ts +46 -0
  40. package/dist/browser/system-chrome-cookies.d.ts.map +1 -0
  41. package/dist/browser/target-manager.d.ts +1 -0
  42. package/dist/browser/target-manager.d.ts.map +1 -1
  43. package/dist/cache/chrome-locator.d.ts.map +1 -1
  44. package/dist/cache/chrome-user-data.d.ts +17 -0
  45. package/dist/cache/chrome-user-data.d.ts.map +1 -0
  46. package/dist/canvas/adapter-plugins/loader.d.ts +13 -0
  47. package/dist/canvas/adapter-plugins/loader.d.ts.map +1 -0
  48. package/dist/canvas/adapter-plugins/manifest.d.ts +146 -0
  49. package/dist/canvas/adapter-plugins/manifest.d.ts.map +1 -0
  50. package/dist/canvas/adapter-plugins/types.d.ts +83 -0
  51. package/dist/canvas/adapter-plugins/types.d.ts.map +1 -0
  52. package/dist/canvas/adapter-plugins/validator.d.ts +10 -0
  53. package/dist/canvas/adapter-plugins/validator.d.ts.map +1 -0
  54. package/dist/canvas/code-sync/apply-tsx.d.ts +3 -1
  55. package/dist/canvas/code-sync/apply-tsx.d.ts.map +1 -1
  56. package/dist/canvas/code-sync/import.d.ts +1 -0
  57. package/dist/canvas/code-sync/import.d.ts.map +1 -1
  58. package/dist/canvas/code-sync/manifest.d.ts +2 -1
  59. package/dist/canvas/code-sync/manifest.d.ts.map +1 -1
  60. package/dist/canvas/code-sync/tsx-adapter.d.ts.map +1 -1
  61. package/dist/canvas/code-sync/types.d.ts +102 -10
  62. package/dist/canvas/code-sync/types.d.ts.map +1 -1
  63. package/dist/canvas/document-store.d.ts +11 -1
  64. package/dist/canvas/document-store.d.ts.map +1 -1
  65. package/dist/canvas/export.d.ts.map +1 -1
  66. package/dist/canvas/framework-adapters/custom-elements-v1.d.ts +3 -0
  67. package/dist/canvas/framework-adapters/custom-elements-v1.d.ts.map +1 -0
  68. package/dist/canvas/framework-adapters/html-static-v1.d.ts +3 -0
  69. package/dist/canvas/framework-adapters/html-static-v1.d.ts.map +1 -0
  70. package/dist/canvas/framework-adapters/markup.d.ts +9 -0
  71. package/dist/canvas/framework-adapters/markup.d.ts.map +1 -0
  72. package/dist/canvas/framework-adapters/react-tsx-v2.d.ts +3 -0
  73. package/dist/canvas/framework-adapters/react-tsx-v2.d.ts.map +1 -0
  74. package/dist/canvas/framework-adapters/registry.d.ts +12 -0
  75. package/dist/canvas/framework-adapters/registry.d.ts.map +1 -0
  76. package/dist/canvas/framework-adapters/svelte-sfc-v1.d.ts +3 -0
  77. package/dist/canvas/framework-adapters/svelte-sfc-v1.d.ts.map +1 -0
  78. package/dist/canvas/framework-adapters/types.d.ts +57 -0
  79. package/dist/canvas/framework-adapters/types.d.ts.map +1 -0
  80. package/dist/canvas/framework-adapters/vue-sfc-v1.d.ts +3 -0
  81. package/dist/canvas/framework-adapters/vue-sfc-v1.d.ts.map +1 -0
  82. package/dist/canvas/kits/catalog.d.ts +5 -0
  83. package/dist/canvas/kits/catalog.d.ts.map +1 -0
  84. package/dist/canvas/library-adapters/react/index.d.ts +3 -0
  85. package/dist/canvas/library-adapters/react/index.d.ts.map +1 -0
  86. package/dist/canvas/library-adapters/registry.d.ts +11 -0
  87. package/dist/canvas/library-adapters/registry.d.ts.map +1 -0
  88. package/dist/canvas/library-adapters/types.d.ts +43 -0
  89. package/dist/canvas/library-adapters/types.d.ts.map +1 -0
  90. package/dist/canvas/repo-store.d.ts +2 -0
  91. package/dist/canvas/repo-store.d.ts.map +1 -1
  92. package/dist/canvas/starters/catalog.d.ts +34 -0
  93. package/dist/canvas/starters/catalog.d.ts.map +1 -0
  94. package/dist/canvas/token-references.d.ts +22 -0
  95. package/dist/canvas/token-references.d.ts.map +1 -0
  96. package/dist/canvas/types.d.ts +345 -6
  97. package/dist/canvas/types.d.ts.map +1 -1
  98. package/dist/challenges/action-loop.d.ts +13 -0
  99. package/dist/challenges/action-loop.d.ts.map +1 -0
  100. package/dist/challenges/capability-matrix.d.ts +3 -0
  101. package/dist/challenges/capability-matrix.d.ts.map +1 -0
  102. package/dist/challenges/evidence-bundle.d.ts +48 -0
  103. package/dist/challenges/evidence-bundle.d.ts.map +1 -0
  104. package/dist/challenges/governed-adapter-gateway.d.ts +4 -0
  105. package/dist/challenges/governed-adapter-gateway.d.ts.map +1 -0
  106. package/dist/challenges/human-yield-gate.d.ts +20 -0
  107. package/dist/challenges/human-yield-gate.d.ts.map +1 -0
  108. package/dist/challenges/index.d.ts +15 -0
  109. package/dist/challenges/index.d.ts.map +1 -0
  110. package/dist/challenges/interpreter.d.ts +3 -0
  111. package/dist/challenges/interpreter.d.ts.map +1 -0
  112. package/dist/challenges/optional-computer-use-bridge.d.ts +9 -0
  113. package/dist/challenges/optional-computer-use-bridge.d.ts.map +1 -0
  114. package/dist/challenges/orchestrator.d.ts +32 -0
  115. package/dist/challenges/orchestrator.d.ts.map +1 -0
  116. package/dist/challenges/outcome-recorder.d.ts +8 -0
  117. package/dist/challenges/outcome-recorder.d.ts.map +1 -0
  118. package/dist/challenges/owned-environment-lane.d.ts +3 -0
  119. package/dist/challenges/owned-environment-lane.d.ts.map +1 -0
  120. package/dist/challenges/policy-gate.d.ts +9 -0
  121. package/dist/challenges/policy-gate.d.ts.map +1 -0
  122. package/dist/challenges/sanctioned-identity-lane.d.ts +3 -0
  123. package/dist/challenges/sanctioned-identity-lane.d.ts.map +1 -0
  124. package/dist/challenges/service-adapter-lane.d.ts +3 -0
  125. package/dist/challenges/service-adapter-lane.d.ts.map +1 -0
  126. package/dist/challenges/strategy-selector.d.ts +10 -0
  127. package/dist/challenges/strategy-selector.d.ts.map +1 -0
  128. package/dist/challenges/types.d.ts +277 -0
  129. package/dist/challenges/types.d.ts.map +1 -0
  130. package/dist/challenges/verification-gate.d.ts +15 -0
  131. package/dist/challenges/verification-gate.d.ts.map +1 -0
  132. package/dist/chunk-5FZQJRBQ.js +15256 -0
  133. package/dist/chunk-5FZQJRBQ.js.map +1 -0
  134. package/dist/chunk-L57D35TB.js +33513 -0
  135. package/dist/chunk-L57D35TB.js.map +1 -0
  136. package/dist/chunk-YBQECXZX.js +409 -0
  137. package/dist/chunk-YBQECXZX.js.map +1 -0
  138. package/dist/cli/args.d.ts +4 -4
  139. package/dist/cli/args.d.ts.map +1 -1
  140. package/dist/cli/commands/artifacts.d.ts.map +1 -1
  141. package/dist/cli/commands/canvas.d.ts +7 -7
  142. package/dist/cli/commands/canvas.d.ts.map +1 -1
  143. package/dist/cli/commands/daemon.d.ts +7 -0
  144. package/dist/cli/commands/daemon.d.ts.map +1 -1
  145. package/dist/cli/commands/desktop/accessibility-snapshot.d.ts +3 -0
  146. package/dist/cli/commands/desktop/accessibility-snapshot.d.ts.map +1 -0
  147. package/dist/cli/commands/desktop/active-window.d.ts +3 -0
  148. package/dist/cli/commands/desktop/active-window.d.ts.map +1 -0
  149. package/dist/cli/commands/desktop/capture-desktop.d.ts +3 -0
  150. package/dist/cli/commands/desktop/capture-desktop.d.ts.map +1 -0
  151. package/dist/cli/commands/desktop/capture-window.d.ts +3 -0
  152. package/dist/cli/commands/desktop/capture-window.d.ts.map +1 -0
  153. package/dist/cli/commands/desktop/shared.d.ts +19 -0
  154. package/dist/cli/commands/desktop/shared.d.ts.map +1 -0
  155. package/dist/cli/commands/desktop/status.d.ts +3 -0
  156. package/dist/cli/commands/desktop/status.d.ts.map +1 -0
  157. package/dist/cli/commands/desktop/windows.d.ts +3 -0
  158. package/dist/cli/commands/desktop/windows.d.ts.map +1 -0
  159. package/dist/cli/commands/devtools/dialog.d.ts +19 -0
  160. package/dist/cli/commands/devtools/dialog.d.ts.map +1 -0
  161. package/dist/cli/commands/devtools/screencast-start.d.ts +20 -0
  162. package/dist/cli/commands/devtools/screencast-start.d.ts.map +1 -0
  163. package/dist/cli/commands/devtools/screencast-stop.d.ts +17 -0
  164. package/dist/cli/commands/devtools/screencast-stop.d.ts.map +1 -0
  165. package/dist/cli/commands/devtools/screenshot.d.ts +2 -0
  166. package/dist/cli/commands/devtools/screenshot.d.ts.map +1 -1
  167. package/dist/cli/commands/interact/click.d.ts.map +1 -1
  168. package/dist/cli/commands/interact/pointer-down.d.ts +7 -0
  169. package/dist/cli/commands/interact/pointer-down.d.ts.map +1 -0
  170. package/dist/cli/commands/interact/pointer-drag.d.ts +7 -0
  171. package/dist/cli/commands/interact/pointer-drag.d.ts.map +1 -0
  172. package/dist/cli/commands/interact/pointer-move.d.ts +7 -0
  173. package/dist/cli/commands/interact/pointer-move.d.ts.map +1 -0
  174. package/dist/cli/commands/interact/pointer-shared.d.ts +6 -0
  175. package/dist/cli/commands/interact/pointer-shared.d.ts.map +1 -0
  176. package/dist/cli/commands/interact/pointer-up.d.ts +7 -0
  177. package/dist/cli/commands/interact/pointer-up.d.ts.map +1 -0
  178. package/dist/cli/commands/interact/upload.d.ts +18 -0
  179. package/dist/cli/commands/interact/upload.d.ts.map +1 -0
  180. package/dist/cli/commands/macro-resolve.d.ts +2 -0
  181. package/dist/cli/commands/macro-resolve.d.ts.map +1 -1
  182. package/dist/cli/commands/native.d.ts +10 -7
  183. package/dist/cli/commands/native.d.ts.map +1 -1
  184. package/dist/cli/commands/nav/review.d.ts +7 -0
  185. package/dist/cli/commands/nav/review.d.ts.map +1 -0
  186. package/dist/cli/commands/nav/snapshot.d.ts.map +1 -1
  187. package/dist/cli/commands/pages/open.d.ts.map +1 -1
  188. package/dist/cli/commands/product-video.d.ts +2 -0
  189. package/dist/cli/commands/product-video.d.ts.map +1 -1
  190. package/dist/cli/commands/research.d.ts +3 -0
  191. package/dist/cli/commands/research.d.ts.map +1 -1
  192. package/dist/cli/commands/run.d.ts +14 -0
  193. package/dist/cli/commands/run.d.ts.map +1 -1
  194. package/dist/cli/commands/serve.d.ts +1 -26
  195. package/dist/cli/commands/serve.d.ts.map +1 -1
  196. package/dist/cli/commands/session/connect.d.ts.map +1 -1
  197. package/dist/cli/commands/session/disconnect.d.ts.map +1 -1
  198. package/dist/cli/commands/session/inspector.d.ts +21 -0
  199. package/dist/cli/commands/session/inspector.d.ts.map +1 -0
  200. package/dist/cli/commands/session/launch.d.ts.map +1 -1
  201. package/dist/cli/commands/shopping.d.ts +5 -0
  202. package/dist/cli/commands/shopping.d.ts.map +1 -1
  203. package/dist/cli/commands/status.d.ts +2 -14
  204. package/dist/cli/commands/status.d.ts.map +1 -1
  205. package/dist/cli/commands/targets/new.d.ts.map +1 -1
  206. package/dist/cli/daemon-autostart.d.ts +11 -0
  207. package/dist/cli/daemon-autostart.d.ts.map +1 -1
  208. package/dist/cli/daemon-client.d.ts +3 -0
  209. package/dist/cli/daemon-client.d.ts.map +1 -1
  210. package/dist/cli/daemon-commands.d.ts.map +1 -1
  211. package/dist/cli/daemon-state.d.ts +16 -0
  212. package/dist/cli/daemon-state.d.ts.map +1 -1
  213. package/dist/cli/daemon-status.d.ts +7 -2
  214. package/dist/cli/daemon-status.d.ts.map +1 -1
  215. package/dist/cli/daemon.d.ts +1 -0
  216. package/dist/cli/daemon.d.ts.map +1 -1
  217. package/dist/cli/help.d.ts +15 -4
  218. package/dist/cli/help.d.ts.map +1 -1
  219. package/dist/cli/index.js +2467 -1033
  220. package/dist/cli/index.js.map +1 -1
  221. package/dist/cli/install-autostart-output.d.ts +6 -0
  222. package/dist/cli/install-autostart-output.d.ts.map +1 -0
  223. package/dist/cli/install-autostart-reconciliation.d.ts +23 -0
  224. package/dist/cli/install-autostart-reconciliation.d.ts.map +1 -0
  225. package/dist/cli/installers/skills.d.ts +42 -6
  226. package/dist/cli/installers/skills.d.ts.map +1 -1
  227. package/dist/cli/output.d.ts +3 -0
  228. package/dist/cli/output.d.ts.map +1 -1
  229. package/dist/cli/remote-desktop-runtime.d.ts +15 -0
  230. package/dist/cli/remote-desktop-runtime.d.ts.map +1 -0
  231. package/dist/cli/remote-manager.d.ts +24 -2
  232. package/dist/cli/remote-manager.d.ts.map +1 -1
  233. package/dist/cli/transport-timeouts.d.ts +8 -0
  234. package/dist/cli/transport-timeouts.d.ts.map +1 -0
  235. package/dist/cli/utils/http.d.ts +9 -0
  236. package/dist/cli/utils/http.d.ts.map +1 -1
  237. package/dist/cli/utils/parse.d.ts +2 -0
  238. package/dist/cli/utils/parse.d.ts.map +1 -1
  239. package/dist/cli/utils/skills.d.ts +1 -2
  240. package/dist/cli/utils/skills.d.ts.map +1 -1
  241. package/dist/cli/utils/workflow-message.d.ts +2 -0
  242. package/dist/cli/utils/workflow-message.d.ts.map +1 -0
  243. package/dist/config.d.ts +47 -0
  244. package/dist/config.d.ts.map +1 -1
  245. package/dist/core/bootstrap.d.ts.map +1 -1
  246. package/dist/core/index.d.ts +1 -0
  247. package/dist/core/index.d.ts.map +1 -1
  248. package/dist/core/logging.d.ts +3 -1
  249. package/dist/core/logging.d.ts.map +1 -1
  250. package/dist/core/runtime-assemblies.d.ts +22 -0
  251. package/dist/core/runtime-assemblies.d.ts.map +1 -0
  252. package/dist/core/types.d.ts +15 -0
  253. package/dist/core/types.d.ts.map +1 -1
  254. package/dist/desktop/audit.d.ts +37 -0
  255. package/dist/desktop/audit.d.ts.map +1 -0
  256. package/dist/desktop/errors.d.ts +7 -0
  257. package/dist/desktop/errors.d.ts.map +1 -0
  258. package/dist/desktop/index.d.ts +6 -0
  259. package/dist/desktop/index.d.ts.map +1 -0
  260. package/dist/desktop/runtime.d.ts +26 -0
  261. package/dist/desktop/runtime.d.ts.map +1 -0
  262. package/dist/desktop/types.d.ts +76 -0
  263. package/dist/desktop/types.d.ts.map +1 -0
  264. package/dist/extension-extractor.d.ts +6 -0
  265. package/dist/extension-extractor.d.ts.map +1 -1
  266. package/dist/index.d.ts.map +1 -1
  267. package/dist/index.js +1103 -467
  268. package/dist/index.js.map +1 -1
  269. package/dist/integrations/figma/assets.d.ts +13 -0
  270. package/dist/integrations/figma/assets.d.ts.map +1 -0
  271. package/dist/integrations/figma/auth.d.ts +3 -0
  272. package/dist/integrations/figma/auth.d.ts.map +1 -0
  273. package/dist/integrations/figma/client.d.ts +42 -0
  274. package/dist/integrations/figma/client.d.ts.map +1 -0
  275. package/dist/integrations/figma/mappers.d.ts +23 -0
  276. package/dist/integrations/figma/mappers.d.ts.map +1 -0
  277. package/dist/integrations/figma/normalize.d.ts +99 -0
  278. package/dist/integrations/figma/normalize.d.ts.map +1 -0
  279. package/dist/integrations/figma/url.d.ts +17 -0
  280. package/dist/integrations/figma/url.d.ts.map +1 -0
  281. package/dist/integrations/figma/variables.d.ts +21 -0
  282. package/dist/integrations/figma/variables.d.ts.map +1 -0
  283. package/dist/macros/execute-runtime.d.ts +19 -0
  284. package/dist/macros/execute-runtime.d.ts.map +1 -0
  285. package/dist/macros/execute.d.ts +3 -1
  286. package/dist/macros/execute.d.ts.map +1 -1
  287. package/dist/opendevbrowser.d.ts.map +1 -1
  288. package/dist/opendevbrowser.js +1103 -467
  289. package/dist/opendevbrowser.js.map +1 -1
  290. package/dist/providers/blocker.d.ts.map +1 -1
  291. package/dist/providers/browser-fallback.d.ts +30 -0
  292. package/dist/providers/browser-fallback.d.ts.map +1 -0
  293. package/dist/providers/constraint.d.ts +45 -0
  294. package/dist/providers/constraint.d.ts.map +1 -0
  295. package/dist/providers/index.d.ts +11 -2
  296. package/dist/providers/index.d.ts.map +1 -1
  297. package/dist/providers/policy.d.ts.map +1 -1
  298. package/dist/providers/product-video-compiler.d.ts +92 -0
  299. package/dist/providers/product-video-compiler.d.ts.map +1 -0
  300. package/dist/providers/registry.d.ts +37 -1
  301. package/dist/providers/registry.d.ts.map +1 -1
  302. package/dist/providers/renderer.d.ts.map +1 -1
  303. package/dist/providers/research-compiler.d.ts +64 -0
  304. package/dist/providers/research-compiler.d.ts.map +1 -0
  305. package/dist/providers/research-executor.d.ts +27 -0
  306. package/dist/providers/research-executor.d.ts.map +1 -0
  307. package/dist/providers/runtime-bundle.d.ts +26 -0
  308. package/dist/providers/runtime-bundle.d.ts.map +1 -0
  309. package/dist/providers/runtime-factory.d.ts +6 -1
  310. package/dist/providers/runtime-factory.d.ts.map +1 -1
  311. package/dist/providers/runtime-policy.d.ts +24 -0
  312. package/dist/providers/runtime-policy.d.ts.map +1 -0
  313. package/dist/providers/shared/anti-bot-policy.d.ts +3 -2
  314. package/dist/providers/shared/anti-bot-policy.d.ts.map +1 -1
  315. package/dist/providers/shopping/index.d.ts +11 -1
  316. package/dist/providers/shopping/index.d.ts.map +1 -1
  317. package/dist/providers/shopping-compiler.d.ts +51 -0
  318. package/dist/providers/shopping-compiler.d.ts.map +1 -0
  319. package/dist/providers/shopping-executor.d.ts +18 -0
  320. package/dist/providers/shopping-executor.d.ts.map +1 -0
  321. package/dist/providers/shopping-postprocess.d.ts +46 -0
  322. package/dist/providers/shopping-postprocess.d.ts.map +1 -0
  323. package/dist/providers/shopping-workflow.d.ts +33 -0
  324. package/dist/providers/shopping-workflow.d.ts.map +1 -0
  325. package/dist/providers/social/platform.d.ts +2 -1
  326. package/dist/providers/social/platform.d.ts.map +1 -1
  327. package/dist/providers/social/search-quality.d.ts +16 -0
  328. package/dist/providers/social/search-quality.d.ts.map +1 -0
  329. package/dist/providers/social/youtube-resolver.d.ts +2 -1
  330. package/dist/providers/social/youtube-resolver.d.ts.map +1 -1
  331. package/dist/providers/social/youtube.d.ts.map +1 -1
  332. package/dist/providers/types.d.ts +116 -4
  333. package/dist/providers/types.d.ts.map +1 -1
  334. package/dist/providers/web/crawl-worker.d.ts.map +1 -1
  335. package/dist/providers/web/extract.d.ts +16 -0
  336. package/dist/providers/web/extract.d.ts.map +1 -1
  337. package/dist/providers/web/index.d.ts.map +1 -1
  338. package/dist/providers/workflow-contracts.d.ts +53 -0
  339. package/dist/providers/workflow-contracts.d.ts.map +1 -0
  340. package/dist/providers/workflows.d.ts +30 -6
  341. package/dist/providers/workflows.d.ts.map +1 -1
  342. package/dist/{providers-G3LRHQXX.js → providers-G36AM3Z2.js} +2 -2
  343. package/dist/public-surface/generated-manifest.d.ts +1168 -0
  344. package/dist/public-surface/generated-manifest.d.ts.map +1 -0
  345. package/dist/public-surface/source.d.ts +437 -0
  346. package/dist/public-surface/source.d.ts.map +1 -0
  347. package/dist/relay/protocol.d.ts +25 -3
  348. package/dist/relay/protocol.d.ts.map +1 -1
  349. package/dist/relay/relay-endpoints.d.ts +21 -0
  350. package/dist/relay/relay-endpoints.d.ts.map +1 -1
  351. package/dist/relay/relay-server.d.ts +18 -0
  352. package/dist/relay/relay-server.d.ts.map +1 -1
  353. package/dist/skills/bundled-skill-directories.d.ts +8 -0
  354. package/dist/skills/bundled-skill-directories.d.ts.map +1 -0
  355. package/dist/skills/skill-loader.d.ts +9 -1
  356. package/dist/skills/skill-loader.d.ts.map +1 -1
  357. package/dist/skills/skill-loader.js +7 -0
  358. package/dist/skills/skill-nudge.d.ts.map +1 -1
  359. package/dist/skills/types.d.ts +31 -0
  360. package/dist/skills/types.d.ts.map +1 -1
  361. package/dist/snapshot/ops-snapshot.d.ts +1 -1
  362. package/dist/snapshot/ops-snapshot.d.ts.map +1 -1
  363. package/dist/snapshot/refs.d.ts +6 -1
  364. package/dist/snapshot/refs.d.ts.map +1 -1
  365. package/dist/snapshot/snapshotter.d.ts.map +1 -1
  366. package/dist/tools/connect.d.ts.map +1 -1
  367. package/dist/tools/deps.d.ts +4 -0
  368. package/dist/tools/deps.d.ts.map +1 -1
  369. package/dist/tools/desktop-shared.d.ts +6 -0
  370. package/dist/tools/desktop-shared.d.ts.map +1 -0
  371. package/dist/tools/desktop_accessibility_snapshot.d.ts +4 -0
  372. package/dist/tools/desktop_accessibility_snapshot.d.ts.map +1 -0
  373. package/dist/tools/desktop_active_window.d.ts +4 -0
  374. package/dist/tools/desktop_active_window.d.ts.map +1 -0
  375. package/dist/tools/desktop_capture_desktop.d.ts +4 -0
  376. package/dist/tools/desktop_capture_desktop.d.ts.map +1 -0
  377. package/dist/tools/desktop_capture_window.d.ts +4 -0
  378. package/dist/tools/desktop_capture_window.d.ts.map +1 -0
  379. package/dist/tools/desktop_status.d.ts +4 -0
  380. package/dist/tools/desktop_status.d.ts.map +1 -0
  381. package/dist/tools/desktop_windows.d.ts +4 -0
  382. package/dist/tools/desktop_windows.d.ts.map +1 -0
  383. package/dist/tools/dialog.d.ts +4 -0
  384. package/dist/tools/dialog.d.ts.map +1 -0
  385. package/dist/tools/index.d.ts +3 -0
  386. package/dist/tools/index.d.ts.map +1 -1
  387. package/dist/tools/launch.d.ts.map +1 -1
  388. package/dist/tools/macro_resolve.d.ts.map +1 -1
  389. package/dist/tools/pointer_down.d.ts +4 -0
  390. package/dist/tools/pointer_down.d.ts.map +1 -0
  391. package/dist/tools/pointer_drag.d.ts +4 -0
  392. package/dist/tools/pointer_drag.d.ts.map +1 -0
  393. package/dist/tools/pointer_move.d.ts +4 -0
  394. package/dist/tools/pointer_move.d.ts.map +1 -0
  395. package/dist/tools/pointer_up.d.ts +4 -0
  396. package/dist/tools/pointer_up.d.ts.map +1 -0
  397. package/dist/tools/product_video_run.d.ts.map +1 -1
  398. package/dist/tools/prompting_guide.d.ts.map +1 -1
  399. package/dist/tools/research_run.d.ts.map +1 -1
  400. package/dist/tools/review.d.ts +4 -0
  401. package/dist/tools/review.d.ts.map +1 -0
  402. package/dist/tools/screencast_start.d.ts +4 -0
  403. package/dist/tools/screencast_start.d.ts.map +1 -0
  404. package/dist/tools/screencast_stop.d.ts +4 -0
  405. package/dist/tools/screencast_stop.d.ts.map +1 -0
  406. package/dist/tools/screenshot.d.ts.map +1 -1
  407. package/dist/tools/session_inspector.d.ts +4 -0
  408. package/dist/tools/session_inspector.d.ts.map +1 -0
  409. package/dist/tools/shopping_run.d.ts.map +1 -1
  410. package/dist/tools/skill_list.d.ts.map +1 -1
  411. package/dist/tools/skill_load.d.ts.map +1 -1
  412. package/dist/tools/upload.d.ts +4 -0
  413. package/dist/tools/upload.d.ts.map +1 -0
  414. package/dist/tools/workflow-runtime.d.ts +4 -1
  415. package/dist/tools/workflow-runtime.d.ts.map +1 -1
  416. package/dist/utils/package-assets.d.ts +4 -0
  417. package/dist/utils/package-assets.d.ts.map +1 -0
  418. package/extension/canvas.html +379 -9
  419. package/extension/dist/annotate-content.js +62 -32
  420. package/extension/dist/annotation-payload.js +57 -21
  421. package/extension/dist/background.js +406 -61
  422. package/extension/dist/canvas/canvas-runtime.js +481 -52
  423. package/extension/dist/canvas/model.js +129 -1
  424. package/extension/dist/canvas-page.js +1882 -74
  425. package/extension/dist/ops/dom-bridge.js +139 -0
  426. package/extension/dist/ops/ops-runtime.js +2854 -295
  427. package/extension/dist/ops/ops-session-store.js +83 -5
  428. package/extension/dist/ops/snapshot-builder.js +2 -2
  429. package/extension/dist/ops/snapshot-shared.js +2 -2
  430. package/extension/dist/ops/target-session-coordinator.js +5 -3
  431. package/extension/dist/popup.js +50 -15
  432. package/extension/dist/services/CDPRouter.js +1567 -63
  433. package/extension/dist/services/ConnectionManager.js +436 -78
  434. package/extension/dist/services/RelayClient.js +70 -30
  435. package/extension/dist/services/TabManager.js +83 -10
  436. package/extension/dist/services/TargetSessionMap.js +127 -3
  437. package/extension/dist/services/attach-errors.js +20 -0
  438. package/extension/dist/services/cdp-router-commands.js +135 -8
  439. package/extension/dist/services/url-restrictions.js +9 -13
  440. package/extension/manifest.json +2 -2
  441. package/extension/popup.html +7 -6
  442. package/package.json +15 -7
  443. package/skills/AGENTS.md +9 -8
  444. package/skills/opendevbrowser-best-practices/SKILL.md +118 -9
  445. package/skills/opendevbrowser-best-practices/artifacts/browser-agent-known-issues-matrix.md +1 -0
  446. package/skills/opendevbrowser-best-practices/artifacts/command-channel-reference.md +26 -12
  447. package/skills/opendevbrowser-best-practices/artifacts/parity-gates.md +9 -2
  448. package/skills/opendevbrowser-best-practices/artifacts/provider-workflows.md +6 -0
  449. package/skills/opendevbrowser-best-practices/artifacts/skill-runtime-surface-matrix.md +58 -0
  450. package/skills/opendevbrowser-best-practices/assets/templates/skill-runtime-pack-matrix.json +674 -0
  451. package/skills/opendevbrowser-best-practices/assets/templates/surface-audit-checklist.json +9 -4
  452. package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +89 -20
  453. package/skills/opendevbrowser-best-practices/scripts/resolve-odb-cli.sh +100 -0
  454. package/skills/opendevbrowser-best-practices/scripts/run-robustness-audit.sh +1 -0
  455. package/skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh +256 -116
  456. package/skills/opendevbrowser-best-practices/scripts/validator-fixture-cli.sh +208 -0
  457. package/skills/opendevbrowser-continuity-ledger/SKILL.md +14 -1
  458. package/skills/opendevbrowser-continuity-ledger/scripts/validate-skill-assets.sh +61 -0
  459. package/skills/opendevbrowser-data-extraction/SKILL.md +6 -0
  460. package/skills/opendevbrowser-data-extraction/scripts/validate-skill-assets.sh +112 -0
  461. package/skills/opendevbrowser-design-agent/SKILL.md +275 -0
  462. package/skills/opendevbrowser-design-agent/artifacts/app-shell-and-state-wiring.md +84 -0
  463. package/skills/opendevbrowser-design-agent/artifacts/async-search-state-ownership.md +58 -0
  464. package/skills/opendevbrowser-design-agent/artifacts/component-pattern-index.md +130 -0
  465. package/skills/opendevbrowser-design-agent/artifacts/design-contract-playbook.md +157 -0
  466. package/skills/opendevbrowser-design-agent/artifacts/design-release-gate.md +40 -0
  467. package/skills/opendevbrowser-design-agent/artifacts/design-workflows.md +153 -0
  468. package/skills/opendevbrowser-design-agent/artifacts/existing-surface-adaptation.md +56 -0
  469. package/skills/opendevbrowser-design-agent/artifacts/external-pattern-synthesis.md +103 -0
  470. package/skills/opendevbrowser-design-agent/artifacts/frontend-evaluation-rubric.md +61 -0
  471. package/skills/opendevbrowser-design-agent/artifacts/implementation-anti-patterns.md +163 -0
  472. package/skills/opendevbrowser-design-agent/artifacts/isolated-preview-validation.md +68 -0
  473. package/skills/opendevbrowser-design-agent/artifacts/loading-and-feedback-surfaces.md +56 -0
  474. package/skills/opendevbrowser-design-agent/artifacts/opendevbrowser-ui-example-map.md +44 -0
  475. package/skills/opendevbrowser-design-agent/artifacts/performance-audit-playbook.md +70 -0
  476. package/skills/opendevbrowser-design-agent/artifacts/research-harvest-workflow.md +81 -0
  477. package/skills/opendevbrowser-design-agent/artifacts/scroll-reveal-surface-planning.md +64 -0
  478. package/skills/opendevbrowser-design-agent/artifacts/state-ownership-matrix.md +36 -0
  479. package/skills/opendevbrowser-design-agent/artifacts/theming-and-token-ownership.md +43 -0
  480. package/skills/opendevbrowser-design-agent/assets/templates/canvas-generation-plan.design.v1.json +58 -0
  481. package/skills/opendevbrowser-design-agent/assets/templates/design-audit-report.v1.md +34 -0
  482. package/skills/opendevbrowser-design-agent/assets/templates/design-brief.v1.md +40 -0
  483. package/skills/opendevbrowser-design-agent/assets/templates/design-contract.v1.json +226 -0
  484. package/skills/opendevbrowser-design-agent/assets/templates/design-release-gate.v1.json +35 -0
  485. package/skills/opendevbrowser-design-agent/assets/templates/design-review-checklist.json +57 -0
  486. package/skills/opendevbrowser-design-agent/assets/templates/real-surface-design-matrix.json +32 -0
  487. package/skills/opendevbrowser-design-agent/assets/templates/reference-pattern-board.v1.json +31 -0
  488. package/skills/opendevbrowser-design-agent/scripts/design-workflow.sh +171 -0
  489. package/skills/opendevbrowser-design-agent/scripts/extract-canvas-plan.sh +56 -0
  490. package/skills/opendevbrowser-design-agent/scripts/validate-skill-assets.sh +223 -0
  491. package/skills/opendevbrowser-form-testing/SKILL.md +19 -3
  492. package/skills/opendevbrowser-form-testing/artifacts/form-workflows.md +5 -4
  493. package/skills/opendevbrowser-form-testing/assets/templates/challenge-decision-tree.json +2 -0
  494. package/skills/opendevbrowser-form-testing/scripts/validate-skill-assets.sh +109 -0
  495. package/skills/opendevbrowser-login-automation/SKILL.md +21 -3
  496. package/skills/opendevbrowser-login-automation/artifacts/login-workflows.md +5 -4
  497. package/skills/opendevbrowser-login-automation/assets/templates/auth-signals.json +5 -0
  498. package/skills/opendevbrowser-login-automation/assets/templates/login-scenario-matrix.json +3 -2
  499. package/skills/opendevbrowser-login-automation/scripts/run-login-workflow.sh +17 -1
  500. package/skills/opendevbrowser-login-automation/scripts/validate-skill-assets.sh +133 -0
  501. package/skills/opendevbrowser-product-presentation-asset/SKILL.md +23 -11
  502. package/skills/opendevbrowser-product-presentation-asset/artifacts/asset-pack-assembly.md +5 -3
  503. package/skills/opendevbrowser-product-presentation-asset/assets/templates/shot-list.md +2 -0
  504. package/skills/opendevbrowser-product-presentation-asset/assets/templates/video-assembly.md +3 -2
  505. package/skills/opendevbrowser-product-presentation-asset/scripts/capture-screenshots.sh +5 -1
  506. package/skills/opendevbrowser-product-presentation-asset/scripts/collect-product.sh +6 -2
  507. package/skills/opendevbrowser-product-presentation-asset/scripts/download-images.sh +5 -1
  508. package/skills/opendevbrowser-product-presentation-asset/scripts/render-video-brief.sh +20 -7
  509. package/skills/opendevbrowser-product-presentation-asset/scripts/validate-skill-assets.sh +39 -0
  510. package/skills/opendevbrowser-product-presentation-asset/scripts/write-manifest.sh +5 -1
  511. package/skills/opendevbrowser-research/SKILL.md +14 -6
  512. package/skills/opendevbrowser-research/scripts/render-output.sh +5 -1
  513. package/skills/opendevbrowser-research/scripts/run-research.sh +5 -1
  514. package/skills/opendevbrowser-research/scripts/validate-skill-assets.sh +45 -0
  515. package/skills/opendevbrowser-research/scripts/write-artifacts.sh +5 -1
  516. package/skills/opendevbrowser-shopping/SKILL.md +20 -1
  517. package/skills/opendevbrowser-shopping/scripts/normalize-offers.sh +6 -2
  518. package/skills/opendevbrowser-shopping/scripts/run-deal-hunt.sh +5 -1
  519. package/skills/opendevbrowser-shopping/scripts/run-shopping.sh +5 -1
  520. package/skills/opendevbrowser-shopping/scripts/validate-skill-assets.sh +54 -0
  521. package/dist/chunk-5J3IFL3X.js +0 -16706
  522. package/dist/chunk-5J3IFL3X.js.map +0 -1
  523. package/dist/chunk-D633UO34.js +0 -8149
  524. package/dist/chunk-D633UO34.js.map +0 -1
  525. package/dist/chunk-V7KUDHDG.js +0 -276
  526. package/dist/chunk-V7KUDHDG.js.map +0 -1
  527. package/dist/runtime-factory-BICHDPE7.js +0 -13
  528. /package/dist/{providers-G3LRHQXX.js.map → providers-G36AM3Z2.js.map} +0 -0
  529. /package/dist/{runtime-factory-BICHDPE7.js.map → skills/skill-loader.js.map} +0 -0
@@ -1,5 +1,5 @@
1
- import { summarizeCanvasProjectionState, normalizeCanvasSessionSummary, normalizeCanvasTargetStateSummaries } from "./canvas/model.js";
2
- import { buildCanvasAnnotationPayload, describeAnnotationItem } from "./annotation-payload.js";
1
+ import { summarizeCanvasProjectionState, summarizeCanvasHistoryState, readLatestImportProvenance, readSelectedBindingIdentity, normalizeCanvasSessionSummary, normalizeCanvasTargetStateSummaries } from "./canvas/model.js";
2
+ import { buildCanvasAnnotationPayload, describeAnnotationItem, formatAnnotationDispatchReceipt } from "./annotation-payload.js";
3
3
  import { DEFAULT_EDITOR_VIEWPORT, computeFittedViewport, computeViewportCanvasCenter, isDefaultEditorViewport } from "./canvas/viewport-fit.js";
4
4
  const DB_NAME = "opendevbrowser-canvas";
5
5
  const DB_VERSION = 2;
@@ -13,17 +13,66 @@ const metaElement = requiredElement("canvas-meta");
13
13
  const toolbarMetaElement = requiredElement("canvas-toolbar-meta");
14
14
  const summaryElement = requiredElement("canvas-summary");
15
15
  const feedbackElement = requiredElement("canvas-feedback");
16
+ const pageDetailsElement = requiredElement("canvas-page-details");
17
+ const pageSelectElement = requiredElement("canvas-page-select");
18
+ const layersTreeElement = requiredElement("canvas-layers-tree");
16
19
  const selectionMetaElement = requiredElement("canvas-selection-meta");
17
20
  const stageElement = requiredElement("canvas-stage");
18
21
  const stageInnerElement = requiredElement("canvas-stage-inner");
22
+ const stageOverlayElement = requiredElement("canvas-stage-overlay");
19
23
  const stageMetaElement = requiredElement("canvas-stage-meta");
24
+ const stageHintElement = requiredElement("canvas-stage-hint");
20
25
  const previewElement = requiredElement("canvas-preview");
21
26
  const emptyElement = requiredElement("canvas-empty");
27
+ const historyUndoButton = requiredElement("canvas-history-undo");
28
+ const historyRedoButton = requiredElement("canvas-history-redo");
29
+ const panelHistoryUndoButton = requiredElement("canvas-history-panel-undo");
30
+ const panelHistoryRedoButton = requiredElement("canvas-history-panel-redo");
31
+ const historyStatusElement = requiredElement("canvas-history-status");
22
32
  const nameInput = requiredElement("canvas-node-name");
23
33
  const textInput = requiredElement("canvas-node-text");
34
+ const nodeXInput = requiredElement("canvas-node-x");
35
+ const nodeYInput = requiredElement("canvas-node-y");
36
+ const nodeWidthInput = requiredElement("canvas-node-width");
37
+ const nodeHeightInput = requiredElement("canvas-node-height");
38
+ const paddingInput = requiredElement("canvas-style-padding");
39
+ const gapInput = requiredElement("canvas-style-gap");
40
+ const fontSizeInput = requiredElement("canvas-style-font-size");
41
+ const fontWeightInput = requiredElement("canvas-style-font-weight");
42
+ const lineHeightInput = requiredElement("canvas-style-line-height");
43
+ const colorInput = requiredElement("canvas-style-color");
44
+ const backgroundInput = requiredElement("canvas-style-background");
45
+ const borderColorInput = requiredElement("canvas-style-border-color");
46
+ const borderWidthInput = requiredElement("canvas-style-border-width");
47
+ const borderRadiusInput = requiredElement("canvas-style-border-radius");
48
+ const shadowInput = requiredElement("canvas-style-shadow");
49
+ const bindingKindInput = requiredElement("canvas-binding-kind");
50
+ const bindingComponentInput = requiredElement("canvas-binding-component");
51
+ const bindingSelectorInput = requiredElement("canvas-binding-selector");
52
+ const a11yRoleInput = requiredElement("canvas-a11y-role");
53
+ const a11yLabelInput = requiredElement("canvas-a11y-label");
54
+ const propertiesStatusElement = requiredElement("canvas-properties-status");
55
+ const tokenStatusElement = requiredElement("canvas-token-status");
56
+ const tokenCollectionSelect = requiredElement("canvas-token-collection-select");
57
+ const tokenCollectionNameInput = requiredElement("canvas-token-collection-name");
58
+ const tokenCollectionCreateButton = requiredElement("canvas-token-collection-create");
59
+ const tokenModeSelect = requiredElement("canvas-token-mode-select");
60
+ const tokenModeNameInput = requiredElement("canvas-token-mode-name");
61
+ const tokenModeCreateButton = requiredElement("canvas-token-mode-create");
62
+ const tokenPathInput = requiredElement("canvas-token-path");
63
+ const tokenValueInput = requiredElement("canvas-token-value");
64
+ const tokenAliasInput = requiredElement("canvas-token-alias");
65
+ const tokenBindingPropertySelect = requiredElement("canvas-token-binding-property");
66
+ const tokenSaveButton = requiredElement("canvas-token-save");
67
+ const tokenBindButton = requiredElement("canvas-token-bind");
68
+ const tokenBindingClearButton = requiredElement("canvas-token-binding-clear");
69
+ const tokenSummaryElement = requiredElement("canvas-token-summary");
70
+ const tokenUsageElement = requiredElement("canvas-token-usage");
71
+ const duplicateNodeButton = requiredElement("canvas-duplicate-node");
24
72
  const addNoteButton = requiredElement("canvas-add-note");
25
73
  const resetViewButton = requiredElement("canvas-reset-view");
26
74
  const deleteNodeButton = requiredElement("canvas-delete-node");
75
+ const annotationModeSelect = requiredElement("canvas-annotation-mode");
27
76
  const annotationAddButton = requiredElement("canvas-annotation-add");
28
77
  const annotationCopyButton = requiredElement("canvas-annotation-copy");
29
78
  const annotationSendButton = requiredElement("canvas-annotation-send");
@@ -36,9 +85,18 @@ let currentTabId = null;
36
85
  let databasePromise = null;
37
86
  let persistTimer = null;
38
87
  let fitViewportFrame = null;
88
+ let activePageId = null;
89
+ let annotationMode = "selected";
39
90
  let annotationDrafts = [];
91
+ let expandedLayerNodeIds = new Set();
92
+ let selectedTokenCollectionId = "__values__";
93
+ let selectedTokenModeId = "__base__";
94
+ let selectedTokenPath = "";
40
95
  let draggingNode = null;
96
+ let layerDragState = null;
41
97
  let panningState = null;
98
+ let marqueeState = null;
99
+ let spacePanActive = false;
42
100
  void bootstrap();
43
101
  async function bootstrap() {
44
102
  currentTabId = await getCurrentTabId();
@@ -62,8 +120,11 @@ async function bootstrap() {
62
120
  port.postMessage({ type: "canvas-page-ready" });
63
121
  bindStageInteractions();
64
122
  bindInspector();
123
+ bindTokenPanel();
65
124
  bindToolbar();
66
125
  bindAnnotationPanel();
126
+ bindPageSelector();
127
+ bindKeyboardShortcuts();
67
128
  document.addEventListener("visibilitychange", () => {
68
129
  if (document.visibilityState === "hidden") {
69
130
  flushPersist();
@@ -74,11 +135,24 @@ async function bootstrap() {
74
135
  });
75
136
  }
76
137
  function bindToolbar() {
138
+ const handleUndo = () => {
139
+ requestHistory("undo");
140
+ };
141
+ const handleRedo = () => {
142
+ requestHistory("redo");
143
+ };
144
+ historyUndoButton.addEventListener("click", handleUndo);
145
+ historyRedoButton.addEventListener("click", handleRedo);
146
+ panelHistoryUndoButton.addEventListener("click", handleUndo);
147
+ panelHistoryRedoButton.addEventListener("click", handleRedo);
148
+ duplicateNodeButton.addEventListener("click", () => {
149
+ duplicateSelectedNode();
150
+ });
77
151
  addNoteButton.addEventListener("click", () => {
78
152
  if (!currentState || currentState.pendingMutation) {
79
153
  return;
80
154
  }
81
- const page = currentState.document.pages[0];
155
+ const page = getActivePage();
82
156
  if (!page) {
83
157
  return;
84
158
  }
@@ -129,7 +203,15 @@ function bindToolbar() {
129
203
  });
130
204
  }
131
205
  function bindAnnotationPanel() {
206
+ annotationModeSelect.addEventListener("change", () => {
207
+ annotationMode = annotationModeSelect.value === "region" ? "region" : "selected";
208
+ renderState();
209
+ });
132
210
  annotationAddButton.addEventListener("click", () => {
211
+ if (annotationMode === "region") {
212
+ setCanvasButtonFeedback(annotationAddButton, "Drag on stage");
213
+ return;
214
+ }
133
215
  addSelectedAnnotationDraft();
134
216
  });
135
217
  annotationCopyButton.addEventListener("click", () => {
@@ -145,6 +227,71 @@ function bindAnnotationPanel() {
145
227
  });
146
228
  });
147
229
  }
230
+ function bindPageSelector() {
231
+ pageSelectElement.addEventListener("change", () => {
232
+ if (!currentState) {
233
+ return;
234
+ }
235
+ const pageId = pageSelectElement.value || null;
236
+ setActivePage(pageId, { clearSelectionIfMissing: true, broadcast: true });
237
+ });
238
+ }
239
+ function bindKeyboardShortcuts() {
240
+ document.addEventListener("keydown", (event) => {
241
+ if (!currentState) {
242
+ return;
243
+ }
244
+ if (event.key === " ") {
245
+ if (!isEditableTarget(event.target)) {
246
+ spacePanActive = true;
247
+ stageElement.dataset.mode = "panning";
248
+ event.preventDefault();
249
+ }
250
+ return;
251
+ }
252
+ if (isEditableTarget(event.target)) {
253
+ return;
254
+ }
255
+ const modifier = event.metaKey || event.ctrlKey;
256
+ if (modifier && event.key.toLowerCase() === "z") {
257
+ event.preventDefault();
258
+ requestHistory(event.shiftKey ? "redo" : "undo");
259
+ return;
260
+ }
261
+ if (modifier && event.key.toLowerCase() === "d") {
262
+ event.preventDefault();
263
+ duplicateSelectedNode();
264
+ return;
265
+ }
266
+ if ((event.key === "Delete" || event.key === "Backspace") && currentState.selection.nodeId) {
267
+ event.preventDefault();
268
+ deleteNodeButton.click();
269
+ return;
270
+ }
271
+ if (event.key.startsWith("Arrow") && currentState.selection.nodeId && !event.altKey) {
272
+ event.preventDefault();
273
+ nudgeSelectedNode(event.key, event.shiftKey ? 10 : 1);
274
+ return;
275
+ }
276
+ if (event.key === "0") {
277
+ event.preventDefault();
278
+ fitActivePageViewport();
279
+ return;
280
+ }
281
+ if (event.key === "1") {
282
+ event.preventDefault();
283
+ resetZoomToDefault();
284
+ }
285
+ });
286
+ document.addEventListener("keyup", (event) => {
287
+ if (event.key === " ") {
288
+ spacePanActive = false;
289
+ if (!panningState) {
290
+ stageElement.dataset.mode = "";
291
+ }
292
+ }
293
+ });
294
+ }
148
295
  function bindInspector() {
149
296
  nameInput.addEventListener("change", () => {
150
297
  const node = getSelectedNode();
@@ -168,22 +315,142 @@ function bindInspector() {
168
315
  changes: { "props.text": textInput.value }
169
316
  }], currentState.selection);
170
317
  });
318
+ bindFieldCommit(nodeXInput, () => {
319
+ commitSelectedNodeChanges({ "rect.x": readNumberInput(nodeXInput, getSelectedNode()?.rect.x ?? 0) });
320
+ });
321
+ bindFieldCommit(nodeYInput, () => {
322
+ commitSelectedNodeChanges({ "rect.y": readNumberInput(nodeYInput, getSelectedNode()?.rect.y ?? 0) });
323
+ });
324
+ bindFieldCommit(nodeWidthInput, () => {
325
+ commitSelectedNodeChanges({ "rect.width": Math.max(readNumberInput(nodeWidthInput, getSelectedNode()?.rect.width ?? 1), 1) });
326
+ });
327
+ bindFieldCommit(nodeHeightInput, () => {
328
+ commitSelectedNodeChanges({ "rect.height": Math.max(readNumberInput(nodeHeightInput, getSelectedNode()?.rect.height ?? 1), 1) });
329
+ });
330
+ bindFieldCommit(paddingInput, () => {
331
+ commitSelectedNodeChanges({ "style.padding": readTextInput(paddingInput) });
332
+ });
333
+ bindFieldCommit(gapInput, () => {
334
+ commitSelectedNodeChanges({ "style.gap": readTextInput(gapInput) });
335
+ });
336
+ bindFieldCommit(fontSizeInput, () => {
337
+ commitSelectedNodeChanges({ "style.fontSize": readTextInput(fontSizeInput) });
338
+ });
339
+ bindFieldCommit(fontWeightInput, () => {
340
+ commitSelectedNodeChanges({ "style.fontWeight": readTextInput(fontWeightInput) });
341
+ });
342
+ bindFieldCommit(lineHeightInput, () => {
343
+ commitSelectedNodeChanges({ "style.lineHeight": readTextInput(lineHeightInput) });
344
+ });
345
+ bindFieldCommit(colorInput, () => {
346
+ commitSelectedNodeChanges({ "style.color": readTextInput(colorInput) });
347
+ });
348
+ bindFieldCommit(backgroundInput, () => {
349
+ commitSelectedNodeChanges({ "style.backgroundColor": readTextInput(backgroundInput) });
350
+ });
351
+ bindFieldCommit(borderColorInput, () => {
352
+ commitSelectedNodeChanges({ "style.borderColor": readTextInput(borderColorInput) });
353
+ });
354
+ bindFieldCommit(borderWidthInput, () => {
355
+ commitSelectedNodeChanges({ "style.borderWidth": readTextInput(borderWidthInput) });
356
+ });
357
+ bindFieldCommit(borderRadiusInput, () => {
358
+ commitSelectedNodeChanges({ "style.borderRadius": readTextInput(borderRadiusInput) });
359
+ });
360
+ bindFieldCommit(shadowInput, () => {
361
+ commitSelectedNodeChanges({ "style.boxShadow": readTextInput(shadowInput) });
362
+ });
363
+ bindFieldCommit(a11yRoleInput, () => {
364
+ commitSelectedNodeChanges({ "metadata.accessibility.role": readTextInput(a11yRoleInput) });
365
+ });
366
+ bindFieldCommit(a11yLabelInput, () => {
367
+ commitSelectedNodeChanges({ "metadata.accessibility.label": readTextInput(a11yLabelInput) });
368
+ });
369
+ bindFieldCommit(bindingKindInput, () => {
370
+ commitSelectedBindingPatch();
371
+ });
372
+ bindFieldCommit(bindingComponentInput, () => {
373
+ commitSelectedBindingPatch();
374
+ });
375
+ bindFieldCommit(bindingSelectorInput, () => {
376
+ commitSelectedBindingPatch();
377
+ });
378
+ }
379
+ function bindTokenPanel() {
380
+ tokenCollectionSelect.addEventListener("change", () => {
381
+ selectedTokenCollectionId = tokenCollectionSelect.value || "__values__";
382
+ selectedTokenModeId = selectedTokenCollectionId === "__values__" ? "__base__" : tokenModeSelect.value || "__base__";
383
+ syncTokenEditorSelection();
384
+ renderTokenSummary();
385
+ });
386
+ tokenCollectionCreateButton.addEventListener("click", () => {
387
+ createTokenCollection();
388
+ });
389
+ tokenModeSelect.addEventListener("change", () => {
390
+ selectedTokenModeId = tokenModeSelect.value || "__base__";
391
+ updateActiveTokenMode();
392
+ syncTokenEditorSelection();
393
+ renderTokenSummary();
394
+ });
395
+ tokenModeCreateButton.addEventListener("click", () => {
396
+ createTokenMode();
397
+ });
398
+ tokenPathInput.addEventListener("input", () => {
399
+ selectedTokenPath = normalizeTokenPath(tokenPathInput.value);
400
+ renderTokenSummary();
401
+ });
402
+ tokenSaveButton.addEventListener("click", () => {
403
+ saveTokenEditor();
404
+ });
405
+ tokenBindButton.addEventListener("click", () => {
406
+ bindSelectedNodeToToken();
407
+ });
408
+ tokenBindingClearButton.addEventListener("click", () => {
409
+ clearSelectedTokenBinding();
410
+ });
171
411
  }
172
412
  function bindStageInteractions() {
173
413
  stageElement.addEventListener("pointerdown", (event) => {
174
414
  const target = event.target instanceof HTMLElement ? event.target.closest("[data-node-id]") : null;
415
+ const targetNodeId = target?.dataset.nodeId ?? null;
416
+ if (!currentState) {
417
+ return;
418
+ }
419
+ if (annotationMode === "region" && event.button === 0) {
420
+ marqueeState = {
421
+ originClientX: event.clientX,
422
+ originClientY: event.clientY,
423
+ currentClientX: event.clientX,
424
+ currentClientY: event.clientY,
425
+ targetNodeId
426
+ };
427
+ renderStageOverlay();
428
+ stageElement.setPointerCapture(event.pointerId);
429
+ return;
430
+ }
431
+ if (spacePanActive) {
432
+ panningState = {
433
+ originX: event.clientX,
434
+ originY: event.clientY,
435
+ startX: currentState.viewport.x,
436
+ startY: currentState.viewport.y
437
+ };
438
+ stageElement.dataset.mode = "panning";
439
+ stageElement.setPointerCapture(event.pointerId);
440
+ return;
441
+ }
175
442
  if (target) {
176
- const nodeId = target.dataset.nodeId;
177
- const node = nodeId ? findNode(currentState?.document ?? null, nodeId) : null;
178
- if (!node || !currentState) {
443
+ const node = targetNodeId ? findNode(currentState.document, targetNodeId) : null;
444
+ if (!node) {
179
445
  return;
180
446
  }
181
447
  currentState.selection = {
182
- pageId: node.pageId ?? currentState.document.pages[0]?.id ?? null,
448
+ pageId: node.pageId ?? getActivePage()?.id ?? null,
183
449
  nodeId: node.id,
184
450
  targetId: currentState.selection.targetId,
185
451
  updatedAt: new Date().toISOString()
186
452
  };
453
+ activePageId = node.pageId ?? activePageId;
187
454
  draggingNode = {
188
455
  nodeId: node.id,
189
456
  originX: event.clientX,
@@ -196,9 +463,6 @@ function bindStageInteractions() {
196
463
  stageElement.setPointerCapture(event.pointerId);
197
464
  return;
198
465
  }
199
- if (!currentState) {
200
- return;
201
- }
202
466
  panningState = {
203
467
  originX: event.clientX,
204
468
  originY: event.clientY,
@@ -212,6 +476,12 @@ function bindStageInteractions() {
212
476
  if (!currentState) {
213
477
  return;
214
478
  }
479
+ if (marqueeState) {
480
+ marqueeState.currentClientX = event.clientX;
481
+ marqueeState.currentClientY = event.clientY;
482
+ renderStageOverlay();
483
+ return;
484
+ }
215
485
  if (draggingNode) {
216
486
  const node = findNode(currentState.document, draggingNode.nodeId);
217
487
  if (!node) {
@@ -237,10 +507,20 @@ function bindStageInteractions() {
237
507
  if (!currentState) {
238
508
  draggingNode = null;
239
509
  panningState = null;
510
+ marqueeState = null;
240
511
  stageElement.dataset.mode = "";
512
+ renderStageOverlay();
241
513
  return;
242
514
  }
243
- if (draggingNode && !currentState.pendingMutation) {
515
+ if (marqueeState) {
516
+ const completedMarquee = marqueeState;
517
+ marqueeState = null;
518
+ renderStageOverlay();
519
+ if (annotationMode === "region") {
520
+ commitAnnotationCapture(completedMarquee);
521
+ }
522
+ }
523
+ else if (draggingNode && !currentState.pendingMutation) {
244
524
  const node = findNode(currentState.document, draggingNode.nodeId);
245
525
  if (node) {
246
526
  applyOptimisticPatch([{
@@ -258,7 +538,7 @@ function bindStageInteractions() {
258
538
  }
259
539
  draggingNode = null;
260
540
  panningState = null;
261
- stageElement.dataset.mode = "";
541
+ stageElement.dataset.mode = spacePanActive ? "panning" : "";
262
542
  };
263
543
  stageElement.addEventListener("pointerup", endPointerInteraction);
264
544
  stageElement.addEventListener("pointercancel", endPointerInteraction);
@@ -486,6 +766,10 @@ function runCanvasPageAction(selector, action) {
486
766
  }
487
767
  function applyState(state, persist) {
488
768
  currentState = state;
769
+ if (!activePageId || !state.document.pages.some((page) => page.id === activePageId)) {
770
+ activePageId = state.selection.pageId ?? state.document.pages[0]?.id ?? null;
771
+ }
772
+ annotationModeSelect.value = annotationMode;
489
773
  titleElement.textContent = state.title;
490
774
  renderBadges(state);
491
775
  renderMeta(`Document ${state.documentId} updated ${formatTimestamp(state.updatedAt)}`);
@@ -503,22 +787,32 @@ function applyState(state, persist) {
503
787
  function renderState() {
504
788
  if (!currentState) {
505
789
  renderAnnotationPanel();
790
+ renderHistoryState();
791
+ renderTokenSummary();
506
792
  return;
507
793
  }
508
794
  const projectionSummary = summarizeCanvasProjectionState(currentState.summary, currentState.targets);
795
+ const activePage = getActivePage();
509
796
  const syncFragments = [
510
797
  currentState.pendingMutation ? "sync pending" : "live",
511
798
  currentState.summary.codeSyncState ?? null,
512
799
  projectionSummary.conflictCount > 0 ? `${projectionSummary.conflictCount} conflict${projectionSummary.conflictCount === 1 ? "" : "s"}` : null
513
800
  ].filter((entry) => typeof entry === "string");
514
- stageMetaElement.textContent = `${currentState.document.pages[0]?.nodes.length ?? 0} nodes • ${syncFragments.join(" • ")}`;
801
+ stageMetaElement.textContent = `${activePage?.nodes.length ?? 0} nodes • ${syncFragments.join(" • ")}`;
802
+ stageHintElement.textContent = annotationMode === "region"
803
+ ? "Drag on the stage to capture a region, or click a node to capture it."
804
+ : "Drag to pan. Scroll to zoom. Drag nodes to move them.";
805
+ renderPagesAndLayers();
806
+ renderHistoryState();
515
807
  renderStage();
516
808
  renderInspector();
809
+ renderTokenSummary();
517
810
  syncAnnotationDrafts();
518
811
  }
519
812
  function renderBadges(state) {
520
813
  badgesElement.innerHTML = "";
521
814
  const projectionSummary = summarizeCanvasProjectionState(state.summary, state.targets);
815
+ const latestImport = readLatestImportProvenance(state.summary, state.document);
522
816
  for (const label of [
523
817
  state.previewState,
524
818
  state.previewMode,
@@ -526,7 +820,8 @@ function renderBadges(state) {
526
820
  state.pendingMutation ? "sync pending" : "synced",
527
821
  state.summary.codeSyncState,
528
822
  projectionSummary.activeProjections[0],
529
- projectionSummary.conflictCount > 0 ? `${projectionSummary.conflictCount} conflicts` : null
823
+ projectionSummary.conflictCount > 0 ? `${projectionSummary.conflictCount} conflicts` : null,
824
+ latestImport ? `import ${latestImport}` : null
530
825
  ]) {
531
826
  if (typeof label !== "string" || label.trim().length === 0) {
532
827
  continue;
@@ -547,6 +842,8 @@ function renderSummary(summary, state) {
547
842
  const stylingLibraries = formatSummaryList(readSummaryLibraryList(summary, "styling"));
548
843
  const inventorySources = formatSummaryList(readSummaryStringArray(summary.componentSourceKinds));
549
844
  const projectionSummary = summarizeCanvasProjectionState(state.summary, state.targets);
845
+ const appliedStarter = formatAppliedStarterSummary(summary, state.document);
846
+ const latestImport = readLatestImportProvenance(summary, state.document);
550
847
  const items = [
551
848
  ["Target", state.targetId],
552
849
  ["Session", formatSummaryValue(summary.canvasSessionId)],
@@ -562,10 +859,14 @@ function renderSummary(summary, state) {
562
859
  ["Icons", iconLibraries],
563
860
  ["Styling", stylingLibraries],
564
861
  ["Inventory", `${formatSummaryValue(summary.componentInventoryCount)} mapped`],
862
+ ["Starters", typeof summary.availableStarterCount === "number" ? `${summary.availableStarterCount} available` : "n/a"],
863
+ ["Applied starter", appliedStarter],
565
864
  ["Inventory sources", inventorySources],
566
865
  ["Targets", String(state.targets.length)],
567
866
  ["Overlays", String(state.overlayMounts.length)],
568
- ["Feedback", String(countFeedbackItems(state.feedback))]
867
+ ["Feedback", String(countFeedbackItems(state.feedback))],
868
+ ["History", summarizeCanvasHistoryState(summary)],
869
+ ["Latest import", latestImport ?? "none"]
569
870
  ];
570
871
  for (const [label, value] of items) {
571
872
  const row = document.createElement("div");
@@ -580,6 +881,21 @@ function renderSummary(summary, state) {
580
881
  summaryElement.append(row);
581
882
  }
582
883
  }
884
+ function formatAppliedStarterSummary(summary, documentState) {
885
+ const starter = isRecord(documentState.meta.starter) ? documentState.meta.starter : null;
886
+ const name = summary.starterName
887
+ ?? (typeof starter?.template === "object" && starter.template && typeof starter.template.name === "string"
888
+ ? starter.template.name
889
+ : null);
890
+ if (!name) {
891
+ return "none";
892
+ }
893
+ const frameworkId = summary.starterFrameworkId
894
+ ?? (typeof starter?.frameworkId === "string" ? starter.frameworkId : null);
895
+ const appliedAt = summary.starterAppliedAt
896
+ ?? (typeof starter?.appliedAt === "string" ? starter.appliedAt : null);
897
+ return [name, frameworkId, appliedAt].filter((entry) => typeof entry === "string" && entry.trim().length > 0).join(" • ");
898
+ }
583
899
  function renderFeedback(events) {
584
900
  feedbackElement.innerHTML = "";
585
901
  const latest = events.slice(-8).reverse();
@@ -613,25 +929,325 @@ function renderFeedback(events) {
613
929
  feedbackElement.append(row);
614
930
  }
615
931
  }
932
+ function renderHistoryState() {
933
+ const history = currentState?.summary.history;
934
+ const canUndo = Boolean(history?.canUndo) && !currentState?.pendingMutation;
935
+ const canRedo = Boolean(history?.canRedo) && !currentState?.pendingMutation;
936
+ historyUndoButton.disabled = !canUndo;
937
+ historyRedoButton.disabled = !canRedo;
938
+ panelHistoryUndoButton.disabled = !canUndo;
939
+ panelHistoryRedoButton.disabled = !canRedo;
940
+ historyStatusElement.textContent = currentState ? summarizeCanvasHistoryState(currentState.summary) : "No history yet";
941
+ }
942
+ function renderPagesAndLayers() {
943
+ pageSelectElement.innerHTML = "";
944
+ layersTreeElement.innerHTML = "";
945
+ if (!currentState) {
946
+ pageDetailsElement.textContent = "Waiting for canvas state.";
947
+ const empty = document.createElement("div");
948
+ empty.className = "canvas-empty-inline";
949
+ empty.textContent = "No pages loaded.";
950
+ layersTreeElement.append(empty);
951
+ return;
952
+ }
953
+ const activePage = getActivePage();
954
+ for (const page of currentState.document.pages) {
955
+ const option = document.createElement("option");
956
+ option.value = page.id;
957
+ option.textContent = `${page.name} (${page.nodes.length})`;
958
+ option.selected = page.id === activePage?.id;
959
+ pageSelectElement.append(option);
960
+ }
961
+ pageSelectElement.disabled = currentState.document.pages.length <= 1;
962
+ if (!activePage) {
963
+ pageDetailsElement.textContent = "No page selected.";
964
+ const empty = document.createElement("div");
965
+ empty.className = "canvas-empty-inline";
966
+ empty.textContent = "No nodes on this page.";
967
+ layersTreeElement.append(empty);
968
+ return;
969
+ }
970
+ ensureExpandedNodePath(activePage, currentState.selection.nodeId);
971
+ pageDetailsElement.textContent = `${activePage.path} • ${activePage.nodes.length} nodes`;
972
+ const rootNodes = getRootNodes(activePage);
973
+ if (rootNodes.length === 0) {
974
+ const empty = document.createElement("div");
975
+ empty.className = "canvas-empty-inline";
976
+ empty.textContent = "No nodes on this page.";
977
+ layersTreeElement.append(empty);
978
+ return;
979
+ }
980
+ for (const node of rootNodes) {
981
+ layersTreeElement.append(renderLayerNode(activePage, node, 0));
982
+ }
983
+ }
984
+ function renderLayerNode(page, node, depth) {
985
+ const row = document.createElement("div");
986
+ row.className = "canvas-layer-row";
987
+ row.dataset.nodeId = node.id;
988
+ row.dataset.selected = String(currentState?.selection.nodeId === node.id);
989
+ row.dataset.hidden = String(isNodeHidden(node));
990
+ row.draggable = true;
991
+ row.style.marginLeft = `${depth * 14}px`;
992
+ const head = document.createElement("div");
993
+ head.className = "canvas-layer-head";
994
+ const toggleButton = document.createElement("button");
995
+ toggleButton.className = "canvas-button canvas-layer-toggle";
996
+ toggleButton.type = "button";
997
+ toggleButton.textContent = node.childIds.length > 0 ? (expandedLayerNodeIds.has(node.id) ? "−" : "+") : "·";
998
+ toggleButton.disabled = node.childIds.length === 0;
999
+ toggleButton.addEventListener("click", (event) => {
1000
+ event.stopPropagation();
1001
+ if (expandedLayerNodeIds.has(node.id)) {
1002
+ expandedLayerNodeIds.delete(node.id);
1003
+ }
1004
+ else {
1005
+ expandedLayerNodeIds.add(node.id);
1006
+ }
1007
+ renderPagesAndLayers();
1008
+ });
1009
+ const visibilityButton = document.createElement("button");
1010
+ visibilityButton.className = "canvas-button canvas-layer-visibility";
1011
+ visibilityButton.type = "button";
1012
+ visibilityButton.textContent = isNodeHidden(node) ? "🙈" : "👁";
1013
+ visibilityButton.addEventListener("click", (event) => {
1014
+ event.stopPropagation();
1015
+ if (!currentState?.pendingMutation) {
1016
+ applyOptimisticPatch([{ op: "node.visibility.set", nodeId: node.id, hidden: !isNodeHidden(node) }], {
1017
+ pageId: page.id,
1018
+ nodeId: node.id,
1019
+ targetId: currentState?.selection.targetId ?? null
1020
+ });
1021
+ }
1022
+ });
1023
+ const main = document.createElement("div");
1024
+ main.className = "canvas-layer-main";
1025
+ main.addEventListener("click", () => {
1026
+ selectNode(node.id, page.id);
1027
+ });
1028
+ const renameInput = document.createElement("input");
1029
+ renameInput.className = "canvas-layer-name-input";
1030
+ renameInput.value = node.name;
1031
+ renameInput.disabled = Boolean(currentState?.pendingMutation);
1032
+ renameInput.addEventListener("click", (event) => {
1033
+ event.stopPropagation();
1034
+ });
1035
+ renameInput.addEventListener("change", () => {
1036
+ if (!currentState?.pendingMutation && renameInput.value.trim().length > 0 && renameInput.value !== node.name) {
1037
+ applyOptimisticPatch([{ op: "node.update", nodeId: node.id, changes: { name: renameInput.value.trim() } }], {
1038
+ pageId: page.id,
1039
+ nodeId: node.id,
1040
+ targetId: currentState?.selection.targetId ?? null
1041
+ });
1042
+ }
1043
+ });
1044
+ const meta = document.createElement("div");
1045
+ meta.className = "canvas-layer-meta";
1046
+ meta.textContent = [node.kind, node.childIds.length > 0 ? `${node.childIds.length} children` : "leaf", isNodeHidden(node) ? "hidden" : "visible"].join(" • ");
1047
+ main.append(renameInput, meta);
1048
+ head.append(toggleButton, visibilityButton, main);
1049
+ row.append(head);
1050
+ row.addEventListener("dragstart", (event) => {
1051
+ layerDragState = { nodeId: node.id };
1052
+ event.dataTransfer?.setData("text/plain", node.id);
1053
+ });
1054
+ row.addEventListener("dragend", () => {
1055
+ layerDragState = null;
1056
+ });
1057
+ row.addEventListener("dragover", (event) => {
1058
+ event.preventDefault();
1059
+ });
1060
+ row.addEventListener("drop", (event) => {
1061
+ event.preventDefault();
1062
+ const draggedId = layerDragState?.nodeId;
1063
+ if (!draggedId || draggedId === node.id) {
1064
+ return;
1065
+ }
1066
+ commitLayerMove(draggedId, node.parentId ?? null, findSiblingInsertIndex(page, node.id));
1067
+ });
1068
+ if (node.childIds.length > 0) {
1069
+ const children = document.createElement("div");
1070
+ children.className = "canvas-layer-children";
1071
+ children.addEventListener("dragover", (event) => {
1072
+ event.preventDefault();
1073
+ });
1074
+ children.addEventListener("drop", (event) => {
1075
+ event.preventDefault();
1076
+ const draggedId = layerDragState?.nodeId;
1077
+ if (!draggedId || draggedId === node.id) {
1078
+ return;
1079
+ }
1080
+ commitLayerMove(draggedId, node.id, node.childIds.length);
1081
+ });
1082
+ if (expandedLayerNodeIds.has(node.id)) {
1083
+ for (const childId of node.childIds) {
1084
+ const child = page.nodes.find((entry) => entry.id === childId);
1085
+ if (child) {
1086
+ children.append(renderLayerNode(page, child, depth + 1));
1087
+ }
1088
+ }
1089
+ }
1090
+ row.append(children);
1091
+ }
1092
+ return row;
1093
+ }
1094
+ function renderTokenSummary() {
1095
+ populateTokenControls();
1096
+ tokenSummaryElement.innerHTML = "";
1097
+ tokenUsageElement.innerHTML = "";
1098
+ const controlsDisabled = !currentState || Boolean(currentState.pendingMutation);
1099
+ for (const control of [
1100
+ tokenCollectionSelect,
1101
+ tokenCollectionNameInput,
1102
+ tokenCollectionCreateButton,
1103
+ tokenModeSelect,
1104
+ tokenModeNameInput,
1105
+ tokenModeCreateButton,
1106
+ tokenPathInput,
1107
+ tokenValueInput,
1108
+ tokenAliasInput,
1109
+ tokenBindingPropertySelect,
1110
+ tokenSaveButton,
1111
+ tokenBindButton,
1112
+ tokenBindingClearButton
1113
+ ]) {
1114
+ control.disabled = controlsDisabled;
1115
+ }
1116
+ if (!currentState) {
1117
+ tokenStatusElement.textContent = "Waiting for canvas state...";
1118
+ const empty = document.createElement("div");
1119
+ empty.className = "canvas-empty-inline";
1120
+ empty.textContent = "Waiting for canvas state...";
1121
+ tokenSummaryElement.append(empty.cloneNode(true));
1122
+ tokenUsageElement.append(empty);
1123
+ return;
1124
+ }
1125
+ const node = getSelectedNode();
1126
+ const normalizedPath = normalizeTokenPath(selectedTokenPath || tokenPathInput.value);
1127
+ const resolvedValue = normalizedPath
1128
+ ? resolveTokenValue(currentState.document.tokens, normalizedPath, selectedTokenModeId === "__base__" ? null : selectedTokenModeId)
1129
+ : null;
1130
+ const aliasTarget = normalizedPath
1131
+ ? readTokenAliasTarget(currentState.document.tokens, normalizedPath, selectedTokenModeId === "__base__" ? null : selectedTokenModeId)
1132
+ : null;
1133
+ const bindingProperty = tokenBindingPropertySelect.value || "backgroundColor";
1134
+ tokenPathInput.value = selectedTokenPath;
1135
+ tokenValueInput.value = formatTokenEditorValue(resolvedValue);
1136
+ tokenAliasInput.value = aliasTarget ?? "";
1137
+ tokenBindButton.disabled = controlsDisabled || !node || normalizedPath.length === 0;
1138
+ tokenBindingClearButton.disabled = controlsDisabled || !node || !hasSelectedTokenBinding(node, bindingProperty);
1139
+ tokenStatusElement.textContent = normalizedPath
1140
+ ? aliasTarget
1141
+ ? `${normalizedPath} aliases ${aliasTarget}${resolvedValue !== null ? ` • ${formatTokenEditorValue(resolvedValue)}` : ""}`
1142
+ : `${normalizedPath}${resolvedValue !== null ? ` • ${formatTokenEditorValue(resolvedValue)}` : ""}`
1143
+ : "Edit collections, modes, aliases, and bindings.";
1144
+ if (!node) {
1145
+ const empty = document.createElement("div");
1146
+ empty.className = "canvas-empty-inline";
1147
+ empty.textContent = "Select a node to inspect token usage.";
1148
+ tokenSummaryElement.append(empty);
1149
+ }
1150
+ else {
1151
+ const tokenRefs = Object.entries(isRecord(node.tokenRefs) ? node.tokenRefs : {});
1152
+ if (tokenRefs.length === 0) {
1153
+ const empty = document.createElement("div");
1154
+ empty.className = "canvas-empty-inline";
1155
+ empty.textContent = "No token bindings on this node.";
1156
+ tokenSummaryElement.append(empty);
1157
+ }
1158
+ else {
1159
+ for (const [property, value] of tokenRefs) {
1160
+ const row = document.createElement("div");
1161
+ row.className = "canvas-token-item";
1162
+ const title = document.createElement("div");
1163
+ title.className = "canvas-summary-label";
1164
+ title.textContent = property;
1165
+ const body = document.createElement("div");
1166
+ body.className = "canvas-summary-value";
1167
+ const tokenPath = readTokenPath(value);
1168
+ const resolved = tokenPath
1169
+ ? resolveTokenValue(currentState.document.tokens, tokenPath, readActiveTokenModeId(currentState.document.tokens))
1170
+ : null;
1171
+ body.textContent = tokenPath
1172
+ ? resolved !== null ? `${tokenPath} → ${formatTokenEditorValue(resolved)}` : tokenPath
1173
+ : JSON.stringify(value);
1174
+ row.append(title, body);
1175
+ tokenSummaryElement.append(row);
1176
+ }
1177
+ }
1178
+ }
1179
+ const usages = normalizedPath ? collectTokenUsages(currentState.document, normalizedPath) : [];
1180
+ if (usages.length === 0) {
1181
+ const empty = document.createElement("div");
1182
+ empty.className = "canvas-empty-inline";
1183
+ empty.textContent = normalizedPath ? "No bound nodes use this token yet." : "Choose a token path to inspect usage.";
1184
+ tokenUsageElement.append(empty);
1185
+ return;
1186
+ }
1187
+ for (const usage of usages) {
1188
+ const row = document.createElement("div");
1189
+ row.className = "canvas-summary-item";
1190
+ const title = document.createElement("div");
1191
+ title.className = "canvas-summary-label";
1192
+ title.textContent = `${usage.pageName} • ${usage.property}`;
1193
+ const body = document.createElement("div");
1194
+ body.className = "canvas-summary-value";
1195
+ body.textContent = `${usage.nodeName} (${usage.nodeId})${usage.resolvedValue !== null ? ` → ${formatTokenEditorValue(usage.resolvedValue)}` : ""}`;
1196
+ row.append(title, body);
1197
+ tokenUsageElement.append(row);
1198
+ }
1199
+ }
1200
+ function populateTokenControls() {
1201
+ if (!currentState) {
1202
+ tokenCollectionSelect.innerHTML = "";
1203
+ tokenModeSelect.innerHTML = "";
1204
+ return;
1205
+ }
1206
+ const tokens = currentState.document.tokens;
1207
+ const collections = [
1208
+ { id: "__values__", name: "Values" },
1209
+ ...tokens.collections.map((collection) => ({ id: collection.id, name: collection.name }))
1210
+ ];
1211
+ if (!collections.some((entry) => entry.id === selectedTokenCollectionId)) {
1212
+ selectedTokenCollectionId = findTokenCollectionId(tokens, selectedTokenPath) ?? "__values__";
1213
+ }
1214
+ tokenCollectionSelect.innerHTML = collections
1215
+ .map((collection) => `<option value="${escapeHtmlAttribute(collection.id)}">${escapeHtmlText(collection.name)}</option>`)
1216
+ .join("");
1217
+ tokenCollectionSelect.value = selectedTokenCollectionId;
1218
+ const modes = listTokenModes(tokens, selectedTokenCollectionId);
1219
+ if (!modes.some((entry) => entry.id === selectedTokenModeId)) {
1220
+ const activeModeId = readActiveTokenModeId(tokens);
1221
+ selectedTokenModeId = activeModeId && modes.some((entry) => entry.id === activeModeId) ? activeModeId : "__base__";
1222
+ }
1223
+ tokenModeSelect.innerHTML = modes
1224
+ .map((mode) => `<option value="${escapeHtmlAttribute(mode.id)}">${escapeHtmlText(mode.name)}</option>`)
1225
+ .join("");
1226
+ tokenModeSelect.value = selectedTokenModeId;
1227
+ }
616
1228
  function renderStage() {
617
1229
  if (!currentState) {
618
1230
  stageInnerElement.innerHTML = "";
1231
+ renderStageOverlay();
619
1232
  return;
620
1233
  }
621
- const page = currentState.document.pages[0];
1234
+ const page = getActivePage();
622
1235
  if (!page) {
623
1236
  stageInnerElement.innerHTML = "";
1237
+ renderStageOverlay();
624
1238
  return;
625
1239
  }
626
- const bounds = computeDocumentBounds(page.nodes);
1240
+ const visibleNodes = page.nodes.filter((node) => !isNodeHidden(node));
1241
+ const bounds = computeDocumentBounds(visibleNodes);
627
1242
  stageInnerElement.style.width = `${bounds.width}px`;
628
1243
  stageInnerElement.style.height = `${bounds.height}px`;
629
1244
  stageInnerElement.style.transform = `translate(${currentState.viewport.x}px, ${currentState.viewport.y}px) scale(${currentState.viewport.zoom})`;
630
1245
  stageInnerElement.innerHTML = "";
631
- const sortedNodes = [...page.nodes].sort(compareStageNodes);
1246
+ const sortedNodes = [...visibleNodes].sort(compareStageNodes);
632
1247
  for (const node of sortedNodes) {
633
1248
  stageInnerElement.append(buildStageNodeElement(currentState.document, node, currentState.selection.nodeId === node.id));
634
1249
  }
1250
+ renderStageOverlay();
635
1251
  }
636
1252
  function compareStageNodes(left, right) {
637
1253
  const rootOrder = Number(left.parentId !== null) - Number(right.parentId !== null);
@@ -682,7 +1298,7 @@ function buildStageNodeElement(documentState, node, selected) {
682
1298
  element.style.top = `${node.rect.y}px`;
683
1299
  element.style.width = `${Math.max(node.rect.width, 40)}px`;
684
1300
  element.style.minHeight = `${Math.max(node.rect.height, node.kind === "connector" ? 2 : componentKind === "badge" ? 28 : 40)}px`;
685
- applyDeclaredStageStyles(element, node.style);
1301
+ applyDeclaredStageStyles(element, documentState, node);
686
1302
  if (text.includes("\n") && element.style.whiteSpace.length === 0) {
687
1303
  element.style.whiteSpace = "pre-line";
688
1304
  }
@@ -1083,8 +1699,8 @@ function buildStageFluentIcon(identifier) {
1083
1699
  }
1084
1700
  }
1085
1701
  }
1086
- function applyDeclaredStageStyles(element, style) {
1087
- for (const [key, value] of Object.entries(style)) {
1702
+ function applyDeclaredStageStyles(element, documentState, node) {
1703
+ for (const [key, value] of Object.entries(resolveStageStyle(documentState, node))) {
1088
1704
  if (typeof value !== "string" && typeof value !== "number") {
1089
1705
  continue;
1090
1706
  }
@@ -1095,20 +1711,84 @@ function applyDeclaredStageStyles(element, style) {
1095
1711
  }
1096
1712
  function renderInspector() {
1097
1713
  const node = getSelectedNode();
1098
- nameInput.disabled = !node || Boolean(currentState?.pendingMutation);
1099
- textInput.disabled = !node || Boolean(currentState?.pendingMutation);
1100
- deleteNodeButton.disabled = !node || Boolean(currentState?.pendingMutation);
1714
+ const disabled = !node || Boolean(currentState?.pendingMutation);
1715
+ const inputs = [
1716
+ nameInput,
1717
+ textInput,
1718
+ nodeXInput,
1719
+ nodeYInput,
1720
+ nodeWidthInput,
1721
+ nodeHeightInput,
1722
+ paddingInput,
1723
+ gapInput,
1724
+ fontSizeInput,
1725
+ fontWeightInput,
1726
+ lineHeightInput,
1727
+ colorInput,
1728
+ backgroundInput,
1729
+ borderColorInput,
1730
+ borderWidthInput,
1731
+ borderRadiusInput,
1732
+ shadowInput,
1733
+ bindingKindInput,
1734
+ bindingComponentInput,
1735
+ bindingSelectorInput,
1736
+ a11yRoleInput,
1737
+ a11yLabelInput
1738
+ ];
1739
+ for (const input of inputs) {
1740
+ input.disabled = disabled;
1741
+ }
1742
+ duplicateNodeButton.disabled = disabled;
1743
+ deleteNodeButton.disabled = disabled;
1744
+ propertiesStatusElement.textContent = node
1745
+ ? `${node.kind} • ${node.rect.width}×${node.rect.height}`
1746
+ : "No node selected.";
1101
1747
  nameInput.value = node?.name ?? "";
1102
1748
  textInput.value = node ? nodeText(node) : "";
1749
+ nodeXInput.value = node ? String(node.rect.x) : "";
1750
+ nodeYInput.value = node ? String(node.rect.y) : "";
1751
+ nodeWidthInput.value = node ? String(node.rect.width) : "";
1752
+ nodeHeightInput.value = node ? String(node.rect.height) : "";
1753
+ paddingInput.value = node ? readStyleText(node.style.padding) : "";
1754
+ gapInput.value = node ? readStyleText(node.style.gap) : "";
1755
+ fontSizeInput.value = node ? readStyleText(node.style.fontSize) : "";
1756
+ fontWeightInput.value = node ? readStyleText(node.style.fontWeight) : "";
1757
+ lineHeightInput.value = node ? readStyleText(node.style.lineHeight) : "";
1758
+ colorInput.value = node ? readStyleText(node.style.color) : "";
1759
+ backgroundInput.value = node ? readStyleText(node.style.backgroundColor) : "";
1760
+ borderColorInput.value = node ? readStyleText(node.style.borderColor) : "";
1761
+ borderWidthInput.value = node ? readStyleText(node.style.borderWidth) : "";
1762
+ borderRadiusInput.value = node ? readStyleText(node.style.borderRadius) : "";
1763
+ shadowInput.value = node ? readStyleText(node.style.boxShadow) : "";
1764
+ const binding = currentState && node ? readSelectedBindingIdentity(currentState.document, node.id) : null;
1765
+ bindingKindInput.value = binding?.bindingKind ?? "";
1766
+ bindingComponentInput.value = binding?.componentName ?? "";
1767
+ bindingSelectorInput.value = node && currentState
1768
+ ? readBindingSelector(currentState.document, node.id)
1769
+ : "";
1770
+ const accessibility = node && isRecord(node.metadata.accessibility) ? node.metadata.accessibility : {};
1771
+ a11yRoleInput.value = typeof accessibility.role === "string" ? accessibility.role : "";
1772
+ a11yLabelInput.value = typeof accessibility.label === "string" ? accessibility.label : "";
1103
1773
  renderSelectionMeta();
1774
+ syncTokenEditorSelection();
1104
1775
  }
1105
1776
  function renderSelectionMeta() {
1106
1777
  selectionMetaElement.innerHTML = "";
1107
1778
  const node = getSelectedNode();
1779
+ const bindingIdentity = currentState ? readSelectedBindingIdentity(currentState.document, node?.id ?? null) : null;
1780
+ const latestImport = currentState ? readLatestImportProvenance(currentState.summary, currentState.document) : null;
1108
1781
  const items = [
1109
1782
  ["Selected", node?.id ?? "none"],
1783
+ ["Page", getActivePage()?.name ?? "n/a"],
1110
1784
  ["Position", node ? `${node.rect.x}, ${node.rect.y}` : "n/a"],
1111
- ["Size", node ? `${node.rect.width} × ${node.rect.height}` : "n/a"]
1785
+ ["Size", node ? `${node.rect.width} × ${node.rect.height}` : "n/a"],
1786
+ ["Binding", bindingIdentity?.componentName ?? "none"],
1787
+ ["Framework", bindingIdentity?.framework ?? "n/a"],
1788
+ ["Adapter", bindingIdentity?.adapter ?? "n/a"],
1789
+ ["Plugin", bindingIdentity?.plugin ?? "n/a"],
1790
+ ["Variants", node ? String(node.variantPatches.length) : "0"],
1791
+ ["Latest import", latestImport ?? "none"]
1112
1792
  ];
1113
1793
  for (const [label, value] of items) {
1114
1794
  const row = document.createElement("div");
@@ -1125,7 +1805,10 @@ function renderSelectionMeta() {
1125
1805
  }
1126
1806
  function renderAnnotationPanel() {
1127
1807
  const node = getSelectedNode();
1128
- annotationAddButton.disabled = !node || !currentState || annotationDrafts.some((entry) => entry.nodeId === node.id);
1808
+ annotationAddButton.textContent = annotationMode === "region" ? "Capture Region" : "Add Selected";
1809
+ annotationAddButton.disabled = annotationMode === "selected"
1810
+ ? (!node || !currentState || annotationDrafts.some((entry) => entry.kind !== "region" && entry.nodeId === node.id))
1811
+ : !currentState;
1129
1812
  annotationCopyButton.disabled = annotationDrafts.length === 0 || !currentState;
1130
1813
  annotationSendButton.disabled = annotationDrafts.length === 0 || !currentState;
1131
1814
  annotationListElement.innerHTML = "";
@@ -1139,7 +1822,11 @@ function renderAnnotationPanel() {
1139
1822
  for (const draft of annotationDrafts) {
1140
1823
  const itemPayload = buildCanvasAnnotationPayloadForDrafts([draft]);
1141
1824
  const annotation = itemPayload?.annotations[0];
1142
- const nodeLabel = annotation ? describeAnnotationItem(annotation) : draft.nodeId;
1825
+ const nodeLabel = annotation
1826
+ ? describeAnnotationItem(annotation)
1827
+ : draft.kind === "region"
1828
+ ? draft.label ?? draft.regionId
1829
+ : draft.nodeId;
1143
1830
  const row = document.createElement("div");
1144
1831
  row.className = "canvas-annotation-item";
1145
1832
  const head = document.createElement("div");
@@ -1150,22 +1837,26 @@ function renderAnnotationPanel() {
1150
1837
  title.textContent = nodeLabel;
1151
1838
  const meta = document.createElement("div");
1152
1839
  meta.className = "canvas-annotation-meta";
1153
- meta.textContent = annotation ? `${annotation.tag} • ${Math.round(annotation.rect.width)}×${Math.round(annotation.rect.height)}` : draft.nodeId;
1840
+ meta.textContent = annotation
1841
+ ? `${annotation.tag} • ${Math.round(annotation.rect.width)}×${Math.round(annotation.rect.height)}`
1842
+ : draft.kind === "region"
1843
+ ? `${Math.round(draft.rect.width)}×${Math.round(draft.rect.height)}`
1844
+ : draft.nodeId;
1154
1845
  summary.append(title, meta);
1155
1846
  const removeButton = document.createElement("button");
1156
1847
  removeButton.className = "canvas-button";
1157
1848
  removeButton.type = "button";
1158
1849
  removeButton.textContent = "Remove";
1159
1850
  removeButton.addEventListener("click", () => {
1160
- removeAnnotationDraft(draft.nodeId);
1851
+ removeAnnotationDraft(getDraftId(draft));
1161
1852
  });
1162
1853
  head.append(summary, removeButton);
1163
1854
  const noteField = document.createElement("textarea");
1164
1855
  noteField.className = "canvas-textarea";
1165
1856
  noteField.value = draft.note ?? "";
1166
- noteField.placeholder = "Add note for this node";
1857
+ noteField.placeholder = draft.kind === "region" ? "Add note for this captured region" : "Add note for this node";
1167
1858
  noteField.addEventListener("input", () => {
1168
- updateAnnotationDraft(draft.nodeId, { note: noteField.value });
1859
+ updateAnnotationDraft(getDraftId(draft), { note: noteField.value });
1169
1860
  });
1170
1861
  const actions = document.createElement("div");
1171
1862
  actions.className = "canvas-actions-grid";
@@ -1198,7 +1889,7 @@ function buildCanvasAnnotationPayloadForDrafts(drafts) {
1198
1889
  if (!currentState) {
1199
1890
  return null;
1200
1891
  }
1201
- const page = currentState.document.pages[0];
1892
+ const page = getActivePage();
1202
1893
  if (!page || drafts.length === 0) {
1203
1894
  return null;
1204
1895
  }
@@ -1211,18 +1902,20 @@ function buildCanvasAnnotationPayloadForDrafts(drafts) {
1211
1902
  }
1212
1903
  function addSelectedAnnotationDraft() {
1213
1904
  const node = getSelectedNode();
1214
- if (!node || annotationDrafts.some((entry) => entry.nodeId === node.id)) {
1905
+ if (!node || annotationDrafts.some((entry) => entry.kind !== "region" && entry.nodeId === node.id)) {
1215
1906
  renderAnnotationPanel();
1216
1907
  return;
1217
1908
  }
1218
- annotationDrafts = [...annotationDrafts, { nodeId: node.id, note: "" }];
1909
+ annotationDrafts = [...annotationDrafts, { kind: "node", nodeId: node.id, note: "" }];
1219
1910
  renderAnnotationPanel();
1220
1911
  }
1221
- function updateAnnotationDraft(nodeId, patch) {
1222
- annotationDrafts = annotationDrafts.map((entry) => entry.nodeId === nodeId ? { ...entry, ...patch } : entry);
1912
+ function updateAnnotationDraft(draftId, patch) {
1913
+ annotationDrafts = annotationDrafts.map((entry) => getDraftId(entry) === draftId
1914
+ ? { ...entry, note: patch.note ?? entry.note }
1915
+ : entry);
1223
1916
  }
1224
- function removeAnnotationDraft(nodeId) {
1225
- annotationDrafts = annotationDrafts.filter((entry) => entry.nodeId !== nodeId);
1917
+ function removeAnnotationDraft(draftId) {
1918
+ annotationDrafts = annotationDrafts.filter((entry) => getDraftId(entry) !== draftId);
1226
1919
  renderAnnotationPanel();
1227
1920
  }
1228
1921
  function syncAnnotationDrafts() {
@@ -1231,9 +1924,9 @@ function syncAnnotationDrafts() {
1231
1924
  renderAnnotationPanel();
1232
1925
  return;
1233
1926
  }
1234
- const page = currentState.document.pages[0];
1927
+ const page = getActivePage();
1235
1928
  const validIds = new Set(page?.nodes.map((node) => node.id) ?? []);
1236
- const next = annotationDrafts.filter((entry) => validIds.has(entry.nodeId));
1929
+ const next = annotationDrafts.filter((entry) => entry.kind === "region" || validIds.has(entry.nodeId));
1237
1930
  if (next.length !== annotationDrafts.length) {
1238
1931
  annotationDrafts = next;
1239
1932
  }
@@ -1272,7 +1965,7 @@ async function sendCanvasAnnotation(drafts, source, label, button) {
1272
1965
  if (!response.ok) {
1273
1966
  throw new Error(response.error?.message ?? "Canvas annotation send failed.");
1274
1967
  }
1275
- setCanvasButtonFeedback(button, "Sent");
1968
+ setCanvasButtonFeedback(button, formatAnnotationDispatchReceipt(response.receipt));
1276
1969
  }
1277
1970
  async function writeTextToClipboard(text) {
1278
1971
  if (navigator.clipboard?.writeText) {
@@ -1327,13 +2020,15 @@ function applyOptimisticPatch(patches, selection, options = {}) {
1327
2020
  targetId: selection?.targetId ?? currentState.selection.targetId,
1328
2021
  updatedAt: new Date().toISOString()
1329
2022
  };
2023
+ activePageId = currentState.selection.pageId ?? activePageId;
1330
2024
  renderState();
1331
2025
  schedulePersist(currentState);
1332
2026
  port.postMessage({
1333
2027
  type: "canvas-page-patch-request",
1334
2028
  baseRevision: currentState.documentRevision,
1335
2029
  patches,
1336
- selection: currentState.selection
2030
+ selection: currentState.selection,
2031
+ viewport: currentState.viewport
1337
2032
  });
1338
2033
  }
1339
2034
  function applyLocalPatchMutation(document, patches) {
@@ -1362,7 +2057,9 @@ function applyLocalPatchMutation(document, patches) {
1362
2057
  } : { x: 0, y: 0, width: 240, height: 120 },
1363
2058
  props: isRecord(node.props) ? { ...node.props } : {},
1364
2059
  style: isRecord(node.style) ? { ...node.style } : {},
2060
+ tokenRefs: isRecord(node.tokenRefs) ? { ...node.tokenRefs } : {},
1365
2061
  bindingRefs: isRecord(node.bindingRefs) ? { ...node.bindingRefs } : {},
2062
+ variantPatches: Array.isArray(node.variantPatches) ? node.variantPatches.filter(isRecord) : [],
1366
2063
  metadata: isRecord(node.metadata) ? { ...node.metadata } : {}
1367
2064
  };
1368
2065
  page.nodes.push(inserted);
@@ -1379,11 +2076,16 @@ function applyLocalPatchMutation(document, patches) {
1379
2076
  }
1380
2077
  if (patch.op === "node.remove" && typeof patch.nodeId === "string") {
1381
2078
  for (const page of document.pages) {
1382
- page.nodes = page.nodes.filter((entry) => entry.id !== patch.nodeId);
2079
+ const removedIds = collectNodeSubtreeIds(page, patch.nodeId);
2080
+ if (removedIds.length === 0) {
2081
+ continue;
2082
+ }
2083
+ const removedSet = new Set(removedIds);
2084
+ page.nodes = page.nodes.filter((entry) => !removedSet.has(entry.id));
1383
2085
  for (const node of page.nodes) {
1384
- node.childIds = node.childIds.filter((entry) => entry !== patch.nodeId);
2086
+ node.childIds = node.childIds.filter((entry) => !removedSet.has(entry));
1385
2087
  }
1386
- if (page.rootNodeId === patch.nodeId) {
2088
+ if (page.rootNodeId && removedSet.has(page.rootNodeId)) {
1387
2089
  page.rootNodeId = null;
1388
2090
  }
1389
2091
  }
@@ -1397,42 +2099,1003 @@ function applyLocalPatchMutation(document, patches) {
1397
2099
  for (const [path, value] of Object.entries(patch.changes)) {
1398
2100
  setNestedValue(node, path, value);
1399
2101
  }
2102
+ continue;
2103
+ }
2104
+ if (patch.op === "node.visibility.set" && typeof patch.nodeId === "string" && typeof patch.hidden === "boolean") {
2105
+ const node = findNode(document, patch.nodeId);
2106
+ if (!node) {
2107
+ continue;
2108
+ }
2109
+ const visibility = isRecord(node.metadata.visibility) ? { ...node.metadata.visibility } : {};
2110
+ visibility.hidden = patch.hidden;
2111
+ node.metadata.visibility = visibility;
2112
+ continue;
2113
+ }
2114
+ if (patch.op === "node.reorder" && typeof patch.nodeId === "string" && typeof patch.index === "number") {
2115
+ const page = findPageForNode(document, patch.nodeId);
2116
+ const node = page?.nodes.find((entry) => entry.id === patch.nodeId);
2117
+ if (!page || !node) {
2118
+ continue;
2119
+ }
2120
+ const siblings = getSiblingIds(page, node.parentId ?? null);
2121
+ const currentIndex = siblings.indexOf(node.id);
2122
+ if (currentIndex === -1) {
2123
+ continue;
2124
+ }
2125
+ siblings.splice(currentIndex, 1);
2126
+ const nextIndex = clampIndex(patch.index, siblings.length);
2127
+ siblings.splice(nextIndex, 0, node.id);
2128
+ assignSiblingIds(page, node.parentId ?? null, siblings);
2129
+ continue;
2130
+ }
2131
+ if (patch.op === "node.reparent" && typeof patch.nodeId === "string" && typeof patch.index === "number") {
2132
+ const page = findPageForNode(document, patch.nodeId);
2133
+ const node = page?.nodes.find((entry) => entry.id === patch.nodeId);
2134
+ if (!page || !node) {
2135
+ continue;
2136
+ }
2137
+ const previousParentId = node.parentId ?? null;
2138
+ const currentSiblings = getSiblingIds(page, previousParentId).filter((entry) => entry !== node.id);
2139
+ assignSiblingIds(page, previousParentId, currentSiblings);
2140
+ node.parentId = typeof patch.parentId === "string" ? patch.parentId : null;
2141
+ const nextSiblings = getSiblingIds(page, node.parentId ?? null);
2142
+ const nextIndex = clampIndex(patch.index, nextSiblings.length);
2143
+ nextSiblings.splice(nextIndex, 0, node.id);
2144
+ assignSiblingIds(page, node.parentId ?? null, nextSiblings);
2145
+ continue;
2146
+ }
2147
+ if (patch.op === "node.duplicate" && typeof patch.nodeId === "string") {
2148
+ const page = findPageForNode(document, patch.nodeId);
2149
+ const sourceNode = page?.nodes.find((entry) => entry.id === patch.nodeId);
2150
+ if (!page || !sourceNode) {
2151
+ continue;
2152
+ }
2153
+ const idMap = isRecord(patch.idMap) ? patch.idMap : {};
2154
+ const sourceIds = collectNodeSubtreeIds(page, sourceNode.id);
2155
+ for (const sourceId of sourceIds) {
2156
+ if (typeof idMap[sourceId] !== "string") {
2157
+ idMap[sourceId] = `node_${crypto.randomUUID().slice(0, 8)}`;
2158
+ }
2159
+ }
2160
+ const clones = [];
2161
+ for (const sourceId of sourceIds) {
2162
+ const original = page.nodes.find((entry) => entry.id === sourceId);
2163
+ if (!original) {
2164
+ continue;
2165
+ }
2166
+ const cloneNode = structuredCloneNode(original);
2167
+ cloneNode.id = String(idMap[sourceId]);
2168
+ cloneNode.pageId = page.id;
2169
+ cloneNode.parentId = sourceId === sourceNode.id
2170
+ ? (typeof patch.parentId === "string" ? patch.parentId : sourceNode.parentId)
2171
+ : (original.parentId ? String(idMap[original.parentId] ?? original.parentId) : null);
2172
+ cloneNode.childIds = original.childIds.map((childId) => String(idMap[childId] ?? childId));
2173
+ cloneNode.rect = {
2174
+ ...cloneNode.rect,
2175
+ x: cloneNode.rect.x + 24,
2176
+ y: cloneNode.rect.y + 24
2177
+ };
2178
+ if (cloneNode.bindingRefs.primary && typeof cloneNode.bindingRefs.primary === "string") {
2179
+ cloneNode.bindingRefs.primary = `${cloneNode.bindingRefs.primary}_${cloneNode.id}`;
2180
+ }
2181
+ clones.push(cloneNode);
2182
+ }
2183
+ page.nodes.push(...clones);
2184
+ const duplicateRootId = String(idMap[sourceNode.id]);
2185
+ const parentId = typeof patch.parentId === "string" ? patch.parentId : sourceNode.parentId ?? null;
2186
+ const siblings = getSiblingIds(page, parentId);
2187
+ const nextIndex = clampIndex(typeof patch.index === "number" ? patch.index : siblings.length, siblings.length);
2188
+ siblings.splice(nextIndex, 0, duplicateRootId);
2189
+ assignSiblingIds(page, parentId, siblings);
2190
+ continue;
2191
+ }
2192
+ if (patch.op === "token.set" && typeof patch.path === "string") {
2193
+ setNestedValue(document.tokens.values, normalizeTokenPath(patch.path), patch.value);
2194
+ continue;
2195
+ }
2196
+ if (patch.op === "tokens.merge" && isRecord(patch.tokens)) {
2197
+ document.tokens = normalizeTokenStore({
2198
+ ...structuredClone(document.tokens),
2199
+ ...patch.tokens
2200
+ });
2201
+ continue;
2202
+ }
2203
+ if (patch.op === "tokens.replace" && isRecord(patch.tokens)) {
2204
+ document.tokens = normalizeTokenStore(patch.tokens);
2205
+ continue;
1400
2206
  }
1401
2207
  }
1402
2208
  }
1403
- function postViewState() {
2209
+ function getActivePage() {
2210
+ if (!currentState) {
2211
+ return null;
2212
+ }
2213
+ const pageId = activePageId ?? currentState.selection.pageId ?? currentState.document.pages[0]?.id ?? null;
2214
+ return currentState.document.pages.find((entry) => entry.id === pageId) ?? currentState.document.pages[0] ?? null;
2215
+ }
2216
+ function setActivePage(pageId, options = {}) {
1404
2217
  if (!currentState) {
2218
+ activePageId = pageId;
1405
2219
  return;
1406
2220
  }
1407
- port.postMessage({
1408
- type: "canvas-page-view-state",
1409
- viewport: currentState.viewport,
1410
- selection: currentState.selection
1411
- });
2221
+ const page = currentState.document.pages.find((entry) => entry.id === pageId) ?? currentState.document.pages[0] ?? null;
2222
+ activePageId = page?.id ?? null;
2223
+ if (!page) {
2224
+ renderState();
2225
+ return;
2226
+ }
2227
+ const selectedNode = currentState.selection.nodeId ? findNode(currentState.document, currentState.selection.nodeId) : null;
2228
+ if (!selectedNode || selectedNode.pageId !== page.id || options.clearSelectionIfMissing) {
2229
+ currentState.selection = {
2230
+ pageId: page.id,
2231
+ nodeId: selectedNode?.pageId === page.id ? selectedNode.id : null,
2232
+ targetId: currentState.selection.targetId,
2233
+ updatedAt: new Date().toISOString()
2234
+ };
2235
+ }
2236
+ else {
2237
+ currentState.selection.pageId = page.id;
2238
+ }
2239
+ renderState();
2240
+ if (options.broadcast) {
2241
+ postViewState();
2242
+ schedulePersist(currentState);
2243
+ }
1412
2244
  }
1413
- function schedulePersist(state) {
1414
- if (persistTimer !== null) {
1415
- window.clearTimeout(persistTimer);
2245
+ function selectNode(nodeId, pageId) {
2246
+ if (!currentState) {
2247
+ return;
1416
2248
  }
1417
- persistTimer = window.setTimeout(() => {
1418
- void flushPersist(state);
1419
- }, SAVE_DEBOUNCE_MS);
2249
+ activePageId = pageId;
2250
+ currentState.selection = {
2251
+ pageId,
2252
+ nodeId,
2253
+ targetId: currentState.selection.targetId,
2254
+ updatedAt: new Date().toISOString()
2255
+ };
2256
+ renderState();
2257
+ postViewState();
2258
+ schedulePersist(currentState);
1420
2259
  }
1421
- async function flushPersist(state = currentState) {
1422
- if (!state) {
2260
+ function requestHistory(direction) {
2261
+ if (!currentState || currentState.pendingMutation) {
1423
2262
  return;
1424
2263
  }
1425
- if (persistTimer !== null) {
1426
- window.clearTimeout(persistTimer);
1427
- persistTimer = null;
2264
+ const history = currentState.summary.history;
2265
+ if ((direction === "undo" && !history?.canUndo) || (direction === "redo" && !history?.canRedo)) {
2266
+ return;
1428
2267
  }
1429
- await saveCachedState(currentTabId, state);
1430
- broadcastChannel?.postMessage({ type: "canvas-page:broadcast", state });
2268
+ currentState.pendingMutation = true;
2269
+ renderState();
2270
+ port.postMessage({
2271
+ type: "canvas-page-history-request",
2272
+ direction
2273
+ });
1431
2274
  }
1432
- async function getCurrentTabId() {
1433
- return await new Promise((resolve) => {
1434
- chrome.tabs.getCurrent((tab) => {
1435
- const tabId = tab?.id;
2275
+ function duplicateSelectedNode() {
2276
+ if (!currentState || currentState.pendingMutation) {
2277
+ return;
2278
+ }
2279
+ const node = getSelectedNode();
2280
+ const page = getActivePage();
2281
+ if (!node || !page) {
2282
+ return;
2283
+ }
2284
+ const siblingIds = getSiblingIds(page, node.parentId ?? null);
2285
+ const currentIndex = siblingIds.indexOf(node.id);
2286
+ const idMap = buildDuplicateIdMap(page, node.id);
2287
+ const duplicateRootId = idMap[node.id];
2288
+ if (!duplicateRootId) {
2289
+ return;
2290
+ }
2291
+ applyOptimisticPatch([{
2292
+ op: "node.duplicate",
2293
+ nodeId: node.id,
2294
+ parentId: node.parentId ?? null,
2295
+ index: currentIndex >= 0 ? currentIndex + 1 : siblingIds.length,
2296
+ idMap
2297
+ }], {
2298
+ pageId: page.id,
2299
+ nodeId: duplicateRootId,
2300
+ targetId: currentState.selection.targetId
2301
+ });
2302
+ }
2303
+ function nudgeSelectedNode(key, delta) {
2304
+ const node = getSelectedNode();
2305
+ if (!node || !currentState || currentState.pendingMutation) {
2306
+ return;
2307
+ }
2308
+ const changes = {};
2309
+ if (key === "ArrowUp") {
2310
+ changes["rect.y"] = node.rect.y - delta;
2311
+ }
2312
+ else if (key === "ArrowDown") {
2313
+ changes["rect.y"] = node.rect.y + delta;
2314
+ }
2315
+ else if (key === "ArrowLeft") {
2316
+ changes["rect.x"] = node.rect.x - delta;
2317
+ }
2318
+ else if (key === "ArrowRight") {
2319
+ changes["rect.x"] = node.rect.x + delta;
2320
+ }
2321
+ if (Object.keys(changes).length > 0) {
2322
+ applyOptimisticPatch([{ op: "node.update", nodeId: node.id, changes }], currentState.selection);
2323
+ }
2324
+ }
2325
+ function fitActivePageViewport() {
2326
+ if (!currentState) {
2327
+ return;
2328
+ }
2329
+ currentState.viewport = resolvePreferredViewport(currentState);
2330
+ renderState();
2331
+ postViewState();
2332
+ schedulePersist(currentState);
2333
+ }
2334
+ function resetZoomToDefault() {
2335
+ if (!currentState) {
2336
+ return;
2337
+ }
2338
+ currentState.viewport = {
2339
+ ...currentState.viewport,
2340
+ zoom: DEFAULT_EDITOR_VIEWPORT.zoom
2341
+ };
2342
+ renderState();
2343
+ postViewState();
2344
+ schedulePersist(currentState);
2345
+ }
2346
+ function commitSelectedNodeChanges(changes) {
2347
+ const node = getSelectedNode();
2348
+ if (!node || !currentState || currentState.pendingMutation) {
2349
+ return;
2350
+ }
2351
+ applyOptimisticPatch([{ op: "node.update", nodeId: node.id, changes }], currentState.selection);
2352
+ }
2353
+ function commitSelectedBindingPatch() {
2354
+ const node = getSelectedNode();
2355
+ if (!node || !currentState || currentState.pendingMutation) {
2356
+ return;
2357
+ }
2358
+ const page = getActivePage();
2359
+ if (!page) {
2360
+ return;
2361
+ }
2362
+ const bindingIdentity = readSelectedBindingIdentity(currentState.document, node.id);
2363
+ const bindingId = bindingIdentity.bindingId ?? `binding_${crypto.randomUUID().slice(0, 8)}`;
2364
+ applyOptimisticPatch([{
2365
+ op: "binding.set",
2366
+ nodeId: node.id,
2367
+ binding: {
2368
+ id: bindingId,
2369
+ kind: readTextInput(bindingKindInput) || bindingIdentity.bindingKind || "component",
2370
+ selector: readTextInput(bindingSelectorInput) || undefined,
2371
+ componentName: readTextInput(bindingComponentInput) || undefined,
2372
+ metadata: {
2373
+ ...(readExistingBindingMetadata(currentState.document, bindingId) ?? {}),
2374
+ sourceKind: bindingIdentity.sourceKind ?? undefined
2375
+ }
2376
+ }
2377
+ }], {
2378
+ pageId: page.id,
2379
+ nodeId: node.id,
2380
+ targetId: currentState.selection.targetId
2381
+ });
2382
+ }
2383
+ function commitLayerMove(nodeId, parentId, index) {
2384
+ if (!currentState || currentState.pendingMutation) {
2385
+ return;
2386
+ }
2387
+ const page = getActivePage();
2388
+ const node = findNode(currentState.document, nodeId);
2389
+ if (!page || !node || parentId === node.id) {
2390
+ return;
2391
+ }
2392
+ const patch = node.parentId === parentId
2393
+ ? { op: "node.reorder", nodeId, index }
2394
+ : { op: "node.reparent", nodeId, parentId, index };
2395
+ applyOptimisticPatch([patch], {
2396
+ pageId: page.id,
2397
+ nodeId,
2398
+ targetId: currentState.selection.targetId
2399
+ });
2400
+ }
2401
+ function commitAnnotationCapture(marquee) {
2402
+ const page = getActivePage();
2403
+ if (!currentState || !page) {
2404
+ return;
2405
+ }
2406
+ const dx = Math.abs(marquee.currentClientX - marquee.originClientX);
2407
+ const dy = Math.abs(marquee.currentClientY - marquee.originClientY);
2408
+ if (dx < 6 && dy < 6) {
2409
+ if (marquee.targetNodeId && !annotationDrafts.some((entry) => entry.kind !== "region" && entry.nodeId === marquee.targetNodeId)) {
2410
+ annotationDrafts = [...annotationDrafts, { kind: "node", nodeId: marquee.targetNodeId, note: "" }];
2411
+ renderAnnotationPanel();
2412
+ }
2413
+ return;
2414
+ }
2415
+ const rect = marqueeClientRectToCanvasRect(marquee);
2416
+ const intersectingNodes = page.nodes.filter((node) => rectsIntersect(node.rect, rect));
2417
+ const regionId = `region_${crypto.randomUUID().slice(0, 8)}`;
2418
+ annotationDrafts = [
2419
+ ...annotationDrafts,
2420
+ {
2421
+ kind: "region",
2422
+ regionId,
2423
+ rect,
2424
+ pageId: page.id,
2425
+ label: intersectingNodes.length > 0 ? `Region • ${intersectingNodes.length} nodes` : "Region",
2426
+ note: ""
2427
+ }
2428
+ ];
2429
+ renderAnnotationPanel();
2430
+ }
2431
+ function renderStageOverlay() {
2432
+ stageOverlayElement.innerHTML = "";
2433
+ if (!marqueeState) {
2434
+ return;
2435
+ }
2436
+ const marquee = document.createElement("div");
2437
+ marquee.className = "canvas-stage-marquee";
2438
+ const bounds = marqueeClientRectToStageRect(marqueeState);
2439
+ marquee.style.left = `${bounds.left}px`;
2440
+ marquee.style.top = `${bounds.top}px`;
2441
+ marquee.style.width = `${bounds.width}px`;
2442
+ marquee.style.height = `${bounds.height}px`;
2443
+ stageOverlayElement.append(marquee);
2444
+ }
2445
+ function bindFieldCommit(input, callback) {
2446
+ input.addEventListener("change", callback);
2447
+ input.addEventListener("keydown", (event) => {
2448
+ const keyboardEvent = event;
2449
+ if (keyboardEvent.key === "Enter" && !(input instanceof HTMLTextAreaElement && !keyboardEvent.metaKey && !keyboardEvent.ctrlKey)) {
2450
+ keyboardEvent.preventDefault();
2451
+ callback();
2452
+ }
2453
+ });
2454
+ }
2455
+ function readTextInput(input) {
2456
+ return input.value.trim();
2457
+ }
2458
+ function readNumberInput(input, fallback) {
2459
+ const value = Number(input.value);
2460
+ return Number.isFinite(value) ? value : fallback;
2461
+ }
2462
+ function readStyleText(value) {
2463
+ if (typeof value === "string") {
2464
+ return value;
2465
+ }
2466
+ if (typeof value === "number" && Number.isFinite(value)) {
2467
+ return String(value);
2468
+ }
2469
+ return "";
2470
+ }
2471
+ function readExistingBindingMetadata(document, bindingId) {
2472
+ const binding = document.bindings.find((entry) => entry.id === bindingId);
2473
+ return binding ? { ...binding.metadata } : null;
2474
+ }
2475
+ function readBindingSelector(document, nodeId) {
2476
+ const bindingIdentity = readSelectedBindingIdentity(document, nodeId);
2477
+ const binding = bindingIdentity.bindingId
2478
+ ? document.bindings.find((entry) => entry.id === bindingIdentity.bindingId) ?? null
2479
+ : null;
2480
+ if (binding && typeof binding.selector === "string") {
2481
+ return binding.selector;
2482
+ }
2483
+ return binding && typeof binding.metadata.selector === "string"
2484
+ ? binding.metadata.selector
2485
+ : "";
2486
+ }
2487
+ function getRootNodes(page) {
2488
+ if (page.rootNodeId) {
2489
+ const root = page.nodes.find((entry) => entry.id === page.rootNodeId);
2490
+ if (root) {
2491
+ return [root, ...page.nodes.filter((entry) => entry.parentId === null && entry.id !== root.id)];
2492
+ }
2493
+ }
2494
+ return page.nodes.filter((entry) => entry.parentId === null);
2495
+ }
2496
+ function ensureExpandedNodePath(page, nodeId) {
2497
+ if (!page.rootNodeId) {
2498
+ return;
2499
+ }
2500
+ expandedLayerNodeIds.add(page.rootNodeId);
2501
+ let cursor = nodeId ? page.nodes.find((entry) => entry.id === nodeId) ?? null : null;
2502
+ while (cursor?.parentId) {
2503
+ expandedLayerNodeIds.add(cursor.parentId);
2504
+ cursor = page.nodes.find((entry) => entry.id === cursor?.parentId) ?? null;
2505
+ }
2506
+ }
2507
+ function getSiblingIds(page, parentId) {
2508
+ if (parentId) {
2509
+ return [...(page.nodes.find((entry) => entry.id === parentId)?.childIds ?? [])];
2510
+ }
2511
+ return page.nodes.filter((entry) => entry.parentId === null).map((entry) => entry.id);
2512
+ }
2513
+ function assignSiblingIds(page, parentId, ids) {
2514
+ if (parentId) {
2515
+ const parent = page.nodes.find((entry) => entry.id === parentId);
2516
+ if (parent) {
2517
+ parent.childIds = ids;
2518
+ }
2519
+ return;
2520
+ }
2521
+ for (const node of page.nodes) {
2522
+ if (ids.includes(node.id)) {
2523
+ node.parentId = null;
2524
+ }
2525
+ }
2526
+ page.rootNodeId = ids[0] ?? null;
2527
+ }
2528
+ function findSiblingInsertIndex(page, nodeId) {
2529
+ const node = page.nodes.find((entry) => entry.id === nodeId);
2530
+ if (!node) {
2531
+ return 0;
2532
+ }
2533
+ const siblings = getSiblingIds(page, node.parentId ?? null);
2534
+ const currentIndex = siblings.indexOf(node.id);
2535
+ return currentIndex >= 0 ? currentIndex : siblings.length;
2536
+ }
2537
+ function clampIndex(index, maxLength) {
2538
+ return Math.max(0, Math.min(index, maxLength));
2539
+ }
2540
+ function findPageForNode(document, nodeId) {
2541
+ for (const page of document.pages) {
2542
+ if (page.nodes.some((entry) => entry.id === nodeId)) {
2543
+ return page;
2544
+ }
2545
+ }
2546
+ return null;
2547
+ }
2548
+ function collectNodeSubtreeIds(page, nodeId) {
2549
+ const collected = [];
2550
+ const visit = (id) => {
2551
+ const node = page.nodes.find((entry) => entry.id === id);
2552
+ if (!node) {
2553
+ return;
2554
+ }
2555
+ collected.push(node.id);
2556
+ for (const childId of node.childIds) {
2557
+ visit(childId);
2558
+ }
2559
+ };
2560
+ visit(nodeId);
2561
+ return collected;
2562
+ }
2563
+ function buildDuplicateIdMap(page, nodeId) {
2564
+ return Object.fromEntries(collectNodeSubtreeIds(page, nodeId).map((id) => [id, `node_${crypto.randomUUID().slice(0, 8)}`]));
2565
+ }
2566
+ function structuredCloneNode(node) {
2567
+ return {
2568
+ ...node,
2569
+ rect: { ...node.rect },
2570
+ props: { ...node.props },
2571
+ style: { ...node.style },
2572
+ tokenRefs: { ...node.tokenRefs },
2573
+ bindingRefs: { ...node.bindingRefs },
2574
+ variantPatches: node.variantPatches.map((entry) => ({ ...entry })),
2575
+ metadata: { ...node.metadata }
2576
+ };
2577
+ }
2578
+ function marqueeClientRectToStageRect(marquee) {
2579
+ const stageRect = stageElement.getBoundingClientRect();
2580
+ const left = Math.min(marquee.originClientX, marquee.currentClientX) - stageRect.left;
2581
+ const top = Math.min(marquee.originClientY, marquee.currentClientY) - stageRect.top;
2582
+ const width = Math.abs(marquee.currentClientX - marquee.originClientX);
2583
+ const height = Math.abs(marquee.currentClientY - marquee.originClientY);
2584
+ return { left, top, width, height };
2585
+ }
2586
+ function marqueeClientRectToCanvasRect(marquee) {
2587
+ const stageRect = marqueeClientRectToStageRect(marquee);
2588
+ const viewport = currentState?.viewport ?? DEFAULT_EDITOR_VIEWPORT;
2589
+ return {
2590
+ x: Math.round((stageRect.left - viewport.x) / viewport.zoom),
2591
+ y: Math.round((stageRect.top - viewport.y) / viewport.zoom),
2592
+ width: Math.round(stageRect.width / viewport.zoom),
2593
+ height: Math.round(stageRect.height / viewport.zoom)
2594
+ };
2595
+ }
2596
+ function rectsIntersect(left, right) {
2597
+ return left.x < right.x + right.width
2598
+ && left.x + left.width > right.x
2599
+ && left.y < right.y + right.height
2600
+ && left.y + left.height > right.y;
2601
+ }
2602
+ function isNodeHidden(node) {
2603
+ return isRecord(node.metadata.visibility) && node.metadata.visibility.hidden === true;
2604
+ }
2605
+ function readTokenPath(value) {
2606
+ if (typeof value === "string" && value.trim().length > 0) {
2607
+ return normalizeTokenPath(value);
2608
+ }
2609
+ if (isRecord(value)) {
2610
+ if (typeof value.path === "string" && value.path.trim().length > 0) {
2611
+ return normalizeTokenPath(value.path);
2612
+ }
2613
+ if (typeof value.tokenPath === "string" && value.tokenPath.trim().length > 0) {
2614
+ return normalizeTokenPath(value.tokenPath);
2615
+ }
2616
+ }
2617
+ return null;
2618
+ }
2619
+ function normalizeTokenPath(path) {
2620
+ return path.trim().replace(/^tokens\./, "");
2621
+ }
2622
+ function escapeHtmlText(value) {
2623
+ return value
2624
+ .replaceAll("&", "&amp;")
2625
+ .replaceAll("<", "&lt;")
2626
+ .replaceAll(">", "&gt;");
2627
+ }
2628
+ function escapeHtmlAttribute(value) {
2629
+ return escapeHtmlText(value).replaceAll("\"", "&quot;");
2630
+ }
2631
+ function readActiveTokenModeId(tokens) {
2632
+ return typeof tokens.metadata.activeModeId === "string" && tokens.metadata.activeModeId.trim().length > 0
2633
+ ? tokens.metadata.activeModeId
2634
+ : null;
2635
+ }
2636
+ function readNestedTokenValue(values, tokenPath) {
2637
+ const segments = normalizeTokenPath(tokenPath).split(".").filter(Boolean);
2638
+ let current = values;
2639
+ for (const segment of segments) {
2640
+ if (!isRecord(current) || !(segment in current)) {
2641
+ return null;
2642
+ }
2643
+ current = current[segment];
2644
+ }
2645
+ return current;
2646
+ }
2647
+ function findTokenCollectionId(tokens, tokenPath) {
2648
+ const normalized = normalizeTokenPath(tokenPath);
2649
+ for (const collection of tokens.collections) {
2650
+ if (collection.items.some((item) => item.path === normalized)) {
2651
+ return collection.id;
2652
+ }
2653
+ }
2654
+ return null;
2655
+ }
2656
+ function findTokenItem(tokens, tokenPath) {
2657
+ const normalized = normalizeTokenPath(tokenPath);
2658
+ for (const collection of tokens.collections) {
2659
+ const item = collection.items.find((entry) => entry.path === normalized);
2660
+ if (item) {
2661
+ return { collection, item };
2662
+ }
2663
+ }
2664
+ return null;
2665
+ }
2666
+ function readTokenAliasTarget(tokens, tokenPath, modeId) {
2667
+ const normalized = normalizeTokenPath(tokenPath);
2668
+ const exact = tokens.aliases.find((entry) => entry.path === normalized && (entry.modeId ?? null) === modeId);
2669
+ if (exact) {
2670
+ return exact.targetPath;
2671
+ }
2672
+ const shared = tokens.aliases.find((entry) => entry.path === normalized && (entry.modeId ?? null) === null);
2673
+ return shared?.targetPath ?? null;
2674
+ }
2675
+ function resolveTokenValue(tokens, tokenPath, modeId, seen = new Set()) {
2676
+ const normalized = normalizeTokenPath(tokenPath);
2677
+ const visitKey = `${normalized}:${modeId ?? ""}`;
2678
+ if (seen.has(visitKey)) {
2679
+ return null;
2680
+ }
2681
+ seen.add(visitKey);
2682
+ const aliasTarget = readTokenAliasTarget(tokens, normalized, modeId);
2683
+ if (aliasTarget) {
2684
+ const aliasedValue = resolveTokenValue(tokens, aliasTarget, modeId, seen);
2685
+ if (aliasedValue !== null && aliasedValue !== undefined) {
2686
+ return aliasedValue;
2687
+ }
2688
+ }
2689
+ const location = findTokenItem(tokens, normalized);
2690
+ if (location) {
2691
+ if (modeId) {
2692
+ const mode = location.item.modes.find((entry) => entry.id === modeId);
2693
+ if (mode) {
2694
+ return mode.value;
2695
+ }
2696
+ }
2697
+ if (location.item.value !== undefined) {
2698
+ return location.item.value;
2699
+ }
2700
+ }
2701
+ return readNestedTokenValue(tokens.values, normalized);
2702
+ }
2703
+ function formatTokenEditorValue(value) {
2704
+ if (typeof value === "string") {
2705
+ return value;
2706
+ }
2707
+ if (typeof value === "number" && Number.isFinite(value)) {
2708
+ return String(value);
2709
+ }
2710
+ if (typeof value === "boolean") {
2711
+ return value ? "true" : "false";
2712
+ }
2713
+ return "";
2714
+ }
2715
+ function parseTokenEditorValue(raw) {
2716
+ const value = raw.trim();
2717
+ if (value === "true") {
2718
+ return true;
2719
+ }
2720
+ if (value === "false") {
2721
+ return false;
2722
+ }
2723
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) {
2724
+ const parsed = Number(value);
2725
+ if (Number.isFinite(parsed)) {
2726
+ return parsed;
2727
+ }
2728
+ }
2729
+ return value;
2730
+ }
2731
+ function collectLeafTokenPaths(value, prefix, target) {
2732
+ if (!isRecord(value)) {
2733
+ if (prefix.length > 0) {
2734
+ target.add(prefix);
2735
+ }
2736
+ return;
2737
+ }
2738
+ for (const [key, entry] of Object.entries(value)) {
2739
+ const nextPath = prefix ? `${prefix}.${key}` : key;
2740
+ if (isRecord(entry)) {
2741
+ collectLeafTokenPaths(entry, nextPath, target);
2742
+ continue;
2743
+ }
2744
+ if (!Array.isArray(entry)) {
2745
+ target.add(nextPath);
2746
+ }
2747
+ }
2748
+ }
2749
+ function listTokenModes(tokens, collectionId) {
2750
+ const modeMap = new Map([["__base__", "Base value"]]);
2751
+ if (collectionId !== "__values__") {
2752
+ const collection = tokens.collections.find((entry) => entry.id === collectionId);
2753
+ for (const item of collection?.items ?? []) {
2754
+ for (const mode of item.modes) {
2755
+ modeMap.set(mode.id, mode.name);
2756
+ }
2757
+ }
2758
+ }
2759
+ const activeModeId = readActiveTokenModeId(tokens);
2760
+ if (activeModeId && !modeMap.has(activeModeId)) {
2761
+ modeMap.set(activeModeId, activeModeId);
2762
+ }
2763
+ return [...modeMap.entries()].map(([id, name]) => ({ id, name }));
2764
+ }
2765
+ function collectTokenUsages(document, tokenPath) {
2766
+ const normalized = normalizeTokenPath(tokenPath);
2767
+ const modeId = readActiveTokenModeId(document.tokens);
2768
+ return document.pages.flatMap((page) => page.nodes.flatMap((node) => {
2769
+ const entries = Object.entries(node.tokenRefs)
2770
+ .flatMap(([property, value]) => {
2771
+ const refPath = readTokenPath(value);
2772
+ return refPath === normalized ? [{
2773
+ pageName: page.name,
2774
+ nodeId: node.id,
2775
+ nodeName: node.name,
2776
+ property,
2777
+ resolvedValue: resolveTokenValue(document.tokens, normalized, modeId)
2778
+ }] : [];
2779
+ });
2780
+ return entries;
2781
+ }));
2782
+ }
2783
+ function cloneTokenStore(tokens) {
2784
+ return structuredClone(tokens);
2785
+ }
2786
+ function hasSelectedTokenBinding(node, property) {
2787
+ return typeof readTokenPath(node.tokenRefs[property]) === "string";
2788
+ }
2789
+ function syncTokenEditorSelection() {
2790
+ if (!currentState) {
2791
+ selectedTokenPath = "";
2792
+ selectedTokenCollectionId = "__values__";
2793
+ selectedTokenModeId = "__base__";
2794
+ return;
2795
+ }
2796
+ const node = getSelectedNode();
2797
+ const selectedEntry = node
2798
+ ? Object.entries(node.tokenRefs)
2799
+ .map(([property, value]) => ({ property, path: readTokenPath(value) }))
2800
+ .find((entry) => typeof entry.path === "string")
2801
+ : null;
2802
+ const knownPaths = new Set();
2803
+ collectLeafTokenPaths(currentState.document.tokens.values, "", knownPaths);
2804
+ for (const collection of currentState.document.tokens.collections) {
2805
+ for (const item of collection.items) {
2806
+ knownPaths.add(item.path);
2807
+ }
2808
+ }
2809
+ if (selectedEntry && selectedTokenPath.length === 0) {
2810
+ selectedTokenPath = selectedEntry.path;
2811
+ tokenBindingPropertySelect.value = selectedEntry.property;
2812
+ }
2813
+ else if (selectedTokenPath.length === 0 || !knownPaths.has(selectedTokenPath)) {
2814
+ selectedTokenPath = [...knownPaths][0] ?? "";
2815
+ }
2816
+ const collectionStillExists = selectedTokenCollectionId === "__values__"
2817
+ || currentState.document.tokens.collections.some((entry) => entry.id === selectedTokenCollectionId);
2818
+ if (!collectionStillExists) {
2819
+ selectedTokenCollectionId = selectedTokenPath
2820
+ ? findTokenCollectionId(currentState.document.tokens, selectedTokenPath) ?? "__values__"
2821
+ : "__values__";
2822
+ }
2823
+ const activeModeId = readActiveTokenModeId(currentState.document.tokens);
2824
+ selectedTokenModeId = activeModeId ?? "__base__";
2825
+ }
2826
+ function updateActiveTokenMode() {
2827
+ if (!currentState || currentState.pendingMutation) {
2828
+ return;
2829
+ }
2830
+ const nextTokens = cloneTokenStore(currentState.document.tokens);
2831
+ if (selectedTokenModeId === "__base__") {
2832
+ delete nextTokens.metadata.activeModeId;
2833
+ }
2834
+ else {
2835
+ nextTokens.metadata.activeModeId = selectedTokenModeId;
2836
+ }
2837
+ applyOptimisticPatch([{ op: "tokens.replace", tokens: nextTokens }], currentState.selection);
2838
+ }
2839
+ function createTokenCollection() {
2840
+ if (!currentState || currentState.pendingMutation) {
2841
+ return;
2842
+ }
2843
+ const rawName = tokenCollectionNameInput.value.trim();
2844
+ if (rawName.length === 0) {
2845
+ tokenStatusElement.textContent = "Enter a collection name first.";
2846
+ return;
2847
+ }
2848
+ const collectionId = rawName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") || `collection-${crypto.randomUUID().slice(0, 6)}`;
2849
+ const nextTokens = cloneTokenStore(currentState.document.tokens);
2850
+ if (!nextTokens.collections.some((entry) => entry.id === collectionId)) {
2851
+ nextTokens.collections.push({
2852
+ id: collectionId,
2853
+ name: rawName,
2854
+ items: [],
2855
+ metadata: {}
2856
+ });
2857
+ }
2858
+ selectedTokenCollectionId = collectionId;
2859
+ tokenCollectionNameInput.value = "";
2860
+ applyOptimisticPatch([{ op: "tokens.replace", tokens: nextTokens }], currentState.selection);
2861
+ }
2862
+ function createTokenMode() {
2863
+ if (!currentState || currentState.pendingMutation) {
2864
+ return;
2865
+ }
2866
+ const rawName = tokenModeNameInput.value.trim();
2867
+ const tokenPath = normalizeTokenPath(tokenPathInput.value);
2868
+ if (rawName.length === 0 || tokenPath.length === 0) {
2869
+ tokenStatusElement.textContent = "Choose a token path and enter a mode name first.";
2870
+ return;
2871
+ }
2872
+ const nextTokens = cloneTokenStore(currentState.document.tokens);
2873
+ const collectionId = selectedTokenCollectionId === "__values__"
2874
+ ? (findTokenCollectionId(nextTokens, tokenPath) ?? "__values__")
2875
+ : selectedTokenCollectionId;
2876
+ if (collectionId === "__values__") {
2877
+ tokenStatusElement.textContent = "Create or select a token collection before adding modes.";
2878
+ return;
2879
+ }
2880
+ const collection = nextTokens.collections.find((entry) => entry.id === collectionId);
2881
+ if (!collection) {
2882
+ return;
2883
+ }
2884
+ let item = collection.items.find((entry) => entry.path === tokenPath);
2885
+ if (!item) {
2886
+ item = {
2887
+ id: tokenPath.replace(/[^a-z0-9]+/gi, "_").toLowerCase(),
2888
+ path: tokenPath,
2889
+ value: parseTokenEditorValue(tokenValueInput.value || formatTokenEditorValue(resolveTokenValue(nextTokens, tokenPath, null))),
2890
+ modes: [],
2891
+ metadata: {}
2892
+ };
2893
+ collection.items.push(item);
2894
+ }
2895
+ const modeId = rawName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") || `mode-${crypto.randomUUID().slice(0, 6)}`;
2896
+ if (!item.modes.some((entry) => entry.id === modeId)) {
2897
+ item.modes.push({
2898
+ id: modeId,
2899
+ name: rawName,
2900
+ value: parseTokenEditorValue(tokenValueInput.value || formatTokenEditorValue(item.value)),
2901
+ metadata: {}
2902
+ });
2903
+ }
2904
+ nextTokens.metadata.activeModeId = modeId;
2905
+ selectedTokenModeId = modeId;
2906
+ tokenModeNameInput.value = "";
2907
+ applyOptimisticPatch([{ op: "tokens.replace", tokens: nextTokens }], currentState.selection);
2908
+ }
2909
+ function saveTokenEditor() {
2910
+ if (!currentState || currentState.pendingMutation) {
2911
+ return;
2912
+ }
2913
+ const tokenPath = normalizeTokenPath(tokenPathInput.value);
2914
+ if (tokenPath.length === 0) {
2915
+ tokenStatusElement.textContent = "Enter a token path first.";
2916
+ return;
2917
+ }
2918
+ const nextTokens = cloneTokenStore(currentState.document.tokens);
2919
+ const nextValue = parseTokenEditorValue(tokenValueInput.value);
2920
+ if (selectedTokenCollectionId === "__values__") {
2921
+ setNestedValue(nextTokens.values, tokenPath, nextValue);
2922
+ }
2923
+ else {
2924
+ const collection = nextTokens.collections.find((entry) => entry.id === selectedTokenCollectionId);
2925
+ if (!collection) {
2926
+ tokenStatusElement.textContent = "Choose a valid token collection.";
2927
+ return;
2928
+ }
2929
+ let item = collection.items.find((entry) => entry.path === tokenPath);
2930
+ if (!item) {
2931
+ item = {
2932
+ id: tokenPath.replace(/[^a-z0-9]+/gi, "_").toLowerCase(),
2933
+ path: tokenPath,
2934
+ value: nextValue,
2935
+ modes: [],
2936
+ metadata: {}
2937
+ };
2938
+ collection.items.push(item);
2939
+ }
2940
+ if (selectedTokenModeId === "__base__") {
2941
+ item.value = nextValue;
2942
+ }
2943
+ else {
2944
+ const modeName = tokenModeSelect.selectedOptions[0]?.textContent?.trim() || selectedTokenModeId;
2945
+ const existingMode = item.modes.find((entry) => entry.id === selectedTokenModeId);
2946
+ if (existingMode) {
2947
+ existingMode.value = nextValue;
2948
+ }
2949
+ else {
2950
+ item.modes.push({
2951
+ id: selectedTokenModeId,
2952
+ name: modeName,
2953
+ value: nextValue,
2954
+ metadata: {}
2955
+ });
2956
+ }
2957
+ nextTokens.metadata.activeModeId = selectedTokenModeId;
2958
+ }
2959
+ }
2960
+ const aliasTarget = normalizeTokenPath(tokenAliasInput.value);
2961
+ nextTokens.aliases = nextTokens.aliases.filter((entry) => !(entry.path === tokenPath && (entry.modeId ?? null) === (selectedTokenModeId === "__base__" ? null : selectedTokenModeId)));
2962
+ if (aliasTarget.length > 0) {
2963
+ nextTokens.aliases.push({
2964
+ path: tokenPath,
2965
+ targetPath: aliasTarget,
2966
+ modeId: selectedTokenModeId === "__base__" ? null : selectedTokenModeId,
2967
+ metadata: {}
2968
+ });
2969
+ }
2970
+ selectedTokenPath = tokenPath;
2971
+ applyOptimisticPatch([{ op: "tokens.replace", tokens: nextTokens }], currentState.selection);
2972
+ }
2973
+ function bindSelectedNodeToToken() {
2974
+ const node = getSelectedNode();
2975
+ if (!node || !currentState || currentState.pendingMutation) {
2976
+ return;
2977
+ }
2978
+ const tokenPath = normalizeTokenPath(tokenPathInput.value);
2979
+ const property = tokenBindingPropertySelect.value || "backgroundColor";
2980
+ if (tokenPath.length === 0) {
2981
+ tokenStatusElement.textContent = "Choose a token path first.";
2982
+ return;
2983
+ }
2984
+ const nextTokenRefs = { ...node.tokenRefs, [property]: tokenPath };
2985
+ const nextStyle = { ...node.style };
2986
+ const resolvedValue = resolveTokenValue(currentState.document.tokens, tokenPath, readActiveTokenModeId(currentState.document.tokens));
2987
+ if (typeof resolvedValue === "string" || typeof resolvedValue === "number") {
2988
+ nextStyle[property] = resolvedValue;
2989
+ }
2990
+ const nextTokens = cloneTokenStore(currentState.document.tokens);
2991
+ const bindingIdentity = readSelectedBindingIdentity(currentState.document, node.id);
2992
+ nextTokens.bindings = nextTokens.bindings.filter((entry) => !(entry.nodeId === node.id && entry.property === property));
2993
+ nextTokens.bindings.push({
2994
+ path: tokenPath,
2995
+ nodeId: node.id,
2996
+ bindingId: bindingIdentity.bindingId ?? null,
2997
+ property,
2998
+ metadata: {}
2999
+ });
3000
+ selectedTokenPath = tokenPath;
3001
+ applyOptimisticPatch([
3002
+ {
3003
+ op: "node.update",
3004
+ nodeId: node.id,
3005
+ changes: {
3006
+ tokenRefs: nextTokenRefs,
3007
+ style: nextStyle
3008
+ }
3009
+ },
3010
+ {
3011
+ op: "tokens.replace",
3012
+ tokens: nextTokens
3013
+ }
3014
+ ], currentState.selection);
3015
+ }
3016
+ function clearSelectedTokenBinding() {
3017
+ const node = getSelectedNode();
3018
+ if (!node || !currentState || currentState.pendingMutation) {
3019
+ return;
3020
+ }
3021
+ const property = tokenBindingPropertySelect.value || "backgroundColor";
3022
+ if (!hasSelectedTokenBinding(node, property)) {
3023
+ return;
3024
+ }
3025
+ const nextTokenRefs = { ...node.tokenRefs };
3026
+ delete nextTokenRefs[property];
3027
+ const nextTokens = cloneTokenStore(currentState.document.tokens);
3028
+ nextTokens.bindings = nextTokens.bindings.filter((entry) => !(entry.nodeId === node.id && entry.property === property));
3029
+ applyOptimisticPatch([
3030
+ {
3031
+ op: "node.update",
3032
+ nodeId: node.id,
3033
+ changes: {
3034
+ tokenRefs: nextTokenRefs
3035
+ }
3036
+ },
3037
+ {
3038
+ op: "tokens.replace",
3039
+ tokens: nextTokens
3040
+ }
3041
+ ], currentState.selection);
3042
+ }
3043
+ function resolveStageStyle(documentState, node) {
3044
+ const style = { ...node.style };
3045
+ const modeId = readActiveTokenModeId(documentState.tokens);
3046
+ for (const [property, value] of Object.entries(node.tokenRefs)) {
3047
+ const tokenPath = readTokenPath(value);
3048
+ if (!tokenPath) {
3049
+ continue;
3050
+ }
3051
+ const resolvedValue = resolveTokenValue(documentState.tokens, tokenPath, modeId);
3052
+ if (typeof resolvedValue === "string" || typeof resolvedValue === "number") {
3053
+ style[property] = resolvedValue;
3054
+ }
3055
+ }
3056
+ return style;
3057
+ }
3058
+ function getDraftId(draft) {
3059
+ return draft.kind === "region" ? draft.regionId : draft.nodeId;
3060
+ }
3061
+ function isEditableTarget(target) {
3062
+ return target instanceof HTMLInputElement
3063
+ || target instanceof HTMLTextAreaElement
3064
+ || (target instanceof HTMLElement && target.isContentEditable);
3065
+ }
3066
+ function postViewState() {
3067
+ if (!currentState) {
3068
+ return;
3069
+ }
3070
+ port.postMessage({
3071
+ type: "canvas-page-view-state",
3072
+ viewport: currentState.viewport,
3073
+ selection: currentState.selection
3074
+ });
3075
+ }
3076
+ function schedulePersist(state) {
3077
+ if (persistTimer !== null) {
3078
+ window.clearTimeout(persistTimer);
3079
+ }
3080
+ persistTimer = window.setTimeout(() => {
3081
+ void flushPersist(state);
3082
+ }, SAVE_DEBOUNCE_MS);
3083
+ }
3084
+ async function flushPersist(state = currentState) {
3085
+ if (!state) {
3086
+ return;
3087
+ }
3088
+ if (persistTimer !== null) {
3089
+ window.clearTimeout(persistTimer);
3090
+ persistTimer = null;
3091
+ }
3092
+ await saveCachedState(currentTabId, state);
3093
+ broadcastChannel?.postMessage({ type: "canvas-page:broadcast", state });
3094
+ }
3095
+ async function getCurrentTabId() {
3096
+ return await new Promise((resolve) => {
3097
+ chrome.tabs.getCurrent((tab) => {
3098
+ const tabId = tab?.id;
1436
3099
  resolve(typeof tabId === "number" ? tabId : null);
1437
3100
  });
1438
3101
  });
@@ -1547,10 +3210,149 @@ function normalizeDocument(value) {
1547
3210
  bindings: Array.isArray(value.bindings) ? value.bindings.flatMap((entry) => normalizeBinding(entry)) : [],
1548
3211
  assets: Array.isArray(value.assets) ? value.assets.flatMap((entry) => normalizeAsset(entry)) : [],
1549
3212
  componentInventory: Array.isArray(value.componentInventory)
1550
- ? value.componentInventory.filter(isRecord)
1551
- : []
3213
+ ? value.componentInventory.flatMap((entry, index) => normalizeComponentInventoryItem(entry, index))
3214
+ : [],
3215
+ tokens: normalizeTokenStore(value.tokens),
3216
+ meta: normalizeDocumentMeta(value.meta)
3217
+ };
3218
+ }
3219
+ function normalizeComponentInventoryItem(value, index) {
3220
+ if (!isRecord(value)) {
3221
+ return [];
3222
+ }
3223
+ const id = typeof value.id === "string" ? value.id : `inventory_${index + 1}`;
3224
+ const name = typeof value.name === "string"
3225
+ ? value.name
3226
+ : typeof value.componentName === "string"
3227
+ ? value.componentName
3228
+ : id;
3229
+ return [{
3230
+ id,
3231
+ name,
3232
+ componentName: typeof value.componentName === "string" ? value.componentName : undefined,
3233
+ sourceKind: typeof value.sourceKind === "string" ? value.sourceKind : undefined,
3234
+ sourceFamily: typeof value.sourceFamily === "string" ? value.sourceFamily : undefined,
3235
+ origin: typeof value.origin === "string" ? value.origin : undefined,
3236
+ framework: normalizeComponentRef(value.framework),
3237
+ adapter: normalizeComponentRef(value.adapter),
3238
+ plugin: normalizeComponentRef(value.plugin),
3239
+ variants: Array.isArray(value.variants) ? value.variants.filter(isRecord) : [],
3240
+ props: Array.isArray(value.props) ? value.props.filter(isRecord) : [],
3241
+ slots: Array.isArray(value.slots) ? value.slots.filter(isRecord) : [],
3242
+ events: Array.isArray(value.events) ? value.events.filter(isRecord) : [],
3243
+ content: isRecord(value.content) ? value.content : {},
3244
+ metadata: isRecord(value.metadata) ? value.metadata : {}
3245
+ }];
3246
+ }
3247
+ function normalizeComponentRef(value) {
3248
+ if (!isRecord(value) || typeof value.id !== "string") {
3249
+ return null;
3250
+ }
3251
+ return {
3252
+ id: value.id,
3253
+ label: typeof value.label === "string" ? value.label : typeof value.name === "string" ? value.name : undefined,
3254
+ packageName: typeof value.packageName === "string" ? value.packageName : undefined,
3255
+ version: typeof value.version === "string" ? value.version : undefined,
3256
+ metadata: isRecord(value.metadata) ? value.metadata : {}
3257
+ };
3258
+ }
3259
+ function normalizeTokenStore(value) {
3260
+ if (!isRecord(value)) {
3261
+ return { values: {}, collections: [], aliases: [], bindings: [], metadata: {} };
3262
+ }
3263
+ const structured = "values" in value || "collections" in value || "aliases" in value || "bindings" in value || "metadata" in value;
3264
+ return {
3265
+ values: structured && isRecord(value.values) ? value.values : structured ? {} : value,
3266
+ collections: Array.isArray(value.collections) ? value.collections.flatMap((entry) => normalizeTokenCollection(entry)) : [],
3267
+ aliases: Array.isArray(value.aliases) ? value.aliases.flatMap((entry) => normalizeTokenAlias(entry)) : [],
3268
+ bindings: Array.isArray(value.bindings) ? value.bindings.flatMap((entry) => normalizeTokenBinding(entry)) : [],
3269
+ metadata: isRecord(value.metadata) ? value.metadata : {}
3270
+ };
3271
+ }
3272
+ function normalizeTokenCollection(value) {
3273
+ if (!isRecord(value) || typeof value.id !== "string") {
3274
+ return [];
3275
+ }
3276
+ return [{
3277
+ id: value.id,
3278
+ name: typeof value.name === "string" ? value.name : value.id,
3279
+ items: Array.isArray(value.items) ? value.items.flatMap((entry) => normalizeTokenItem(entry)) : [],
3280
+ metadata: isRecord(value.metadata) ? value.metadata : {}
3281
+ }];
3282
+ }
3283
+ function normalizeTokenItem(value) {
3284
+ if (!isRecord(value) || typeof value.id !== "string" || typeof value.path !== "string") {
3285
+ return [];
3286
+ }
3287
+ return [{
3288
+ id: value.id,
3289
+ path: normalizeTokenPath(value.path),
3290
+ value: value.value,
3291
+ type: typeof value.type === "string" ? value.type : undefined,
3292
+ description: typeof value.description === "string" ? value.description : undefined,
3293
+ modes: Array.isArray(value.modes) ? value.modes.flatMap((entry) => normalizeTokenMode(entry)) : [],
3294
+ metadata: isRecord(value.metadata) ? value.metadata : {}
3295
+ }];
3296
+ }
3297
+ function normalizeTokenMode(value) {
3298
+ if (!isRecord(value) || typeof value.id !== "string" || typeof value.name !== "string") {
3299
+ return [];
3300
+ }
3301
+ return [{
3302
+ id: value.id,
3303
+ name: value.name,
3304
+ value: value.value,
3305
+ metadata: isRecord(value.metadata) ? value.metadata : {}
3306
+ }];
3307
+ }
3308
+ function normalizeTokenAlias(value) {
3309
+ if (!isRecord(value) || typeof value.path !== "string" || typeof value.targetPath !== "string") {
3310
+ return [];
3311
+ }
3312
+ return [{
3313
+ path: normalizeTokenPath(value.path),
3314
+ targetPath: normalizeTokenPath(value.targetPath),
3315
+ modeId: typeof value.modeId === "string" ? value.modeId : null,
3316
+ metadata: isRecord(value.metadata) ? value.metadata : {}
3317
+ }];
3318
+ }
3319
+ function normalizeTokenBinding(value) {
3320
+ if (!isRecord(value) || typeof value.path !== "string") {
3321
+ return [];
3322
+ }
3323
+ return [{
3324
+ path: normalizeTokenPath(value.path),
3325
+ nodeId: typeof value.nodeId === "string" ? value.nodeId : null,
3326
+ bindingId: typeof value.bindingId === "string" ? value.bindingId : null,
3327
+ property: typeof value.property === "string" ? value.property : null,
3328
+ metadata: isRecord(value.metadata) ? value.metadata : {}
3329
+ }];
3330
+ }
3331
+ function normalizeDocumentMeta(value) {
3332
+ if (!isRecord(value)) {
3333
+ return { imports: [], starter: null, adapterPlugins: [], pluginErrors: [], metadata: {} };
3334
+ }
3335
+ return {
3336
+ imports: Array.isArray(value.imports) ? value.imports.filter(isRecord) : [],
3337
+ starter: isRecord(value.starter) ? value.starter : null,
3338
+ adapterPlugins: Array.isArray(value.adapterPlugins) ? value.adapterPlugins.filter(isRecord) : [],
3339
+ pluginErrors: Array.isArray(value.pluginErrors)
3340
+ ? value.pluginErrors.flatMap((entry) => normalizePluginError(entry))
3341
+ : [],
3342
+ metadata: isRecord(value.metadata) ? value.metadata : {}
1552
3343
  };
1553
3344
  }
3345
+ function normalizePluginError(value) {
3346
+ if (!isRecord(value) || typeof value.code !== "string" || typeof value.message !== "string") {
3347
+ return [];
3348
+ }
3349
+ return [{
3350
+ pluginId: typeof value.pluginId === "string" ? value.pluginId : undefined,
3351
+ code: value.code,
3352
+ message: value.message,
3353
+ details: isRecord(value.details) ? value.details : {}
3354
+ }];
3355
+ }
1554
3356
  function normalizePage(value) {
1555
3357
  if (!isRecord(value) || typeof value.id !== "string") {
1556
3358
  return [];
@@ -1585,7 +3387,9 @@ function normalizeNode(value, pageId) {
1585
3387
  } : { x: 0, y: 0, width: 240, height: 120 },
1586
3388
  props: isRecord(value.props) ? value.props : {},
1587
3389
  style: isRecord(value.style) ? value.style : {},
3390
+ tokenRefs: isRecord(value.tokenRefs) ? value.tokenRefs : {},
1588
3391
  bindingRefs: isRecord(value.bindingRefs) ? value.bindingRefs : {},
3392
+ variantPatches: Array.isArray(value.variantPatches) ? value.variantPatches.filter(isRecord) : [],
1589
3393
  metadata: isRecord(value.metadata) ? value.metadata : {}
1590
3394
  }];
1591
3395
  }
@@ -1597,6 +3401,7 @@ function normalizeBinding(value) {
1597
3401
  id: value.id,
1598
3402
  nodeId: value.nodeId,
1599
3403
  kind: typeof value.kind === "string" ? value.kind : "component",
3404
+ selector: typeof value.selector === "string" ? value.selector : undefined,
1600
3405
  componentName: typeof value.componentName === "string" ? value.componentName : undefined,
1601
3406
  metadata: isRecord(value.metadata) ? value.metadata : {}
1602
3407
  }];
@@ -1722,7 +3527,7 @@ function queueViewportFitIfNeeded() {
1722
3527
  if (!currentState || !isDefaultEditorViewport(currentState.viewport)) {
1723
3528
  return;
1724
3529
  }
1725
- const page = currentState.document.pages[0];
3530
+ const page = getActivePage();
1726
3531
  if (!page || page.nodes.length === 0) {
1727
3532
  return;
1728
3533
  }
@@ -1748,11 +3553,14 @@ function queueViewportFitIfNeeded() {
1748
3553
  });
1749
3554
  }
1750
3555
  function resolvePreferredViewport(state) {
1751
- const page = state.document.pages[0];
1752
- if (!page || page.nodes.length === 0) {
3556
+ const page = (activePageId ? state.document.pages.find((entry) => entry.id === activePageId) : null)
3557
+ ?? state.document.pages.find((entry) => entry.id === state.selection.pageId)
3558
+ ?? state.document.pages[0];
3559
+ const visibleNodes = page?.nodes.filter((node) => !isNodeHidden(node)) ?? [];
3560
+ if (!page || visibleNodes.length === 0) {
1753
3561
  return { ...DEFAULT_EDITOR_VIEWPORT };
1754
3562
  }
1755
- return computeFittedViewport(page.nodes, stageElement.clientWidth, stageElement.clientHeight);
3563
+ return computeFittedViewport(visibleNodes, stageElement.clientWidth, stageElement.clientHeight);
1756
3564
  }
1757
3565
  function findNode(document, nodeId) {
1758
3566
  if (!document) {