@jxsuite/studio 2.4.2 → 3.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 (82) hide show
  1. package/dist/chunks/{cf-settings-g5fbf2zp.js → cf-settings-zj2cj1ar.js} +6 -3
  2. package/dist/chunks/{content-source-k9724vcv.js → content-source-7fy9qrm0.js} +10 -9
  3. package/dist/chunks/{convert-to-repeater-k10dvcc7.js → convert-to-repeater-6vvx96n2.js} +9 -8
  4. package/dist/chunks/{extension-sections-0ada0xcw.js → extension-sections-kwzvpemr.js} +13 -12
  5. package/dist/chunks/{files-yew27sn9.js → files-xcybanxe.js} +14 -13
  6. package/dist/chunks/{format-host-wewba4nq.js → format-host-8fr3545e.js} +2 -2
  7. package/dist/chunks/{git-panel-2dh1aw50.js → git-panel-g01d93vc.js} +9 -8
  8. package/dist/chunks/{layers-panel-qnsghwwd.js → layers-panel-27essgkc.js} +12 -11
  9. package/dist/chunks/{publish-panel-a0pdft5w.js → publish-panel-09sy2bab.js} +8 -7
  10. package/dist/chunks/{quick-search-9avyc4pc.js → quick-search-sefb61v8.js} +15 -14
  11. package/dist/chunks/{right-panel-kcnhtc5j.js → right-panel-9r32jd5j.js} +11 -10
  12. package/dist/chunks/{studio-aweyr4v1.js → studio-2x9zbxmx.js} +2 -2
  13. package/dist/chunks/{studio-ef8t53vc.js → studio-3dkcd4c5.js} +3 -3
  14. package/dist/chunks/{studio-hb7c46jy.js → studio-5efrtn1m.js} +9 -7
  15. package/dist/chunks/{studio-v1va0vhd.js → studio-6z5eyjwn.js} +24 -25
  16. package/dist/chunks/{studio-ke244pam.js → studio-7p35254k.js} +33 -11
  17. package/dist/chunks/{studio-f5yyvqxv.js → studio-8ezdegcx.js} +13 -11
  18. package/dist/chunks/{studio-cz2j1c0g.js → studio-a0yp7syn.js} +55 -18
  19. package/dist/chunks/{studio-26sv44br.js → studio-dc8s8heb.js} +34 -23
  20. package/dist/chunks/{studio-jnakhg87.js → studio-g5gdqnyd.js} +4 -4
  21. package/dist/chunks/{studio-rqemfhz6.js → studio-hcxzyk3x.js} +2 -2
  22. package/dist/chunks/{studio-ws6ef6js.js → studio-hj2se583.js} +12 -10
  23. package/dist/chunks/{studio-pyrw8h0w.js → studio-j595e748.js} +407 -167
  24. package/dist/chunks/{studio-dgj7gq44.js → studio-k4jvjcfx.js} +4 -4
  25. package/dist/chunks/studio-kfpek536.js +28 -0
  26. package/dist/chunks/{studio-sccge2wf.js → studio-p5dja96s.js} +2 -2
  27. package/dist/chunks/{studio-0b6fs05q.js → studio-pkp61mh8.js} +5 -5
  28. package/dist/chunks/studio-qpfq7xhm.js +1802 -0
  29. package/dist/chunks/{studio-ra7ar50f.js → studio-r5xbtctf.js} +51 -1565
  30. package/dist/chunks/{studio-v2c5pmjj.js → studio-rh1cnamv.js} +17 -15
  31. package/dist/chunks/{studio-28xc8ws4.js → studio-ry1vz5sv.js} +10 -8
  32. package/dist/chunks/{studio-hyn3vhwr.js → studio-xntfdy1x.js} +5 -5
  33. package/dist/iframe-entry.js +344 -26
  34. package/dist/studio.js +374 -248
  35. package/package.json +9 -9
  36. package/src/browse/library-layouts.ts +3 -3
  37. package/src/browse/library-pane.ts +2 -2
  38. package/src/browse/library-preview.ts +19 -3
  39. package/src/canvas/asset-refs.ts +119 -0
  40. package/src/canvas/asset-resolve.ts +256 -0
  41. package/src/canvas/canvas-live-render.ts +23 -15
  42. package/src/canvas/iframe-entry.ts +1 -0
  43. package/src/canvas/iframe-host.ts +4 -0
  44. package/src/canvas/iframe-protocol.ts +9 -0
  45. package/src/canvas/iframe-render.ts +28 -10
  46. package/src/canvas/site-style-css.ts +2 -6
  47. package/src/editor/file-drop-action.ts +2 -1
  48. package/src/files/files.ts +2 -2
  49. package/src/files/media-paths.ts +37 -14
  50. package/src/files/media-upload.ts +47 -6
  51. package/src/grid/grid-idle.ts +45 -0
  52. package/src/grid/grid-view.ts +21 -0
  53. package/src/grid/schema-columns.ts +2 -1
  54. package/src/grid/sources/content-source.ts +24 -3
  55. package/src/new-project/import-tab.ts +3 -2
  56. package/src/panels/ai-chat/composer.ts +30 -13
  57. package/src/panels/frontmatter-fields.ts +6 -2
  58. package/src/panels/properties-panel.ts +8 -2
  59. package/src/panels/seo-modal.ts +5 -6
  60. package/src/panels/signals-panel.ts +2 -1
  61. package/src/platforms/cloud.ts +75 -5
  62. package/src/platforms/devserver.ts +5 -1
  63. package/src/services/ai-models.ts +101 -11
  64. package/src/services/ai-settings.ts +69 -71
  65. package/src/services/cf-settings.ts +19 -37
  66. package/src/services/document-assistant.ts +4 -3
  67. package/src/services/idle.ts +6 -1
  68. package/src/services/settings/definitions.ts +164 -0
  69. package/src/services/settings/kernel.ts +335 -0
  70. package/src/services/settings/write-queue.ts +116 -0
  71. package/src/settings/preferences-accounts.ts +6 -19
  72. package/src/settings/preferences-keymap.ts +8 -18
  73. package/src/settings/schema-field-ui.ts +2 -2
  74. package/src/shell.ts +6 -14
  75. package/src/studio.ts +32 -1
  76. package/src/types.ts +61 -3
  77. package/src/ui/ai-credentials-form.ts +52 -27
  78. package/src/ui/media-picker.ts +6 -11
  79. package/src/utils/studio-utils.ts +45 -3
  80. package/dist/chunks/studio-a7pzveq1.js +0 -110
  81. package/src/canvas/content-assets.ts +0 -263
  82. package/src/services/settings-store.ts +0 -99
package/dist/studio.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  seedPublishConnected
3
- } from "./chunks/studio-dgj7gq44.js";
3
+ } from "./chunks/studio-k4jvjcfx.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-f5yyvqxv.js";
70
+ } from "./chunks/studio-8ezdegcx.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-ws6ef6js.js";
102
+ } from "./chunks/studio-hj2se583.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-hyn3vhwr.js";
114
+ } from "./chunks/studio-xntfdy1x.js";
115
115
  import {
116
116
  DERIVE_PRESETS,
117
117
  JxColorPopover,
@@ -144,23 +144,20 @@ import {
144
144
  spNumberField,
145
145
  spTextArea,
146
146
  spTextField
147
- } from "./chunks/studio-v1va0vhd.js";
147
+ } from "./chunks/studio-6z5eyjwn.js";
148
148
  import {
149
149
  LitElement,
150
150
  css
151
151
  } from "./chunks/studio-0rqqhrdy.js";
