@jxsuite/studio 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +21080 -32585
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +7 -0
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +221 -245
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -53
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +30 -1
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +7 -1
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
  363. package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
  364. package/dist/chunks/files-j8a1t06k.js +0 -50
  365. package/dist/chunks/files-j8a1t06k.js.map +0 -9
  366. package/dist/chunks/git-panel-xtq5z81x.js +0 -28
  367. package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
  368. package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
  369. package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
  370. package/dist/chunks/settings-modal-17ttr96g.js +0 -29
  371. package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-0xgs7xrs.js +0 -429
  381. package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
  382. package/dist/chunks/studio-4d8hzcz5.js +0 -3427
  383. package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
  384. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  385. package/dist/chunks/studio-9smfzqhc.js +0 -19
  386. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  387. package/dist/chunks/studio-jm0z592w.js.map +0 -115
  388. package/dist/chunks/studio-m2t4mdg5.js +0 -1854
  389. package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
  390. package/dist/chunks/studio-n4p11dfy.js +0 -853
  391. package/dist/chunks/studio-n4p11dfy.js.map +0 -12
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-sjh1wt1a.js +0 -3744
  394. package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
  395. package/dist/chunks/studio-v7jkjqng.js.map +0 -46
  396. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  397. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  398. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  399. package/dist/chunks/studio-yfcstdzy.js +0 -399
  400. package/dist/chunks/studio-yfcstdzy.js.map +0 -12
  401. package/dist/chunks/studio-zp7jncs8.js +0 -469
  402. package/dist/chunks/studio-zp7jncs8.js.map +0 -14
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -22,8 +22,12 @@
22
22
  import {
23
23
  commitActiveBlock,
24
24
  getActiveElement,
25
+ handleSlashTrigger,
25
26
  isEditableBlock,
26
27
  isEditing,
28
+ isSlashActive,
29
+ openSlashMenu,
30
+ refreshSlashMenu,
27
31
  splitActiveBlock,
28
32
  startEditing,
29
33
  stopEditing,
@@ -452,6 +456,23 @@ export function startIframeInlineEdit(
452
456
  onSplit: () => splitActiveBlock(),
453
457
  });
454
458
 
459
+ /* The slash gesture, at the EDITING HOST.
460
+ `editor/inline-edit.ts` recognised "/" in a `keydown` listener it attached to the BLOCK, and
461
+ for an ordinary block the editing host is this container — so the focused element, and every
462
+ keydown's target, is the container and that listener never fired. Typing "/" in the canvas
463
+ simply inserted a slash: the menu the docs describe, and the shot the manifest quarantined,
464
+ had no trigger at all. It is recognised here, where the keystrokes are, and the engine still
465
+ owns what a slash MEANS (start of a block or after a space; anywhere else it is punctuation).
466
+ The matching `input` half re-filters the open menu, for the same reason: a contenteditable's
467
+ `input` targets its host, not the block the caret happens to be in. */
468
+ const onSlashKey = (e: Event) => handleSlashTrigger(e as KeyboardEvent);
469
+ const onSlashInput = () => {
470
+ if (isSlashActive()) {
471
+ refreshSlashMenu();
472
+ }
473
+ };
474
+ container.addEventListener("keydown", onSlashKey);
475
+ container.addEventListener("input", onSlashInput);
455
476
  doc.addEventListener("mouseup", onMouseUp, true);
456
477
  doc.addEventListener("keyup", onKeyUp, true);
457
478
  doc.addEventListener("blur", onBlurCapture, true);
@@ -461,6 +482,14 @@ export function startIframeInlineEdit(
461
482
  applyFormatIntent(msg.intent);
462
483
  return;
463
484
  }
