@jxsuite/studio 2.4.3 → 4.0.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 (208) hide show
  1. package/README.md +5 -5
  2. package/dist/chunks/Overlay.dev-723bv0df.js +23 -0
  3. package/dist/chunks/cf-settings-45fgyx8y.js +21 -0
  4. package/dist/chunks/{components-z6cnkyy1.js → components-zmx10t62.js} +6 -5
  5. package/dist/chunks/content-source-t88vb2dr.js +49 -0
  6. package/dist/chunks/{convert-to-repeater-k10dvcc7.js → convert-to-repeater-dhw1nrz9.js} +27 -18
  7. package/dist/chunks/entry-commands-6z6bhxg0.js +46 -0
  8. package/dist/chunks/{extension-sections-0ada0xcw.js → extension-sections-3ydgjbdy.js} +32 -23
  9. package/dist/chunks/file-ops-rskmhxyq.js +48 -0
  10. package/dist/chunks/files-0cratsr0.js +92 -0
  11. package/dist/chunks/{format-host-wewba4nq.js → format-host-tgcmfs4p.js} +3 -3
  12. package/dist/chunks/fs-events-hrrg328g.js +21 -0
  13. package/dist/chunks/{git-panel-2dh1aw50.js → git-panel-0yywyk76.js} +21 -15
  14. package/dist/chunks/{jx-validate-jj9rnt4n.js → jx-validate-8q2g05x1.js} +6 -4
  15. package/dist/chunks/layers-panel-z3tqe5cd.js +67 -0
  16. package/dist/chunks/layers-q75tj2mf.js +45 -0
  17. package/dist/chunks/lit-html-j055mkh8.js +22 -0
  18. package/dist/chunks/monaco-buffer-dqw0xxpx.js +25 -0
  19. package/dist/chunks/{monaco-setup-a5wkmbgh.js → monaco-setup-rws7k4br.js} +4 -4
  20. package/dist/chunks/notify-yjm79n4s.js +30 -0
  21. package/dist/chunks/parse-24d0gay3.js +19 -0
  22. package/dist/chunks/platform-ba1j1gbh.js +16 -0
  23. package/dist/chunks/publish-panel-54jvhskq.js +28 -0
  24. package/dist/chunks/quick-search-cgfmjwq5.js +65 -0
  25. package/dist/chunks/references-3qx8fkyp.js +29 -0
  26. package/dist/chunks/right-panel-cqp8bc4v.js +46 -0
  27. package/dist/chunks/sp-overlay.dev-yztmtrs1.js +18 -0
  28. package/dist/chunks/sp-popover.dev-n2etmqdp.js +9 -0
  29. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +15 -0
  30. package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +15 -0
  31. package/dist/chunks/{sp-tray.dev-64v4pxw8.js → sp-tray.dev-tkbscm06.js} +5 -5
  32. package/dist/chunks/{studio-f5yyvqxv.js → studio-1hbqnt19.js} +37 -31
  33. package/dist/chunks/studio-2zkz8evn.js +3163 -0
  34. package/dist/chunks/{studio-0b6fs05q.js → studio-4pqqxbrt.js} +23 -15
  35. package/dist/chunks/{studio-kvfx9z2c.js → studio-4rzyrtjz.js} +70 -31
  36. package/dist/chunks/{studio-ef8t53vc.js → studio-5q69gfag.js} +60 -56
  37. package/dist/chunks/{studio-v2c5pmjj.js → studio-5rywrkxz.js} +565 -133
  38. package/dist/chunks/{studio-6kv3apa9.js → studio-68n67094.js} +25 -9
  39. package/dist/chunks/{studio-0rqqhrdy.js → studio-7nte2hj4.js} +2 -2
  40. package/dist/chunks/{studio-hyn3vhwr.js → studio-8swrjbny.js} +31 -18
  41. package/dist/chunks/studio-92ab2jqe.js +282 -0
  42. package/dist/chunks/{studio-sas7mt3k.js → studio-9ab8gqsm.js} +2 -2
  43. package/dist/chunks/{studio-28xc8ws4.js → studio-9c4k4vhv.js} +14 -10
  44. package/dist/chunks/studio-9n4mrmnb.js +184 -0
  45. package/dist/chunks/{studio-tr20m8g8.js → studio-9r6j5ev1.js} +37 -2
  46. package/dist/chunks/{studio-aweyr4v1.js → studio-ahjfdy6e.js} +9 -7
  47. package/dist/chunks/{studio-s519zc99.js → studio-bgxt93ga.js} +3 -3
  48. package/dist/chunks/{studio-26sv44br.js → studio-cb12yz9w.js} +8543 -8784
  49. package/dist/chunks/studio-cft9r4r7.js +119 -0
  50. package/dist/chunks/{studio-rqemfhz6.js → studio-eenfkt4e.js} +6 -6
  51. package/dist/chunks/studio-eesa1myv.js +498 -0
  52. package/dist/chunks/{studio-ay8wcr2b.js → studio-eqq1w638.js} +7 -9
  53. package/dist/chunks/{studio-ke244pam.js → studio-ex8zyp98.js} +51 -25
  54. package/dist/chunks/studio-gv8vvshc.js +118 -0
  55. package/dist/chunks/studio-hbtp0eh1.js +1315 -0
  56. package/dist/chunks/{studio-cz2j1c0g.js → studio-htsf0w6m.js} +75 -123
  57. package/dist/chunks/{studio-pyrw8h0w.js → studio-kjjsjak4.js} +528 -212
  58. package/dist/chunks/{studio-5x5774ak.js → studio-kryvsw6j.js} +3 -3
  59. package/dist/chunks/studio-kyh9avvv.js +28 -0
  60. package/dist/chunks/{studio-jnakhg87.js → studio-kyk2v92a.js} +14 -14
  61. package/dist/chunks/{studio-twsafr1e.js → studio-m3hwn7y1.js} +3 -3
  62. package/dist/chunks/{studio-tzx3xmvx.js → studio-mmv72a27.js} +8 -8
  63. package/dist/chunks/{studio-vhk5fmfv.js → studio-n8b9wqwy.js} +5 -5
  64. package/dist/chunks/{studio-cjm7fdjf.js → studio-p4ngc046.js} +8 -104
  65. package/dist/chunks/{studio-sccge2wf.js → studio-p8j86wag.js} +2 -2
  66. package/dist/chunks/{studio-v1va0vhd.js → studio-pdfjntbk.js} +75 -139
  67. package/dist/chunks/studio-q43a5y9h.js +394 -0
  68. package/dist/chunks/studio-q7v10cg2.js +106 -0
  69. package/dist/chunks/{studio-2w11grav.js → studio-qq6jym04.js} +3 -3
  70. package/dist/chunks/studio-r1r7afxx.js +1066 -0
  71. package/dist/chunks/{studio-3fk1ac9z.js → studio-rdx8jcxy.js} +4 -4
  72. package/dist/chunks/{studio-4mqb9a0j.js → studio-rn9ta2mz.js} +4 -4
  73. package/dist/chunks/studio-sf7zjgv1.js +91 -0
  74. package/dist/chunks/studio-t7qb9db8.js +665 -0
  75. package/dist/chunks/studio-tmc5emrw.js +8 -0
  76. package/dist/chunks/{studio-wdt9y92a.js → studio-txwkm6p9.js} +2 -2
  77. package/dist/chunks/{studio-dgj7gq44.js → studio-v38hmvx1.js} +7 -7
  78. package/dist/chunks/{studio-7ejemhej.js → studio-vadzgkrz.js} +6 -6
  79. package/dist/chunks/studio-wcy8trfx.js +263 -0
  80. package/dist/chunks/{studio-hb7c46jy.js → studio-wx5609xd.js} +21 -15
  81. package/dist/chunks/studio-xrapxwwc.js +1539 -0
  82. package/dist/chunks/{studio-ws6ef6js.js → studio-yxrkyv8a.js} +41 -29
  83. package/dist/chunks/{studio-xm3dbxvn.js → studio-zczps70q.js} +2 -2
  84. package/dist/chunks/workspace-xpres40v.js +89 -0
  85. package/dist/iframe-entry.js +450 -48
  86. package/dist/studio.js +2455 -1761
  87. package/package.json +16 -15
  88. package/src/browse/library-layouts.ts +3 -3
  89. package/src/browse/library-pane.ts +59 -5
  90. package/src/browse/library-preview.ts +19 -3
  91. package/src/canvas/asset-refs.ts +119 -0
  92. package/src/canvas/asset-resolve.ts +256 -0
  93. package/src/canvas/canvas-live-render.ts +23 -15
  94. package/src/canvas/canvas-render.ts +23 -1
  95. package/src/canvas/iframe-entry.ts +1 -0
  96. package/src/canvas/iframe-host.ts +4 -0
  97. package/src/canvas/iframe-patch.ts +47 -21
  98. package/src/canvas/iframe-protocol.ts +9 -0
  99. package/src/canvas/iframe-render.ts +50 -16
  100. package/src/commands/app-commands.ts +4 -0
  101. package/src/commands/command-args.ts +29 -0
  102. package/src/commands/context.ts +15 -1
  103. package/src/commands/defaults.ts +29 -1
  104. package/src/commands/levels.ts +16 -0
  105. package/src/commands/live-context.ts +3 -0
  106. package/src/content/collection-match.ts +267 -0
  107. package/src/content/entry-commands.ts +38 -29
  108. package/src/content/entry-fields.ts +6 -0
  109. package/src/editor/file-drop-action.ts +2 -1
  110. package/src/editor/shortcuts.ts +5 -0
  111. package/src/files/file-ops.ts +10 -42
  112. package/src/files/files.ts +487 -61
  113. package/src/files/fs-events.ts +9 -0
  114. package/src/files/gitignore.ts +432 -0
  115. package/src/files/media-paths.ts +37 -14
  116. package/src/files/media-upload.ts +47 -6
  117. package/src/files/serialize-document.ts +60 -0
  118. package/src/format/convert-file.ts +511 -0
  119. package/src/format/format-choices.ts +199 -0
  120. package/src/grid/grid-idle.ts +45 -0
  121. package/src/grid/grid-view.ts +35 -0
  122. package/src/grid/schema-columns.ts +2 -1
  123. package/src/grid/sources/content-source.ts +24 -3
  124. package/src/media/media-open.ts +58 -0
  125. package/src/media/media-pane.ts +310 -0
  126. package/src/new-project/import-tab.ts +333 -160
  127. package/src/new-project/new-project-modal.ts +27 -34
  128. package/src/packages/ensure-deps.ts +14 -7
  129. package/src/packages/jxsuite-update.ts +11 -11
  130. package/src/page-params.ts +10 -46
  131. package/src/panels/activity-bar.ts +45 -16
  132. package/src/panels/ai-chat/attached-context.ts +6 -1
  133. package/src/panels/ai-chat/chat-view.ts +255 -13
  134. package/src/panels/ai-chat/composer.ts +21 -80
  135. package/src/panels/ai-panel.ts +116 -2
  136. package/src/panels/frontmatter-fields.ts +6 -2
  137. package/src/panels/navigator-panels.ts +7 -6
  138. package/src/panels/properties-panel.ts +8 -2
  139. package/src/panels/seo-modal.ts +5 -6
  140. package/src/panels/settings-menu.ts +799 -0
  141. package/src/panels/signals-panel.ts +2 -1
  142. package/src/panels/toolbar.ts +135 -15
  143. package/src/platforms/cloud.ts +34 -3
  144. package/src/platforms/devserver.ts +72 -34
  145. package/src/preview/preview-overlay.ts +226 -0
  146. package/src/services/ai-ask.ts +247 -0
  147. package/src/services/ai-import-tools.ts +494 -0
  148. package/src/services/ai-models.ts +101 -11
  149. package/src/services/ai-project-tools.ts +20 -26
  150. package/src/services/ai-settings.ts +69 -71
  151. package/src/services/ai-system-prompt.ts +88 -2
  152. package/src/services/ai-turn-signal.ts +70 -0
  153. package/src/services/cf-settings.ts +19 -37
  154. package/src/services/connection.ts +74 -0
  155. package/src/services/context-manager.ts +89 -0
  156. package/src/services/document-assistant.ts +49 -7
  157. package/src/services/idle.ts +6 -1
  158. package/src/services/import-client.ts +33 -6
  159. package/src/services/import-run.ts +158 -0
  160. package/src/services/import-seed.ts +120 -0
  161. package/src/services/jx-validate.ts +58 -6
  162. package/src/services/project-adoption.ts +71 -0
  163. package/src/services/references.ts +0 -0
  164. package/src/services/settings/definitions.ts +181 -0
  165. package/src/services/settings/kernel.ts +335 -0
  166. package/src/services/settings/write-queue.ts +116 -0
  167. package/src/services/tool-executor.ts +34 -2
  168. package/src/settings/contexts-section.ts +45 -3
  169. package/src/settings/dependencies-editor.ts +43 -23
  170. package/src/settings/general-settings.ts +30 -12
  171. package/src/settings/preferences-accounts.ts +6 -19
  172. package/src/settings/preferences-dialog.ts +18 -41
  173. package/src/settings/preferences-keymap.ts +8 -18
  174. package/src/settings/preferences-sections.ts +57 -0
  175. package/src/settings/schema-field-ui.ts +2 -2
  176. package/src/settings/settings-document.ts +65 -9
  177. package/src/shell.ts +6 -14
  178. package/src/studio.ts +65 -7
  179. package/src/tabs/transact.ts +42 -19
  180. package/src/types.ts +164 -8
  181. package/src/ui/ai-credentials-form.ts +52 -27
  182. package/src/ui/ai-model-picker.ts +162 -0
  183. package/src/ui/choice-field.ts +72 -0
  184. package/src/ui/layers.ts +83 -5
  185. package/src/ui/media-picker.ts +6 -11
  186. package/src/utils/studio-utils.ts +43 -31
  187. package/styles/forced-colors.css +7 -0
  188. package/styles/overlays.css +256 -0
  189. package/styles/panels.css +13 -1
  190. package/styles/shell.css +111 -0
  191. package/dist/chunks/Overlay.dev-d2ajy6sj.js +0 -23
  192. package/dist/chunks/cf-settings-g5fbf2zp.js +0 -17
  193. package/dist/chunks/content-source-k9724vcv.js +0 -39
  194. package/dist/chunks/files-yew27sn9.js +0 -69
  195. package/dist/chunks/layers-panel-qnsghwwd.js +0 -59
  196. package/dist/chunks/publish-panel-a0pdft5w.js +0 -21
  197. package/dist/chunks/quick-search-9avyc4pc.js +0 -54
  198. package/dist/chunks/right-panel-kcnhtc5j.js +0 -38
  199. package/dist/chunks/sp-overlay.dev-s18hymn9.js +0 -18
  200. package/dist/chunks/sp-popover.dev-dj9x7e7a.js +0 -9
  201. package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +0 -15
  202. package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +0 -15
  203. package/dist/chunks/studio-a7pzveq1.js +0 -110
  204. package/dist/chunks/studio-c31yb4t6.js +0 -55
  205. package/dist/chunks/studio-ra7ar50f.js +0 -6682
  206. package/src/canvas/content-assets.ts +0 -263
  207. package/src/canvas/site-style-css.ts +0 -121
  208. package/src/services/settings-store.ts +0 -99