152
152
  import {
153
+ clearCfConnection,
153
154
  getCfAccountId,
154
- getCfToken,
155
- hydrateSettings,
156
- persistSettings,
157
- setCfAccountId,
158
- setCfToken
159
- } from "./chunks/studio-a7pzveq1.js";
155
+ getCfToken
156
+ } from "./chunks/studio-kfpek536.js";
160
157
  import {
161
158
  initQuickSearch,
162
159
  openQuickSearch
163
- } from "./chunks/studio-jnakhg87.js";
160
+ } from "./chunks/studio-g5gdqnyd.js";
164
161
  import {
165
162
  addRecentProject,
166
163
  clearRecentProjects,
@@ -187,10 +184,10 @@ import {
187
184
  removeRecentProject,
188
185
  renderFilesTemplate,
189
186
  seedEntry
190
- } from "./chunks/studio-v2c5pmjj.js";
187
+ } from "./chunks/studio-rh1cnamv.js";
191
188
  import {
192
189
  computeWindow
193
- } from "./chunks/studio-sccge2wf.js";
190
+ } from "./chunks/studio-p5dja96s.js";
194
191
  import {
195
192
  autoSyncProjectOnOpen,
196
193
  cleanupGitPanel,
@@ -208,16 +205,17 @@ import {
208
205
  platformSupportsClone,
209
206
  refreshGitStatus,
210
207
  registerGitPanel,
208
+ registerGridProbe,
211
209
  registerSourceControlCommands,
212
210
  renderGitPanel,
213
211
  stripRange
214
- } from "./chunks/studio-ke244pam.js";
212
+ } from "./chunks/studio-7p35254k.js";
215
213
  import {
216
214
  createPanelScheduler
217
215
  } from "./chunks/studio-8jpzqk5s.js";
218
216
  import {
219
217
  selectContributedEntry
220
- } from "./chunks/studio-hb7c46jy.js";
218
+ } from "./chunks/studio-5efrtn1m.js";
221
219
  import {
222
220
  notifySettingsDocument,
223
221
  onSettingsDocumentChanged,
@@ -238,7 +236,7 @@ import {
238
236
  renderForm,
239
237
  renderProvenanceChip,
240
238
  resolveContextPointer
241
- } from "./chunks/studio-ef8t53vc.js";
239
+ } from "./chunks/studio-3dkcd4c5.js";
242
240
  import {
243
241
  validateDoc,
244
242
  validateProjectConfig
@@ -263,7 +261,7 @@ import {
263
261
  registerGridCommands,
264
262
  scanLibrary,
265
263
  uploadDirForCategory
266
- } from "./chunks/studio-0b6fs05q.js";
264
+ } from "./chunks/studio-pkp61mh8.js";
267
265
  import {
268
266
  live
269
267
  } from "./chunks/studio-c31yb4t6.js";
@@ -307,27 +305,28 @@ import {
307
305
  setIncludeDrafts,
308
306
  startFsSync,
309
307
  writesForTurn
310
- } from "./chunks/studio-26sv44br.js";
308
+ } from "./chunks/studio-dc8s8heb.js";
311
309
  import"./chunks/studio-2w11grav.js";
312
310
  import"./chunks/studio-bw5dz6km.js";
