@jxsuite/studio 2.3.0 → 2.4.1

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 (47) hide show
  1. package/dist/chunks/{content-source-56xpvx83.js → content-source-cn9te96r.js} +8 -8
  2. package/dist/chunks/{convert-to-repeater-0xh9wwek.js → convert-to-repeater-65gjgdst.js} +7 -7
  3. package/dist/chunks/{extension-sections-g34239vm.js → extension-sections-mdgmqrry.js} +11 -11
  4. package/dist/chunks/{files-gkz202dp.js → files-dsh5g8j4.js} +12 -12
  5. package/dist/chunks/{format-host-pej3srnw.js → format-host-m9h45a79.js} +2 -2
  6. package/dist/chunks/{git-panel-4j6xbh8g.js → git-panel-rfs8pqjj.js} +7 -7
  7. package/dist/chunks/{layers-panel-8mvn8sgd.js → layers-panel-6cj5nf6a.js} +10 -10
  8. package/dist/chunks/{publish-panel-ptddc3kg.js → publish-panel-b0xh7b4y.js} +5 -5
  9. package/dist/chunks/{quick-search-j88azfs2.js → quick-search-a83fhnsh.js} +13 -13
  10. package/dist/chunks/{right-panel-egkaxy89.js → right-panel-hrw5kdh5.js} +9 -9
  11. package/dist/chunks/{studio-4058pg09.js → studio-0svvdm0a.js} +4 -4
  12. package/dist/chunks/{studio-5amdrjm4.js → studio-1d6bm7eg.js} +7 -7
  13. package/dist/chunks/{studio-50eed4c9.js → studio-40fm3hyr.js} +2 -2
  14. package/dist/chunks/{studio-0e4h32g6.js → studio-4m39t01b.js} +5 -5
  15. package/dist/chunks/{studio-5ne8mdx9.js → studio-675v08qj.js} +2 -2
  16. package/dist/chunks/{studio-cf4rk7kv.js → studio-91sdrn9x.js} +2 -2
  17. package/dist/chunks/{studio-bak2cyjr.js → studio-9sxn0eaa.js} +4 -4
  18. package/dist/chunks/{studio-3q65xvzv.js → studio-d1f2rqxh.js} +4 -4
  19. package/dist/chunks/{studio-a11d3a1b.js → studio-gfzyxa0m.js} +5 -5
  20. package/dist/chunks/{studio-x3etxs0s.js → studio-gqpbrrj5.js} +8 -8
  21. package/dist/chunks/{studio-10s469da.js → studio-h6553a8h.js} +71 -22
  22. package/dist/chunks/{studio-ncnet0ba.js → studio-jand3kjg.js} +10 -10
  23. package/dist/chunks/{studio-hk523hby.js → studio-kfq9ed73.js} +3 -3
  24. package/dist/chunks/{studio-hs8x23fv.js → studio-n9k15qdg.js} +8 -8
  25. package/dist/chunks/{studio-yxzgjq8q.js → studio-qb6vy1z1.js} +8 -8
  26. package/dist/chunks/{studio-vdpcpada.js → studio-qymkxbcq.js} +3 -3
  27. package/dist/chunks/{studio-zdkgwhkv.js → studio-rkr6pm84.js} +3 -3
  28. package/dist/chunks/{studio-cv606rs1.js → studio-rz861p2b.js} +5 -5
  29. package/dist/chunks/{studio-4enps87v.js → studio-tdt7b6qb.js} +6 -6
  30. package/dist/iframe-entry.js +73 -9
  31. package/dist/studio.js +46 -34
  32. package/package.json +6 -6
  33. package/src/canvas/canvas-live-render.ts +2 -4
  34. package/src/canvas/canvas-origin.ts +25 -0
  35. package/src/canvas/editable-actions.ts +37 -1
  36. package/src/canvas/iframe-editable-root.ts +27 -0
  37. package/src/canvas/iframe-host.ts +118 -7
  38. package/src/canvas/iframe-inline-edit.ts +64 -2
  39. package/src/canvas/iframe-protocol.ts +7 -0
  40. package/src/canvas/iframe-render.ts +38 -1
  41. package/src/editor/inline-edit-apply.ts +10 -4
  42. package/src/editor/inline-edit.ts +62 -4
  43. package/src/platforms/cloud.ts +9 -0
  44. package/src/services/ai-models.ts +14 -1
  45. package/src/types.ts +17 -0
  46. package/src/ui/ai-managed-connect.ts +18 -3
  47. package/styles/shell.css +7 -0
