@jxsuite/studio 0.37.1 → 1.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 (38) hide show
  1. package/dist/studio.js +77654 -76269
  2. package/dist/studio.js.map +128 -119
  3. package/package.json +44 -44
  4. package/src/account-status.ts +39 -0
  5. package/src/browse/browse.ts +10 -14
  6. package/src/canvas/iframe-host.ts +1 -1
  7. package/src/editor/context-menu.ts +1 -1
  8. package/src/editor/repeater-scope.ts +8 -13
  9. package/src/files/files.ts +3 -0
  10. package/src/format/format-host.ts +63 -1
  11. package/src/new-project/add-repo-modal.ts +183 -0
  12. package/src/new-project/new-project-modal.ts +22 -3
  13. package/src/page-params.ts +34 -8
  14. package/src/panels/ai-chat/chat-markdown.ts +2 -2
  15. package/src/panels/ai-panel.ts +61 -4
  16. package/src/panels/data-grid.ts +619 -0
  17. package/src/panels/signals-panel.ts +102 -437
  18. package/src/panels/statusbar.ts +1 -1
  19. package/src/panels/welcome-screen.ts +50 -0
  20. package/src/platform-errors.ts +30 -0
  21. package/src/platforms/cloud.ts +172 -89
  22. package/src/platforms/devserver.ts +172 -0
  23. package/src/services/context-resolver.ts +73 -0
  24. package/src/services/data-service.ts +155 -0
  25. package/src/services/monaco-setup.ts +75 -26
  26. package/src/settings/contributed-section.ts +406 -0
  27. package/src/settings/extension-sections.ts +145 -0
  28. package/src/settings/schema-field-ui.ts +4 -2
  29. package/src/settings/settings-modal.ts +101 -42
  30. package/src/site-context.ts +10 -1
  31. package/src/studio.ts +24 -0
  32. package/src/tabs/transact.ts +1 -1
  33. package/src/types.ts +120 -1
  34. package/src/ui/form-controls.ts +322 -0
  35. package/src/ui/progress-modal.ts +2 -2
  36. package/src/ui/schema-form.ts +524 -0
  37. package/src/utils/studio-utils.ts +4 -3
  38. package/src/settings/content-types-editor.ts +0 -599
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { getPlatform } from "./platform";
14
+ import { loadExtensions } from "./format/format-host";
14
15
  import type { JxMutableNode, JxPathsDef } from "@jxsuite/schema/types";
15
16
 
16
17
  /** Param name → candidate values, in `$paths` declaration order. */
@@ -135,10 +136,15 @@ async function resolveParamValues(pathsDef: JxPathsDef): Promise<ParamValues> {
135
136
  return out;
136
137
  }
137
138
 
