@jxsuite/studio 2.0.0 → 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-0hzentwq.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-0hzentwq.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-2c188n41.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-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-97qcgvdn.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-hhj92gcr.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-ygzav7z5.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-n9zmpxjg.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 +23711 -35185
  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 +34 -9
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +270 -262
  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 -120
  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 +75 -2
  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 +113 -0
  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-qct0gt8f.js +0 -531
  363. package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
  364. package/dist/chunks/files-02zn5y24.js +0 -50
  365. package/dist/chunks/files-02zn5y24.js.map +0 -9
  366. package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
  367. package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
  368. package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
  369. package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
  370. package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
  371. package/dist/chunks/settings-modal-gj0hrah4.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-4k0m4fmp.js.map +0 -13
  381. package/dist/chunks/studio-568nx4t5.js +0 -469
  382. package/dist/chunks/studio-568nx4t5.js.map +0 -14
  383. package/dist/chunks/studio-9smfzqhc.js +0 -19
  384. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  385. package/dist/chunks/studio-asvf0j9e.js +0 -429
  386. package/dist/chunks/studio-asvf0j9e.js.map +0 -13
  387. package/dist/chunks/studio-d77qm3pk.js +0 -3427
  388. package/dist/chunks/studio-d77qm3pk.js.map +0 -26
  389. package/dist/chunks/studio-j6gqyjf4.js +0 -1854
  390. package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
  391. package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-rq0vxb19.js +0 -3744
  394. package/dist/chunks/studio-rq0vxb19.js.map +0 -22
  395. package/dist/chunks/studio-wd6ewdvd.js +0 -853
  396. package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
  397. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  398. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  399. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  400. package/dist/chunks/studio-ygzav7z5.js.map +0 -46
  401. package/dist/chunks/studio-zmy7241e.js +0 -399
  402. package/dist/chunks/studio-zmy7241e.js.map +0 -12
  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-2c188n41.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-hhj92gcr.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