@@ -34,6 +34,10 @@ import { undo } from "../tabs/transact";
34
34
  import { activeTab } from "../workspace/workspace";
35
35
  import { setOpenAiKey } from "../services/ai-settings";
36
36
  import { hasAiCredentials } from "../services/ai-models";
37
+ import { answerAsk, isAwaitingAnswer, pendingAsk, skipAsk } from "../services/ai-ask";
38
+ import { activeImportRun, importRun } from "../services/import-run";
39
+ import type { ImportBrief } from "../services/import-seed";
40
+ import type { ImportBreakpointPolicy } from "../types";
37
41
  import { openPreferences } from "../settings/preferences-dialog";
38
42
  import { onCredentialsChanged } from "../settings/preferences-accounts";
39
43
  import { setDockCollapsed } from "../shell";
@@ -42,6 +46,7 @@ import { clearMarkdownCache } from "./ai-chat/chat-markdown";
42
46
  import { renderChatHeader, renderMessageList } from "./ai-chat/chat-view";
43
47
  import { createComposer } from "./ai-chat/composer";
44
48
  import { renderSessionsList } from "./ai-chat/sessions-view";
49
+ import { buildMessageWithContext } from "./ai-chat/attached-context";
45
50
  import { setInspectorTab } from "./right-panel";