138
- // Content type-based: { contentType: "blog", param: "slug", field: "id" } — resolved through the
139
- // Backend's ContentCollection pipeline (the same one the canvas's ContentEntry uses), so every
140
- // Offered value is guaranteed to resolve in preview.
141
- if ("contentType" in pathsDef && pathsDef.contentType) {
139
+ // Content type-based: { contentType: "blog", param: "slug", field: "id" } — an open extension
140
+ // Discriminator member on JxPathsDef, so narrow to string. Resolved through the backend's
141
+ // ContentCollection pipeline (the same one the canvas's ContentEntry uses), so every offered
142
+ // Value is guaranteed to resolve in preview.
143
+ if (
144
+ "contentType" in pathsDef &&
145
+ typeof pathsDef.contentType === "string" &&
146
+ pathsDef.contentType
147
+ ) {
142
148
  const param = pathsDef.param ?? "slug";
143
149
  const field = pathsDef.field ?? "id";
144
150
  const entries = (await resolveContentCollection(pathsDef.contentType)) as {
@@ -165,8 +171,9 @@ async function resolveParamValues(pathsDef: JxPathsDef): Promise<ParamValues> {
165
171
  return out;
166
172
  }
167
173
 
168
- // Data file ref: { "$ref": "./data/products.json", param: "id", field: "sku" }
169
- if ("$ref" in pathsDef && pathsDef.$ref) {
174
+ // Data file ref: { "$ref": "./data/products.json", param: "id", field: "sku" } — `$ref` also
175
+ // Reaches here as an open discriminator key, so narrow to a non-empty string.
176
+ if ("$ref" in pathsDef && typeof pathsDef.$ref === "string" && pathsDef.$ref) {
170
177
  const param = pathsDef.param ?? "id";
171
178
  const field = pathsDef.field ?? "id";
172
179
  const content = await getPlatform().readFile(pathsDef.$ref.replace(/^\.\//, ""));
@@ -184,6 +191,25 @@ async function resolveParamValues(pathsDef: JxPathsDef): Promise<ParamValues> {
184
191
  return {};
185
192
  }
186
193
 
194
+ /**
195
+ * The `$src` of the ContentCollection class from the enabled extensions payload — whichever
196
+ * extension's manifest declares a class named `ContentCollection` (its backend-resolved descriptor
197
+ * path feeds `/__jx_resolve__` directly). Platforms without an extensions payload fall back to the
198
+ * historical parser-package specifier.
199
+ *
200
+ * @returns {Promise<string>}
201
+ */
202
+ async function contentCollectionSrc(): Promise<string> {
203
+ const extensions = await loadExtensions();
204
+ for (const ext of extensions) {
205
+ const cls = ext.classes?.find((c) => c.name === "ContentCollection");
206
+ if (cls) {
207
+ return cls.path;
208
+ }
209
+ }
210
+ return "@jxsuite/parser/ContentCollection.class.json";
211
+ }
212
+
187
213
  /**
188
214
  * Resolve a ContentCollection for a content type via the PAL, falling back to a plain dev-proxy
189
215
  * fetch on platforms that predate `resolveClass`.
@@ -191,10 +217,10 @@ async function resolveParamValues(pathsDef: JxPathsDef): Promise<ParamValues> {
191
217
  * @param {string} contentType
192
218
  * @returns {Promise<unknown>}
193
219
  */
194
- function resolveContentCollection(contentType: string) {
220
+ async function resolveContentCollection(contentType: string) {
195
221
  const body = {
196
222
  $prototype: "ContentCollection",
197
- $src: "@jxsuite/parser/ContentCollection.class.json",
223
+ $src: await contentCollectionSrc(),
198
224
  contentType,
199
225
  };
200
226
  const platform = getPlatform();
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Chat-markdown.js — memoized markdown rendering for assistant chat messages.
3
3
  *
4
- * Wraps @jxsuite/parser/md-html (sanitized markdown → HTML) with a per-message cache
4
+ * Wraps @jxsuite/markup/md-html (sanitized markdown → HTML) with a per-message cache
5
5
  * keyed by message id + content length, so re-renders during streaming only re-parse
6
6
  * the message that actually grew. The HTML goes through unsafeHTML, which is safe here
7
7
  * because md-html sanitizes (raw HTML dropped, javascript: URLs stripped).
@@ -12,7 +12,7 @@
12
12
  import { html } from "lit-html";
13
13
  import type { TemplateResult } from "lit-html";
14
14
  import { unsafeHTML } from "lit-html/directives/unsafe-html.js";
15
- import { markdownToHtml } from "@jxsuite/parser/md-html";
15
+ import { markdownToHtml } from "@jxsuite/markup/md-html";
16
16
 
17
17
  const cache = new Map<string, { len: number; html: string }>();
18
18
 
@@ -16,12 +16,13 @@
16
16
  * @license MIT
17
17
  */
18
18
 
19
- import { html, render as litRender } from "lit-html";
19
+ import { html, render as litRender, nothing } from "lit-html";
20
20
  import type { TemplateResult } from "lit-html";
21
+ import { getPlatform } from "../platform";
21
22
  import { effect, effectScope } from "../reactivity";
22
23
  import { createDocumentAssistant } from "../services/document-assistant";
23
24
  import { hasOpenAiKey } from "../services/ai-settings";
24
- import { fetchAvailableModels, isProxyConfigured } from "../services/ai-models";
25
+ import { fetchAvailableModels, isManagedProxy, isProxyConfigured } from "../services/ai-models";
25
26
  import { createAiCredentialsForm } from "../ui/ai-credentials-form";
26
27
  import { clearMarkdownCache } from "./ai-chat/chat-markdown";
27
28
  import { renderChatHeader, renderMessageList } from "./ai-chat/chat-view";
@@ -168,11 +169,67 @@ function startEditApiKey() {
168
169
  credsForm.startEdit();
169
170
  }
170
171
 
171
- /** The OpenAI (or compatible) key + endpoint settings form, shown as a gate when no key is set. */
172
+ // ─── Managed Cloudflare connect (Workers AI) ─────────────────────────────────
173
+
174
+ let cfConnectBusy = false;
175
+ let cfConnectError = "";
176
+
177
+ /**
178
+ * Managed platforms broker Workers AI on the user's own Cloudflare account: offer connecting it as
179
+ * the keyless alternative whenever the proxy says managed-but-unconfigured and the platform can run
180
+ * the hosted OAuth flow (the PAL seam — desktop shells can implement cfConnect later).
181
+ */
182
+ function canOfferManagedConnect(): boolean {
183
+ return isManagedProxy() && !isProxyConfigured() && Boolean(getPlatform().cfConnect);
184
+ }
185
+
186
+ async function connectCloudflareForAi() {
187
+ if (cfConnectBusy) {
188
+ return;
189
+ }
190
+ cfConnectBusy = true;
191
+ cfConnectError = "";
192
+ scheduleAiRender();
193
+ try {
194
+ const connection = await getPlatform().cfConnect?.();
195
+ if (connection) {
196
+ // Re-probe: /models flips to configured once the connection lands, opening the gate.
197
+ await fetchAvailableModels({ force: true });
198
+ } else {
199
+ cfConnectError = "Cloudflare connection was not completed.";
200
+ }
201
+ } catch (error) {
202
+ cfConnectError = error instanceof Error ? error.message : String(error);
203
+ }
204
+ cfConnectBusy = false;
205
+ scheduleAiRender();
206
+ }
207
+
208
+ function renderManagedConnect() {
209
+ return html`
210
+ <div class="ai-managed-connect">
211
+ <div>Use Workers AI on your own Cloudflare account — no API key needed.</div>
212
+ <sp-button size="s" ?disabled=${cfConnectBusy} @click=${() => void connectCloudflareForAi()}>
213
+ ${cfConnectBusy ? "Connecting…" : "Connect Cloudflare"}
214
+ </sp-button>
215
+ ${cfConnectError
216
+ ? html`<div class="ai-managed-connect-error">${cfConnectError}</div>`
217
+ : nothing}
218
+ <div class="ai-managed-connect-divider">— or bring your own key —</div>
219
+ </div>
220
+ `;
221
+ }
222
+
223
+ /**
224
+ * The credentials gate: on managed platforms a keyless "Connect Cloudflare" (Workers AI) option
225
+ * sits above the OpenAI-compatible key form — both are real, working paths.
226
+ */
172
227
  function renderKeyGate() {
173
228
  return html`
174
229
  <div class="ai-tab-body">
175
- <div class="ai-status-center">${credsForm.render()}</div>
230
+ <div class="ai-status-center">
231
+ ${canOfferManagedConnect() ? renderManagedConnect() : nothing} ${credsForm.render()}
232
+ </div>
176
233
  </div>
177
234
  `;
178
235
  }