313
311
  import {
314
- UPLOAD_ACCEPT,
315
312
  invalidateUsages,
316
313
  isImage,
314
+ previewFileSrc,
317
315
  setMediaChangedHandler,
316
+ uploadAccept,
318
317
  uploadAssets
319
- } from "./chunks/studio-cz2j1c0g.js";
318
+ } from "./chunks/studio-a0yp7syn.js";
320
319
  import {
321
320
  beginActivity,
322
321
  registerActivityPanel,
323
322
  showProgressModal
324
- } from "./chunks/studio-28xc8ws4.js";
323
+ } from "./chunks/studio-ry1vz5sv.js";
325
324
  import {
326
325
  currentDeploy,
327
326
  latestDeployment,
328
327
  noteDeployment,
329
328
  platformSupportsPublish
330
- } from "./chunks/studio-aweyr4v1.js";
329
+ } from "./chunks/studio-2x9zbxmx.js";
331
330
  import {
332
331
  CATEGORIES,
333
332
  SPAN_FULL_RENDER,
@@ -339,6 +338,7 @@ import {
339
338
  applyDropInstruction,
340
339
  applyEditZoom,
341
340
  applyTransform,
341
+ assetContextFor,
342
342
  beginDragSession,
343
343
  bubbleInlinePath,
344
344
  buildScope,
@@ -368,6 +368,7 @@ import {
368
368
  getPanel,
369
369
  getPreviewNavigateHandler,
370
370
  hasElement,
371
+ hostAssetDeclarations,
371
372
  hostDragGeometry,
372
373
  invalidateParamValues,
373
374
  isCaretActive,
@@ -377,7 +378,6 @@ import {
377
378
  listPanels,
378
379
  liveDragHostAt,
379
380
  loadParamValues,
380
- loopbackAssetSrc,
381
381
  markExplicitZoom,
382
382
  mediaDisplayName,
383
383
  mountIframeCanvas,
@@ -414,8 +414,10 @@ import {
414
414
  renderNode,
415
415
  requestEditZoom,
416
416
  resetZoom,
417
+ resolveAssetRef,
417
418
  runScoped,
418
419
  sawIframeDragOver,
420
+ setCanvasAssetResolver,
419
421
  setCanvasContextMenuHandler,
420
422
  setCanvasPointerDownHandler,
421
423
  setCanvasSlashHandler,
@@ -439,7 +441,7 @@ import {
439
441
  timeSpan,
440
442
  timeSpanAsync,
441
443
  updateActivePanelHeaders
442
- } from "./chunks/studio-pyrw8h0w.js";
444
+ } from "./chunks/studio-j595e748.js";
443
445
  import {
444
446
  BOTTOM_TAB_IDS,
445
447
  BUFFER_COMMIT,
@@ -472,7 +474,6 @@ import {
472
474
  canUndo,
473
475
  canonicalizeLocale,
474
476
  canvasViewForMode,
475
- clearProblems,
476
477
  closeAllTabs,
477
478
  closeTab,
478
479
  collabReadOnly,
@@ -485,13 +486,10 @@ import {
485
486
  configureCollabParser,
486
487
  configureCollabSerializer,
487
488
  createPaneSurface,
488
- dismiss,
489
489
  disposePaneSurface,
490
490
  editorKindForMode,
491
491
  editorKindOf,
492
492
  editorKindsOf,
493
- effect,
494
- effectScope,
495
493
  emptyContext,
496
494
  endBatch,
497
495
  enumArg,
@@ -514,6 +512,7 @@ import {
514
512
  isBatching,
515
513
  isBottomTabId,
516
514
  isCollabEnabled,
515
+ isMediaFormat,
517
516
  isModalOpen,
518
517
  isPaneFocused,
519
518
  isWellFormedLocale,
@@ -537,8 +536,6 @@ import {
537
536
  mutateUpdateStyle,
538
537
  navigatorPanelRegion,
539
538
  nextRenderGeneration,
540
- notify,
541
- now,
542
539
  nullablePathArg,
543
540
  openModal,
544
541
  openTab,
@@ -552,11 +549,8 @@ import {
552
549
  pathProperty,
553
550
  persistDocks,
554
551
  primarySelection,
555
- problemCount,
556
- problems,
557
552
  promoteDirtyPreviewTabs,
558
553
  promoteTab,
559
- reactive,
560
554
  receivingPane,
561
555
  recordDocOp,
562
556
  redo,
@@ -595,7 +589,6 @@ import {
595
589
  tabBufferUnsaved,
596
590
  tabIsLive,
597
591
  toCamelCase,
598
- toRaw,
599
592
  toggleActivityTab,
600
593
  transact,
601
594
  transactDoc,
@@ -606,7 +599,7 @@ import {
606
599
  updateSiteConfig,
607
600
  view,
608
601
  workspace
609
- } from "./chunks/studio-ra7ar50f.js";
602
+ } from "./chunks/studio-r5xbtctf.js";
610
603
  import {
611
604
  classMap,
612
605
  ifDefined,
@@ -658,6 +651,28 @@ import {
658
651
  updateSession,
659
652
  updateUi
660
653
  } from "./chunks/studio-cjm7fdjf.js";
654
+ import {
655
+ SETTINGS,
656
+ clearProblems,
657
+ clearSettings,
658
+ dismiss,
659
+ effect,
660
+ effectScope,
661
+ hasSetting,
662
+ hydrateSettings,
663
+ notify,
664
+ now,
665
+ onSettingsChanged,
666
+ problemCount,
667
+ problems,
668
+ reactive,
669
+ readStoredSetting,
670
+ setSetting,
671
+ setSettings,
672
+ settingsSettled,
673
+ toRaw,
674
+ watchRemoteSettings
675
+ } from "./chunks/studio-qpfq7xhm.js";
661
676
  import {
662
677
  defaultContentFormat,
663
678
  formatByName,
@@ -669,7 +684,7 @@ import {
669
684
  mountStillWanted,
670
685
  refreshExtensionUi,
671
686
  refreshFormats
672
- } from "./chunks/studio-rqemfhz6.js";
687
+ } from "./chunks/studio-hcxzyk3x.js";
673
688
  import {
674
689
  getPlatform,
675
690
  hasPlatform,
@@ -15567,6 +15582,16 @@ function createGridView(host, controller) {
15567
15582
  refreshData();
15568
15583
  }
15569
15584
  });
15585
+ const probe = () => {
15586
+ if (!built) {
15587
+ return `grid[${gridId}]: building`;
15588
+ }
15589
+ if (controller.effectiveRows().length > 0 && table.getRanges().length === 0) {
15590
+ return `grid[${gridId}]: selection range not laid out`;
15591
+ }
15592
+ return null;
15593
+ };
15594
+ const unregisterProbe = registerGridProbe(probe);
15570
15595
  table.on("cellEdited", (cell) => {
15571
15596
  if (suppress) {
15572
15597
  return;
@@ -15640,6 +15665,7 @@ function createGridView(host, controller) {
15640
15665
  }, true);
15641
15666
  return {
15642
15667
  destroy() {
15668
+ unregisterProbe();
15643
15669
  controller.bindView(null);
15644
15670
  table.destroy();
15645
15671
  },
@@ -16807,7 +16833,7 @@ function entryIdsFor(collection) {
16807
16833
  if (known) {
16808
16834
  return known;
16809
16835
  }
16810
- const pending = import("./chunks/content-source-k9724vcv.js").then((m) => m.listCollectionEntryIds(collection));
16836
+ const pending = import("./chunks/content-source-7fy9qrm0.js").then((m) => m.listCollectionEntryIds(collection));
16811
16837
  entryIdCache.set(collection, pending);
16812
16838
  return pending;
16813
16839
  }
@@ -17068,7 +17094,7 @@ function renderEntryMode(surface, tab) {
17068
17094
  });
17069
17095
  }
17070
17096
  async function openEntryEditor(path) {
17071
- const { openFileInTab: openFileInTab2 } = await import("./chunks/files-yew27sn9.js");
17097
+ const { openFileInTab: openFileInTab2 } = await import("./chunks/files-xcybanxe.js");
17072
17098
  await openFileInTab2(path);
17073
17099
  for (const [id, tab] of workspace.tabs.entries()) {
17074
17100
  if (tab.documentPath !== path) {
@@ -17129,7 +17155,7 @@ async function createEntry(collectionName) {
17129
17155
  });
17130
17156
  return null;
17131
17157
  }
17132
- const { createFileIn: createFileIn2 } = await import("./chunks/files-yew27sn9.js");
17158
+ const { createFileIn: createFileIn2 } = await import("./chunks/files-xcybanxe.js");
17133
17159
  const created = await createFileIn2({
17134
17160
  content,
17135
17161
  dir: collection.dir,
@@ -17288,6 +17314,7 @@ async function renderDocPreview(filePath) {
17288
17314
  try {
17289
17315
  const content = await getPlatform().readFile(filePath);
17290
17316
  setSkipServerFunctions(true);
17317
+ const assets = assetContextFor(filePath, hostAssetDeclarations());
17291
17318
  await loadFormats();
17292
17319
  let document_;
17293
17320
  if (formatForPath(filePath)) {
@@ -17297,8 +17324,13 @@ async function renderDocPreview(filePath) {
17297
17324
  document_ = JSON.parse(content);
17298
17325
  }
17299
17326
  const scope = await buildScope(document_, {}, location.href);
17300
- const element = renderNode(document_, scope);
17301
- return element instanceof HTMLElement ? element : null;
17327
+ setCanvasAssetResolver((value) => resolveAssetRef(value, assets));
17328
+ try {
17329
+ const element = renderNode(document_, scope);
17330
+ return element instanceof HTMLElement ? element : null;
17331
+ } finally {
17332
+ setCanvasAssetResolver(null);
17333
+ }
17302
17334
  } catch {
17303
17335
  return null;
17304
17336
  }
@@ -17438,7 +17470,7 @@ function previewTpl(file, ctx) {
17438
17470
  return html`<img
17439
17471
  class="library-thumb"
17440
17472
  loading="lazy"
17441
- src=${loopbackAssetSrc(`/${file.path}`)}
17473
+ src=${previewFileSrc(file.path)}
17442
17474
  alt=""
17443
17475
  />`;
17444
17476
  }
@@ -17512,7 +17544,7 @@ function mediaTpl(files, ctx) {
17512
17544
  ${isImage(file.ext) ? html`<img
17513
17545
  class="library-thumb"
17514
17546
  loading="lazy"
17515
- src=${loopbackAssetSrc(`/${file.path}`)}
17547
+ src=${previewFileSrc(file.path)}
17516
17548
  alt=""
17517
17549
  />` : html`<sp-icon-document size="l"></sp-icon-document>`}
17518
17550
  </div>
@@ -18013,7 +18045,7 @@ function toolbarTpl2(panel, files) {
18013
18045
  <input
18014
18046
  type="file"
18015
18047
  multiple
18016
- accept=${UPLOAD_ACCEPT}
18048
+ accept=${uploadAccept()}
18017
18049
  class="library-upload-input"
18018
18050
  @change=${(e) => {
18019
18051
  const input2 = e.target;
@@ -18927,7 +18959,7 @@ function renderFmField(tab, field, entry, value, requiredFields) {
18927
18959
  `
18928
18960
  });
18929
18961
  }
18930
- if (entry.format === "image") {
18962
+ if (isMediaFormat(entry.format)) {
18931
18963
  return renderFieldRow({
18932
18964
  hasValue: hasVal,
18933
18965
  label: displayLabel,
@@ -22349,7 +22381,7 @@ function groupProblems(records = problems) {
22349
22381
  return groups;
22350
22382
  }
22351
22383
  function openProblemPath(path) {
22352
- import("./chunks/files-yew27sn9.js").then((m) => m.openFileInTab(path));
22384
+ import("./chunks/files-xcybanxe.js").then((m) => m.openFileInTab(path));
22353
22385
  }
22354
22386
  function actionTpl(record) {
22355
22387
  const id = record.action;
@@ -24231,7 +24263,7 @@ function resolveFileDropTarget(hit, kind, registry = componentRegistry) {
24231
24263
  }
24232
24264
  if (hit.tagName.includes("-")) {
24233
24265
  const entry = registry.find((c) => c.tagName === hit.tagName);
24234
- const imageProps = (entry?.props ?? []).filter((p) => p.format === "image");
24266
+ const imageProps = (entry?.props ?? []).filter((p) => isMediaFormat(p.format));
24235
24267
  if (imageProps.length === 1) {
24236
24268
  return { mode: "replace-prop", path: hit.path, prop: imageProps[0].name };
24237
24269
  }
@@ -26702,66 +26734,139 @@ function buildProjectSummary({
26702
26734
  }
26703
26735
 
26704
26736
  // src/services/ai-settings.ts
26705
- var KEY_STORAGE = "jx.ai.openaiKey";
26706
- var BASE_URL_STORAGE = "jx.ai.baseUrl";
26707
- var MODEL_STORAGE = "jx.ai.model";
26708
- var DEFAULT_MODEL = "gpt-4o";
26709
26737
  function getOpenAiKey() {
26710
- try {
26711
- return globalThis.localStorage?.getItem(KEY_STORAGE) ?? "";
26712
- } catch {
26713
- return "";
26714
- }
26738
+ return readStoredSetting(SETTINGS.aiOpenAiKey);
26715
26739
  }
26716
26740
  function setOpenAiKey(key) {
26717
- try {
26718
- const trimmed = (key || "").trim();
26719
- if (trimmed) {
26720
- globalThis.localStorage?.setItem(KEY_STORAGE, trimmed);
26721
- } else {
26722
- globalThis.localStorage?.removeItem(KEY_STORAGE);
26723
- }
26724
- persistSettings();
26725
- } catch {}
26741
+ setSetting(SETTINGS.aiOpenAiKey, key || "");
26726
26742
  }
26727
26743
  function hasOpenAiKey() {
26728
- return getOpenAiKey().length > 0;
26744
+ return hasSetting(SETTINGS.aiOpenAiKey);
26729
26745
  }
26730
26746
  function getBaseUrl() {
26731
- try {
26732
- return globalThis.localStorage?.getItem(BASE_URL_STORAGE) ?? "";
26733
- } catch {
26734
- return "";
26747
+ return readStoredSetting(SETTINGS.aiBaseUrl);
26748
+ }
26749
+ function storedModel() {
26750
+ return readStoredSetting(SETTINGS.aiModel);
26751
+ }
26752
+ function setModel(modelId) {
26753
+ setSetting(SETTINGS.aiModel, modelId || "");
26754
+ }
26755
+ function saveAiProvider(provider) {
26756
+ setSettings([
26757
+ [SETTINGS.aiOpenAiKey, provider.apiKey],
26758
+ [SETTINGS.aiBaseUrl, provider.baseUrl],
26759
+ [SETTINGS.aiModel, provider.model]
26760
+ ]);
26761
+ }
26762
+ function clearAiProvider() {
26763
+ clearSettings([SETTINGS.aiOpenAiKey, SETTINGS.aiBaseUrl, SETTINGS.aiModel]);
26764
+ }
26765
+
26766
+ // src/services/ai-models.ts
26767
+ function aiConnection() {
26768
+ return { apiKey: getOpenAiKey(), baseUrl: getBaseUrl() };
26769
+ }
26770
+ function fingerprint(credentials) {
26771
+ return `${credentials.apiKey}\x00${credentials.baseUrl}`;
26772
+ }
26773
+ var cache2 = null;
26774
+ var cacheKey = "";
26775
+ var proxyConfigured = false;
26776
+ var proxyManaged = false;
26777
+ var proxyDefaultModel = "";
26778
+ var proxyCode;
26779
+ var proxyProbe = null;
26780
+ var probeListeners = new Set;
26781
+ function resetModelCache() {
26782
+ cache2 = null;
26783
+ cacheKey = "";
26784
+ proxyConfigured = false;
26785
+ proxyManaged = false;
26786
+ proxyDefaultModel = "";
26787
+ proxyCode = undefined;
26788
+ proxyProbe = null;
26789
+ }
26790
+ var CONNECTION_KEYS = new Set([
26791
+ SETTINGS.aiOpenAiKey.key,
26792
+ SETTINGS.aiBaseUrl.key,
26793
+ SETTINGS.cfToken.key,
26794
+ SETTINGS.cfAccountId.key
26795
+ ]);
26796
+ onSettingsChanged((keys) => {
26797
+ if (keys.some((key) => CONNECTION_KEYS.has(key))) {
26798
+ resetModelCache();
26735
26799
  }
26800
+ });
26801
+ function cachedModels(credentials) {
26802
+ return cache2 && cacheKey === fingerprint(credentials) ? cache2 : null;
26736
26803
  }
26737
- function setBaseUrl(url) {
26738
- try {
26739
- const trimmed = (url || "").trim().replace(/\/+$/, "");
26740
- if (trimmed) {
26741
- globalThis.localStorage?.setItem(BASE_URL_STORAGE, trimmed);
26742
- } else {
26743
- globalThis.localStorage?.removeItem(BASE_URL_STORAGE);
26804
+ function ensureProxyProbe(onSettled) {
26805
+ if (onSettled) {
26806
+ probeListeners.add(onSettled);
26807
+ }
26808
+ proxyProbe ??= fetchAvailableModels({ force: true }).catch(() => {}).then(() => {
26809
+ for (const notify2 of probeListeners) {
26810
+ notify2();
26744
26811
  }
26745
- persistSettings();
26746
- } catch {}
26812
+ });
26747
26813
  }
26748
- function getModel() {
26814
+ function hasAiCredentials() {
26815
+ return hasOpenAiKey() || isProxyConfigured();
26816
+ }
26817
+ function isProxyConfigured() {
26818
+ return proxyConfigured;
26819
+ }
26820
+ function isManagedProxy() {
26821
+ return proxyManaged;
26822
+ }
26823
+ function proxyStateCode() {
26824
+ return proxyCode;
26825
+ }
26826
+ function getProxyDefaultModel() {
26827
+ return proxyDefaultModel;
26828
+ }
26829
+ function preferredModel() {
26830
+ return storedModel() || getProxyDefaultModel() || SETTINGS.aiModel.default;
26831
+ }
26832
+ function siblingRoute(chatUrl, segment) {
26833
+ let url;
26749
26834
  try {
26750
- return globalThis.localStorage?.getItem(MODEL_STORAGE) || DEFAULT_MODEL;
26835
+ url = new URL(chatUrl, "http://jx.invalid");
26751
26836
  } catch {
26752
- return DEFAULT_MODEL;
26837
+ return chatUrl;
26753
26838
  }
26839
+ url.pathname = url.pathname.replace(/[^/]*$/, segment);
26840
+ return /^https?:/i.test(chatUrl) ? url.href : `${url.pathname}${url.search}`;
26754
26841
  }
26755
- function setModel(modelId) {
26756
- try {
26757
- const trimmed = (modelId || "").trim();
26758
- if (trimmed) {
26759
- globalThis.localStorage?.setItem(MODEL_STORAGE, trimmed);
26760
- } else {
26761
- globalThis.localStorage?.removeItem(MODEL_STORAGE);
26762
- }
26763
- persistSettings();
26764
- } catch {}
26842
+ async function fetchAvailableModels(opts = {}) {
26843
+ const credentials = opts.credentials ?? aiConnection();
26844
+ const key = fingerprint(credentials);
26845
+ if (cache2 && cacheKey === key && !opts.force) {
26846
+ return cache2;
26847
+ }
26848
+ const plat = getPlatform();
26849
+ const chatUrl = await Promise.resolve(plat.aiChatUrl());
26850
+ const modelsUrl = siblingRoute(chatUrl, "models");
26851
+ const headers = {};
26852
+ if (credentials.apiKey) {
26853
+ headers["X-Api-Key"] = credentials.apiKey;
26854
+ }
26855
+ if (credentials.baseUrl) {
26856
+ headers["X-Api-Base-URL"] = credentials.baseUrl;
26857
+ }
26858
+ const resp = await fetch(modelsUrl, { headers });
26859
+ if (!resp.ok) {
26860
+ throw new Error(`HTTP ${resp.status}`);
26861
+ }
26862
+ const data = await resp.json();
26863
+ proxyConfigured = data.configured === true;
26864
+ proxyManaged = data.managed === true;
26865
+ proxyDefaultModel = data.defaultModel ?? "";
26866
+ proxyCode = data.code;
26867
+ cache2 = (data.models || []).map((m) => ({ id: m.id, name: m.name || m.id }));
26868
+ cacheKey = key;
26869
+ return cache2;
26765
26870
  }
26766
26871
 
26767
26872
  // src/services/context-manager.ts
@@ -27068,7 +27173,7 @@ function projectRoot() {
27068
27173
  return workspace.projectRoot || "";
27069
27174
  }
27070
27175
  function createDocumentAssistant() {
27071
- const chatState = createChatState({ model: getModel() });
27176
+ const chatState = createChatState({ model: preferredModel() });
27072
27177
  let sessionId = null;
27073
27178
  const getProjectStyle = () => workspace.projectConfig?.style;
27074
27179
  const findOpenTab = (path) => {
@@ -27160,7 +27265,7 @@ function createDocumentAssistant() {
27160
27265
  try {
27161
27266
  const plat = getPlatform();
27162
27267
  const chatUrl = await Promise.resolve(plat.aiChatUrl());
27163
- chatState.setModel(getModel());
27268
+ chatState.setModel(preferredModel());
27164
27269
  const streamingClient = createProxyStreamingClient({
27165
27270
  chatUrl,
27166
27271
  model: chatState.model,
@@ -27259,83 +27364,6 @@ function createDocumentAssistant() {
27259
27364
  };
27260
27365
  }
27261
27366
 
27262
- // src/services/ai-models.ts
27263
- var cache2 = null;
27264
- var proxyConfigured = false;
27265
- var proxyManaged = false;
27266
- var proxyDefaultModel = "";
27267
- var proxyCode;
27268
- var proxyProbe = null;
27269
- var probeListeners = new Set;
27270
- function invalidateModelCache() {
27271
- cache2 = null;
27272
- proxyConfigured = false;
27273
- proxyManaged = false;
27274
- proxyDefaultModel = "";
27275
- proxyCode = undefined;
27276
- proxyProbe = null;
27277
- }
27278
- function ensureProxyProbe(onSettled) {
27279
- if (onSettled) {
27280
- probeListeners.add(onSettled);
27281
- }
27282
- proxyProbe ??= fetchAvailableModels({ force: true }).catch(() => {}).then(() => {
27283
- for (const notify2 of probeListeners) {
27284
- notify2();
27285
- }
27286
- });
27287
- }
27288
- function hasAiCredentials() {
27289
- return hasOpenAiKey() || isProxyConfigured();
27290
- }
27291
- function isProxyConfigured() {
27292
- return proxyConfigured;
27293
- }
27294
- function isManagedProxy() {
27295
- return proxyManaged;
27296
- }
27297
- function proxyStateCode() {
27298
- return proxyCode;
27299
- }
27300
- function siblingRoute(chatUrl, segment) {
27301
- let url;
27302
- try {
27303
- url = new URL(chatUrl, "http://jx.invalid");
27304
- } catch {
27305
- return chatUrl;
27306
- }
27307
- url.pathname = url.pathname.replace(/[^/]*$/, segment);
27308
- return /^https?:/i.test(chatUrl) ? url.href : `${url.pathname}${url.search}`;
27309
- }
27310
- async function fetchAvailableModels(opts = {}) {
27311
- if (cache2 && !opts.force) {
27312
- return cache2;
27313
- }
27314
- const plat = getPlatform();
27315
- const chatUrl = await Promise.resolve(plat.aiChatUrl());
27316
- const modelsUrl = siblingRoute(chatUrl, "models");
27317
- const headers = {};
27318
- const apiKey = opts.apiKey || getOpenAiKey();
27319
- if (apiKey) {
27320
- headers["X-Api-Key"] = apiKey;
27321
- }
27322
- const baseUrl = opts.baseUrl || getBaseUrl();
27323
- if (baseUrl) {
27324
- headers["X-Api-Base-URL"] = baseUrl;
27325
- }
27326
- const resp = await fetch(modelsUrl, { headers });
27327
- if (!resp.ok) {
27328
- throw new Error(`HTTP ${resp.status}`);
27329
- }
27330
- const data = await resp.json();
27331
- proxyConfigured = data.configured === true;
27332
- proxyManaged = data.managed === true;
27333
- proxyDefaultModel = data.defaultModel ?? "";
27334
- proxyCode = data.code;
27335
- cache2 = (data.models || []).map((m) => ({ id: m.id, name: m.name || m.id }));
27336
- return cache2;
27337
- }
27338
-
27339
27367
  // src/ui/ai-credentials-form.ts
27340
27368
  function createAiCredentialsForm(opts) {
27341
27369
  let keyDraft = "";
@@ -27344,31 +27372,28 @@ function createAiCredentialsForm(opts) {
27344
27372
  let availableModels = [];
27345
27373
  let modelsLoading = false;
27346
27374
  let modelsError = "";
27347
- function startEdit() {
27375
+ function loadDrafts() {
27348
27376
  keyDraft = getOpenAiKey();
27349
27377
  baseUrlDraft = getBaseUrl();
27350
- modelDraft = getModel();
27378
+ modelDraft = storedModel();
27379
+ }
27380
+ function startEdit() {
27381
+ loadDrafts();
27351
27382
  opts.requestRender();
27352
27383
  if (availableModels.length === 0 && !modelsLoading) {
27353
27384
  fetchModels();
27354
27385
  }
27355
27386
  }
27356
27387
  function save() {
27357
- setOpenAiKey(keyDraft);
27358
- setBaseUrl(baseUrlDraft);
27359
- setModel(modelDraft);
27360
- keyDraft = "";
27361
- baseUrlDraft = "";
27362
- modelDraft = "";
27363
- availableModels = [];
27364
- invalidateModelCache();
27388
+ saveAiProvider({ apiKey: keyDraft, baseUrl: baseUrlDraft, model: modelDraft });
27389
+ loadDrafts();
27390
+ modelsError = "";
27365
27391
  opts.onSaved?.();
27366
27392
  opts.requestRender();
27367
27393
  }
27368
27394
  function cancel() {
27369
- keyDraft = "";
27370
- baseUrlDraft = "";
27371
- modelDraft = "";
27395
+ loadDrafts();
27396
+ modelsError = "";
27372
27397
  opts.onCancel?.();
27373
27398
  opts.requestRender();
27374
27399
  }
@@ -27378,8 +27403,10 @@ function createAiCredentialsForm(opts) {
27378
27403
  opts.requestRender();
27379
27404
  try {
27380
27405
  availableModels = await fetchAvailableModels({
27381
- apiKey: getOpenAiKey() || keyDraft,
27382
- baseUrl: baseUrlDraft || getBaseUrl(),
27406
+ credentials: {
27407
+ apiKey: keyDraft || getOpenAiKey(),
27408
+ baseUrl: baseUrlDraft || getBaseUrl()
27409
+ },
27383
27410
  force: true
27384
27411
  });
27385
27412
  } catch (error) {
@@ -27405,7 +27432,7 @@ function createAiCredentialsForm(opts) {
27405
27432
  type="password"
27406
27433
  size="s"
27407
27434
  placeholder="sk-… or any compatible key"
27408
- .value=${keyDraft}
27435
+ .value=${live(keyDraft)}
27409
27436
  @input=${(e) => {
27410
27437
  keyDraft = e.target.value;
27411
27438
  }}
@@ -27416,7 +27443,7 @@ function createAiCredentialsForm(opts) {
27416
27443
  class="ai-creds-field"
27417
27444
  size="s"
27418
27445
  allows-custom-value
27419
- .value=${modelDraft}
27446
+ .value=${live(modelDraft)}
27420
27447
  @change=${(e) => {
27421
27448
  modelDraft = e.target.value;
27422
27449
  }}
@@ -27431,7 +27458,7 @@ function createAiCredentialsForm(opts) {
27431
27458
  class="ai-creds-field"
27432
27459
  size="s"
27433
27460
  placeholder="Model ID (e.g. gpt-4o, claude-sonnet-4-20250514, etc.)"
27434
- .value=${modelDraft}
27461
+ .value=${live(modelDraft)}
27435
27462
  @input=${(e) => {
27436
27463
  modelDraft = e.target.value;
27437
27464
  }}
@@ -27447,7 +27474,7 @@ function createAiCredentialsForm(opts) {
27447
27474
  class="ai-creds-field"
27448
27475
  size="s"
27449
27476
  placeholder="Endpoint (optional, e.g. http://localhost:11434/v1)"
27450
- .value=${baseUrlDraft}
27477
+ .value=${live(baseUrlDraft)}
27451
27478
  @input=${(e) => {
27452
27479
  baseUrlDraft = e.target.value;
27453
27480
  }}
@@ -27566,7 +27593,7 @@ function listAccounts() {
27566
27593
  const githubToken = githubTokenStored();
27567
27594
  const cfToken = getCfToken();
27568
27595
  const cfAccount = getCfAccountId();
27569
- const model = getModel();
27596
+ const model = preferredModel();
27570
27597
  const endpoint = getBaseUrl();
27571
27598
  return [
27572
27599
  {
@@ -27581,21 +27608,14 @@ function listAccounts() {
27581
27608
  label: "AI provider",
27582
27609
  detail: hasOpenAiKey() ? `Key stored. Model ${model}${endpoint ? ` via ${endpoint}` : ""}.` : "No key stored. The Assistant section is where one is entered.",
27583
27610
  connected: hasOpenAiKey(),
27584
- revoke: () => {
27585
- setOpenAiKey("");
27586
- setBaseUrl("");
27587
- setModel("");
27588
- }
27611
+ revoke: clearAiProvider
27589
27612
  },
27590
27613
  {
27591
27614
  id: "cloudflare",
27592
27615
  label: "Cloudflare",
27593
27616
  detail: cfToken ? `Connected${cfAccount ? ` — account ${cfAccount}` : ""}.` : "Not connected. Publishing will ask when it needs it.",
27594
27617
  connected: Boolean(cfToken),
27595
- revoke: () => {
27596
- setCfToken("");
27597
- setCfAccountId("");
27598
- }
27618
+ revoke: clearCfConnection
27599
27619
  }
27600
27620
  ];
27601
27621
  }
@@ -27622,7 +27642,7 @@ function notifyCredentialsChanged() {
27622
27642
  }
27623
27643
 
27624
27644
  // src/settings/preferences-keymap.ts
27625
- var KEYBINDINGS_STORAGE_KEY = "jx.keybindings";
27645
+ var KEYBINDINGS_STORAGE_KEY = SETTINGS.keybindings.key;
27626
27646
  var UNMODIFIED_KEYS = new Set([
27627
27647
  "escape",
27628
27648
  "enter",
@@ -27662,11 +27682,7 @@ function isBindableChord(chord) {
27662
27682
  return UNMODIFIED_KEYS.has(parsed.key) || FUNCTION_KEY.test(parsed.key);
27663
27683
  }
27664
27684
  function readRaw() {
27665
- try {
27666
- return globalThis.localStorage?.getItem(KEYBINDINGS_STORAGE_KEY) ?? "";
27667
- } catch {
27668
- return "";
27669
- }
27685
+ return readStoredSetting(SETTINGS.keybindings);
27670
27686
  }
27671
27687
  function canonicalise(chords) {
27672
27688
  if (!Array.isArray(chords)) {
@@ -27709,13 +27725,11 @@ function loadKeybindingOverrides() {
27709
27725
  return layer;
27710
27726
  }
27711
27727
  function store(layer) {
27712
- try {
27713
- if (layer.size === 0) {
27714
- globalThis.localStorage?.removeItem(KEYBINDINGS_STORAGE_KEY);
27715
- return;
27716
- }
27717
- globalThis.localStorage?.setItem(KEYBINDINGS_STORAGE_KEY, JSON.stringify(Object.fromEntries(layer)));
27718
- } catch {}
27728
+ if (layer.size === 0) {
27729
+ clearSettings([SETTINGS.keybindings]);
27730
+ return;
27731
+ }
27732
+ setSetting(SETTINGS.keybindings, JSON.stringify(Object.fromEntries(layer)));
27719
27733
  }
27720
27734
  function applyKeybindingOverrides(registry) {
27721
27735
  registry.keymap.setOverrides(loadKeybindingOverrides());
@@ -28147,19 +28161,22 @@ function createComposer(opts) {
28147
28161
  let textareaEl = null;
28148
28162
  let hasText = false;
28149
28163
  let chips = [];
28150
- let models = null;
28151
28164
  let modelsLoading = false;
28152
28165
  let modelsError = "";
28166
+ let attempted = null;
28167
+ function sameConnection(a, b) {
28168
+ return a !== null && a.apiKey === b.apiKey && a.baseUrl === b.baseUrl;
28169
+ }
28153
28170
  function ensureModels(force = false) {
28154
- if (modelsLoading || models !== null && !force) {
28171
+ const credentials = aiConnection();
28172
+ const settled = cachedModels(credentials) !== null || sameConnection(attempted, credentials);
28173
+ if (modelsLoading || settled && !force) {
28155
28174
  return;
28156
28175
  }
28157
28176
  modelsLoading = true;
28158
28177
  modelsError = "";
28159
- fetchAvailableModels({ force }).then((list2) => {
28160
- models = list2;
28161
- }).catch((error) => {
28162
- models = [];
28178
+ attempted = credentials;
28179
+ fetchAvailableModels({ credentials, force }).catch((error) => {
28163
28180
  modelsError = error.message || "Failed to fetch models";
28164
28181
  }).finally(() => {
28165
28182
  modelsLoading = false;
@@ -28169,7 +28186,7 @@ function createComposer(opts) {
28169
28186
  function onModelChange(e) {
28170
28187
  const { value } = e.target;
28171
28188
  if (value === RETRY_MODELS) {
28172
- models = null;
28189
+ attempted = null;
28173
28190
  ensureModels(true);
28174
28191
  opts.requestRender();
28175
28192
  return;
@@ -28180,8 +28197,8 @@ function createComposer(opts) {
28180
28197
  }
28181
28198
  function renderModelPicker() {
28182
28199
  ensureModels();
28183
- const current = getModel();
28184
- const listed = models ?? [];
28200
+ const current = preferredModel();
28201
+ const listed = cachedModels(aiConnection()) ?? [];
28185
28202
  const items = listed.some((m) => m.id === current) ? listed : [{ id: current, name: current }, ...listed];
28186
28203
  return html`
28187
28204
  <sp-picker
@@ -28687,6 +28704,71 @@ function renderAiPanelTemplate() {
28687
28704
  `;
28688
28705
  }
28689
28706
 
28707
+ // ../schema/src/component-meta.ts
28708
+ function collectSlotDefs(node, out = []) {
28709
+ if (!node || typeof node !== "object" || Array.isArray(node)) {
28710
+ return out;
28711
+ }
28712
+ const el = node;
28713
+ if (el.tagName === "slot") {
28714
+ const attrs = el.attributes;
28715
+ const rawName = attrs?.name;
28716
+ const name = typeof rawName === "string" ? rawName.trim() : "";
28717
+ const { children } = el;
28718
+ out.push({
28719
+ name,
28720
+ ...Array.isArray(children) && children.length > 0 ? { fallback: children } : {}
28721
+ });
28722
+ }
28723
+ if (Array.isArray(el.children)) {
28724
+ for (const c of el.children) {
28725
+ collectSlotDefs(c, out);
28726
+ }
28727
+ }
28728
+ return out;
28729
+ }
28730
+ function isPropEntry(def) {
28731
+ if (def == null) {
28732
+ return false;
28733
+ }
28734
+ if (typeof def !== "object") {
28735
+ return true;
28736
+ }
28737
+ const obj = def;
28738
+ return !obj.$prototype && !obj.$handler && !obj.$compute;
28739
+ }
28740
+ function componentMetaFrom(doc, path) {
28741
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
28742
+ return null;
28743
+ }
28744
+ const content = doc;
28745
+ const { tagName } = content;
28746
+ if (typeof tagName !== "string" || !tagName.includes("-")) {
28747
+ return null;
28748
+ }
28749
+ const slots = collectSlotDefs(content);
28750
+ const state2 = content.state ?? {};
28751
+ return {
28752
+ $id: typeof content.$id === "string" && content.$id ? content.$id : null,
28753
+ hasElements: Array.isArray(content.$elements) && content.$elements.length > 0,
28754
+ path,
28755
+ props: Object.entries(state2).filter(([, def]) => isPropEntry(def)).map(([name, def]) => {
28756
+ if (typeof def !== "object" || def === null) {
28757
+ return { default: def, name, type: typeof def };
28758
+ }
28759
+ const obj = def;
28760
+ return {
28761
+ default: obj.default,
28762
+ format: obj.format,
28763
+ name,
28764
+ type: obj.type
28765
+ };
28766
+ }),
28767
+ ...slots.length > 0 ? { slots } : {},
28768
+ tagName
28769
+ };
28770
+ }
28771
+
28690
28772
  // ../protocol/src/problem.ts
28691
28773
  var PROBLEM_TYPE_BASE = "https://jxsuite.com/problems/";
28692
28774
  function problemSlug(type) {
@@ -28760,7 +28842,8 @@ var STUDIO_ROUTES = {
28760
28842
  fileRead: route("GET", "/__studio/file", "Read a file's text content"),
28761
28843
  fileWrite: route("PUT", "/__studio/file", "Write a file's text content"),
28762
28844
  fileDelete: route("DELETE", "/__studio/file", "Delete a file"),
28763
- fileUpload: route("POST", "/__studio/file/upload", "Upload binary content to a path"),
28845
+ fileUpload: route("POST", "/__studio/file/upload", "Upload binary content to a path → UploadResult {path, size?}. `path` is the answer, not an " + "echo: a store that de-duplicates, suffixes, or normalizes names reports what it really wrote"),
28846
+ documentRaw: route("GET", "/__studio/raw/", "A project file as its own bytes, at its own URL — the mount a canvas renders against when " + "the editor origin does not serve the site's URL space. The project-relative path is " + "appended to this prefix", "The canvas cannot address project files by URL, so a host whose origin does not serve the " + "site URL space renders no component $refs and no images."),
28764
28847
  fileRename: route("POST", "/__studio/file/rename", "Rename/move (+ refactor report)"),
28765
28848
  references: route("GET", "/__studio/references", "Where a file or a component tag is used (?path=&tag=, at least one) → ReferencesResult " + "{files, filesReferencing, refsTotal} — the read side of the rename refactor's own walker, " + "cached until the backend's watcher sees the tree move (backs findReferences)", 'Usage counts are hidden: no "Used on N pages" in the inspector, no Selection: Find Usages, ' + "and delete/rename confirmations state no reference count."),
28766
28849
  locate: route("POST", "/__studio/locate", "Find a file by name → {path | null}"),
@@ -28911,7 +28994,7 @@ function createCloudPlatform(project) {
28911
28994
  id: "cloud",
28912
28995
  projectRoot: root,
28913
28996
  canvasUrl: "/canvas.html",
28914
- ...base ? { documentBaseUrl: `${base}/raw/` } : {},
28997
+ ...base ? { assetSpace: "repo", documentBaseUrl: `${base}/raw/` } : {},
28915
28998
  openProjectPicker: "repo-list",
28916
28999
  createDestination: "repo",
28917
29000
  async activate() {
@@ -28970,7 +29053,8 @@ function createCloudPlatform(project) {
28970
29053
  method: "POST",
28971
29054
  body: data
28972
29055
  });
28973
- return okJson(res, `Upload failed: ${path}`);
29056
+ const body = await okJson(res, `Upload failed: ${path}`);
29057
+ return { path: body.path ?? path, ...body.size === undefined ? {} : { size: body.size } };
28974
29058
  },
28975
29059
  async deleteFile(path) {
28976
29060
  const res = await api(`/file?path=${encodeURIComponent(path)}`, { method: "DELETE" });
@@ -29098,7 +29182,41 @@ function createCloudPlatform(project) {
29098
29182
  return data.result;
29099
29183
  },
29100
29184
  async discoverComponents() {
29101
- return [];
29185
+ const SKIP = new Set(["node_modules", "dist", ".git", ".claude", ".obsidian", "build"]);
29186
+ const MAX_FILES = 400;
29187
+ const MAX_DEPTH = 6;
29188
+ const jsonPaths = [];
29189
+ const walk = async (dir, depth) => {
29190
+ if (depth > MAX_DEPTH || jsonPaths.length >= MAX_FILES) {
29191
+ return;
29192
+ }
29193
+ let entries;
29194
+ try {
29195
+ entries = await platform.listDirectory(dir);
29196
+ } catch {
29197
+ return;
29198
+ }
29199
+ const dirs = [];
29200
+ for (const entry of entries) {
29201
+ if (entry.type === "directory") {
29202
+ if (!SKIP.has(entry.name) && !entry.name.startsWith(".")) {
29203
+ dirs.push(entry.path);
29204
+ }
29205
+ } else if (entry.name.endsWith(".json") && jsonPaths.length < MAX_FILES) {
29206
+ jsonPaths.push(entry.path);
29207
+ }
29208
+ }
29209
+ await Promise.all(dirs.map(async (d) => walk(d, depth + 1)));
29210
+ };
29211
+ await walk("", 0);
29212
+ const metas = await Promise.all(jsonPaths.map(async (path) => {
29213
+ try {
29214
+ return componentMetaFrom(JSON.parse(await platform.readFile(path)), path);
29215
+ } catch {
29216
+ return null;
29217
+ }
29218
+ }));
29219
+ return metas.filter((m) => m !== null);
29102
29220
  },
29103
29221
  async codeService() {
29104
29222
  return null;
@@ -29770,7 +29888,8 @@ function createDevServerPlatform() {
29770
29888
  if (!res.ok) {
29771
29889
  throw new Error(`Upload failed: ${path}`);
29772
29890
  }
29773
- return await res.json();
29891
+ const body = await res.json();
29892
+ return { path: body.path ?? path, ...body.size === undefined ? {} : { size: body.size } };
29774
29893
  },
29775
29894
  async deleteFile(path) {
29776
29895
  const res = await fetch(`/__studio/file?path=${encodeURIComponent(serverPath(path))}`, {
@@ -30368,7 +30487,7 @@ function createDevServerPlatform() {
30368
30487
  }
30369
30488
  },
30370
30489
  async cfApi(apiPath, init) {
30371
- const { getCfToken: getCfToken2 } = await import("./chunks/cf-settings-g5fbf2zp.js");
30490
+ const { getCfToken: getCfToken2 } = await import("./chunks/cf-settings-zj2cj1ar.js");
30372
30491
  const token = getCfToken2();
30373
30492
  if (!token) {
30374
30493
  throw new Error("No Cloudflare API token configured");
@@ -30386,7 +30505,7 @@ function createDevServerPlatform() {
30386
30505
  return envelope.result ?? envelope;
30387
30506
  },
30388
30507
  async cfConnection() {
30389
- const { getCfAccountId: getCfAccountId2, getCfToken: getCfToken2, setCfAccountId: setCfAccountId2 } = await import("./chunks/cf-settings-g5fbf2zp.js");
30508
+ const { getCfAccountId: getCfAccountId2, getCfToken: getCfToken2, setCfAccountId } = await import("./chunks/cf-settings-zj2cj1ar.js");
30390
30509
  if (!getCfToken2()) {
30391
30510
  return null;
30392
30511
  }
@@ -30396,7 +30515,7 @@ function createDevServerPlatform() {
30396
30515
  return { connected: false };
30397
30516
  }
30398
30517
  const chosen = accounts.find((a) => a.id === getCfAccountId2()) ?? accounts[0];
30399
- setCfAccountId2(chosen.id);
30518
+ setCfAccountId(chosen.id);
30400
30519
  return { connected: true, accountId: chosen.id, accountName: chosen.name };
30401
30520
  } catch {
30402
30521
  return { connected: false };
@@ -43971,11 +44090,11 @@ function $488c6ddbf4ef74c2$var$getCachedNumberFormatter(locale, options = {}) {
43971
44090
  style: "decimal"
43972
44091
  };
43973
44092
  }
43974
- let cacheKey = locale + (options ? Object.entries(options).sort((a4, b2) => a4[0] < b2[0] ? -1 : 1).join() : "");
43975
- if ($488c6ddbf4ef74c2$var$formatterCache.has(cacheKey))
43976
- return $488c6ddbf4ef74c2$var$formatterCache.get(cacheKey);
44093
+ let cacheKey2 = locale + (options ? Object.entries(options).sort((a4, b2) => a4[0] < b2[0] ? -1 : 1).join() : "");
44094
+ if ($488c6ddbf4ef74c2$var$formatterCache.has(cacheKey2))
44095
+ return $488c6ddbf4ef74c2$var$formatterCache.get(cacheKey2);
43977
44096
  let numberFormatter = new Intl.NumberFormat(locale, options);
43978
- $488c6ddbf4ef74c2$var$formatterCache.set(cacheKey, numberFormatter);
44097
+ $488c6ddbf4ef74c2$var$formatterCache.set(cacheKey2, numberFormatter);
43979
44098
  return numberFormatter;
43980
44099
  }
43981
44100
  function $488c6ddbf4ef74c2$export$711b50b3c525e0f2(numberFormat, signDisplay, num) {
@@ -44048,11 +44167,11 @@ function $6c7bd7858deea686$var$getNumberParserImpl(locale, options, value) {
44048
44167
  return defaultParser;
44049
44168
  }
44050
44169
  function $6c7bd7858deea686$var$getCachedNumberParser(locale, options) {
44051
- let cacheKey = locale + (options ? Object.entries(options).sort((a4, b2) => a4[0] < b2[0] ? -1 : 1).join() : "");
44052
- let parser = $6c7bd7858deea686$var$numberParserCache.get(cacheKey);
44170
+ let cacheKey2 = locale + (options ? Object.entries(options).sort((a4, b2) => a4[0] < b2[0] ? -1 : 1).join() : "");
44171
+ let parser = $6c7bd7858deea686$var$numberParserCache.get(cacheKey2);
44053
44172
  if (!parser) {
44054
44173
  parser = new $6c7bd7858deea686$var$NumberParserImpl(locale, options);
44055
- $6c7bd7858deea686$var$numberParserCache.set(cacheKey, parser);
44174
+ $6c7bd7858deea686$var$numberParserCache.set(cacheKey2, parser);
44056
44175
  }
44057
44176
  return parser;
44058
44177
  }
@@ -56570,7 +56689,7 @@ async function startImport(ctx) {
56570
56689
  try {
56571
56690
  const key = getOpenAiKey();
56572
56691
  const baseUrl = getBaseUrl();
56573
- const model = getModel();
56692
+ const model = preferredModel();
56574
56693
  const result = await platform.importSite({
56575
56694
  url: parsed.href,
56576
56695
  name: ctx.form.name.trim(),
@@ -57258,7 +57377,7 @@ function socialCard(preview) {
57258
57377
  <figure class="seo-card seo-card--social" aria-label="Social card preview">
57259
57378
  <figcaption class="seo-card-label">Social card</figcaption>
57260
57379
  <div class="seo-social-media">
57261
- ${image2 ? html`<img src=${loopbackAssetSrc(previewAssetSrc(image2))} alt="" />` : html`<span class="seo-unset">No image</span>`}
57380
+ ${image2 ? html`<img src=${previewAssetSrc(image2)} alt="" />` : html`<span class="seo-unset">No image</span>`}
57262
57381
  </div>
57263
57382
  <div class="seo-social-text">
57264
57383
  <span class="seo-social-domain"
@@ -58591,9 +58710,9 @@ function renderDefsEditor(container) {
58591
58710
 
58592
58711
  // src/version.ts
58593
58712
  var APP_NAME = "Jx Studio";
58594
- var VERSION = "2.4.2";
58595
- var BUILD_DATE = "2026-08-24T19:48:51.173Z";
58596
- var GIT_COMMIT = "7859073";
58713
+ var VERSION = "3.0.0";
58714
+ var BUILD_DATE = "2026-08-26T00:56:37.432Z";
58715
+ var GIT_COMMIT = "27b8cd9";
58597
58716
  var LINKS = {
58598
58717
  github: "https://github.com/jxsuite/jx",
58599
58718
  docs: "https://github.com/jxsuite/jx#readme",
@@ -59670,7 +59789,7 @@ async function openProjectSettings(section) {
59670
59789
  showSettingsDocument();
59671
59790
  notifySettingsDocument();
59672
59791
  try {
59673
- const { syncExtensionSettingsSections } = await import("./chunks/extension-sections-0ada0xcw.js");
59792
+ const { syncExtensionSettingsSections } = await import("./chunks/extension-sections-kwzvpemr.js");
59674
59793
  await syncExtensionSettingsSections();
59675
59794
  } catch (error) {
59676
59795
  notify.warn(`Could not load extension settings sections — ${errorMessage(error)}`, {
@@ -60294,7 +60413,7 @@ function publishCommands() {
60294
60413
  name: "set_up_publishing"
60295
60414
  },
60296
60415
  run: async () => {
60297
- const { openPublishPanel } = await import("./chunks/publish-panel-a0pdft5w.js");
60416
+ const { openPublishPanel } = await import("./chunks/publish-panel-09sy2bab.js");
60298
60417
  openPublishPanel();
60299
60418
  },
60300
60419
  title: "Set Up Publishing"
@@ -61016,7 +61135,7 @@ function i18nCommands() {
61016
61135
  });
61017
61136
  return;
61018
61137
  }
61019
- const { openFileInTab: openFileInTab2 } = await import("./chunks/files-yew27sn9.js");
61138
+ const { openFileInTab: openFileInTab2 } = await import("./chunks/files-xcybanxe.js");
61020
61139
  await openFileInTab2(path);
61021
61140
  },
61022
61141
  title: "Open Translation"
@@ -61063,7 +61182,7 @@ function i18nCommands() {
61063
61182
  return;
61064
61183
  }
61065
61184
  const cut = path.lastIndexOf("/");
61066
- const { createFileIn: createFileIn2, openFileInTab: openFileInTab2 } = await import("./chunks/files-yew27sn9.js");
61185
+ const { createFileIn: createFileIn2, openFileInTab: openFileInTab2 } = await import("./chunks/files-xcybanxe.js");
61067
61186
  const created = await createFileIn2({
61068
61187
  content,
61069
61188
  dir: cut === -1 ? "." : path.slice(0, cut),
@@ -61663,6 +61782,13 @@ hydrateAccountStatus().then(() => {
61663
61782
  hydrateSettings().then(() => {
61664
61783
  render();
61665
61784
  });
61785
+ onSettingsChanged(() => {
61786
+ render();
61787
+ });
61788
+ watchRemoteSettings();
61789
+ globalThis.addEventListener("beforeunload", () => {
61790
+ settingsSettled();
61791
+ });
61666
61792
  function renderLeftPanel() {
61667
61793
  render10();
61668
61794
  }
@@ -61876,5 +62002,5 @@ export {
61876
62002
  hasUnsavedTabs
61877
62003
  };
61878
62004
 
61879
- //# debugId=31462A53C2ABCD7964756E2164756E21
62005
+ //# debugId=F554526052E4BECE64756E2164756E21
61880
62006
  //# sourceMappingURL=studio.js.map