@jxsuite/studio 0.28.2 → 0.28.4

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 (104) hide show
  1. package/dist/studio.js +86144 -83971
  2. package/dist/studio.js.map +229 -219
  3. package/package.json +5 -5
  4. package/src/browse/browse-modal.ts +9 -3
  5. package/src/browse/browse.ts +139 -82
  6. package/src/canvas/canvas-diff.ts +35 -18
  7. package/src/canvas/canvas-helpers.ts +55 -21
  8. package/src/canvas/canvas-live-render.ts +140 -89
  9. package/src/canvas/canvas-render.ts +127 -82
  10. package/src/canvas/canvas-utils.ts +68 -42
  11. package/src/canvas/nested-site-style.ts +15 -7
  12. package/src/editor/component-inline-edit.ts +90 -51
  13. package/src/editor/content-inline-edit.ts +75 -72
  14. package/src/editor/context-menu.ts +125 -71
  15. package/src/editor/convert-targets.ts +17 -13
  16. package/src/editor/convert-to-component.ts +51 -28
  17. package/src/editor/convert-to-repeater.ts +44 -19
  18. package/src/editor/inline-edit.ts +107 -52
  19. package/src/editor/inline-format.ts +128 -60
  20. package/src/editor/insertion-helper.ts +26 -14
  21. package/src/editor/shortcuts.ts +89 -43
  22. package/src/editor/slash-menu.ts +41 -26
  23. package/src/files/components.ts +9 -7
  24. package/src/files/file-ops.ts +53 -33
  25. package/src/files/files.ts +246 -143
  26. package/src/format/constraints.ts +25 -15
  27. package/src/format/format-host.ts +33 -10
  28. package/src/github/github-auth.ts +24 -14
  29. package/src/github/github-publish.ts +20 -14
  30. package/src/new-project/new-project-modal.ts +27 -18
  31. package/src/panels/activity-bar.ts +27 -26
  32. package/src/panels/ai-panel.ts +100 -44
  33. package/src/panels/block-action-bar.ts +84 -52
  34. package/src/panels/canvas-dnd.ts +47 -28
  35. package/src/panels/data-explorer.ts +24 -11
  36. package/src/panels/dnd.ts +148 -100
  37. package/src/panels/editors.ts +54 -37
  38. package/src/panels/elements-panel.ts +23 -13
  39. package/src/panels/events-panel.ts +48 -38
  40. package/src/panels/git-panel.ts +95 -60
  41. package/src/panels/head-panel.ts +138 -84
  42. package/src/panels/imports-panel.ts +66 -29
  43. package/src/panels/layers-panel.ts +80 -36
  44. package/src/panels/left-panel.ts +86 -53
  45. package/src/panels/overlays.ts +36 -19
  46. package/src/panels/panel-events.ts +48 -27
  47. package/src/panels/panel-scheduler.ts +41 -23
  48. package/src/panels/preview-render.ts +26 -26
  49. package/src/panels/properties-panel.ts +155 -118
  50. package/src/panels/pseudo-preview.ts +31 -15
  51. package/src/panels/quick-search.ts +28 -13
  52. package/src/panels/right-panel.ts +35 -24
  53. package/src/panels/shared.ts +46 -29
  54. package/src/panels/signals-panel.ts +303 -186
  55. package/src/panels/statusbar.ts +27 -12
  56. package/src/panels/style-inputs.ts +30 -24
  57. package/src/panels/style-panel.ts +130 -90
  58. package/src/panels/style-utils.ts +63 -23
  59. package/src/panels/stylebook-layers-panel.ts +27 -30
  60. package/src/panels/stylebook-panel.ts +140 -72
  61. package/src/panels/tab-strip.ts +21 -10
  62. package/src/panels/toolbar.ts +52 -40
  63. package/src/panels/welcome-screen.ts +1 -1
  64. package/src/platform.ts +2 -1
  65. package/src/platforms/devserver.ts +221 -105
  66. package/src/recent-projects.ts +15 -7
  67. package/src/resize-edges.ts +7 -5
  68. package/src/services/cem-export.ts +73 -57
  69. package/src/services/code-services.ts +52 -25
  70. package/src/services/monaco-setup.ts +5 -5
  71. package/src/settings/content-types-editor.ts +133 -62
  72. package/src/settings/css-vars-editor.ts +31 -14
  73. package/src/settings/defs-editor.ts +125 -63
  74. package/src/settings/general-settings.ts +11 -6
  75. package/src/settings/head-editor.ts +30 -15
  76. package/src/settings/schema-field-ui.ts +75 -35
  77. package/src/settings/settings-modal.ts +35 -17
  78. package/src/site-context.ts +80 -47
  79. package/src/state.ts +98 -65
  80. package/src/store.ts +42 -23
  81. package/src/studio.ts +165 -144
  82. package/src/tabs/tab.ts +51 -45
  83. package/src/tabs/transact.ts +230 -109
  84. package/src/types.ts +60 -51
  85. package/src/ui/button-group.ts +3 -2
  86. package/src/ui/color-selector.ts +33 -15
  87. package/src/ui/expression-editor.ts +139 -86
  88. package/src/ui/field-input.ts +35 -18
  89. package/src/ui/field-row.ts +1 -1
  90. package/src/ui/icons.ts +5 -6
  91. package/src/ui/layers.ts +33 -26
  92. package/src/ui/media-picker.ts +13 -7
  93. package/src/ui/panel-resize.ts +39 -20
  94. package/src/ui/spectrum.ts +4 -2
  95. package/src/ui/unit-selector.ts +20 -11
  96. package/src/ui/value-selector.ts +13 -7
  97. package/src/ui/widgets.ts +34 -18
  98. package/src/utils/canvas-media.ts +66 -33
  99. package/src/utils/edit-display.ts +43 -44
  100. package/src/utils/google-fonts.ts +10 -6
  101. package/src/utils/inherited-style.ts +22 -8
  102. package/src/utils/studio-utils.ts +88 -45
  103. package/src/view.ts +9 -5
  104. package/src/workspace/workspace.ts +49 -29
@@ -13,19 +13,25 @@ import { projectState } from "../state";
13
13
  import type { JsonValue } from "../types";
14
14
  import { activeTab } from "../workspace/workspace";