@@ -0,0 +1,282 @@
1
+ /**
2
+ * The content-entry verbs: create one, open one as a form, mark one a draft, and choose whether
3
+ * drafts are listed at all.
4
+ *
5
+ * Each is a record in its owning module, so the palette row, the assistant's tool, the automation
6
+ * step and (for the two that declare one) the context-menu item are renderings of the same
7
+ * declaration. Two of them are **setters and not toggles** — `content.setDraft {draft}` and
8
+ * `content.setIncludeDrafts {include}` name the state they reach, because "toggle the draft flag"
9
+ * run twice by a script leaves the entry exactly where it started and run once against an unknown
10
+ * default publishes something private.
11
+ */
12
+
13
+ import {
14
+ argsSchema,
15
+ booleanArg,
16
+ booleanProperty,
17
+ enumArg,
18
+ stringArg,
19
+ stringProperty,
20
+ } from "../commands/command-args";
21
+ import {
22
+ defaultContentFormat,
23
+ formatByName,
24
+ formatForPath,
25
+ formatSerialize,
26
+ loadFormats,
27
+ } from "../format/format-host";
28
+ import { notify } from "../services/notify";
29
+ import { activeTab } from "../workspace/workspace";
30
+ import { invalidateReferenceEntries } from "../ui/form-controls";
31
+ import { reloadDraftAwareGrids } from "../grid/sources/content-source";
32
+ import { setIncludeDrafts } from "./draft-state";
33
+ import { collectionOfPath, entryCollection, entryCollections, seedEntry } from "./entry-model";
34
+ import { openEntryEditor, setEntryDraft } from "./entry-editor";
35
+ import { errorMessage } from "@jxsuite/schema/parse";
36
+ import type { EntryCollection } from "./entry-model";
37
+ import type { AnyCommand, CommandRegistry } from "../commands/registry";
38
+ import type { Tab } from "../tabs/tab";
39
+
40
+ /** Collection names a New Entry can be created in — the `enum` behind `content.newEntry`. */
41
+ function creatableCollections(): string[] {
42
+ return entryCollections().map((collection) => collection.name);
43
+ }
44
+
45
+ /**
46
+ * An enum property whose values are derived WHEN THE SCHEMA IS READ, not when the record is
47
+ * defined.
48
+ *
49
+ * `enumProperty(creatableCollections(), …)` looks identical and is wrong here, and the difference
50
+ * is invisible in a unit test: a command record is built at module scope —
51
+ * `commands/app-commands.ts` and `studio.ts` both call {@link contentCommands} before a project is
52
+ * open — so the array is frozen at `[]`, and `panels/quick-search.ts`'s `paletteArgs` offered an
53
+ * empty choice list forever after. `run` re-derived the list, which is why the programmatic call
54
+ * worked and the palette did not: two answers to "which collections exist", one of them a snapshot
55
+ * of the empty boot state.
56
+ *
57
+ * A getter is the whole fix — the palette reads `property.enum` when it opens the prompt, the AI
58
+ * tool's parameters serialise it when the tool list is built, and `scripts/check-shot-contract.ts`
59
+ * reads it in a bare Bun process where the honest answer really is "none declared".
60
+ */
61
+ function derivedEnumProperty(declared: () => readonly string[], description: string): object {
62
+ return {
63
+ description,
64
+ get enum() {
65
+ return [...declared()];
66
+ },
67
+ type: "string",
68
+ };
69
+ }
70
+
71
+ /**
72
+ * The source text a new entry starts as.
73
+ *
74
+ * A `json` collection is serialized here rather than through the format registry, because JSON is
75
+ * the one native collection shape (site-architecture.md §6.2) and has no format class to ask.
76
+ *
77
+ * `collectionInfo` derives `.json` exactly when no format class claimed the collection — the
78
+ * extension and the serializer name come off the same three lookups in the same order — so "the
79
+ * extension is .json" and "no format can write this" are one condition, and the second is not a
80
+ * separate failure to report. Writing JSON into a `.md` file is therefore not a state this can
81
+ * reach.
82
+ */
83
+ async function seedText(collection: EntryCollection): Promise<string> {
84
+ const seed = seedEntry(collection.schema);
85
+ const formatName =
86
+ (collection.def.format ? formatByName(collection.def.format)?.name : undefined) ??
87
+ formatForPath(`untitled${collection.ext}`)?.name ??
88
+ defaultContentFormat()?.name;
89
+ if (collection.ext === ".json" || formatName === undefined) {
90
+ return JSON.stringify(seed, null, 2);
91
+ }
92
+ return formatSerialize(
93
+ formatName,
94
+ { ...seed, children: [] },
95
+ { frontmatter: Object.keys(seed).length > 0, mode: "roundtrip" },
96
+ );
97
+ }
98
+
99
+ /**
100
+ * Create an entry in a collection and open it in its form editor.
101
+ *
102
+ * The file goes through `files/files.ts`'s single creation flow — the same name field, the same
103
+ * "already exists" refusal, the same Problem on failure as the Files tree and the Library — with
104
+ * two things this caller supplies that a generic "New File" cannot: the collection's **extension**,
105
+ * so the field asks for a display name and the file is actually matched by the collection it was
106
+ * created in, and a **body seeded from the schema**, so the entry is valid the moment it exists
107
+ * instead of being a pile of absent required fields.
108
+ *
109
+ * @returns The created path, or null when the author cancelled or the write failed.
110
+ */
111
+ export async function createEntry(collectionName: string): Promise<string | null> {
112
+ const collection = entryCollection(collectionName);
113
+ if (!collection) {
114
+ notify.error(`No content collection named "${collectionName}" to create an entry in.`, {
115
+ detail:
116
+ "A collection needs a `content` entry in project.json whose `source` is a directory. A " +
117
+ "collection whose source names a single file (a CSV catalogue) has rows, not entry files.",
118
+ source: "Content",
119
+ });
120
+ return null;
121
+ }
122
+ await loadFormats().catch(() => {
123
+ /* An unreachable format registry is not a reason to refuse: `seedText` falls back to the
124
+ native JSON shape, and the serializer's own failure is reported below with the path. */
125
+ });
126
+ let content: string;
127
+ try {
128
+ content = await seedText(collection);
129
+ } catch (error) {
130
+ notify.error(`Could not prepare a new ${collection.name} entry.`, {
131
+ detail: errorMessage(error),
132
+ path: collection.dir,
133
+ source: "Content",
134
+ });
135
+ return null;
136
+ }
137
+
138
+ const { createFileIn } = await import("../files/files");
139
+ const created = await createFileIn({
140
+ content,
141
+ dir: collection.dir,
142
+ ext: collection.ext,
143
+ source: "Content",
144
+ suggestedName: "untitled",
145
+ title: `New ${collection.name} entry`,
146
+ });
147
+ if (created === null) {
148
+ return null;
149
+ }
150
+ // The picker's choices just changed; a cached list would keep offering the old set.
151
+ invalidateReferenceEntries(collection.name);
152
+ await openEntryEditor(created);
153
+ return created;
154
+ }
155
+
156
+ /** The active tab, when it is a content entry — the subject of the document-level verbs. */
157
+ function activeEntryTab(): Tab | null {
158
+ const tab = activeTab.value as Tab | null;
159
+ return tab && collectionOfPath(tab.documentPath) ? tab : null;
160
+ }
161
+
162
+ /** The content-entry command records. */
163
+ export function contentCommands(): AnyCommand[] {
164
+ return [
165
+ {
166
+ args: argsSchema({
167
+ collection: derivedEnumProperty(
168
+ creatableCollections,
169
+ "The content collection the entry is created in.",
170
+ ),
171
+ }),
172
+ category: "File",
173
+ id: "content.newEntry",
174
+ level: "project",
175
+ menus: ["palette"],
176
+ group: "1_file",
177
+ requires: "a project with a directory-backed content collection",
178
+ when: (ctx) => ctx.project.open,
179
+ enablement: () => creatableCollections().length > 0,
180
+ undo: "project",
181
+ aiTool: {
182
+ description:
183
+ "Create a content entry in one of the project's collections, seeded from that " +
184
+ "collection's schema defaults, and open it in the entry form.",
185
+ name: "new_content_entry",
186
+ },
187
+ run: async (_ctx, args) => {
188
+ const names = creatableCollections();
189
+ await createEntry(enumArg("content.newEntry", args, "collection", names));
190
+ },
191
+ title: "New Entry",
192
+ },
193
+ {
194
+ args: argsSchema({
195
+ path: stringProperty("Project-relative path of the entry file to open."),
196
+ }),
197
+ category: "File",
198
+ id: "content.openEntry",
199
+ level: "project",
200
+ menus: ["context/file", "palette"],
201
+ group: "1_file",
202
+ requires: "an open project",
203
+ when: (ctx) => ctx.project.open,
204
+ aiTool: {
205
+ description:
206
+ "Open a content entry as a schema-driven form — its collection's fields, with a picker " +
207
+ "for every reference to another collection.",
208
+ name: "open_entry_form",
209
+ },
210
+ run: async (_ctx, args) => {
211
+ await openEntryEditor(stringArg("content.openEntry", args, "path"));
212
+ },
213
+ title: "Open Entry Form",
214
+ },
215
+ {
216
+ args: argsSchema({
217
+ draft: booleanProperty(
218
+ "True marks the open entry a draft; false marks it published. Written as a `draft` " +
219
+ "field on the entry.",
220
+ ),
221
+ }),
222
+ category: "Document",
223
+ id: "content.setDraft",
224
+ level: "document",
225
+ menus: ["context/tab", "palette"],
226
+ group: "3_document",
227
+ requires: "a content entry open",
228
+ when: (ctx) => ctx.document.open,
229
+ enablement: () => activeEntryTab() !== null,
230
+ undo: "document",
231
+ aiTool: {
232
+ description:
233
+ "Mark the open content entry a draft, or published. Studio filters drafts out of its " +
234
+ "own lists; the build does not exclude them yet.",
235
+ name: "set_entry_draft",
236
+ },
237
+ run: (_ctx, args) => {
238
+ const tab = activeEntryTab();
239
+ if (!tab) {
240
+ throw new Error("content.setDraft: the active document is not a content entry");
241
+ }
242
+ setEntryDraft(tab, booleanArg("content.setDraft", args, "draft"));
243
+ },
244
+ title: "Set Draft",
245
+ },
246
+ {
247
+ args: argsSchema({
248
+ include: booleanProperty("True lists entries marked `draft: true` alongside the rest."),
249
+ }),
250
+ category: "View",
251
+ id: "content.setIncludeDrafts",
252
+ level: "project",
253
+ menus: ["palette"],
254
+ group: "2_view",
255
+ requires: "an open project",
256
+ when: (ctx) => ctx.project.open,
257
+ aiTool: {
258
+ description: "Choose whether draft entries appear in Studio's content listings.",
259
+ name: "set_including_drafts",
260
+ },
261
+ /*
262
+ * Set the flag, then repaint what reads it.
263
+ *
264
+ * The second half is the wiring, and without it this command sets a value nobody looks at
265
+ * again: a collection grid holds the rows it loaded once, so an author who asks to see their
266
+ * drafts watches an unchanged table. `draft-state.ts` cannot do it — it is imported BY the
267
+ * listings and reaching back into the workspace from there is the cycle. The command can,
268
+ * because a command is exactly the place where "the state changed" becomes "the app changed".
269
+ */
270
+ run: async (_ctx, args) => {
271
+ setIncludeDrafts(booleanArg("content.setIncludeDrafts", args, "include"));
272
+ await reloadDraftAwareGrids();
273
+ },
274
+ title: "Include Drafts",
275
+ },
276
+ ];
277
+ }
278
+
279
+ /** Register the content-entry verbs. */
280
+ export function registerContentCommands(registry: CommandRegistry): void {
281
+ registry.registerAll(contentCommands());
282
+ }
@@ -0,0 +1,327 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * The **Entry editor** — a content entry's fields, as a form, in a pane (§7.4, plan §9.2).
4
+ *
5
+ * Until now a markdown entry could be edited two ways and neither was its schema: the canvas edited
6
+ * its BODY, and the Document Header card drew a field list from `panels/frontmatter-fields.ts`'s
7
+ * own type→widget ladder, which is a second implementation of `ui/schema-form.ts` with no reference
8
+ * branch, no validation and no value-source ladder. A JSON entry (`authors/ada.json`) had no field
9
+ * editor at all — it opened as a component tree, so `bio` was a node's property and `links` was a
10
+ * child array.
11
+ *
12
+ * This is one editor over `renderForm`, so every improvement to the engine reaches content entries,
13
+ * settings and the inspector at once — and the `reference` control in particular arrives here for
14
+ * free, which is what makes `author: { "$ref": "#/content/authors" }` a picker instead of a text
15
+ * field you have to type an id into.
16
+ *
17
+ * **It is an editor of the same tab, not a second document.** Like `settings/settings-document.ts`,
18
+ * it unshifts its mode onto the tab the file already has, so ⌘S, the dirty flag, the transaction
19
+ * log and undo are the ordinary document verbs. Every field commits through
20
+ * {@link mutateEntryField}, so an edit made here is undone by ⌘Z and replayed by a collaborator
21
+ * exactly as one made in the Document Header card.
22
+ *
23
+ * **One editor, two storage shapes.** A markdown entry keeps its fields in frontmatter; a JSON
24
+ * entry IS its fields. `content/entry-fields.ts` owns that single question, and this file never
25
+ * touches `content.frontmatter` directly — reading it unconditionally is what made a JSON entry's
26
+ * form blank, its valid required fields look absent, and every edit vanish at save time.
27
+ *
28
+ * **The mode is registered in all three tables.** `commands/context.ts` holds the one map from mode
29
+ * to editor kind; a mode missing from it does not fail, it silently answers `"canvas"`, and that is
30
+ * how ⌘V once inserted an element node into `project.json`. `entry` → `entry` → "Entry" is added to
31
+ * `EDITOR_KIND_BY_MODE`, `EditorKind` and `EDITOR_KIND_LABELS` in the same change as this file.
32
+ *
33
+ * @docs studio/projects/content-types
34
+ */
35
+
36
+ import { html, nothing, render as litRender } from "lit-html";
37
+ import { activeRegistry } from "../commands/active-registry";
38
+ import { effect, effectScope } from "../reactivity";
39
+ import { projectState } from "../store";
40
+ import { resolveContextPointer } from "../services/context-resolver";
41
+ import { transactDoc } from "../tabs/transact";
42
+ import { renderForm } from "../ui/schema-form";
43
+ import { paneRegion } from "../ui/regions";
44
+ import { activateTab, workspace } from "../workspace/workspace";
45
+ import { commitEntryFields, entryFields, mutateEntryField } from "./entry-fields";
46
+ import { DRAFT_FIELD, DRAFT_MEANING, hasDraftAxis, isDraftEntry } from "./draft-state";
47
+ import { collectionOfPath, missingRequired } from "./entry-model";
48
+ import type { EntryCollection } from "./entry-model";
49
+ import type { JsonSchema, SchemaFormContext } from "../ui/schema-form";
50
+ import type { Tab } from "../tabs/tab";
51
+ import type { CanvasSurface } from "../canvas/canvas-surface";
52
+ import type { JsonValue } from "../types";
53
+
54
+ /** The `canvasMode` the entry form draws under. */
55
+ export const ENTRY_MODE = "entry";
56
+
57
+ /** What an entry tab's mode list becomes when the Entry editor opens it: the form leads. */
58
+ const ENTRY_TAB_MODES = [ENTRY_MODE];
59
+
60
+ // ─── Mounting ────────────────────────────────────────────────────────────────
61
+
62
+ interface ActiveEntryPane {
63
+ /** The pane whose stage this form is drawn on. */
64
+ paneId: string;
65
+ tabId: string;
66
+ wrap: HTMLElement;
67
+ scope: { stop: () => void; run: <T>(fn: () => T) => T | undefined };
68
+ }
69
+
70
+ /**
71
+ * The entry form mounted in each pane, keyed by pane id.
72
+ *
73
+ * `entry` is one of the kinds the side pane may host, so this singleton was reachable from two
74
+ * panes at once the day the grid drew a second cell: pane B's mount stopped pane A's effect scope,
75
+ * leaving a form on screen that no longer tracked its own frontmatter.
76
+ */
77
+ const _active = new Map<string, ActiveEntryPane>();
78
+
79
+ /** The form mounted in a pane, or null. */
80
+ function activeIn(paneId: string): ActiveEntryPane | null {
81
+ return _active.get(paneId) ?? null;
82
+ }
83
+
84
+ /** Whether this tab's entry form is already mounted in this pane and still in the document. */
85
+ export function entryPaneMounted(paneId: string, tab: Tab): boolean {
86
+ const panel = activeIn(paneId);
87
+ return panel !== null && panel.tabId === tab.id && panel.wrap.isConnected;
88
+ }
89
+
90
+ /** Tear one pane's entry form down (mode change, tab switch, project close). Idempotent. */
91
+ export function detachEntryPane(paneId: string): void {
92
+ const panel = _active.get(paneId);
93
+ if (!panel) {
94
+ return;
95
+ }
96
+ panel.scope.stop();
97
+ _active.delete(paneId);
98
+ }
99
+
100
+ // ─── Drafts ──────────────────────────────────────────────────────────────────
101
+
102
+ /**
103
+ * Set this entry's draft flag. A setter, never a toggle — the pill, the command and the assistant
104
+ * all reach the same state whatever it was before.
105
+ *
106
+ * `draft: false` is written rather than removed. The absence of the key and the value `false` mean
107
+ * the same thing to a reader, but only one of them survives a glance at the file, and an author who
108
+ * has explicitly published something should be able to see that they did.
109
+ */
110
+ export function setEntryDraft(tab: Tab, draft: boolean): void {
111
+ transactDoc(tab, (t) => mutateEntryField(t, DRAFT_FIELD, draft as JsonValue));
112
+ }
113
+
114
+ /**
115
+ * The draft pill for a tab, or `nothing`.
116
+ *
117
+ * Drawn on the pane's tab chip (`panels/tab-strip.ts`) rather than only inside the editor, because
118
+ * the failure this exists to prevent is publishing something you thought was private — and that
119
+ * mistake is made while looking at a tab, not while looking at a form. It renders for any entry
120
+ * whose collection declares the field, so "Published" is as visible as "Draft"; a collection with
121
+ * no draft workflow shows neither.
122
+ */
123
+ export function entryDraftPill(tab: Tab) {
124
+ const collection = collectionOfPath(tab.documentPath);
125
+ const fields = entryFields(tab);
126
+ if (!collection || !hasDraftAxis(collection.schema, fields)) {
127
+ return nothing;
128
+ }
129
+ const draft = isDraftEntry(fields);
130
+ return html`<span
131
+ class=${draft ? "entry-pill entry-pill--draft" : "entry-pill"}
132
+ title=${draft ? DRAFT_MEANING : "Not marked a draft."}
133
+ >${draft ? "Draft" : "Published"}</span
134
+ >`;
135
+ }
136
+
137
+ // ─── The form ────────────────────────────────────────────────────────────────
138
+
139
+ /** The form's host context: enum pointers resolve against this project's configuration. */
140
+ function formContext(tab: Tab): SchemaFormContext {
141
+ return {
142
+ fieldKeyPrefix: `entry:${tab.id}`,
143
+ resolvePointer: (pointer, scope) =>
144
+ resolveContextPointer(pointer, {
145
+ projectConfig: (projectState?.projectConfig ?? {}) as Record<string, unknown>,
146
+ scope,
147
+ }),
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Required fields this entry does not have.
153
+ *
154
+ * Deliberately keyed on ABSENCE, not emptiness. A seeded entry's `title: ""` is present and the
155
+ * author has not done anything wrong yet, so §7.1 says not to paint it red; a required key that is
156
+ * missing from the file is a fact about the document, and saying so is the whole reason the form
157
+ * knows the schema.
158
+ */
159
+ function absentRequiredErrors(
160
+ collection: EntryCollection,
161
+ fields: Record<string, unknown>,
162
+ ): Record<string, string> {
163
+ const errors: Record<string, string> = {};
164
+ for (const field of missingRequired(collection.schema, fields)) {
165
+ errors[field] = "Required — this entry does not have one.";
166
+ }
167
+ return errors;
168
+ }
169
+
170
+ /** The header: what this is, and whether it is a draft. */
171
+ function headerTpl(tab: Tab, collection: EntryCollection) {
172
+ const fields = entryFields(tab);
173
+ const draftAxis = hasDraftAxis(collection.schema, fields);
174
+ const draft = isDraftEntry(fields);
175
+ return html`
176
+ <div class="entry-editor-header">
177
+ <h3>${tab.documentPath?.split("/").pop() ?? "Untitled"}</h3>
178
+ <span class="entry-editor-collection">${collection.name}</span>
179
+ ${
180
+ draftAxis
181
+ ? html`
182
+ <sp-switch
183
+ size="s"
184
+ class="entry-draft-switch"
185
+ ?checked=${draft}
186
+ title=${DRAFT_MEANING}
187
+ @change=${(e: Event) => setEntryDraft(tab, (e.target as HTMLInputElement).checked)}
188
+ >Draft</sp-switch
189
+ >
190
+ `
191
+ : nothing
192
+ }
193
+ </div>
194
+ ${draft ? html`<p class="entry-editor-note">${DRAFT_MEANING}</p>` : nothing}
195
+ `;
196
+ }
197
+
198
+ /**
199
+ * What the editor says when the document is not an entry.
200
+ *
201
+ * The mode is reachable from the palette and from `__jxAutomation` on any tab, so this state is
202
+ * real and must name the reason and the fix rather than drawing an empty form — "no fields" and
203
+ * "this file belongs to no collection" are different sentences, and only one of them is true here.
204
+ */
205
+ function notAnEntryTpl(tab: Tab) {
206
+ return html`
207
+ <div class="entry-editor-empty">
208
+ <p>
209
+ ${tab.documentPath ?? "This document"} is not an entry of any content collection, so there
210
+ is no schema to draw a form from.
211
+ </p>
212
+ <p class="entry-editor-note">
213
+ A collection is a <code>content</code> entry in <code>project.json</code> whose
214
+ <code>source</code> directory contains this file and which declares a <code>schema</code>.
215
+ </p>
216
+ <sp-action-button
217
+ size="s"
218
+ @click=${() => {
219
+ void activeRegistry()?.run("settings.open", { section: "content" });
220
+ }}
221
+ >Content types…</sp-action-button
222
+ >
223
+ </div>
224
+ `;
225
+ }
226
+
227
+ /**
228
+ * Mount the entry form into the pane.
229
+ *
230
+ * The same non-iframe-editor pattern as the grid, the Library and Project Settings: this owns its
231
+ * own effect scope from here, so a field commit repaints the form and nothing else — repainting
232
+ * through the canvas pipeline would remount the document's iframe on every keystroke.
233
+ */
234
+ export function renderEntryMode(surface: CanvasSurface, tab: Tab): void {
235
+ const { paneId, wrap: canvasWrap } = surface;
236
+ if (entryPaneMounted(paneId, tab)) {
237
+ return;
238
+ }
239
+ detachEntryPane(paneId);
240
+
241
+ const scope = effectScope();
242
+ const panel: ActiveEntryPane = { paneId, scope, tabId: tab.id, wrap: canvasWrap };
243
+ _active.set(paneId, panel);
244
+
245
+ const rerender = () => {
246
+ if (activeIn(paneId) === panel) {
247
+ draw();
248
+ }
249
+ };
250
+
251
+ const draw = () => {
252
+ const collection = collectionOfPath(tab.documentPath);
253
+ if (!collection) {
254
+ litRender(
255
+ html`<div class="entry-editor" data-jx-region=${paneRegion(paneId, "entry")}>
256
+ ${notAnEntryTpl(tab)}
257
+ </div>`,
258
+ canvasWrap,
259
+ );
260
+ return;
261
+ }
262
+ const fields = entryFields(tab);
263
+ litRender(
264
+ html`
265
+ <div class="entry-editor" data-jx-region=${paneRegion(paneId, "entry")}>
266
+ ${headerTpl(tab, collection)}
267
+ <div class="entry-editor-fields" data-jx-region=${paneRegion(paneId, "entry/fields")}>
268
+ ${renderForm(collection.schema as JsonSchema, fields, {
269
+ context: formContext(tab),
270
+ errors: absentRequiredErrors(collection, fields),
271
+ onChange: (patch) => commitEntryFields(tab, patch),
272
+ rerender,
273
+ })}
274
+ </div>
275
+ </div>
276
+ `,
277
+ canvasWrap,
278
+ );
279
+ };
280
+
281
+ scope.run(() => {
282
+ effect(() => {
283
+ if (activeIn(paneId) !== panel) {
284
+ return;
285
+ }
286
+ // Everything the form draws from: the entry's own fields, and the schema that types them.
287
+ // BOTH stores are read, because which one holds the fields is `entry-fields.ts`'s answer,
288
+ // Never this effect's guess. `tab.doc.document` is also the reference every transaction
289
+ // Replaces, so it is how an undo reaches the form whichever store the edit was in.
290
+ void tab.doc.document;
291
+ void tab.doc.content?.frontmatter;
292
+ void projectState?.projectConfig?.content;
293
+ draw();
294
+ });
295
+ });
296
+ }
297
+
298
+ // ─── Opening ─────────────────────────────────────────────────────────────────
299
+
300
+ /**
301
+ * Open a content entry in its form editor.
302
+ *
303
+ * Reveals the tab the file already has rather than building a second one — same id, same history,
304
+ * same dirty flag — and only then switches the editor, so an author who was editing the body keeps
305
+ * their undo stack. Returns null when the file could not be opened at all.
306
+ */
307
+ export async function openEntryEditor(path: string): Promise<Tab | null> {
308
+ /* Dynamic: `files/files.ts` reaches the platform, the format registry and the packages layer, and
309
+ a static edge from here would drag all of it into every importer of the content model. */
310
+ const { openFileInTab } = await import("../files/files");
311
+ await openFileInTab(path);
312
+ for (const [id, tab] of workspace.tabs.entries()) {
313
+ if (tab.documentPath !== path) {
314
+ continue;
315
+ }
316
+ for (const mode of ENTRY_TAB_MODES) {
317
+ if (!tab.capabilities.modes.includes(mode)) {
318
+ tab.capabilities.modes.unshift(mode);
319
+ }
320
+ }
321
+ tab.session.ui.canvasMode = ENTRY_MODE;
322
+ tab.session.ui.preview = false;
323
+ activateTab(id);
324
+ return tab as Tab;
325
+ }
326
+ return null;
327
+ }