46
51
 
47
52
  import type { AnyCommand } from "../commands/registry";
@@ -109,6 +114,17 @@ function watchAssistant() {
109
114
  void last?.toolCalls?.length;
110
115
  void cs.status;
111
116
  void cs.error;
117
+ // The question is not chat state — it lives in `services/ai-ask.ts` — but it is drawn into
118
+ // This panel, so the same effect has to track it or a question would appear a frame late
119
+ // (and its ANSWER would never repaint the card at all).
120
+ void pendingAsk();
121
+ /* The run record, for the same reason: an import reports a line at a time from a store that
122
+ is not chat state, and its chip has to repaint on every one of them. Reading the ACTIVE
123
+ run tracks the whole record, so any field moving schedules a frame. */
124
+ const run = activeImportRun();
125
+ void run?.message;
126
+ void run?.log.length;
127
+ void run?.status;
112
128
  scheduleAiRender();
113
129
  });
114
130
  });
@@ -179,11 +195,23 @@ function renderSetupNotice(): TemplateResult {
179
195
 
180
196
  // ─── Sending ────────────────────────────────────────────────────────────────
181
197
 
182
- /** Send a message through the document assistant agent loop. */
198
+ /**
199
+ * Send a message through the document assistant agent loop — or answer the question it is waiting
200
+ * on, which is the same keystroke and a different act.
201
+ *
202
+ * The answer must NOT become a user message. `toMessagesArray` serialises the array verbatim and a
203
+ * provider requires a `tool` reply to follow its `tool_calls` request; a user turn spliced between
204
+ * them is a 400. It travels as the tool result instead, and the question's own card renders it.
205
+ */
183
206
  async function handleAssistantSend(text: string) {
184
207
  if (!text.trim() || assistant.chatState.status === "streaming") {
185
208
  return;
186
209
  }
210
+ if (answerAsk(text.trim())) {
211
+ stickToBottom = true;
212
+ scheduleAiRender();
213
+ return;
214
+ }
187
215
  // A send always lands in the chat view, pinned to the newest message.
188
216
  view = "chat";
189
217
  stickToBottom = true;
@@ -264,6 +292,66 @@ export async function seedAssistantPrompt(text: string): Promise<void> {
264
292
  await handleAssistantSend(text);
265
293
  }
266
294
 
295
+ /**
296
+ * Take the New Project Import form's brief and start the run as an assistant turn.
297
+ *
298
+ * The wizard closes without having created anything: `import_site` is `no-project` tiered and does
299
+ * the creating, so the window is still on the welcome screen when this runs — which is exactly the
300
+ * state the assistant is designed to be usable in (`specs/studio.md` §6).
301
+ *
302
+ * A fresh chat, deliberately. An import is the start of a project, and threading it onto whatever
303
+ * conversation the previous project left behind would put another project's document context in
304
+ * front of the model on its very first decision.
305
+ *
306
+ * The brief is already in `services/import-seed.ts` — the form that gathered it put it there, so
307
+ * `import_site` can read the destination later whether or not this hand-off is what started it.
308
+ *
309
+ * @param {ImportBrief} brief
310
+ */
311
+ export async function revealImportHandoff(brief: ImportBrief): Promise<void> {
312
+ revealAssistant();
313
+ newChat();
314
+ await seedAssistantPrompt(buildImportTurn(brief));
315
+ }
316
+
317
+ /**
318
+ * The user message that opens an import turn.
319
+ *
320
+ * The parameters ride in an attached-context block — the composer's own convention for facts the
321
+ * model must see but the reader should not have to re-read (`ai-chat/attached-context.ts`). The
322
+ * body is the user's own brief, so the transcript reads as what they asked for rather than as a
323
+ * form submission.
324
+ *
325
+ * @param {ImportBrief} brief
326
+ * @returns {string}
327
+ */
328
+ /** The breakpoint policy in a sentence the model can act on rather than a JSON blob. */
329
+ function describeBreakpoints(policy: ImportBreakpointPolicy): string {
330
+ if (policy.mode === "all") {
331
+ return "keep every one the site declares";
332
+ }
333
+ if (policy.mode === "explicit") {
334
+ return `${(policy.widths ?? []).join(", ")} (rounding ${policy.rounding ?? "nearest"})`;
335
+ }
336
+ return `keep ${policy.count ?? 3}, evenly spaced (rounding ${policy.rounding ?? "nearest"})`;
337
+ }
338
+
339
+ export function buildImportTurn(brief: ImportBrief): string {
340
+ const body = brief.prompt.trim() || `Import ${brief.url} and get it ready for me to work on.`;
341
+ return buildMessageWithContext(body, [
342
+ {
343
+ detail:
344
+ `Import request from the New Project form — url: ${brief.url}, ` +
345
+ `destination: ${brief.directory}, depth: ${brief.depth}, ` +
346
+ `max pages: ${brief.maxPages}, AI component naming: ${brief.aiComponents}, ` +
347
+ `breakpoints: ${describeBreakpoints(brief.breakpoints)}. ` +
348
+ "Call import_site with the url; the destination and options above are already settled.",
349
+ kind: "import",
350
+ label: `Import ${brief.url}`,
351
+ },
352
+ ]);
353
+ }
354
+
267
355
  // ─── Automation seeding (screenshot runner) ─────────────────────────────────
268
356
 
269
357
  /** A canned tool-call chip for {@link seedAssistantMessages}. */
@@ -331,6 +419,17 @@ export function revealAssistant(): void {
331
419
  setInspectorTab("assistant");
332
420
  }
333
421
 
422
+ /**
423
+ * Whether a turn is suspended on the reader.
424
+ *
425
+ * Not the same as streaming and not the opposite of idle: no tokens are moving, but the loop is
426
+ * alive and holding a tool open. `assistant.stop` is enabled on the union of the two, because a
427
+ * turn waiting forever on a question nobody wants to answer is exactly what Stop is for.
428
+ */
429
+ export function isAssistantWaiting(): boolean {
430
+ return isAwaitingAnswer();
431
+ }
432
+
334
433
  /**
335
434
  * Whether a turn is in flight — the probe `commands/live-context.ts` declares as `aiStreaming` and
336
435
  * projects onto `ctx.ai.streaming`.
@@ -388,6 +487,7 @@ function activeSessionTitle(): string | null {
388
487
  // ─── Composer ───────────────────────────────────────────────────────────────
389
488
 
390
489
  const composer = createComposer({
490
+ isAwaiting: isAssistantWaiting,
391
491
  isStreaming: isAssistantStreaming,
392
492
  onOpenSettings: () => {
393
493
  void openPreferences("assistant");
@@ -533,7 +633,10 @@ export function assistantCommands(): AnyCommand[] {
533
633
  menus: ["palette"],
534
634
  group: "2_turn",
535
635
  requires: "a turn in flight",
536
- enablement: (ctx: CommandContext) => ctx.ai.streaming,
636
+ /* The union, not just `streaming`. A turn suspended on `ask_user` moves no tokens, so
637
+ `ctx.ai.streaming` reads false — and that is precisely the turn a reader who does not want
638
+ to answer needs to end. */
639
+ enablement: (ctx: CommandContext) => ctx.ai.streaming || ctx.ai.waiting,
537
640
  run: stop,
538
641
  },
539
642
  ];