@@ -2200,11 +2200,15 @@ async function resolve(source) {
2200
2200
  if (_resolveCache.has(source)) {
2201
2201
  return _resolveCache.get(source);
2202
2202
  }
2203
- const p = fetch(source).then((res) => {
2203
+ const p = fetch(source).then(async (res) => {
2204
2204
  if (!res.ok) {
2205
2205
  throw new Error(`Jx: failed to fetch ${source} (${res.status})`);
2206
2206
  }
2207
- return res.json();
2207
+ const contentType = res.headers?.get("Content-Type") ?? "";
2208
+ if (contentType.includes("text/html")) {
2209
+ throw new Error(`Jx: ${source} returned HTML, not a document — the host answered a missing file with its ` + `app shell (a single-page fallback), so this path is not served.`);
2210
+ }
2211
+ return await res.json();
2208
2212
  });
2209
2213
  _resolveCache.set(source, p);
2210
2214
  return p;
@@ -3476,6 +3480,16 @@ function getPath2(obj, path) {
3476
3480
  return readPath(obj, path);
3477
3481
  }
3478
3482
  var _privatePropWarned = new Set;
3483
+ function instanceSupplies(el, key) {
3484
+ if (Object.hasOwn(el, key)) {
3485
+ return true;
3486
+ }
3487
+ if (el.hasAttribute(key)) {
3488
+ return true;
3489
+ }
3490
+ const kebab = key.replaceAll(/[A-Z]/gu, (c) => `-${c.toLowerCase()}`);
3491
+ return kebab !== key && el.hasAttribute(kebab);
3492
+ }
3479
3493
  function refusePrivateProp(key, where) {
3480
3494
  if (!isPrivateStateKey(key)) {
3481
3495
  return false;
@@ -3626,7 +3640,7 @@ async function defineElement(source, baseUrl) {
3626
3640
  if (isPrivateStateKey(key)) {
3627
3641
  continue;
3628
3642
  }
3629
- if (key in this && this[key] !== undefined) {
3643
+ if (key in this && this[key] !== undefined && instanceSupplies(this, key)) {
3630
3644
  state[key] = this[key];
3631
3645
  }
3632
3646
  }
@@ -4195,6 +4209,8 @@ function injectHead(doc) {
4195
4209
  }
4196
4210
  }
4197
4211
  function makeStamper(ctx) {
4212
+ const islands = [];
4213
+ const insideIsland = (path) => islands.some((island) => path.length > island.length && island.every((seg, i) => path[i] === seg));
4198
4214
  return (created, path, def2) => {
4199
4215
  if (!(created instanceof HTMLElement)) {
4200
4216
  return;
@@ -4203,8 +4219,10 @@ function makeStamper(ctx) {
4203
4219
  if (isDefinitionRoot) {
4204
4220
  created.dataset.jxDefinitionRoot = "";
4205
4221
  }
4206
- if (!isDefinitionRoot && created.tagName.includes("-") && isEditableMode(ctx.canvasMode)) {
4222
+ const isIsland = !isDefinitionRoot && created.tagName.includes("-") && isEditableMode(ctx.canvasMode);
4223
+ if (isIsland) {
4207
4224
  created.contentEditable = "false";
4225
+ islands.push(path);
4208
4226
  }
4209
4227
  const classified = classifyRenderNode(path, def2, ctx);
4210
4228
  if (classified.kind === "layout") {
@@ -4221,6 +4239,9 @@ function makeStamper(ctx) {
4221
4239
  return;
4222
4240
  }
4223
4241
  created.dataset.jxPath = serializeJxPath(classified.path);
4242
+ if (!isIsland && isEditableMode(ctx.canvasMode) && insideIsland(path)) {
4243
+ created.contentEditable = "true";
4244
+ }
4224
4245
  };
4225
4246
  }
4226
4247
  function installCanvasImageRetry(root, maxAttempts = 3) {
@@ -5184,6 +5205,7 @@ var insertFn = null;
5184
5205
  var endFn = null;
5185
5206
  var _plainMode = false;
5186
5207
  var _plainOriginal = "";
5208
+ var _plainPosted = false;
5187
5209
  var editableVerdicts = null;
5188
5210
  function setEditableVerdicts(verdicts) {
5189
5211
  editableVerdicts = verdicts;
@@ -5203,6 +5225,7 @@ function startEditing(el, path, callbacks, opts) {
5203
5225
  endFn = callbacks.onEnd;
5204
5226
  _plainMode = opts?.plainText === true;
5205
5227
  _plainOriginal = _plainMode ? el.textContent ?? "" : "";
5228
+ _plainPosted = false;
5206
5229
  el.dataset.jxActiveBlock = "";
5207
5230
  if (_plainMode) {
5208
5231
  try {
@@ -5236,6 +5259,7 @@ function stopEditing(silent = false) {
5236
5259
  insertFn = null;
5237
5260
  _plainMode = false;
5238
5261
  _plainOriginal = "";
5262
+ _plainPosted = false;
5239
5263
  if (silent) {
5240
5264
  endFn = null;
5241
5265
  } else if (endFn) {
@@ -5247,6 +5271,9 @@ function stopEditing(silent = false) {
5247
5271
  function commitActiveBlock() {
5248
5272
  commitChanges(true);
5249
5273
  }
5274
+ function isPlainSession() {
5275
+ return _plainMode && activeEl !== null;
5276
+ }
5250
5277
  function isEditing() {
5251
5278
  return activeEl !== null;
5252
5279
  }
@@ -5332,6 +5359,7 @@ function releaseWithoutCommit() {
5332
5359
  insertFn = null;
5333
5360
  _plainMode = false;
5334
5361
  _plainOriginal = "";
5362
+ _plainPosted = false;
5335
5363
  if (endFn) {
5336
5364
  const fn = endFn;
5337
5365
  endFn = null;
@@ -5343,8 +5371,14 @@ function commitChanges(inPlace) {
5343
5371
  return;
5344
5372
  }
5345
5373
  if (_plainMode) {
5346
- commitFn(activePath, null, (activeEl.textContent ?? "").replaceAll(`
5347
- `, " "), inPlace);
5374
+ const text = (activeEl.textContent ?? "").replaceAll(`
5375
+ `, " ");
5376
+ if (text === _plainOriginal.replaceAll(`
5377
+ `, " ") && !_plainPosted) {
5378
+ return;
5379
+ }
5380
+ _plainPosted = true;
5381
+ commitFn(activePath, null, text, inPlace);
5348
5382
  return;
5349
5383
  }
5350
5384
  normalizeInlineContent(activeEl);
@@ -5467,6 +5501,9 @@ function openSlashMenu(opts) {
5467
5501
  if (!activeEl || !insertFn || !activePath) {
5468
5502
  return;
5469
5503
  }
5504
+ if (_plainMode) {
5505
+ return;
5506
+ }
5470
5507
  const sel = window.getSelection();
5471
5508
  if (!sel || !sel.rangeCount) {
5472
5509
  return;
@@ -5982,6 +6019,7 @@ var INSERT_TEXT = new Set([
5982
6019
  "insertTranspose"
5983
6020
  ]);
5984
6021
  var NATIVE_FORMAT_PREFIX = "format";
6022
+ var PROP_STRUCTURAL = new Set(["insertParagraph", "insertLineBreak"]);
5985
6023
  function classifyBeforeInput(ctx) {
5986
6024
  const { inputType, from, to } = ctx;
5987
6025
  if (inputType === "insertCompositionText" || inputType === "insertFromComposition") {
@@ -5996,6 +6034,9 @@ function classifyBeforeInput(ctx) {
5996
6034
  if (inputType === "insertFromDrop" || inputType === "deleteByDrag") {
5997
6035
  return { kind: "reject" };
5998
6036
  }
6037
+ if (ctx.inPropHost) {
6038
+ return PROP_STRUCTURAL.has(inputType) ? { kind: "reject" } : { kind: "native" };
6039
+ }
5999
6040
  if (!from || !to) {
6000
6041
  return { kind: "reject" };
6001
6042
  }
@@ -6103,6 +6144,11 @@ function startEditableRoot(container, deps) {
6103
6144
  composing = false;
6104
6145
  scheduleCommitTick();
6105
6146
  };
6147
+ const forget = () => {
6148
+ cancelCommitTick();
6149
+ activeEl2 = null;
6150
+ activeKey = null;
6151
+ };
6106
6152
  const deactivate = () => {
6107
6153
  if (!activeEl2) {
6108
6154
  return;
@@ -6177,6 +6223,7 @@ function startEditableRoot(container, deps) {
6177
6223
  atBlockStart: resolved ? isAtBlockStart(resolved.from) : false,
6178
6224
  data: e.data ?? "",
6179
6225
  from: resolved?.from ?? null,
6226
+ inPropHost: activeEl2 !== null && activeKey === null,
6180
6227
  inputType: e.inputType,
6181
6228
  to: resolved?.to ?? null
6182
6229
  });
@@ -6247,6 +6294,7 @@ function startEditableRoot(container, deps) {
6247
6294
  doc.addEventListener("pointerdown", onPointerDownCapture, true);
6248
6295
  return {
6249
6296
  capture: () => captureDocSelection(container, deps.isEditableBlock),
6297
+ forget,
6250
6298
  isComposing: () => composing,
6251
6299
  flush: () => {
6252
6300
  if (composing) {
@@ -6742,14 +6790,18 @@ function startIframeInlineEdit(channel, container, opts) {
6742
6790
  return true;
6743
6791
  }
6744
6792
  const node = getNodeAtPath(shadowDoc, hostPath);
6793
+ const attrs = node?.attributes;
6794
+ if (attrs?.[`props.${prop}`] !== undefined || attrs?.[prop] !== undefined) {
6795
+ return false;
6796
+ }
6745
6797
  const raw = node?.$props?.[prop];
6746
6798
  if (raw == null) {
6747
6799
  return true;
6748
6800
  }
6749
- if (typeof raw === "object") {
6801
+ if (typeof raw !== "string") {
6750
6802
  return false;
6751
6803
  }
6752
- return !(typeof raw === "string" && raw.includes("${"));
6804
+ return !raw.includes("${");
6753
6805
  };
6754
6806
  const enterPropEditAt = (el, hostPath, prop) => {
6755
6807
  channel.post({ kind: "editStart", path: hostPath, prop });
@@ -6764,6 +6816,7 @@ function startIframeInlineEdit(channel, container, opts) {
6764
6816
  onEnd: () => {
6765
6817
  clearHighlight();
6766
6818
  lastNonEmptyRange = null;
6819
+ root.forget();
6767
6820
  channel.post({ kind: "editEnd" });
6768
6821
  },
6769
6822
  onInsert: () => {},
@@ -6775,6 +6828,9 @@ function startIframeInlineEdit(channel, container, opts) {
6775
6828
  if (!el) {
6776
6829
  return;
6777
6830
  }
6831
+ if (isPlainSession()) {
6832
+ return;
6833
+ }
6778
6834
  if (lastNonEmptyRange && lastNonEmptyRange.startContainer.isConnected && el.contains(lastNonEmptyRange.commonAncestorContainer)) {
6779
6835
  const sel = window.getSelection();
6780
6836
  el.focus();
@@ -6889,6 +6945,14 @@ function startIframeInlineEdit(channel, container, opts) {
6889
6945
  if (!editingAllowed()) {
6890
6946
  return;
6891
6947
  }
6948
+ if (msg.prop !== undefined) {
6949
+ const instance = elementForPath2(container, msg.path);
6950
+ const marker = instance?.querySelector(`[data-jx-bound-prop="${CSS.escape(msg.prop)}"]`);
6951
+ if (marker) {
6952
+ activateProp(marker);
6953
+ }
6954
+ return;
6955
+ }
6892
6956
  const el = elementForPath2(container, msg.path);
6893
6957
  if (el && isEditableBlock(el)) {
6894
6958
  root.placeCaret({ offset: msg.offset ?? 0, path: msg.path });
@@ -8166,5 +8230,5 @@ export {
8166
8230
  startCanvasIframe
8167
8231
  };
8168
8232
 
8169
- //# debugId=244C80B20BAE5F4364756E2164756E21
8233
+ //# debugId=8BEF1FC0BD88A0F164756E2164756E21
8170
8234
  //# sourceMappingURL=iframe-entry.js.map
package/dist/studio.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  seedPublishConnected
3
- } from "./chunks/studio-vdpcpada.js";
3
+ } from "./chunks/studio-qymkxbcq.js";
4
4
  import"./chunks/studio-wdt9y92a.js";
5
5
  import {
6
6
  ElementResolutionController,
@@ -67,7 +67,7 @@ import {
67
67
  registerStyleCommands,
68
68
  render as render3,
69
69
  setInspectorTab
70
- } from "./chunks/studio-5amdrjm4.js";
70
+ } from "./chunks/studio-1d6bm7eg.js";
71
71
  import {
72
72
  CommandUnavailableError,
73
73
  chordFromEvent,
@@ -99,7 +99,7 @@ import {
99
99
  showSlashMenu,
100
100
  showSlashMenuAtRect,
101
101
  suppressBlockActionBar
102
- } from "./chunks/studio-yxzgjq8q.js";
102
+ } from "./chunks/studio-qb6vy1z1.js";
103
103
  import {
104
104
  addableContexts,
105
105
  defaultCommands,
@@ -111,7 +111,7 @@ import {
111
111
  tokenLabel,
112
112
  transposeStylebookStyle,
113
113
  writeTokenOverride
114
- } from "./chunks/studio-a11d3a1b.js";
114
+ } from "./chunks/studio-gfzyxa0m.js";
115
115
  import {
116
116
  DERIVE_PRESETS,
117
117
  JxColorPopover,
@@ -144,7 +144,7 @@ import {
144
144
  spNumberField,
145
145
  spTextArea,
146
146
  spTextField
147
- } from "./chunks/studio-hs8x23fv.js";
147
+ } from "./chunks/studio-n9k15qdg.js";
148
148
  import {
149
149
  LitElement,
150
150
  css
@@ -160,7 +160,7 @@ import {
160
160
  import {
161
161
  initQuickSearch,
162
162
  openQuickSearch
163
- } from "./chunks/studio-bak2cyjr.js";
163
+ } from "./chunks/studio-9sxn0eaa.js";
164
164
  import {
165
165
  addRecentProject,
166
166
  clearRecentProjects,
@@ -187,10 +187,10 @@ import {
187
187
  removeRecentProject,
188
188
  renderFilesTemplate,
189
189
  seedEntry
190
- } from "./chunks/studio-ncnet0ba.js";
190
+ } from "./chunks/studio-jand3kjg.js";
191
191
  import {
192
192
  computeWindow
193
- } from "./chunks/studio-cf4rk7kv.js";
193
+ } from "./chunks/studio-91sdrn9x.js";
194
194
  import {
195
195
  autoSyncProjectOnOpen,
196
196
  cleanupGitPanel,
@@ -211,13 +211,13 @@ import {
211
211
  registerSourceControlCommands,
212
212
  renderGitPanel,
213
213
  stripRange
214
- } from "./chunks/studio-4enps87v.js";
214
+ } from "./chunks/studio-tdt7b6qb.js";
215
215
  import {
216
216
  createPanelScheduler
217
217
  } from "./chunks/studio-8jpzqk5s.js";
218
218
  import {
219
219
  selectContributedEntry
220
- } from "./chunks/studio-cv606rs1.js";
220
+ } from "./chunks/studio-rz861p2b.js";
221
221
  import {
222
222
  notifySettingsDocument,
223
223
  onSettingsDocumentChanged,
@@ -238,7 +238,7 @@ import {
238
238
  renderForm,
239
239
  renderProvenanceChip,
240
240
  resolveContextPointer
241
- } from "./chunks/studio-zdkgwhkv.js";
241
+ } from "./chunks/studio-rkr6pm84.js";
242
242
  import {
243
243
  validateDoc,
244
244
  validateProjectConfig
@@ -263,7 +263,7 @@ import {
263
263
  registerGridCommands,
264
264
  scanLibrary,
265
265
  uploadDirForCategory
266
- } from "./chunks/studio-0e4h32g6.js";
266
+ } from "./chunks/studio-4m39t01b.js";
267
267
  import {
268
268
  live
269
269
  } from "./chunks/studio-c31yb4t6.js";
@@ -307,7 +307,7 @@ import {
307
307
  setIncludeDrafts,
308
308
  startFsSync,
309
309
  writesForTurn
310
- } from "./chunks/studio-x3etxs0s.js";
310
+ } from "./chunks/studio-gqpbrrj5.js";
311
311
  import"./chunks/studio-2w11grav.js";
312
312
  import"./chunks/studio-bw5dz6km.js";
313
313
  import {
@@ -316,18 +316,18 @@ import {
316
316
  isImage,
317
317
  setMediaChangedHandler,
318
318
  uploadAssets
319
- } from "./chunks/studio-4058pg09.js";
319
+ } from "./chunks/studio-0svvdm0a.js";
320
320
  import {
321
321
  beginActivity,
322
322
  registerActivityPanel,
323
323
  showProgressModal
324
- } from "./chunks/studio-3q65xvzv.js";
324
+ } from "./chunks/studio-d1f2rqxh.js";
325
325
  import {
326
326
  currentDeploy,
327
327
  latestDeployment,
328
328
  noteDeployment,
329
329
  platformSupportsPublish
330
- } from "./chunks/studio-5ne8mdx9.js";
330
+ } from "./chunks/studio-675v08qj.js";
331
331
  import {
332
332
  CATEGORIES,
333
333
  SPAN_FULL_RENDER,
@@ -439,7 +439,7 @@ import {
439
439
  timeSpan,
440
440
  timeSpanAsync,
441
441
  updateActivePanelHeaders
442
- } from "./chunks/studio-10s469da.js";
442
+ } from "./chunks/studio-h6553a8h.js";
443
443
  import {
444
444
  BOTTOM_TAB_IDS,
445
445
  BUFFER_COMMIT,
@@ -606,7 +606,7 @@ import {
606
606
  updateSiteConfig,
607
607
  view,
608
608
  workspace
609
- } from "./chunks/studio-hk523hby.js";
609
+ } from "./chunks/studio-kfq9ed73.js";
610
610
  import {
611
611
  classMap,
612
612
  ifDefined,
@@ -669,7 +669,7 @@ import {
669
669
  mountStillWanted,
670
670
  refreshExtensionUi,
671
671
  refreshFormats
672
- } from "./chunks/studio-50eed4c9.js";
672
+ } from "./chunks/studio-40fm3hyr.js";
673
673
  import {
674
674
  getPlatform,
675
675
  hasPlatform,
@@ -16807,7 +16807,7 @@ function entryIdsFor(collection) {
16807
16807
  if (known) {
16808
16808
  return known;
16809
16809
  }
16810
- const pending = import("./chunks/content-source-56xpvx83.js").then((m) => m.listCollectionEntryIds(collection));
16810
+ const pending = import("./chunks/content-source-cn9te96r.js").then((m) => m.listCollectionEntryIds(collection));
16811
16811
  entryIdCache.set(collection, pending);
16812
16812
  return pending;
16813
16813
  }
@@ -17068,7 +17068,7 @@ function renderEntryMode(surface, tab) {
17068
17068
  });
17069
17069
  }
17070
17070
  async function openEntryEditor(path) {
17071
- const { openFileInTab: openFileInTab2 } = await import("./chunks/files-gkz202dp.js");
17071
+ const { openFileInTab: openFileInTab2 } = await import("./chunks/files-dsh5g8j4.js");
17072
17072
  await openFileInTab2(path);
17073
17073
  for (const [id, tab] of workspace.tabs.entries()) {
17074
17074
  if (tab.documentPath !== path) {
@@ -17129,7 +17129,7 @@ async function createEntry(collectionName) {
17129
17129
  });
17130
17130
  return null;
17131
17131
  }
17132
- const { createFileIn: createFileIn2 } = await import("./chunks/files-gkz202dp.js");
17132
+ const { createFileIn: createFileIn2 } = await import("./chunks/files-dsh5g8j4.js");
17133
17133
  const created = await createFileIn2({
17134
17134
  content,
17135
17135
  dir: collection.dir,
@@ -22349,7 +22349,7 @@ function groupProblems(records = problems) {
22349
22349
  return groups;
22350
22350
  }
22351
22351
  function openProblemPath(path) {
22352
- import("./chunks/files-gkz202dp.js").then((m) => m.openFileInTab(path));
22352
+ import("./chunks/files-dsh5g8j4.js").then((m) => m.openFileInTab(path));
22353
22353
  }
22354
22354
  function actionTpl(record) {
22355
22355
  const id = record.action;
@@ -27264,6 +27264,7 @@ var cache2 = null;
27264
27264
  var proxyConfigured = false;
27265
27265
  var proxyManaged = false;
27266
27266
  var proxyDefaultModel = "";
27267
+ var proxyCode;
27267
27268
  var proxyProbe = null;
27268
27269
  var probeListeners = new Set;
27269
27270
  function invalidateModelCache() {
@@ -27271,6 +27272,7 @@ function invalidateModelCache() {
27271
27272
  proxyConfigured = false;
27272
27273
  proxyManaged = false;
27273
27274
  proxyDefaultModel = "";
27275
+ proxyCode = undefined;
27274
27276
  proxyProbe = null;
27275
27277
  }
27276
27278
  function ensureProxyProbe(onSettled) {
@@ -27292,6 +27294,9 @@ function isProxyConfigured() {
27292
27294
  function isManagedProxy() {
27293
27295
  return proxyManaged;
27294
27296
  }
27297
+ function proxyStateCode() {
27298
+ return proxyCode;
27299
+ }
27295
27300
  function siblingRoute(chatUrl, segment) {
27296
27301
  let url;
27297
27302
  try {
@@ -27326,6 +27331,7 @@ async function fetchAvailableModels(opts = {}) {
27326
27331
  proxyConfigured = data.configured === true;
27327
27332
  proxyManaged = data.managed === true;
27328
27333
  proxyDefaultModel = data.defaultModel ?? "";
27334
+ proxyCode = data.code;
27329
27335
  cache2 = (data.models || []).map((m) => ({ id: m.id, name: m.name || m.id }));
27330
27336
  return cache2;
27331
27337
  }
@@ -27490,17 +27496,22 @@ function createManagedConnect(opts) {
27490
27496
  if (!canOffer()) {
27491
27497
  return nothing;
27492
27498
  }
27499
+ const lapsed = proxyStateCode() === "cf_reconnect_required";
27500
+ const busyLabel = lapsed ? "Reconnecting…" : "Connecting…";
27493
27501
  return html`
27494
- <div class="ai-managed-connect">
27495
- <div>Use Workers AI on your own Cloudflare account — no API key needed.</div>
27502
+ <div class="ai-managed-connect" data-jx-recommended="cloudflare">
27503
+ <div class="ai-managed-connect-lede">
27504
+ ${lapsed ? "Your Cloudflare connection has expired. Reconnect to keep using the assistant." : "Recommended — run the assistant on Workers AI in your own Cloudflare account. No API key to create, copy or rotate."}
27505
+ </div>
27496
27506
  <sp-button
27497
27507
  size="s"
27508
+ variant="accent"
27498
27509
  ?disabled=${busy}
27499
27510
  @click=${() => {
27500
27511
  connect();
27501
27512
  }}
27502
27513
  >
27503
- ${busy ? "Connecting…" : "Connect Cloudflare"}
27514
+ ${busy ? busyLabel : lapsed ? "Reconnect Cloudflare" : "Connect Cloudflare"}
27504
27515
  </sp-button>
27505
27516
  ${connectError ? html`<div class="ai-managed-connect-error">${connectError}</div>` : nothing}
27506
27517
  <div class="ai-managed-connect-divider">— or bring your own key —</div>
@@ -28900,6 +28911,7 @@ function createCloudPlatform(project) {
28900
28911
  id: "cloud",
28901
28912
  projectRoot: root,
28902
28913
  canvasUrl: "/canvas.html",
28914
+ ...base ? { documentBaseUrl: `${base}/raw/` } : {},
28903
28915
  openProjectPicker: "repo-list",
28904
28916
  createDestination: "repo",
28905
28917
  async activate() {
@@ -58579,9 +58591,9 @@ function renderDefsEditor(container) {
58579
58591
 
58580
58592
  // src/version.ts
58581
58593
  var APP_NAME = "Jx Studio";
58582
- var VERSION = "2.3.0";
58583
- var BUILD_DATE = "2026-08-23T02:43:07.261Z";
58584
- var GIT_COMMIT = "968e4d4";
58594
+ var VERSION = "2.4.1";
58595
+ var BUILD_DATE = "2026-08-24T11:52:26.010Z";
58596
+ var GIT_COMMIT = "eb4fe5d";
58585
58597
  var LINKS = {
58586
58598
  github: "https://github.com/jxsuite/jx",
58587
58599
  docs: "https://github.com/jxsuite/jx#readme",
@@ -59658,7 +59670,7 @@ async function openProjectSettings(section) {
59658
59670
  showSettingsDocument();
59659
59671
  notifySettingsDocument();
59660
59672
  try {
59661
- const { syncExtensionSettingsSections } = await import("./chunks/extension-sections-g34239vm.js");
59673
+ const { syncExtensionSettingsSections } = await import("./chunks/extension-sections-mdgmqrry.js");
59662
59674
  await syncExtensionSettingsSections();
59663
59675
  } catch (error) {
59664
59676
  notify.warn(`Could not load extension settings sections — ${errorMessage(error)}`, {
@@ -60282,7 +60294,7 @@ function publishCommands() {
60282
60294
  name: "set_up_publishing"
60283
60295
  },
60284
60296
  run: async () => {
60285
- const { openPublishPanel } = await import("./chunks/publish-panel-ptddc3kg.js");
60297
+ const { openPublishPanel } = await import("./chunks/publish-panel-b0xh7b4y.js");
60286
60298
  openPublishPanel();
60287
60299
  },
60288
60300
  title: "Set Up Publishing"
@@ -61004,7 +61016,7 @@ function i18nCommands() {
61004
61016
  });
61005
61017
  return;
61006
61018
  }
61007
- const { openFileInTab: openFileInTab2 } = await import("./chunks/files-gkz202dp.js");
61019
+ const { openFileInTab: openFileInTab2 } = await import("./chunks/files-dsh5g8j4.js");
61008
61020
  await openFileInTab2(path);
61009
61021
  },
61010
61022
  title: "Open Translation"
@@ -61051,7 +61063,7 @@ function i18nCommands() {
61051
61063
  return;
61052
61064
  }
61053
61065
  const cut = path.lastIndexOf("/");
61054
- const { createFileIn: createFileIn2, openFileInTab: openFileInTab2 } = await import("./chunks/files-gkz202dp.js");
61066
+ const { createFileIn: createFileIn2, openFileInTab: openFileInTab2 } = await import("./chunks/files-dsh5g8j4.js");
61055
61067
  const created = await createFileIn2({
61056
61068
  content,
61057
61069
  dir: cut === -1 ? "." : path.slice(0, cut),
@@ -61864,5 +61876,5 @@ export {
61864
61876
  hasUnsavedTabs
61865
61877
  };
61866
61878
 
61867
- //# debugId=7167B13925BF1BB364756E2164756E21
61879
+ //# debugId=C20E21297FE0976064756E2164756E21
61868
61880
  //# sourceMappingURL=studio.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/studio",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "Jx Studio — visual builder for Jx documents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -57,13 +57,13 @@
57
57
  "dependencies": {
58
58
  "@atlaskit/pragmatic-drag-and-drop": "^3.0.0",
59
59
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.1.0",
60
- "@jxsuite/ai": "^0.36.1",
60
+ "@jxsuite/ai": "^0.36.2",
61
61
  "@jxsuite/collab": "^0.8.1",
62
- "@jxsuite/create": "^1.3.3",
63
- "@jxsuite/formulas": "^0.0.9",
62
+ "@jxsuite/create": "^1.3.5",
63
+ "@jxsuite/formulas": "^0.0.11",
64
64
  "@jxsuite/markup": "^0.4.5",
65
- "@jxsuite/protocol": "^1.1.2",
66
- "@jxsuite/runtime": "^2.0.2",
65
+ "@jxsuite/protocol": "^1.2.0",
66
+ "@jxsuite/runtime": "^2.0.4",
67
67
  "@jxsuite/schema": "^1.8.0",
68
68
  "@spectrum-web-components/accordion": "^1.12.2",
69
69
  "@spectrum-web-components/action-bar": "1.12.2",
@@ -19,7 +19,7 @@ import {
19
19
  getEffectiveMedia,
20
20
  resolveLayoutDoc,
21
21
  } from "../site-context";
22
- import { canvasBaseOrigin } from "./canvas-origin";
22
+ import { documentBase } from "./canvas-origin";
23
23
  import { componentRegistry, computeRelativePath } from "../files/components";
24
24
  import { prepareForEditMode } from "../utils/edit-display";
25
25
  import {
@@ -179,10 +179,8 @@ export async function resolveCanvasDocument(
179
179
  }
180
180
  }
181
181
 
182
- const root = projectState?.projectRoot || "";
183
- const docPrefix = root ? `${root}/` : "";
184
182
  const docBase = S.documentPath
185
- ? `${canvasBaseOrigin()}/${docPrefix}${S.documentPath}`
183
+ ? new URL(S.documentPath, documentBase(projectState?.projectRoot)).href
186
184
  : undefined;
187
185
 
188
186
  // Substitute chosen dynamic route params ({$ref: "#/$params/x"} → literal), inject state.$page,
@@ -25,6 +25,31 @@ export function canvasBaseOrigin(): string {
25
25
  return location.origin;
26
26
  }
27
27
 
28
+ /**
29
+ * The base the canvas fetches PROJECT FILES against — component `$ref`s, `$src` modules, images.
30
+ *
31
+ * Two shapes, and the platform chooses. Most hosts serve the project tree from their web root, so
32
+ * `<canvas origin>/<projectRoot>/` is a real URL: the dev server serves by path (absolute
33
+ * filesystem paths included) and the desktop loopback does the same. Those set nothing.
34
+ *
35
+ * A host whose `projectRoot` is an IDENTIFIER rather than a served path sets `documentBaseUrl`. Jx
36
+ * Cloud is one — its root is `owner/repo@branch`, and the default produced a URL nothing answered,
37
+ * so every `$ref` fetch fell through to the SPA fallback. That fallback returns the marketing page
38
+ * at **HTTP 200**, so `res.ok` passed and the runtime's `res.json()` died on `Unexpected token
39
+ * '<'`; images failed the same way and reported nothing.
40
+ *
41
+ * @param {string | undefined} projectRoot - The active project root, for the default shape.
42
+ * @returns {string} A base URL ending in `/`, safe to pass to `new URL(relativePath, base)`.
43
+ */
44
+ export function documentBase(projectRoot?: string): string {
45
+ const declared = hasPlatform() ? getPlatform().documentBaseUrl : undefined;
46
+ if (declared) {
47
+ return declared.endsWith("/") ? declared : `${declared}/`;
48
+ }
49
+ const root = projectRoot || "";
50
+ return `${canvasBaseOrigin()}/${root ? `${root}/` : ""}`;
51
+ }
52
+
28
53
  /**
29
54
  * Build an absolute loopback-origin src for a PROJECT asset path referenced by a PARENT-realm
30
55
  * (shell) preview <img>. On the views:// shell a relative "/images/foo.png" resolves to
@@ -49,6 +49,15 @@ export interface BeforeInputContext {
49
49
  atBlockEnd: boolean;
50
50
  /** The event's `data` (inserted text), empty for deletions. */
51
51
  data: string;
52
+ /**
53
+ * The caret is in a PROP-BOUND nested host — text inside a component instance that is an
54
+ * invertible prop binding (studio.md §8.2.5), not a block of the page document.
55
+ *
56
+ * Such a host has no `data-jx-path` of its own, so `from`/`to` are always null for it. Without
57
+ * this flag the null-position rule below rejected every keystroke: the caret appeared, and
58
+ * nothing typed into a component slot ever landed.
59
+ */
60
+ inPropHost: boolean;
52
61
  }
53
62
 
54
63
  /**
@@ -91,6 +100,20 @@ const INSERT_TEXT = new Set([
91
100
  */
92
101
  const NATIVE_FORMAT_PREFIX = "format";
93
102
 
103
+ /**
104
+ * Intents refused inside a prop-bound host, because a prop value is ONE PLAIN STRING.
105
+ *
106
+ * `docs/studio/editing/writing.md` puts it as "paragraph splits are off": there is no second block
107
+ * for Enter to make, and a `<br>` would serialise into a value that is supposed to be a single
108
+ * line. Everything else — insert, delete, paste, cut, IME — is ordinary text editing on a
109
+ * `plaintext-only` host and runs natively.
110
+ *
111
+ * These are refused rather than re-expressed: Enter FINISHES the edit (handled as a keystroke by
112
+ * the editing engine, not as an input event), so the chokepoint's only job is to stop the browser
113
+ * inserting the newline first.
114
+ */
115
+ const PROP_STRUCTURAL = new Set(["insertParagraph", "insertLineBreak"]);
116
+
94
117
  /**
95
118
  * Classify one `beforeinput`.
96
119
  *
@@ -124,7 +147,20 @@ export function classifyBeforeInput(ctx: BeforeInputContext): EditAction {
124
147
  return { kind: "reject" };
125
148
  }
126
149
 
127
- // 2. No resolvable position means the caret is in canvas chrome or a `contenteditable="false"`
150
+ /*
151
+ * 2. A prop-bound host is editable text with NO document path — it is written back as a prop
152
+ * value by its own commit (`editCommitProp`), not as a block. It must be judged before the
153
+ * null-position rule below, which would otherwise reject it for having no path: that is exactly
154
+ * what made a component slot show a caret and swallow every keystroke.
155
+ *
156
+ * Nothing here is structural, so nothing is re-expressed; the browser edits the text and the
157
+ * session commits the string.
158
+ */
159
+ if (ctx.inPropHost) {
160
+ return PROP_STRUCTURAL.has(inputType) ? { kind: "reject" } : { kind: "native" };
161
+ }
162
+
163
+ // 3. No resolvable position means the caret is in canvas chrome or a `contenteditable="false"`
128
164
  // Island — there is no document node to write to.
129
165
  if (!from || !to) {
130
166
  return { kind: "reject" };