485
+ if (msg.kind === "openSlash") {
486
+ /* Opened BY NAME, so there is no "/" in the document to anchor it — the engine gives this
487
+ menu its own filter field and deletes nothing when a block is chosen. `openSlashMenu`
488
+ refuses when no caret session is live, which is the same refusal the record's `requires`
489
+ sentence states. */
490
+ openSlashMenu({ anchored: false });
491
+ return;
492
+ }
464
493
  if (msg.kind === "flushEdits") {
465
494
  // Commit anything the idle tick has not yet written, THEN acknowledge. The commit is posted
466
495
  // First, so a parent that has seen the acknowledgement has already applied the text.
@@ -492,6 +521,8 @@ export function startIframeInlineEdit(
492
521
  });
493
522
 
494
523
  return () => {
524
+ container.removeEventListener("keydown", onSlashKey);
525
+ container.removeEventListener("input", onSlashInput);
495
526
  doc.removeEventListener("mouseup", onMouseUp, true);
496
527
  doc.removeEventListener("keyup", onKeyUp, true);
497
528
  doc.removeEventListener("blur", onBlurCapture, true);
@@ -95,8 +95,10 @@ export interface InteractionDeps {
95
95
  */
96
96
  getShadowDoc: () => JxMutableNode | null;
97
97
  /**
98
- * The live render's canvas mode. Insertion "+" zones are a document-editing affordance —
99
- * suppressed for stylebook renders (specimens aren't insert targets). Absent = permissive.
98
+ * The live render's canvas mode. Editing affordances are suppressed per mode: insertion "+" zones
99
+ * are meaningless for stylebook renders (specimens aren't insert targets), and preview reports
100
+ * nothing at all beyond link intent — no hit, no hover, no zones, no Jx context menu — because a
101
+ * preview is the shipped page, not a document you can point at. Absent = permissive.
100
102
  */
101
103
  getMode?: () => string;
102
104
  /**
@@ -129,6 +131,31 @@ export function startInteraction(
129
131
  let lastZonesKey: string | null = null;
130
132
  let lastGen = deps?.getGen?.() ?? 0;
131
133
 
134
+ /**
135
+ * Whether this render is a preview. Preview is the fidelity view: it has no selection, no hover
136
+ * box, no insertion "+" and no Jx element menu, so the frame reports none of them. (The host
137
+ * refuses the same messages independently — the iframe bundle ships prebuilt, so neither side may
138
+ * rely on the other's build being current.)
139
+ */
140
+ const isPreview = () => deps?.getMode?.() === "preview";
141
+
142
+ /**
143
+ * Report that the person is now working in this frame's pane — every mode, every button, every
144
+ * target.
145
+ *
146
+ * `pointerdown` rather than `click`, and unconditional rather than mode-gated, because both of
147
+ * `hit`'s holes are holes in what a CLICK means here. In preview there is no `hit` by design (a
148
+ * click in preview is a click on the page), and in edit/design `hit` is posted only when the
149
+ * click lands on a `[data-jx-path]` node — so an artboard's empty margin, a right-click, and
150
+ * every single interaction with a Preview pane all left the keyboard in the other pane.
151
+ *
152
+ * It carries nothing, which is what makes it safe to send from preview: the parent moves pane
153
+ * focus and does not touch the document, so the page underneath is still just a page.
154
+ */
155
+ const onPointerDown = () => {
156
+ channel.post({ kind: "paneFocus" });
157
+ };
158
+
132
159
  const onClick = (e: Event) => {
133
160
  /*
134
161
  * Preview keeps anchors live, so a click would navigate this iframe and destroy the render (and
@@ -139,18 +166,29 @@ export function startInteraction(
139
166
  * Design/edit never reach this: the runtime de-links anchors onto `data-jx-href` there, so there
140
167
  * is no navigation to intercept and a click means "select this element".
141
168
  */
142
- if (deps?.getMode?.() === "preview" && e.target instanceof Element) {
143
- const anchor = e.target.closest("a[href]");
144
- const href = anchor?.getAttribute("href");
145
- if (href && !href.startsWith("#")) {
146
- e.preventDefault();
147
- channel.post({ href, kind: "previewNavigate" });
148
- return;
169
+ if (isPreview()) {
170
+ if (e.target instanceof Element) {
171
+ const anchor = e.target.closest("a[href]");
172
+ const href = anchor?.getAttribute("href");
173
+ if (href && !href.startsWith("#")) {
174
+ e.preventDefault();
175
+ channel.post({ href, kind: "previewNavigate" });
176
+ }
149
177
  }
178
+ // No hit post: a click in preview is a click on the page, never a selection.
179
+ return;
150
180
  }
151
181
  const hit = nearestHit(e.target);
152
182
  if (hit) {
153
- channel.post({ hit, kind: "hit" });
183
+ // Ctrl/Cmd is the accumulate gesture (§6.5). The iframe reports the modifier and nothing
184
+ // Else — it holds no selection state, so what a modified click MEANS is the parent's to
185
+ // Decide, exactly as the unmodified click already was.
186
+ const mouse = e as MouseEvent;
187
+ channel.post({
188
+ additive: mouse.ctrlKey === true || mouse.metaKey === true,
189
+ hit,
190
+ kind: "hit",
191
+ });
154
192
  }
155
193
  };
156
194
 
@@ -163,6 +201,9 @@ export function startInteraction(
163
201
  * the key was ever compared — a forced layout per mouse move, thrown away almost every time.
164
202
  */
165
203
  const reportHover = (el: HTMLElement | null) => {
204
+ if (isPreview()) {
205
+ return;
206
+ }
166
207
  const key = el?.dataset.jxPath ?? null;
167
208
  if (key === lastHoverKey) {
168
209
  return;
@@ -186,7 +227,7 @@ export function startInteraction(
186
227
 
187
228
  /** Resolve + post the insertion "+" zones for an iframe-viewport cursor, deduped by key. */
188
229
  const reportInsertZones = (target: EventTarget | null, cursor: { x: number; y: number }) => {
189
- if (!deps || deps.getMode?.() === "stylebook") {
230
+ if (!deps || deps.getMode?.() === "stylebook" || isPreview()) {
190
231
  return;
191
232
  }
192
233
  syncZoneGen();
@@ -242,7 +283,7 @@ export function startInteraction(
242
283
  return;
243
284
  }
244
285
  reportHover(null);
245
- if (deps && lastZonesKey !== "none") {
286
+ if (deps && !isPreview() && lastZonesKey !== "none") {
246
287
  lastZonesKey = "none";
247
288
  channel.post({ kind: "insertZones", zones: null });
248
289
  }
@@ -250,23 +291,41 @@ export function startInteraction(
250
291
 
251
292
  const onContextMenu = (e: Event) => {
252
293
  const me = e as MouseEvent;
294
+ // Preview keeps the NATIVE menu: the Jx element menu's verbs (duplicate, delete, paste, wrap)
295
+ // Are document edits, and preview does not edit. Copy Link Address does what it says instead.
296
+ if (isPreview()) {
297
+ return;
298
+ }
253
299
  // Inside the ACTIVE editable keep the NATIVE menu (spellcheck / paste) — the session owns it.
254
300
  const active = isEditing() ? getActiveElement() : null;
255
301
  if (active && e.target instanceof Node && active.contains(e.target)) {
256
302
  return;
257
303
  }
258
- // Suppress the browser menu everywhere else (legacy parity — the deleted panel-events handler
259
- // PreventDefaulted even with no element hit) and let the parent show the Jx element menu.
260
- e.preventDefault();
261
304
  const hit = nearestHit(e.target);
305
+ /*
306
+ * NO ELEMENT UNDER THE POINTER — keep the browser's menu.
307
+ *
308
+ * This called `preventDefault()` before looking, "legacy parity" with a handler that did the
309
+ * same. The parent then posts `path: null`, `showContextMenu` returns early on it, and the
310
+ * result is a right-click that suppresses the browser menu and shows nothing in its place:
311
+ * plan §10's dead zone, named there as the thing to fix. The margin around the artboard is
312
+ * exactly where a reader reaches for View Source or Inspect.
313
+ */
314
+ if (!hit) {
315
+ return;
316
+ }
317
+ // An element IS under the pointer, so the Jx element menu is the right answer and the browser's
318
+ // Would be a worse one.
319
+ e.preventDefault();
262
320
  channel.post({
263
321
  kind: "contextMenu",
264
- path: hit ? hit.path : null,
322
+ path: hit.path,
265
323
  x: me.clientX,
266
324
  y: me.clientY,
267
325
  });
268
326
  };
269
327
 
328
+ doc.addEventListener("pointerdown", onPointerDown, true);
270
329
  doc.addEventListener("click", onClick, true);
271
330
  doc.addEventListener("pointermove", onMove, true);
272
331
  doc.addEventListener("pointerleave", onLeave, true);
@@ -277,6 +336,7 @@ export function startInteraction(
277
336
  cancelRaf(movePending);
278
337
  movePending = 0;
279
338
  }
339
+ doc.removeEventListener("pointerdown", onPointerDown, true);
280
340
  doc.removeEventListener("click", onClick, true);
281
341
  doc.removeEventListener("pointermove", onMove, true);
282
342
  doc.removeEventListener("pointerleave", onLeave, true);
@@ -1,66 +1,121 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * In-iframe keyboard forwarding. Studio's shortcuts are bound to the EDITOR document, so once focus
4
- * moves into the canvas iframe (a click selects a node there) undo/redo/save/delete/duplicate/arrow
5
- * navigation stop firing. This captures the global-shortcut subset inside the iframe, prevents the
6
- * browser default, and posts a flattened event the parent re-dispatches to its shortcut handler.
3
+ * In-iframe keyboard forwarding — one authority, synced from the host's keymap.
7
4
  *
8
- * The subset mirrors `editor/shortcuts.ts`: any Ctrl/Cmd combo, plus the bare editing/navigation
9
- * keys. Keystrokes while an editable element (input / contenteditable — e.g. a future inline edit)
10
- * is focused inside the iframe are left alone so typing still works.
5
+ * Studio's chords are dispatched against the EDITOR document, so once focus moves into the canvas
6
+ * iframe (a click selects a node there) nothing the parent binds fires. This captures the
7
+ * keystrokes the parent actually wants, prevents their default, and posts a flattened event the
8
+ * host re-dispatches into its own registry.
9
+ *
10
+ * **Which keystrokes those are is no longer written down here.** It used to be, in three
11
+ * hand-maintained lists — eight bare keys, four "the editor owns these" chords, three "the browser
12
+ * owns these" chords — kept beside a registry that already knew the answer, and they disagreed with
13
+ * it in both directions:
14
+ *
15
+ * - **⌘A was forwarded AND prevented.** No record binds it, so the host claimed nothing and select-
16
+ * all did nothing — while the `preventDefault` had already suppressed the browser's own, so a
17
+ * writer mid-sentence could not select their paragraph either. Two ways of doing nothing.
18
+ * - **⌘B was withheld**, on the list's stated assumption that "the editing engine implements it
19
+ * itself". It does not: `editor/inline-edit.ts`'s keydown listener is attached to the BLOCK while
20
+ * the editing host is the canvas container, so it never fires, and `canvas/editable-actions.ts`
21
+ * rejects the browser's native `formatBold` because Jx owns its own markup. Bold in the canvas
22
+ * did nothing at all, under a toolbar advertising ⌘B.
23
+ *
24
+ * So the frame is told the table instead ({@link ParentToIframe} `keymap`), and answers the
25
+ * question by RESOLVING: pick the scope stack the host would pick, and forward iff some scope in it
26
+ * claims the chord. Everything the three lists encoded falls out of that, without being encoded:
27
+ *
28
+ * - The clipboard trio is bound at `canvas` scope only (`editor/context-menu.ts`), so with a caret
29
+ * session live the stack is `["caret", "global"]`, nothing claims ⌘C/⌘X/⌘V, and the browser's own
30
+ * copy of the selected TEXT happens — the old `CLIPBOARD_CHORDS` contract, derived rather than
31
+ * listed.
32
+ * - The bare editing keys (Backspace, Enter, the arrows) are `canvas`-scoped for the same reason and
33
+ * behave the same way.
34
+ * - `format.*` is `caret`-scoped, so ⌘B forwards exactly while a caret exists and the host posts
35
+ * `applyFormat` back — the path the toolbar's own buttons have always taken.
36
+ *
37
+ * The remaining local rule is a real one: a genuine `INPUT` / `TEXTAREA` / `SELECT` inside the
38
+ * rendered page (an author's own form) keeps every keystroke, because Backspace and Enter are
39
+ * canvas-bound and a resolved table would happily prevent them mid-field.
11
40
  */
12
41
 
13
- import type { IframeChannel } from "./iframe-channel";
14
- import type { IframeToParent, ParentToIframe, SerializedKey } from "./iframe-protocol";
42
+ import { chordFromEvent } from "../commands/keymap";
15
43
 
16
- /** Bare (non-modifier) keys the editor's shortcut handler acts on. */
17
- const BARE_FORWARD_KEYS: ReadonlySet<string> = new Set([
18
- "Delete",
19
- "Backspace",
20
- "Escape",
21
- "Enter",
22
- "ArrowUp",
23
- "ArrowDown",
24
- "ArrowLeft",
25
- "ArrowRight",
26
- ]);
44
+ import type { KeyScope } from "../commands/levels";
45
+
46
+ import type { IframeChannel } from "./iframe-channel";
47
+ import type { IframeToParent, ParentToIframe, SerializedKey, SyncedChord } from "./iframe-protocol";
27
48
 
28
49
  /**
29
- * Chords the EDITOR owns even while the caret is in text. Everything else with a modifier is a
30
- * document-level command (save, undo, duplicate, zoom) and must reach the parent.
50
+ * The three scopes a canvas frame can ever be dispatching under.
51
+ *
52
+ * The host projects exactly these out of the keymap ({@link import("../commands/keymap").
53
+ * chordsInScopes}) — narrower than "every scope", because the grid and code engines, the focused
54
+ * dock and the palette are other surfaces entirely and their chords must not make a canvas swallow
55
+ * a keystroke.
31
56
  */
32
- const EDITOR_OWNED_CHORDS: ReadonlySet<string> = new Set(["b", "i", "u", "`"]);
57
+ export const FRAME_KEY_SCOPES = ["caret", "canvas", "global"] as const;
58
+
59
+ /** The scope stack a caret session is in — app chords still fire, element-level ones do not. */
60
+ const CARET_STACK = ["caret", "global"] as const;
61
+ /** The stack with a selection but no caret: structural verbs are live. */
62
+ const CANVAS_STACK = ["canvas", "global"] as const;
63
+ /** Preview draws no overlays and posts no hits, so element-level chords have nothing to aim at. */
64
+ const GLOBAL_STACK = ["global"] as const;
65
+
66
+ /** The synced table, plus the platform the chords were computed for. */
67
+ export interface ForwardTable {
68
+ mac: boolean;
69
+ chords: readonly SyncedChord[];
70
+ }
33
71
 
34
- /** Whether `el` is a text-entry target whose own keystrokes must not be hijacked. */
35
- function isEditableTarget(el: EventTarget | null): boolean {
72
+ /** An empty table — what the frame knows before the host's first `keymap` message arrives. */
73
+ export const NO_TABLE: ForwardTable = { chords: [], mac: false };
74
+
75
+ /** Whether `el` is a text-entry control in the RENDERED PAGE whose keystrokes are its own. */
76
+ function isPageTextEntry(el: EventTarget | null): boolean {
36
77
  if (!(el instanceof HTMLElement)) {
37
78
  return false;
38
79
  }
39
- if (el.isContentEditable) {
40
- return true;
41
- }
42
80
  return el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT";
43
81
  }
44
82
 
45
83
  /**
46
- * Whether this keydown belongs to the global-shortcut subset the parent handles.
84
+ * The scope stack this keystroke would be dispatched under, host-side.
47
85
  *
48
- * The canvas is now a persistent `contenteditable` root, so "is the target editable" is true for
49
- * essentially every keystroke on the canvas — using it alone (as this once did) would mean ⌘S, ⌘Z
50
- * and ⌘C never reached the parent at all while the caret was in a document. The split is instead by
51
- * INTENT:
86
+ * The same ladder as `commands/context.ts`'s `keyScopeStack`, minus the branches the frame cannot
87
+ * be in: there is no modal and no focused dock inside the canvas, and the grid and code engines are
88
+ * other editors entirely. Keeping the two in step is what makes the forwarding decision and the
89
+ * dispatch decision the same decision.
90
+ */
91
+ export function frameScopeStack(sessionLive: boolean, mode: string): readonly KeyScope[] {
92
+ if (sessionLive) {
93
+ return CARET_STACK;
94
+ }
95
+ return mode === "preview" ? GLOBAL_STACK : CANVAS_STACK;
96
+ }
97
+
98
+ /**
99
+ * Whether the parent's registry claims this keystroke.
52
100
  *
53
- * - Bare editing/navigation keys (Backspace, Enter, the arrows) belong to the caret when there is
54
- * one, and to structural selection when there is not.
55
- * - Modifier chords are app commands and always forward, EXCEPT the inline-formatting ones the
56
- * editing engine implements itself.
101
+ * `""` from {@link chordFromEvent} means a bare modifier — the start of every chord — and is never
102
+ * forwarded.
57
103
  */
58
- export function shouldForwardKey(e: KeyboardEvent): boolean {
59
- const editable = isEditableTarget(e.target);
60
- if (e.ctrlKey || e.metaKey) {
61
- return !(editable && EDITOR_OWNED_CHORDS.has(e.key.toLowerCase()));
104
+ export function shouldForwardKey(
105
+ e: KeyboardEvent,
106
+ table: ForwardTable = NO_TABLE,
107
+ sessionLive = false,
108
+ mode = "design",
109
+ ): boolean {
110
+ if (isPageTextEntry(e.target)) {
111
+ return false;
62
112
  }
63
- return !editable && BARE_FORWARD_KEYS.has(e.key);
113
+ const chord = chordFromEvent(e, table.mac);
114
+ if (!chord) {
115
+ return false;
116
+ }
117
+ const stack = frameScopeStack(sessionLive, mode);
118
+ return table.chords.some((entry) => entry.chord === chord && stack.includes(entry.scope));
64
119
  }
65
120
 
66
121
  /** Flatten a KeyboardEvent to the structured-cloneable subset the bridge carries. */
@@ -76,15 +131,22 @@ export function serializeKey(e: KeyboardEvent): SerializedKey {
76
131
  }
77
132
 
78
133
  /**
79
- * Listen for global-shortcut keystrokes on the iframe document, prevent their default, and forward
80
- * them to the parent. Returns a teardown function.
134
+ * Listen for the parent's chords on the iframe document, prevent their default, and forward them.
135
+ * Returns a teardown function.
136
+ *
137
+ * All three accessors are read PER KEYSTROKE, never captured: the table is replaced whenever the
138
+ * author rebinds a key, the session comes and goes under the same listener, and the mode changes
139
+ * without one.
81
140
  */
82
141
  export function startKeyForwarding(
83
142
  channel: IframeChannel<IframeToParent, ParentToIframe>,
84
143
  doc: Document = document,
144
+ isSessionLive: () => boolean = () => false,
145
+ getTable: () => ForwardTable = () => NO_TABLE,
146
+ getMode: () => string = () => "design",
85
147
  ): () => void {
86
148
  const onKey = (e: KeyboardEvent) => {
87
- if (!shouldForwardKey(e)) {
149
+ if (!shouldForwardKey(e, getTable(), isSessionLive(), getMode())) {
88
150
  return;
89
151
  }
90
152
  e.preventDefault();
@@ -70,6 +70,12 @@ export interface OverlayLayer {
70
70
  * each call; pass [] to clear. Placements are overlay-local coords like setSelection's.
71
71
  */
72
72
  setPresence: (items: { placement: OverlayPlacement; color: string; label: string }[]) => void;
73
+ /**
74
+ * Draw the NON-primary members of a multi-selection (§6.5). Replaces the whole set each call;
75
+ * pass [] to clear, which is what a selection of one always does — one selected node draws the
76
+ * selection box and nothing else, exactly as it did before the selection became a list.
77
+ */
78
+ setCoSelection: (placements: OverlayPlacement[]) => void;
73
79
  /** Position the hover box (or hide it when `placement` is null). */
74
80
  setHover: (placement: OverlayPlacement | null) => void;
75
81
  /**
@@ -97,6 +103,13 @@ export interface OverlayLayer {
97
103
  * shows exactly one of them. `placement` is overlay-local like the others; null hides it.
98
104
  */
99
105
  setReplaceTarget: (placement: OverlayPlacement | null) => void;
106
+ /**
107
+ * Hide (or restore) the whole layer. Preview renders get it suppressed: the editing affordances
108
+ * are gated message-by-message in the host, and this is the single switch that guarantees none of
109
+ * them can be PAINTED over a page that is supposed to look shipped — including any box a
110
+ * still-in-flight reply from a pre-preview render would otherwise land.
111
+ */
112
+ setSuppressed: (suppressed: boolean) => void;
100
113
  /** The clickable "+" button, so the caller can attach click + hover (grace-timer) listeners. */
101
114
  readonly insertButton: HTMLButtonElement;
102
115
  /** The layer root element (caller appends it over the iframe). */
@@ -164,7 +177,19 @@ export function createOverlayLayer(doc: Document = document): OverlayLayer {
164
177
  const presenceGroup = doc.createElement("div");
165
178
  presenceGroup.className = "overlay-presence-group";
166
179
 
167
- root.append(hoverBox, selectionBox, dropBox, replaceBox, insertButton, presenceGroup);
180
+ // The other members of a multi-selection, same container discipline as presence.
181
+ const coSelectionGroup = doc.createElement("div");
182
+ coSelectionGroup.className = "overlay-coselection-group";
183
+
184
+ root.append(
185
+ hoverBox,
186
+ selectionBox,
187
+ coSelectionGroup,
188
+ dropBox,
189
+ replaceBox,
190
+ insertButton,
191
+ presenceGroup,
192
+ );
168
193
 
169
194
  return {
170
195
  dispose: () => root.remove(),
@@ -174,6 +199,18 @@ export function createOverlayLayer(doc: Document = document): OverlayLayer {
174
199
  setHover: (placement) => place(hoverBox, placement),
175
200
  setInsertZone: (placement, edge = "center") => placeInsertButton(insertButton, placement, edge),
176
201
  setReplaceTarget: (placement) => place(replaceBox, placement),
202
+ setSuppressed: (suppressed) => {
203
+ root.style.display = suppressed ? "none" : "";
204
+ },
205
+ setCoSelection: (placements) => {
206
+ coSelectionGroup.replaceChildren();
207
+ for (const placement of placements) {
208
+ const box = doc.createElement("div");
209
+ box.className = "overlay-box overlay-selection overlay-coselection";
210
+ place(box, placement);
211
+ coSelectionGroup.append(box);
212
+ }
213
+ },
177
214
  setPresence: (items) => {
178
215
  presenceGroup.replaceChildren();
179
216
  for (const item of items) {