@@ -565,6 +668,17 @@ export function renderAiPanelTemplate(): TemplateResult {
565
668
  tokens: cs.tokenCount,
566
669
  })}
567
670
  ${renderMessageList({
671
+ ask: {
672
+ importRun,
673
+ onAnswer: (text) => {
674
+ void handleAssistantSend(text);
675
+ },
676
+ onSkip: () => {
677
+ skipAsk();
678
+ scheduleAiRender();
679
+ },
680
+ pendingId: pendingAsk()?.id ?? null,
681
+ },
568
682
  error: cs.error,
569
683
  listRef: onMessagesListRef,
570
684
  messages: cs.messages,
@@ -14,7 +14,7 @@ import { renderFieldRow } from "../ui/field-row";
14
14
  import { spNumberField, spTextField } from "../ui/field-input";
15
15
  import { renderMediaPicker } from "../ui/media-picker";
16
16
  import { mutateUpdateFrontmatter, transactDoc } from "../tabs/transact";
17
- import { findContentTypeSchema } from "../utils/studio-utils";
17
+ import { findContentTypeSchema, isMediaFormat } from "../utils/studio-utils";
18
18
  import { NULL_FORM_CONTEXT, getFormControl, referenceTarget } from "../ui/schema-form";
19
19
  import type { JsonSchema } from "../ui/schema-form";
20
20
 
@@ -233,7 +233,11 @@ export function renderFmField(
233
233
  });
234
234
  }
235
235
 
236
- if (entry.format === "image") {
236
+ /* Both spellings. `"uri-reference"` is the one the SPEC uses and the one the content loader keys
237
+ its asset rewrite on (`rewriteEntryAssets`), so a schema written against the spec got a plain
238
+ text box here while the same field got a media picker in the properties panel — which reads
239
+ `inferInputType`, and that has always accepted it. */
240
+ if (isMediaFormat(entry.format)) {
237
241
  return renderFieldRow({
238
242
  hasValue: hasVal,
239
243
  label: displayLabel,
@@ -79,7 +79,7 @@ export function registerNavigatorPanels(): void {
79
79
  // ── Off-rail: reachable by command and palette, no rail button ──────────────
80
80
  registerInsertPanel();
81
81
 
82
- // ── The Bottom dock's tabs (§3.2 ⑪): Problems · Diff · Logic · Activity ──────
82
+ // ── The Bottom dock's tabs (§3.2 ⑪): Problems · Logic · Activity ────────────
83
83
  // Composed here, in the one place panel records are gathered, so the shell has ONE composition
84
84
  // Site rather than one per dock. Importing the module is also what attaches the dock to the
85
85
  // Shell's mount lifecycle — see its `registerShellSurface` call. Problems arrives with them and
@@ -110,12 +110,13 @@ export function navigatorPanelSet(): readonly PanelRecord[] {
110
110
  * The roster `panel.focus.*` is generated from: rail order first, then the rail-less Navigator
111
111
  * panels.
112
112
  *
113
- * Not `railPanelSet()`: Insert and State have no rail button and no chord, and they still need a
114
- * `Show …` command and a palette row, which is the whole point of `rail: false` (a surface
115
- * reachable by name, not by number).
113
+ * Not `railPanelSet()`: Insert and Languages have no rail button and no chord, and they still need
114
+ * a `Show …` command and a palette row, which is the whole point of `rail: false` (a surface
115
+ * reachable by name, not by number). State used to be the third of them and is gone: its editor is
116
+ * the Data panel's now, so there is no second record to roster.
116
117
  *
117
- * The Bottom dock's four tabs are deliberately absent — Problems as of this change, and Diff, Logic
118
- * and Activity all along. Each is `rail: false` AND hosted there, so each is addressed by
118
+ * The Bottom dock's three tabs are deliberately absent — Problems as of this change, and Logic and
119
+ * Activity all along. Each is `rail: false` AND hosted there, so each is addressed by
119
120
  * `view.setBottomTab`: one verb per surface, and the dock's own strip is how a human picks between
120
121
  * them. That is also why the filter here is by DOCK and not merely by `rail`, and why Problems
121
122
  * leaving the rail removed `panel.focus.problems` rather than demoting it to a chordless row.
@@ -49,7 +49,13 @@ import { renderFieldRow, renderKvRow } from "../ui/field-row";
49
49
  import type { FieldProvenance } from "./provenance";
50
50
  import { renderDynamicSlot, slotMode } from "../ui/dynamic-slot";
51
51
  import { spTextArea, spTextField } from "../ui/field-input";
52
- import { attrLabel, camelToLabel, inferInputType, parseCemType } from "../utils/studio-utils";
52
+ import {
53
+ attrLabel,
54
+ camelToLabel,
55
+ inferInputType,
56
+ isMediaFormat,
57
+ parseCemType,
58
+ } from "../utils/studio-utils";
53
59
  import { classifyHref, composeHref } from "../utils/link-target";
54
60
  import type { LinkKind } from "../utils/link-target";
55
61
  import {
@@ -274,7 +280,7 @@ function renderComponentPropsFieldsTemplate(
274
280
  /** @type {ReturnType<typeof setTimeout> | undefined} */
275
281
  let debounce: ReturnType<typeof setTimeout> | undefined;
276
282
  let widgetTpl;
277
- if (prop.format === "image") {
283
+ if (isMediaFormat(prop.format)) {
278
284
  widgetTpl = renderMediaPicker(prop.name, staticVal, onChange);
279
285
  } else if (prop.format === "color") {
280
286
  widgetTpl = renderColorSelector(prop.name, staticVal, onChange);
@@ -24,8 +24,7 @@
24
24
 
25
25
  import { html, nothing } from "lit-html";
26
26
  import { activeRegistry } from "../commands/active-registry";
27
- import { loopbackAssetSrc } from "../canvas/canvas-origin";
28
- import { previewAssetSrc } from "../canvas/content-assets";
27
+ import { previewAssetSrc } from "../canvas/asset-refs";
29
28
  import { renderFieldRow } from "../ui/field-row";
30
29
  import { renderMediaPicker } from "../ui/media-picker";
31
30
  import { renderProvenanceChip } from "./provenance";
@@ -140,9 +139,9 @@ function serpCard(preview: SeoPreview): TemplateResult {
140
139
  /**
141
140
  * The mock social card.
142
141
  *
143
- * The image is resolved the same way every other image in the studio chrome is —
144
- * `loopbackAssetSrc(previewAssetSrc(…))` — so a content-relative `./images/hero.jpg` previews at
145
- * its asset-mount URL while the authored ref stays exactly as written.
142
+ * The image is resolved the same way every other image in the studio chrome is — `previewAssetSrc`
143
+ * — so a content-relative `./images/hero.jpg` previews at its asset-mount URL while the authored
144
+ * ref stays exactly as written.
146
145
  */
147
146
  function socialCard(preview: SeoPreview): TemplateResult {
148
147
  const image = seoField(preview, "og:image").value.trim();
@@ -152,7 +151,7 @@ function socialCard(preview: SeoPreview): TemplateResult {
152
151
  <div class="seo-social-media">
153
152
  ${
154
153
  image
155
- ? html`<img src=${loopbackAssetSrc(previewAssetSrc(image))} alt="" />`
154
+ ? html`<img src=${previewAssetSrc(image)} alt="" />`
156
155
  : html`<span class="seo-unset">No image</span>`
157
156
  }
158
157
  </div>