15
15
  import {
16
- transactDoc,
17
- mutateUpdateDef,
18
16
  mutateAddDef,
19
17
  mutateRemoveDef,
20
18
  mutateRenameDef,
19
+ mutateUpdateDef,
20
+ transactDoc,
21
21
  } from "../tabs/transact";
22
22
  import { renderFieldRow } from "../ui/field-row";
23
- import { spTextField, rawTextArea } from "../ui/field-input";
24
- import { renderExpressionEditor, expressionHint } from "../ui/expression-editor";
23
+ import { rawTextArea, spTextField } from "../ui/field-input";
24
+ import { expressionHint, renderExpressionEditor } from "../ui/expression-editor";
25
25
  import { renderMediaPicker } from "../ui/media-picker";
26
26
  import type { TabUi } from "../tabs/tab";
27
- import type { JxMutableNode } from "@jxsuite/schema/types";
27
+ import type {
28
+ CemEvent,
29
+ CemParameter,
30
+ JxMutableNode,
31
+ JxStateDefinition,
32
+ } from "@jxsuite/schema/types";
28
33
  import { fetchPluginSchema, pluginSchemaCache } from "../services/code-services";
34
+ import type { TemplateResult } from "lit-html";
29
35
 
30
36
  interface SignalsPanelState {
31
37
  document: JxMutableNode;
@@ -38,22 +44,9 @@ interface SignalsPanelState {
38
44
  }
39
45
 
40
46
  interface SignalsPanelCtx {
41
- renderLeftPanel(): void;
42
- renderCanvas(): void;
43
- updateSession(patch: Record<string, unknown>): void;
44
- }
45
-
46
- interface CemParameter {
47
- name: string;
48
- type?: { text?: string };
49
- description?: string;
50
- optional?: boolean;
51
- }
52
-
53
- interface CemEvent {
54
- name: string;
55
- type?: { text?: string };
56
- description?: string;
47
+ renderLeftPanel: () => void;
48
+ renderCanvas: () => void;
49
+ updateSession: (patch: Record<string, unknown>) => void;
57
50
  }
58
51
 
59
52
  interface JsonSchema {
@@ -120,19 +113,19 @@ const advancedParamOpen = new Set();
120
113
 
121
114
  /** Default templates for creating new signal definitions. */
122
115
  const DEF_TEMPLATES = {
123
- state: { type: "string", default: "" },
124
116
  computed: { $compute: "", $deps: [] },
125
- request: { $prototype: "Request", url: "", method: "GET", timing: "client" },
126
- localStorage: { $prototype: "LocalStorage", key: "", default: null },
127
- sessionStorage: { $prototype: "SessionStorage", key: "", default: null },
128
- indexedDB: { $prototype: "IndexedDB", database: "", store: "", version: 1 },
129
- cookie: { $prototype: "Cookie", name: "", default: "" },
130
- set: { $prototype: "Set", default: [] },
131
- map: { $prototype: "Map", default: {} },
132
- formData: { $prototype: "FormData", fields: {} },
133
- function: { $prototype: "Function", body: "", parameters: [] },
117
+ cookie: { $prototype: "Cookie", default: "", name: "" },
134
118
  expression: { $expression: { operator: "=", target: null } },
135
119
  external: { $prototype: "", $src: "" },
120
+ formData: { $prototype: "FormData", fields: {} },
121
+ function: { $prototype: "Function", body: "", parameters: [] },
122
+ indexedDB: { $prototype: "IndexedDB", database: "", store: "", version: 1 },
123
+ localStorage: { $prototype: "LocalStorage", default: null, key: "" },
124
+ map: { $prototype: "Map", default: {} },
125
+ request: { $prototype: "Request", method: "GET", timing: "client", url: "" },
126
+ sessionStorage: { $prototype: "SessionStorage", default: null, key: "" },
127
+ set: { $prototype: "Set", default: [] },
128
+ state: { default: "", type: "string" },
136
129
  } as Record<string, SignalDef>;
137
130
 
138
131
  /** Keys handled by the framework — skip when rendering schema fields. */
@@ -154,18 +147,39 @@ const STUDIO_RESERVED_KEYS = new Set([
154
147
 
155
148
  // ─── Signals / defs helpers ──────────────────────────────────────────────────
156
149
 
150
+ /**
151
+ * View a state entry through the panel's flattened editing lens. Naked primitive and array entries
152
+ * surface as an empty view — the renderers guard every field access.
153
+ *
154
+ * @param {import("@jxsuite/schema/types").JxStateDefinition} def
155
+ * @returns {SignalDef}
156
+ */
157
+ function asSignalDef(def: JxStateDefinition): SignalDef {
158
+ return (typeof def === "object" && def !== null && !Array.isArray(def) ? def : {}) as SignalDef;
159
+ }
160
+
157
161
  /**
158
162
  * Classify a state entry into a category string.
159
163
  *
160
164
  * @param {SignalDef | unknown} def
161
165
  */
162
166
  export function defCategory(def: SignalDef | unknown) {
163
- if (!def) return "state";
167
+ if (!def) {
168
+ return "state";
169
+ }
164
170
  const d = def as SignalDef;
165
- if (d.$expression) return "expression";
166
- if (d.$handler || d.$prototype === "Function") return "function";
167
- if (d.$compute) return "computed";
168
- if (d.$prototype) return "data";
171
+ if (d.$expression) {
172
+ return "expression";
173
+ }
174
+ if (d.$handler || d.$prototype === "Function") {
175
+ return "function";
176
+ }
177
+ if (d.$compute) {
178
+ return "computed";
179
+ }
180
+ if (d.$prototype) {
181
+ return "data";
182
+ }
169
183
  return "state";
170
184
  }
171
185
 
@@ -175,12 +189,22 @@ export function defCategory(def: SignalDef | unknown) {
175
189
  * @param {SignalDef | unknown} def
176
190
  */
177
191
  export function defBadgeLabel(def: SignalDef | unknown) {
178
- if (!def) return "S";
192
+ if (!def) {
193
+ return "S";
194
+ }
179
195
  const d = def as SignalDef;
180
- if (d.$expression) return "E";
181
- if (d.$handler || d.$prototype === "Function") return "F";
182
- if (d.$compute) return "C";
183
- if (d.$prototype) return d.$prototype.charAt(0);
196
+ if (d.$expression) {
197
+ return "E";
198
+ }
199
+ if (d.$handler || d.$prototype === "Function") {
200
+ return "F";
201
+ }
202
+ if (d.$compute) {
203
+ return "C";
204
+ }
205
+ if (d.$prototype) {
206
+ return d.$prototype.charAt(0);
207
+ }
184
208
  return "S";
185
209
  }
186
210
 
@@ -191,24 +215,45 @@ export function defBadgeLabel(def: SignalDef | unknown) {
191
215
  * @param {SignalDef | null | undefined} def
192
216
  */
193
217
  export function defHint(name: string, def: SignalDef | null | undefined) {
194
- if (!def) return "";
195
- if (def.$expression) return expressionHint(def.$expression);
218
+ if (!def) {
219
+ return "";
220
+ }
221
+ if (def.$expression) {
222
+ return expressionHint(def.$expression);
223
+ }
196
224
  if (def.$prototype === "Function") {
197
- if (def.body) return def.body.length > 20 ? def.body.slice(0, 20) + "..." : def.body;
198
- if (def.$src) return def.$src;
225
+ if (def.body) {
226
+ return def.body.length > 20 ? `${def.body.slice(0, 20)}...` : def.body;
227
+ }
228
+ if (def.$src) {
229
+ return def.$src;
230
+ }
199
231
  return "function";
200
232
  }
201
- if (def.$handler) return "handler (legacy)";
202
- if (def.$compute)
203
- return "=" + (def.$compute.length > 20 ? def.$compute.slice(0, 20) + "..." : def.$compute);
204
- if (def.$prototype === "Request")
205
- return (def.method || "GET") + " " + (def.url || "").slice(0, 20);
206
- if (def.$prototype === "LocalStorage" || def.$prototype === "SessionStorage")
233
+ if (def.$handler) {
234
+ return "handler (legacy)";
235
+ }
236
+ if (def.$compute) {
237
+ return `=${def.$compute.length > 20 ? `${def.$compute.slice(0, 20)}...` : def.$compute}`;
238
+ }
239
+ if (def.$prototype === "Request") {
240
+ return `${def.method || "GET"} ${(def.url || "").slice(0, 20)}`;
241
+ }
242
+ if (def.$prototype === "LocalStorage" || def.$prototype === "SessionStorage") {
207
243
  return def.key || "";
208
- if (def.$prototype === "IndexedDB") return def.database || "";
209
- if (def.$prototype === "Cookie") return def.name || "";
210
- if (def.$prototype) return def.$prototype;
211
- if (def.attribute) return `[${def.attribute}] ${def.type || ""}`;
244
+ }
245
+ if (def.$prototype === "IndexedDB") {
246
+ return def.database || "";
247
+ }
248
+ if (def.$prototype === "Cookie") {
249
+ return def.name || "";
250
+ }
251
+ if (def.$prototype) {
252
+ return def.$prototype;
253
+ }
254
+ if (def.attribute) {
255
+ return `[${def.attribute}] ${def.type || ""}`;
256
+ }
212
257
  return def.type || "";
213
258
  }
214
259
 
@@ -231,12 +276,17 @@ export function collectCssParts(
231
276
  node: JxMutableNode | null | undefined,
232
277
  parts: { name: string; tag: string }[] = [],
233
278
  ) {
234
- if (node?.attributes?.part)
235
- parts.push({ name: node.attributes.part, tag: node.tagName || "div" });
236
- if (Array.isArray(node?.children))
237
- node.children.forEach((c) => {
238
- if (typeof c !== "string") collectCssParts(c, parts);
239
- });
279
+ const part = node?.attributes?.part;
280
+ if (typeof part === "string" && part) {
281
+ parts.push({ name: part, tag: node?.tagName || "div" });
282
+ }
283
+ if (Array.isArray(node?.children)) {
284
+ for (const c of node.children) {
285
+ if (typeof c !== "string") {
286
+ collectCssParts(c, parts);
287
+ }
288
+ }
289
+ }
240
290
  return parts;
241
291
  }
242
292
 
@@ -249,40 +299,59 @@ export function collectCssParts(
249
299
  */
250
300
  export function resolveDefaultForCanvas(
251
301
  value: unknown,
252
- defs: Record<string, SignalDef> | null | undefined,
302
+ defs: Record<string, JxStateDefinition> | null | undefined,
253
303
  ) {
254
- if (!value || typeof value !== "object" || !(value as Record<string, unknown>).$ref) return value;
304
+ if (!value || typeof value !== "object" || !(value as Record<string, unknown>).$ref) {
305
+ return value;
306
+ }
255
307
  const ref = (value as Record<string, unknown>).$ref as string;
256
308
  /** @type {string | undefined} */
257
309
  let defName;
258
- if (ref.startsWith("#/state/")) defName = ref.slice(8);
259
- else if (ref.startsWith("$")) defName = ref;
260
- else return `{${ref}}`;
310
+ if (ref.startsWith("#/state/")) {
311
+ defName = ref.slice(8);
312
+ } else if (ref.startsWith("$")) {
313
+ defName = ref;
314
+ } else {
315
+ return `{${ref}}`;
316
+ }
261
317
 
262
- const def = defs?.[defName];
263
- if (!def) return `{${defName}}`;
318
+ const rawDef = defs?.[defName];
319
+ if (!rawDef) {
320
+ return `{${defName}}`;
321
+ }
322
+ const def = asSignalDef(rawDef);
264
323
 
265
324
  // State signal → use default
266
325
  if (!def.$compute && !def.$prototype) {
267
326
  if (def.default !== undefined && def.default !== null) {
268
- if (typeof def.default === "object") return JSON.stringify(def.default);
327
+ if (typeof def.default === "object") {
328
+ return JSON.stringify(def.default);
329
+ }
269
330
  return String(def.default);
270
331
  }
271
332
  return "";
272
333
  }
273
334
  // Computed → expression indicator
274
- if (def.$compute) return `\u0192(${defName})`;
335
+ if (def.$compute) {
336
+ return `\u0192(${defName})`;
337
+ }
275
338
  // Request → URL hint
276
- if (def.$prototype === "Request") return `\u27F3 ${def.url || "fetch"}`;
339
+ if (def.$prototype === "Request") {
340
+ return `\u27F3 ${def.url || "fetch"}`;
341
+ }
277
342
  // Storage → use default or key
278
343
  if (def.$prototype === "LocalStorage" || def.$prototype === "SessionStorage") {
279
344
  if (def.default !== undefined && def.default !== null) {
280
- if (typeof def.default === "object") return JSON.stringify(def.default);
345
+ if (typeof def.default === "object") {
346
+ return JSON.stringify(def.default);
347
+ }
281
348
  return String(def.default);
282
349
  }
283
350
  return `[${def.key || "storage"}]`;
284
351
  }
285
- if (def.$prototype) return `{${def.$prototype}}`;
352
+ if (def.$prototype) {
353
+ return `{${def.$prototype}}`;
354
+ }
286
355
  return `{${defName}}`;
287
356
  }
288
357
 
@@ -294,13 +363,15 @@ export function signalFieldRow(label: string, value: string, onChange: (value: s
294
363
  prop: label,
295
364
  label,
296
365
  hasValue: false,
297
- // commitMode "blur": signal fields (rename, src, etc.) commit on blur/Enter only — a debounced
298
- // mid-typing commit would, e.g., rename the signal on every keystroke pause.
366
+ // CommitMode "blur": signal fields (rename, src, etc.) commit on blur/Enter only — a debounced
367
+ // Mid-typing commit would, e.g., rename the signal on every keystroke pause.
299
368
  widget: spTextField(
300
369
  `sig:${label}`,
301
370
  value,
302
371
  (v: string) => {
303
- if (v !== value) onChange(v);
372
+ if (v !== value) {
373
+ onChange(v);
374
+ }
304
375
  },
305
376
  { commitMode: "blur" },
306
377
  ),
@@ -308,8 +379,19 @@ export function signalFieldRow(label: string, value: string, onChange: (value: s
308
379
  }
309
380
 
310
381
  /** Normalize a parameter entry to a CEM object. */
311
- export function normParam(p: string | Record<string, unknown>) {
312
- return typeof p === "string" ? { name: p } : (p as unknown as CemParameter);
382
+ export function normParam(p: string | CemParameter): CemParameter {
383
+ return typeof p === "string" ? { name: p } : p;
384
+ }
385
+
386
+ /** Extract the display text from a CEM `{ text }` type value, if present. */
387
+ function cemTypeText(type: JsonValue | undefined): string {
388
+ if (typeof type === "object" && type !== null && !Array.isArray(type)) {
389
+ const { text } = type;
390
+ if (typeof text === "string") {
391
+ return text;
392
+ }
393
+ }
394
+ return "";
313
395
  }
314
396
 
315
397
  // ─── Left panel: Signals ─────────────────────────────────────────────────────
@@ -324,25 +406,25 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
324
406
 
325
407
  // Group by category
326
408
  const groups = {
327
- state: [],
328
409
  computed: [],
329
410
  data: [],
330
411
  expression: [],
331
412
  function: [],
413
+ state: [],
332
414
  } as Record<string, [string, SignalDef][]>;
333
415
  for (const [name, def] of entries) {
334
- groups[defCategory(def)].push([name, def]);
416
+ groups[defCategory(def)].push([name, asSignalDef(def)]);
335
417
  }
336
418
 
337
419
  const categories = [
338
- { key: "state", label: "State", items: groups.state },
339
- { key: "computed", label: "Computed", items: groups.computed },
340
- { key: "data", label: "Data", items: groups.data },
341
- { key: "expression", label: "Expressions", items: groups.expression },
342
- { key: "function", label: "Functions", items: groups.function },
420
+ { items: groups.state, key: "state", label: "State" },
421
+ { items: groups.computed, key: "computed", label: "Computed" },
422
+ { items: groups.data, key: "data", label: "Data" },
423
+ { items: groups.expression, key: "expression", label: "Expressions" },
424
+ { items: groups.function, key: "function", label: "Functions" },
343
425
  ];
344
426
 
345
- const collapsedCats = S._collapsedSignalCats || (S._collapsedSignalCats = new Set());
427
+ const collapsedCats = (S._collapsedSignalCats ||= new Set());
346
428
 
347
429
  const catTemplates = categories
348
430
  .filter((c) => c.items.length > 0)
@@ -352,8 +434,11 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
352
434
  label="${label} (${items.length})"
353
435
  ?open=${!collapsedCats.has(key)}
354
436
  @sp-accordion-item-toggle=${() => {
355
- if (collapsedCats.has(key)) collapsedCats.delete(key);
356
- else collapsedCats.add(key);
437
+ if (collapsedCats.has(key)) {
438
+ collapsedCats.delete(key);
439
+ } else {
440
+ collapsedCats.add(key);
441
+ }
357
442
  ctx.renderLeftPanel();
358
443
  }}
359
444
  >
@@ -361,7 +446,7 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
361
446
  const isExpanded: boolean = expandedSignal === name;
362
447
  return html`
363
448
  <div
364
- class=${classMap({ "signal-row": true, expanded: isExpanded })}
449
+ class=${classMap({ expanded: isExpanded, "signal-row": true })}
365
450
  @click=${() => {
366
451
  expandedSignal = isExpanded ? null : name;
367
452
  ctx.renderLeftPanel();
@@ -404,17 +489,20 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
404
489
  placeholder="+ Add…"
405
490
  @change=${(e: Event) => {
406
491
  const type = (e.target as HTMLInputElement).value;
407
- if (!type) return;
492
+ if (!type) {
493
+ return;
494
+ }
408
495
 
409
496
  // Handle import-based prototypes (e.g., "import:ContentCollection")
410
497
  if (type.startsWith("import:")) {
411
498
  const protoName = type.slice(7);
412
499
  const src = projectState?.projectConfig?.imports?.[protoName];
413
- let n = "$" + protoName.charAt(0).toLowerCase() + protoName.slice(1);
500
+ let n = `$${protoName.charAt(0).toLowerCase()}${protoName.slice(1)}`;
414
501
  let i = 1;
415
502
  const base = n;
416
503
  while (S.document.state && S.document.state[n]) {
417
- n = base + i++;
504
+ n = base + i;
505
+ i += 1;
418
506
  }
419
507
  transactDoc(activeTab.value, (t) =>
420
508
  mutateAddDef(
@@ -442,13 +530,16 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
442
530
  }
443
531
 
444
532
  const template = DEF_TEMPLATES[type];
445
- if (!template) return;
533
+ if (!template) {
534
+ return;
535
+ }
446
536
  const isFunction = type === "function";
447
- let nameBase = isFunction ? "newFunction" : "$newSignal";
537
+ const nameBase = isFunction ? "newFunction" : "$newSignal";
448
538
  let n = nameBase;
449
539
  let i = 1;
450
540
  while (S.document.state && S.document.state[n]) {
451
- n = nameBase + i++;
541
+ n = nameBase + i;
542
+ i += 1;
452
543
  }
453
544
  transactDoc(activeTab.value, (t) =>
454
545
  mutateAddDef(t, n, structuredClone(template) as Record<string, JsonValue>),
@@ -487,12 +578,10 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
487
578
  function renderSignalEditorTemplate(
488
579
  S: SignalsPanelState,
489
580
  name: string,
490
- def: SignalDef,
581
+ defArg: SignalDef,
491
582
  ctx: SignalsPanelCtx,
492
583
  ) {
493
- if (typeof def !== "object" || def === null) {
494
- def = { default: def };
495
- }
584
+ const def = typeof defArg === "object" && defArg !== null ? defArg : { default: defArg };
496
585
  const cat = defCategory(def);
497
586
 
498
587
  // Helper for picker rows
@@ -501,11 +590,11 @@ function renderSignalEditorTemplate(
501
590
  options: string[],
502
591
  currentVal: string,
503
592
  onChange: (value: string) => void,
504
- ) => {
505
- return renderFieldRow({
506
- prop: label,
507
- label,
593
+ ) =>
594
+ renderFieldRow({
508
595
  hasValue: false,
596
+ label,
597
+ prop: label,
509
598
  widget: html`
510
599
  <sp-picker
511
600
  size="s"
@@ -516,27 +605,25 @@ function renderSignalEditorTemplate(
516
605
  </sp-picker>
517
606
  `,
518
607
  });
519
- };
520
608
 
521
609
  // Helper for textarea rows — uses the shared draft layer (commits on blur/Enter and a 500ms
522
- // debounce) so a panel re-render mid-edit can't truncate the in-progress text.
610
+ // Debounce) so a panel re-render mid-edit can't truncate the in-progress text.
523
611
  const textareaRow = (
524
612
  label: string,
525
613
  value: string,
526
614
  onChange: (value: string) => void,
527
615
  opts: { minHeight?: string; mono?: boolean } = {},
528
- ) => {
529
- return renderFieldRow({
530
- prop: label,
531
- label,
616
+ ) =>
617
+ renderFieldRow({
532
618
  hasValue: false,
619
+ label,
620
+ prop: label,
533
621
  widget: rawTextArea(`sig:${label}`, value, onChange, {
534
622
  debounceMs: 500,
535
623
  ...(opts.minHeight != null && { minHeight: opts.minHeight }),
536
624
  ...(opts.mono != null && { mono: opts.mono }),
537
625
  }),
538
626
  });
539
- };
540
627
 
541
628
  // Name field (common to all)
542
629
  const nameField = signalFieldRow("Name", name, (v: string) => {
@@ -546,7 +633,7 @@ function renderSignalEditorTemplate(
546
633
  }
547
634
  });
548
635
 
549
- let fields: import("lit-html").TemplateResult | typeof nothing = nothing;
636
+ let fields: TemplateResult | typeof nothing = nothing;
550
637
 
551
638
  if (cat === "state") {
552
639
  const defaultVal =
@@ -564,13 +651,13 @@ function renderSignalEditorTemplate(
564
651
  ),
565
652
  )}
566
653
  ${renderFieldRow({
567
- prop: "reflects",
568
- label: "Reflects",
569
654
  hasValue: false,
655
+ label: "Reflects",
656
+ prop: "reflects",
570
657
  widget: html`
571
658
  <sp-checkbox
572
659
  class="field-check"
573
- ?checked=${!!def.reflects}
660
+ ?checked=${Boolean(def.reflects)}
574
661
  @change=${(e: Event) =>
575
662
  transactDoc(activeTab.value, (t) =>
576
663
  mutateUpdateDef(t, name, {
@@ -607,9 +694,9 @@ function renderSignalEditorTemplate(
607
694
  : nothing}
608
695
  ${def.format === "image"
609
696
  ? renderFieldRow({
610
- prop: "Default",
611
- label: "Default",
612
697
  hasValue: false,
698
+ label: "Default",
699
+ prop: "Default",
613
700
  widget: renderMediaPicker("default", defaultVal, (v: string) => {
614
701
  transactDoc(activeTab.value, (t) =>
615
702
  mutateUpdateDef(t, name, { default: v || undefined }),
@@ -618,10 +705,13 @@ function renderSignalEditorTemplate(
618
705
  })
619
706
  : signalFieldRow("Default", defaultVal, (v: string) => {
620
707
  let parsed: unknown = v;
621
- if (def.type === "integer") parsed = parseInt(v, 10) || 0;
622
- else if (def.type === "number") parsed = parseFloat(v) || 0;
623
- else if (def.type === "boolean") parsed = v === "true";
624
- else if (def.type === "array" || def.type === "object") {
708
+ if (def.type === "integer") {
709
+ parsed = Number.parseInt(v, 10) || 0;
710
+ } else if (def.type === "number") {
711
+ parsed = Number.parseFloat(v) || 0;
712
+ } else if (def.type === "boolean") {
713
+ parsed = v === "true";
714
+ } else if (def.type === "array" || def.type === "object") {
625
715
  try {
626
716
  parsed = JSON.parse(v);
627
717
  } catch {
@@ -643,9 +733,9 @@ function renderSignalEditorTemplate(
643
733
  let debounce: ReturnType<typeof setTimeout> | undefined;
644
734
  fields = html`
645
735
  ${renderFieldRow({
646
- prop: "expression",
647
- label: "Expression",
648
736
  hasValue: false,
737
+ label: "Expression",
738
+ prop: "expression",
649
739
  widget: html`
650
740
  <textarea
651
741
  class="field-input"
@@ -667,9 +757,9 @@ function renderSignalEditorTemplate(
667
757
  })}
668
758
  ${def.$deps && def.$deps.length > 0
669
759
  ? renderFieldRow({
670
- prop: "dependencies",
671
- label: "Dependencies",
672
760
  hasValue: false,
761
+ label: "Dependencies",
762
+ prop: "dependencies",
673
763
  widget: html`
674
764
  <span class="signal-hint" style="flex:1;max-width:none"
675
765
  >${def.$deps.map((d: string) => d.replace("#/state/", "")).join(", ")}</span
@@ -687,11 +777,14 @@ function renderSignalEditorTemplate(
687
777
  fields = html`
688
778
  ${renderExpressionEditor(
689
779
  exprNode,
690
- (newNode: any) =>
691
- transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { $expression: newNode })),
780
+ (newNode: unknown) =>
781
+ transactDoc(activeTab.value, (t) =>
782
+ // Expression editors emit JSON expression nodes.
783
+ mutateUpdateDef(t, name, { $expression: newNode as JsonValue }),
784
+ ),
692
785
  {
693
- stateDefs: Object.keys(S.document.state || {}),
694
786
  allowEventRef: false,
787
+ stateDefs: Object.keys(S.document.state || {}),
695
788
  },
696
789
  )}
697
790
  `;
@@ -710,13 +803,13 @@ function renderDataSourceFields(
710
803
  value: string,
711
804
  onChange: (value: string) => void,
712
805
  opts?: { minHeight?: string; mono?: boolean },
713
- ) => import("lit-html").TemplateResult,
806
+ ) => TemplateResult,
714
807
  pickerRow: (
715
808
  label: string,
716
809
  options: string[],
717
810
  currentVal: string,
718
811
  onChange: (value: string) => void,
719
- ) => import("lit-html").TemplateResult,
812
+ ) => TemplateResult,
720
813
  ctx: SignalsPanelCtx,
721
814
  ) {
722
815
  const proto = def.$prototype;
@@ -767,7 +860,7 @@ function renderDataSourceFields(
767
860
  )}
768
861
  ${signalFieldRow("Version", String(def.version || 1), (v: string) =>
769
862
  transactDoc(activeTab.value, (t) =>
770
- mutateUpdateDef(t, name, { version: parseInt(v, 10) || 1 }),
863
+ mutateUpdateDef(t, name, { version: Number.parseInt(v, 10) || 1 }),
771
864
  ),
772
865
  )}
773
866
  `;
@@ -813,7 +906,7 @@ function renderFunctionFields(
813
906
  value: string,
814
907
  onChange: (value: string) => void,
815
908
  opts?: { minHeight?: string; mono?: boolean },
816
- ) => import("lit-html").TemplateResult,
909
+ ) => TemplateResult,
817
910
  ctx: SignalsPanelCtx,
818
911
  ) {
819
912
  const descriptionField = signalFieldRow("Description", def.description || "", (v: string) =>
@@ -840,7 +933,7 @@ function renderFunctionFields(
840
933
  title="Open in code editor"
841
934
  @click=${() => {
842
935
  ctx.updateSession({
843
- ui: { editingFunction: { type: "def", defName: name } },
936
+ ui: { editingFunction: { defName: name, type: "def" } },
844
937
  });
845
938
  ctx.renderCanvas();
846
939
  }}
@@ -874,15 +967,15 @@ function renderParameterEditorTemplate(
874
967
  def: SignalDef,
875
968
  ctx: SignalsPanelCtx,
876
969
  ) {
877
- const params = (def.parameters || []).map(normParam);
970
+ const params = (def.parameters || []).map((p) => normParam(p));
878
971
  const isAdvanced = advancedParamOpen.has(name);
879
972
 
880
973
  if (!isAdvanced) {
881
974
  // Basic mode: name chips
882
975
  return renderFieldRow({
883
- prop: "parameters",
884
- label: "Parameters",
885
976
  hasValue: false,
977
+ label: "Parameters",
978
+ prop: "parameters",
886
979
  widget: html`
887
980
  <div style="display:flex;flex-wrap:wrap;gap:4px;align-items:center">
888
981
  ${params.map(
@@ -896,7 +989,7 @@ function renderParameterEditorTemplate(
896
989
  @click=${() => {
897
990
  transactDoc(activeTab.value, (t) =>
898
991
  mutateUpdateDef(t, name, {
899
- parameters: params.filter((_: unknown, j: number) => j !== i).length
992
+ parameters: params.some((_: unknown, j: number) => j !== i)
900
993
  ? params.filter((_: unknown, j: number) => j !== i)
901
994
  : undefined,
902
995
  }),
@@ -936,9 +1029,9 @@ function renderParameterEditorTemplate(
936
1029
 
937
1030
  // Advanced mode: full rows
938
1031
  return renderFieldRow({
939
- prop: "parameters",
940
- label: "Parameters",
941
1032
  hasValue: false,
1033
+ label: "Parameters",
1034
+ prop: "parameters",
942
1035
  widget: html`
943
1036
  <div style="display:flex;flex-direction:column;gap:4px">
944
1037
  ${params.map(
@@ -962,7 +1055,7 @@ function renderParameterEditorTemplate(
962
1055
  />
963
1056
  <input
964
1057
  class="field-input"
965
- .value=${p.type?.text || ""}
1058
+ .value=${cemTypeText(p.type)}
966
1059
  placeholder="type"
967
1060
  style="flex:1"
968
1061
  @change=${(e: Event) => {
@@ -993,10 +1086,10 @@ function renderParameterEditorTemplate(
993
1086
  <input
994
1087
  type="checkbox"
995
1088
  title="optional"
996
- .checked=${!!p.optional}
1089
+ .checked=${Boolean(p.optional)}
997
1090
  @change=${(e: Event) => {
998
1091
  const next = [...params];
999
- const checked = (e.target as HTMLInputElement).checked;
1092
+ const { checked } = e.target as HTMLInputElement;
1000
1093
  const { optional: _o, ...rest } = next[i];
1001
1094
  next[i] = checked ? { ...rest, optional: true } : rest;
1002
1095
  transactDoc(activeTab.value, (t) =>
@@ -1010,7 +1103,7 @@ function renderParameterEditorTemplate(
1010
1103
  const next = params.filter((_: unknown, j: number) => j !== i);
1011
1104
  transactDoc(activeTab.value, (t) =>
1012
1105
  mutateUpdateDef(t, name, {
1013
- parameters: next.length ? next : undefined,
1106
+ parameters: next.length > 0 ? next : undefined,
1014
1107
  }),
1015
1108
  );
1016
1109
  }}
@@ -1046,7 +1139,9 @@ function renderParameterEditorTemplate(
1046
1139
  /** Render CEM emits editor for function state entries. */
1047
1140
  function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: SignalDef) {
1048
1141
  const emits = def.emits || ([] as CemEvent[]);
1049
- if (emits.length === 0 && !isCustomElementDoc(S)) return nothing;
1142
+ if (emits.length === 0 && !isCustomElementDoc(S)) {
1143
+ return nothing;
1144
+ }
1050
1145
 
1051
1146
  return html`
1052
1147
  <div
@@ -1073,7 +1168,7 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
1073
1168
  />
1074
1169
  <input
1075
1170
  class="field-input"
1076
- .value=${ev.type?.text || ""}
1171
+ .value=${cemTypeText(ev.type)}
1077
1172
  placeholder="type"
1078
1173
  style="flex:1"
1079
1174
  @change=${(e: Event) => {
@@ -1102,7 +1197,7 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
1102
1197
  @click=${() => {
1103
1198
  transactDoc(activeTab.value, (t) =>
1104
1199
  mutateUpdateDef(t, name, {
1105
- emits: emits.filter((_: unknown, j: number) => j !== i).length
1200
+ emits: emits.some((_: unknown, j: number) => j !== i)
1106
1201
  ? emits.filter((_: unknown, j: number) => j !== i)
1107
1202
  : undefined,
1108
1203
  }),
@@ -1137,8 +1232,13 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
1137
1232
  * @param {Record<string, unknown>} [parentDef] - Parent def for resolving dependent refs
1138
1233
  * @returns {string[] | undefined}
1139
1234
  */
1140
- function resolveSchemaEnum(enumDef: unknown, parentDef?: Record<string, unknown>) {
1141
- if (Array.isArray(enumDef)) return enumDef;
1235
+ function resolveSchemaEnum(
1236
+ enumDef: unknown,
1237
+ parentDef?: Record<string, unknown>,
1238
+ ): string[] | undefined {
1239
+ if (Array.isArray(enumDef)) {
1240
+ return enumDef;
1241
+ }
1142
1242
  if (enumDef && typeof enumDef === "object") {
1143
1243
  const ref = (enumDef as Record<string, unknown>).$ref;
1144
1244
  if (ref === "#/$context/contentTypes") {
@@ -1147,7 +1247,7 @@ function resolveSchemaEnum(enumDef: unknown, parentDef?: Record<string, unknown>
1147
1247
  if (typeof ref === "string" && ref.startsWith("#/$context/contentTypes/{@")) {
1148
1248
  const match = ref.match(/#\/\$context\/contentTypes\/\{@(\w+)\}\/schema\/properties/);
1149
1249
  if (match && parentDef) {
1150
- const paramName = match[1];
1250
+ const [, paramName] = match;
1151
1251
  const typeName = parentDef[paramName] as string | undefined;
1152
1252
  if (typeName) {
1153
1253
  const ct = projectState?.projectConfig?.contentTypes?.[typeName] as
@@ -1155,7 +1255,9 @@ function resolveSchemaEnum(enumDef: unknown, parentDef?: Record<string, unknown>
1155
1255
  | undefined;
1156
1256
  const schema = ct?.schema as Record<string, unknown> | undefined;
1157
1257
  const props = schema?.properties as Record<string, unknown> | undefined;
1158
- if (props) return Object.keys(props);
1258
+ if (props) {
1259
+ return Object.keys(props);
1260
+ }
1159
1261
  }
1160
1262
  }
1161
1263
  return undefined;
@@ -1205,7 +1307,7 @@ function renderInlineField(
1205
1307
  if (schema.type === "boolean") {
1206
1308
  return html`<sp-switch
1207
1309
  size="s"
1208
- ?checked=${!!value}
1310
+ ?checked=${Boolean(value)}
1209
1311
  @change=${(e: Event) => onChange((e.target as HTMLInputElement).checked)}
1210
1312
  >${key}</sp-switch
1211
1313
  >`;
@@ -1219,9 +1321,9 @@ function renderInlineField(
1219
1321
  @change=${(e: Event) => {
1220
1322
  const parsed =
1221
1323
  schema.type === "integer"
1222
- ? parseInt((e.target as HTMLInputElement).value, 10)
1223
- : parseFloat((e.target as HTMLInputElement).value);
1224
- onChange(isNaN(parsed) ? undefined : parsed);
1324
+ ? Number.parseInt((e.target as HTMLInputElement).value, 10)
1325
+ : Number.parseFloat((e.target as HTMLInputElement).value);
1326
+ onChange(Number.isNaN(parsed) ? undefined : parsed);
1225
1327
  }}
1226
1328
  ></sp-number-field>`;
1227
1329
  }
@@ -1234,6 +1336,36 @@ function renderInlineField(
1234
1336
  ></sp-textfield>`;
1235
1337
  }
1236
1338
 
1339
+ /** Render a debounced multiline JSON text field for array/object schema properties. */
1340
+ function renderJsonTextField(
1341
+ currentValue: unknown,
1342
+ ps: SchemaProperty,
1343
+ name: string,
1344
+ prop: string,
1345
+ ) {
1346
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
1347
+ let debounce: ReturnType<typeof setTimeout> | undefined;
1348
+ return html`<sp-textfield
1349
+ multiline
1350
+ size="s"
1351
+ style="min-height:40px"
1352
+ .value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
1353
+ placeholder=${ps.default !== undefined ? JSON.stringify(ps.default) : nothing}
1354
+ @input=${(e: Event) => {
1355
+ clearTimeout(debounce);
1356
+ debounce = setTimeout(() => {
1357
+ try {
1358
+ transactDoc(activeTab.value, (t) =>
1359
+ mutateUpdateDef(t, name, {
1360
+ [prop]: JSON.parse((e.target as HTMLInputElement).value),
1361
+ }),
1362
+ );
1363
+ } catch {}
1364
+ }, 500);
1365
+ }}
1366
+ ></sp-textfield>`;
1367
+ }
1368
+
1237
1369
  /**
1238
1370
  * Render config form fields from a JSON Schema `properties` object. Maps schema types to
1239
1371
  * appropriate form controls.
@@ -1245,9 +1377,11 @@ export function renderSchemaFieldsTemplate(
1245
1377
  _S: SignalsPanelState,
1246
1378
  ctx: SignalsPanelCtx | null = null,
1247
1379
  ) {
1248
- if (!schema?.properties) return nothing;
1380
+ if (!schema?.properties) {
1381
+ return nothing;
1382
+ }
1249
1383
 
1250
- const required = new Set(schema.required ?? []);
1384
+ const required = new Set(schema.required);
1251
1385
 
1252
1386
  const propertyFields = Object.entries(schema.properties)
1253
1387
  .filter(([prop]) => !STUDIO_RESERVED_KEYS.has(prop))
@@ -1307,11 +1441,11 @@ export function renderSchemaFieldsTemplate(
1307
1441
  debounce = setTimeout(() => {
1308
1442
  const parsed =
1309
1443
  ps.type === "integer"
1310
- ? parseInt((e.target as HTMLInputElement).value, 10)
1311
- : parseFloat((e.target as HTMLInputElement).value);
1444
+ ? Number.parseInt((e.target as HTMLInputElement).value, 10)
1445
+ : Number.parseFloat((e.target as HTMLInputElement).value);
1312
1446
  transactDoc(activeTab.value, (t) =>
1313
1447
  mutateUpdateDef(t, name, {
1314
- [prop]: isNaN(parsed) ? undefined : parsed,
1448
+ [prop]: Number.isNaN(parsed) ? undefined : parsed,
1315
1449
  }),
1316
1450
  );
1317
1451
  }, 400);
@@ -1344,9 +1478,9 @@ export function renderSchemaFieldsTemplate(
1344
1478
  multiline
1345
1479
  size="s"
1346
1480
  style=${styleMap({
1347
- minHeight: hasValue ? "80px" : "40px",
1348
1481
  fontFamily: "monospace",
1349
1482
  fontSize: "11px",
1483
+ minHeight: hasValue ? "80px" : "40px",
1350
1484
  })}
1351
1485
  .value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
1352
1486
  placeholder=${ps.description ?? "JSON Schema defining the data shape\u2026"}
@@ -1399,7 +1533,7 @@ export function renderSchemaFieldsTemplate(
1399
1533
  const updated = rows.filter((_: unknown, i: number) => i !== idx);
1400
1534
  transactDoc(activeTab.value, (t) =>
1401
1535
  mutateUpdateDef(t, name, {
1402
- [prop]: updated.length ? updated : undefined,
1536
+ [prop]: updated.length > 0 ? updated : undefined,
1403
1537
  }),
1404
1538
  );
1405
1539
  ctx?.renderLeftPanel();
@@ -1417,8 +1551,9 @@ export function renderSchemaFieldsTemplate(
1417
1551
  e.stopPropagation();
1418
1552
  const newRow: Record<string, unknown> = {};
1419
1553
  for (const [k, v] of Object.entries(itemProps)) {
1420
- if ((v as Record<string, unknown>).default !== undefined)
1554
+ if ((v as Record<string, unknown>).default !== undefined) {
1421
1555
  newRow[k] = (v as Record<string, unknown>).default;
1556
+ }
1422
1557
  }
1423
1558
  transactDoc(activeTab.value, (t) =>
1424
1559
  mutateUpdateDef(t, name, { [prop]: [...rows, newRow] }),
@@ -1430,27 +1565,7 @@ export function renderSchemaFieldsTemplate(
1430
1565
  </div>
1431
1566
  `;
1432
1567
  } else if (ps.type === "array" || ps.type === "object") {
1433
- /** @type {ReturnType<typeof setTimeout> | undefined} */
1434
- let debounce: ReturnType<typeof setTimeout> | undefined;
1435
- control = html`<sp-textfield
1436
- multiline
1437
- size="s"
1438
- style="min-height:40px"
1439
- .value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
1440
- placeholder=${ps.default !== undefined ? JSON.stringify(ps.default) : nothing}
1441
- @input=${(e: Event) => {
1442
- clearTimeout(debounce);
1443
- debounce = setTimeout(() => {
1444
- try {
1445
- transactDoc(activeTab.value, (t) =>
1446
- mutateUpdateDef(t, name, {
1447
- [prop]: JSON.parse((e.target as HTMLInputElement).value),
1448
- }),
1449
- );
1450
- } catch {}
1451
- }, 500);
1452
- }}
1453
- ></sp-textfield>`;
1568
+ control = renderJsonTextField(currentValue, ps, name, prop);
1454
1569
  } else {
1455
1570
  /** @type {ReturnType<typeof setTimeout> | undefined} */
1456
1571
  let debounce: ReturnType<typeof setTimeout> | undefined;
@@ -1476,9 +1591,9 @@ export function renderSchemaFieldsTemplate(
1476
1591
  }
1477
1592
 
1478
1593
  return renderFieldRow({
1479
- prop: ps.name || prop,
1480
- label: labelText,
1481
1594
  hasValue: false,
1595
+ label: labelText,
1596
+ prop: ps.name || prop,
1482
1597
  widget: control,
1483
1598
  });
1484
1599
  });
@@ -1497,7 +1612,7 @@ export function renderExternalPrototypeEditorTemplate(
1497
1612
  ctx: SignalsPanelCtx,
1498
1613
  ) {
1499
1614
  // Schema-driven config fields (async with cache)
1500
- let schemaContent: import("lit-html").TemplateResult | typeof nothing = nothing;
1615
+ let schemaContent: TemplateResult | typeof nothing = nothing;
1501
1616
  const importedPath = def.$prototype
1502
1617
  ? projectState?.projectConfig?.imports?.[def.$prototype]
1503
1618
  : null;
@@ -1524,7 +1639,9 @@ export function renderExternalPrototypeEditorTemplate(
1524
1639
  fetchPluginSchema(def, {
1525
1640
  ...(S.documentPath != null && { documentPath: S.documentPath }),
1526
1641
  }).then((schema) => {
1527
- if (schema) ctx.renderLeftPanel();
1642
+ if (schema) {
1643
+ ctx.renderLeftPanel();
1644
+ }
1528
1645
  });
1529
1646
  }
1530
1647
  }