@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
@@ -5,10 +5,11 @@
5
5
  */
6
6
 
7
7
  import { html, render as litRender, nothing } from "lit-html";
8
+ import { errorMessage } from "@jxsuite/schema/parse";
8
9
  import { ref } from "lit-html/directives/ref.js";
9
10
  import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
10
11
 
11
- import { canvasWrap, canvasPanels, updateCanvas } from "../store";
12
+ import { canvasPanels, canvasWrap, updateCanvas } from "../store";
12
13
  import { activeTab } from "../workspace/workspace";
13
14
  import { view } from "../view";
14
15
  import { parseSourceForPath, serializeDocument } from "../files/file-ops";
@@ -16,19 +17,19 @@ import { formatByName, formatForPath } from "../format/format-host";
16
17
  import { renderWelcome } from "../panels/welcome-screen";
17
18
  import { projectState } from "../state";
18
19
  import {
19
- canvasPanelTemplate,
20
20
  applyTransform,
21
+ canvasPanelTemplate,
21
22
  observeCenterUntilStable,
22
23
  renderZoomIndicator,
23
24
  resetZoomIndicator,
24
25
  updateActivePanelHeaders,
25
26
  } from "./canvas-utils";
26
- import { effectiveZoom, overlayBoxDescriptor } from "./canvas-helpers";
27
+ import { effectiveZoom, findCanvasElement, overlayBoxDescriptor } from "./canvas-helpers";
27
28
  import {
28
- parseMediaEntries,
29
29
  activeBreakpointsForWidth,
30
- collectMediaOverrides,
31
30
  applyOverridesToCanvas,
31
+ collectMediaOverrides,
32
+ parseMediaEntries,
32
33
  } from "../utils/canvas-media";
33
34
  import { getEffectiveMedia } from "../site-context";
34
35
  import { renderCanvasLive } from "./canvas-live-render";
@@ -37,10 +38,9 @@ import { registerPanelDnD } from "../panels/canvas-dnd";
37
38
  import { registerPanelEvents } from "../panels/panel-events";
38
39
  import { computeDocumentDiff } from "./canvas-diff";
39
40
  import { updateForcedPseudoPreview } from "../panels/pseudo-preview";
40
- import { findCanvasElement } from "./canvas-helpers";
41
41
  import { enterComponentInlineEdit } from "../editor/component-inline-edit";
42
- import { renderStylebookMode, refreshStylebookStyles } from "../panels/stylebook-panel";
43
- import { dismissLinkPopover, dismissBlockActionBar } from "../panels/block-action-bar";
42
+ import { refreshStylebookStyles, renderStylebookMode } from "../panels/stylebook-panel";
43
+ import { dismissBlockActionBar, dismissLinkPopover } from "../panels/block-action-bar";
44
44
  import { dismissContextMenu } from "../editor/context-menu";
45
45
  import { dismissSlashMenu } from "../editor/slash-menu";
46
46
  import { renderFunctionEditor } from "../panels/editors";
@@ -51,6 +51,7 @@ import * as overlaysPanel from "../panels/overlays";
51
51
  import type { CanvasPanel } from "../panels/canvas-dnd";
52
52
  import type { GitDiffState, InlineEditDef } from "../types";
53
53
  import type { JxMutableNode } from "@jxsuite/schema/types";
54
+ import type { Tab } from "../tabs/tab.js";
54
55
 
55
56
  interface CanvasRenderCtx {
56
57
  getCanvasMode: () => string;
@@ -77,9 +78,11 @@ export function initCanvasRender(ctx: CanvasRenderCtx) {
77
78
  }
78
79
 
79
80
  /** Monaco language for the source view of a tab's document. */
80
- function sourceLang(tab: import("../tabs/tab.js").Tab) {
81
+ function sourceLang(tab: Tab) {
81
82
  const format = formatByName(tab.doc.sourceFormat);
82
- if (format) return format.mediaType?.split("/").pop() ?? "plaintext";
83
+ if (format) {
84
+ return format.mediaType?.split("/").pop() ?? "plaintext";
85
+ }
83
86
  return (tab.documentPath || "").endsWith(".js") ? "javascript" : "json";
84
87
  }
85
88
 
@@ -87,9 +90,13 @@ function sourceLang(tab: import("../tabs/tab.js").Tab) {
87
90
  * The full source text for the source view. Format-class files serialize to their on-disk form
88
91
  * (e.g. frontmatter YAML plus the body for markdown), not just the JSON of the body tree.
89
92
  */
90
- async function sourceContent(tab: import("../tabs/tab.js").Tab, lang: string) {
91
- if (formatByName(tab.doc.sourceFormat)) return serializeDocument(tab);
92
- if (lang === "javascript") return tab.doc.document?.toString?.() || "";
93
+ async function sourceContent(tab: Tab, lang: string) {
94
+ if (formatByName(tab.doc.sourceFormat)) {
95
+ return serializeDocument(tab);
96
+ }
97
+ if (lang === "javascript") {
98
+ return tab.doc.document?.toString?.() || "";
99
+ }
93
100
  return JSON.stringify(tab.doc.document, null, 2);
94
101
  }
95
102
 
@@ -106,25 +113,25 @@ export function renderCanvas() {
106
113
  const ctx = _ctx as CanvasRenderCtx;
107
114
  const S = {
108
115
  document: tab.doc.document,
109
- ui: tab.session.ui,
110
116
  mode: tab.doc.mode,
117
+ ui: tab.session.ui,
111
118
  };
112
119
  const canvasMode = ctx.getCanvasMode();
113
120
 
114
121
  // Advance render generation so stale async renders from the previous cycle bail out
115
- ++view.renderGeneration;
122
+ view.renderGeneration += 1;
116
123
 
117
124
  // Detect whether this is a mode transition or a content-only re-render
118
125
  const modeChanged = canvasMode !== view.prevCanvasMode;
119
126
 
120
127
  // Only clear Lit's internal state on mode transitions (structural panel changes).
121
128
  // For content re-renders in the same mode, Lit's template diffing preserves
122
- // the panel structure. Bailed async renders can't corrupt the DOM because
123
- // renderCanvasLive uses atomic clear (innerHTML = "" right before appendChild).
124
- // @ts-ignore
129
+ // The panel structure. Bailed async renders can't corrupt the DOM because
130
+ // RenderCanvasLive uses atomic clear (innerHTML = "" right before appendChild).
131
+ // @ts-expect-error -- _$litPart$ is Lit's private render-part marker, not in the DOM types
125
132
  if (modeChanged && canvasWrap["_$litPart$"]) {
126
133
  canvasWrap.textContent = "";
127
- // @ts-ignore
134
+ // @ts-expect-error -- _$litPart$ is Lit's private render-part marker, not in the DOM types
128
135
  delete canvasWrap["_$litPart$"];
129
136
  }
130
137
 
@@ -141,13 +148,15 @@ export function renderCanvas() {
141
148
  }
142
149
 
143
150
  // Source mode: update existing Monaco editor without recreating. Don't replace the buffer while
144
- // the user is actively typing in it — that would reformat under the cursor (the source view is
145
- // the editing surface here, mirroring the panel draft-state behaviour).
151
+ // The user is actively typing in it — that would reformat under the cursor (the source view is
152
+ // The editing surface here, mirroring the panel draft-state behaviour).
146
153
  if (canvasMode === "source" && view.monacoEditor) {
147
154
  const editor = view.monacoEditor;
148
155
  sourceContent(tab, sourceLang(tab))
149
156
  .then((newVal) => {
150
- if (view.monacoEditor !== editor) return;
157
+ if (view.monacoEditor !== editor) {
158
+ return;
159
+ }
151
160
  if (!editor.hasTextFocus() && editor.getValue() !== newVal) {
152
161
  editor._ignoreNextChange = true;
153
162
  editor.setValue(newVal);
@@ -162,7 +171,7 @@ export function renderCanvas() {
162
171
  // Stylebook fast-path: re-apply styles without rebuilding DOM
163
172
  if (canvasMode === "stylebook" && !modeChanged) {
164
173
  const curFilter = tab.session.ui.stylebookFilter || "";
165
- const curCustomized = !!tab.session.ui.stylebookCustomizedOnly;
174
+ const curCustomized = Boolean(tab.session.ui.stylebookCustomizedOnly);
166
175
  const filterChanged =
167
176
  curFilter !== _prevStylebookFilter || curCustomized !== _prevStylebookCustomizedOnly;
168
177
  if (!filterChanged) {
@@ -175,13 +184,17 @@ export function renderCanvas() {
175
184
  view.prevCanvasMode = canvasMode;
176
185
 
177
186
  // DnD handlers are registered on inner canvas elements that get replaced on every
178
- // content render, so always clean them up.
179
- for (const fn of view.canvasDndCleanups) fn();
187
+ // Content render, so always clean them up.
188
+ for (const fn of view.canvasDndCleanups) {
189
+ fn();
190
+ }
180
191
  view.canvasDndCleanups = [];
181
192
 
182
193
  // Panel event handlers (click, dblclick, etc.) capture closures over panel references.
183
194
  // Always re-register to keep closures fresh across document switches.
184
- for (const fn of view.canvasEventCleanups) fn();
195
+ for (const fn of view.canvasEventCleanups) {
196
+ fn();
197
+ }
185
198
  view.canvasEventCleanups = [];
186
199
 
187
200
  // Panel JS objects are cheap — always clear and repopulate from templates.
@@ -225,15 +238,15 @@ export function renderCanvas() {
225
238
  // Stylebook mode: render element catalog with panzoom surface
226
239
  if (canvasMode === "stylebook") {
227
240
  _prevStylebookFilter = tab.session.ui.stylebookFilter || "";
228
- _prevStylebookCustomizedOnly = !!tab.session.ui.stylebookCustomizedOnly;
241
+ _prevStylebookCustomizedOnly = Boolean(tab.session.ui.stylebookCustomizedOnly);
229
242
  renderStylebookMode({
230
- canvasPanelTemplate,
231
243
  applyTransform,
244
+ canvasPanelTemplate,
245
+ effectiveZoom,
232
246
  observeCenterUntilStable,
247
+ overlayBoxDescriptor,
233
248
  renderZoomIndicator,
234
249
  updateActivePanelHeaders,
235
- overlayBoxDescriptor,
236
- effectiveZoom,
237
250
  });
238
251
  return;
239
252
  }
@@ -254,7 +267,9 @@ export function renderCanvas() {
254
267
  <div
255
268
  class="source-editor"
256
269
  ${ref((el) => {
257
- if (el) editorContainer = el as HTMLDivElement;
270
+ if (el) {
271
+ editorContainer = el as HTMLDivElement;
272
+ }
258
273
  })}
259
274
  ></div>
260
275
  </div>`,
@@ -263,7 +278,7 @@ export function renderCanvas() {
263
278
 
264
279
  const filePath = tab.documentPath || "document.json";
265
280
  const lang = sourceLang(tab);
266
- const modelUri = monaco.Uri.parse("file:///" + filePath);
281
+ const modelUri = monaco.Uri.parse(`file:///${filePath}`);
267
282
  const model = monaco.editor.createModel("", lang, modelUri);
268
283
  sourceContent(tab, lang)
269
284
  .then((content) => {
@@ -277,53 +292,57 @@ export function renderCanvas() {
277
292
  // Serialization unavailable — leave the buffer empty rather than crash the render
278
293
  });
279
294
  view.monacoEditor = monaco.editor.create(editorContainer as unknown as HTMLElement, {
280
- model,
281
- theme: "vs-dark",
282
295
  automaticLayout: true,
283
- minimap: { enabled: false },
284
- fontSize: 12,
285
296
  fontFamily: "'SF Mono', 'Fira Code', 'Consolas', monospace",
297
+ fontSize: 12,
286
298
  lineNumbers: "on",
299
+ minimap: { enabled: false },
300
+ model,
287
301
  scrollBeyondLastLine: false,
288
- wordWrap: "on",
289
302
  tabSize: 2,
303
+ theme: "vs-dark",
304
+ wordWrap: "on",
290
305
  });
291
306
 
292
307
  // Debounced sync back to state
293
308
  let debounce: ReturnType<typeof setTimeout> | undefined;
294
309
  view.monacoEditor.onDidChangeModelContent(() => {
295
310
  const editor = view.monacoEditor;
296
- if (!editor) return;
311
+ if (!editor) {
312
+ return;
313
+ }
297
314
  if (editor._ignoreNextChange) {
298
315
  editor._ignoreNextChange = false;
299
316
  return;
300
317
  }
301
318
  clearTimeout(debounce);
302
319
  debounce = setTimeout(async () => {
303
- const tab = activeTab.value;
304
- if (!tab) return;
305
- if (formatByName(tab.doc.sourceFormat) && tab.documentPath) {
320
+ const tabNow = activeTab.value;
321
+ if (!tabNow) {
322
+ return;
323
+ }
324
+ if (formatByName(tabNow.doc.sourceFormat) && tabNow.documentPath) {
306
325
  try {
307
326
  // Parse the full source back into body + frontmatter (title, $head, etc.).
308
327
  const { document, frontmatter } = await parseSourceForPath(
309
- tab.documentPath,
328
+ tabNow.documentPath,
310
329
  editor.getValue(),
311
330
  );
312
- tab.doc.document = document as JxMutableNode;
313
- tab.doc.content.frontmatter = frontmatter;
314
- tab.doc.dirty = true;
331
+ tabNow.doc.document = document as JxMutableNode;
332
+ tabNow.doc.content.frontmatter = frontmatter;
333
+ tabNow.doc.dirty = true;
315
334
  } catch {
316
335
  // Unparseable source — don't update state
317
336
  }
318
337
  } else if (lang === "json") {
319
338
  try {
320
- tab.doc.document = JSON.parse(editor.getValue());
321
- tab.doc.dirty = true;
339
+ tabNow.doc.document = JSON.parse(editor.getValue());
340
+ tabNow.doc.dirty = true;
322
341
  } catch {
323
342
  // Invalid JSON — don't update state
324
343
  }
325
344
  } else {
326
- tab.doc.dirty = true;
345
+ tabNow.doc.dirty = true;
327
346
  }
328
347
  }, 600);
329
348
  });
@@ -343,7 +362,7 @@ export function renderCanvas() {
343
362
  canvasWrap.style.overflow = "hidden";
344
363
  }
345
364
 
346
- const gitDiffState = ctx.gitDiffState;
365
+ const { gitDiffState } = ctx;
347
366
  const panelWidth = 800;
348
367
 
349
368
  const { tpl: origTpl, panel: origPanel } = canvasPanelTemplate(
@@ -365,7 +384,9 @@ export function renderCanvas() {
365
384
  class="panzoom-wrap"
366
385
  style="transform-origin:0 0"
367
386
  ${ref((el) => {
368
- if (el) view.panzoomWrap = el as HTMLDivElement;
387
+ if (el) {
388
+ view.panzoomWrap = el as HTMLDivElement;
389
+ }
369
390
  })}
370
391
  >
371
392
  ${origTpl} ${currTpl}
@@ -388,14 +409,14 @@ export function renderCanvas() {
388
409
  return JSON.parse(content);
389
410
  } catch {
390
411
  return {
391
- tagName: "div",
392
412
  children: [{ tagName: "p", textContent: "Failed to parse" }],
413
+ tagName: "div",
393
414
  };
394
415
  }
395
416
  });
396
417
  };
397
418
 
398
- const featureToggles = S.ui.featureToggles;
419
+ const { featureToggles } = S.ui;
399
420
  Promise.all([
400
421
  parseContent(gitDiffState.originalContent || ""),
401
422
  parseContent(gitDiffState.currentContent || ""),
@@ -417,7 +438,9 @@ export function renderCanvas() {
417
438
  });
418
439
 
419
440
  applyTransform();
420
- if (modeChanged) observeCenterUntilStable();
441
+ if (modeChanged) {
442
+ observeCenterUntilStable();
443
+ }
421
444
  renderZoomIndicator();
422
445
  return;
423
446
  }
@@ -462,7 +485,7 @@ export function renderCanvas() {
462
485
  baseWidth,
463
486
  } = parseMediaEntries(getEffectiveMedia(S.document.$media));
464
487
  const hasMedia = sizeBreakpoints.length > 0;
465
- const featureToggles = S.ui.featureToggles;
488
+ const { featureToggles } = S.ui;
466
489
 
467
490
  // Create panzoom wrapper (the element that gets transformed)
468
491
  if (!hasMedia) {
@@ -482,7 +505,9 @@ export function renderCanvas() {
482
505
  class="panzoom-wrap"
483
506
  style="transform-origin:0 0"
484
507
  ${ref((el) => {
485
- if (el) view.panzoomWrap = el as HTMLDivElement;
508
+ if (el) {
509
+ view.panzoomWrap = el as HTMLDivElement;
510
+ }
486
511
  })}
487
512
  >
488
513
  ${panelTpl}
@@ -501,28 +526,28 @@ export function renderCanvas() {
501
526
  }
502
527
 
503
528
  // Build all panels: base first, then breakpoints in declared order (ascending for min-width,
504
- // descending for max-width — matching the direction of the design's media queries).
529
+ // Descending for max-width — matching the direction of the design's media queries).
505
530
  const allPanelDefs = [
506
531
  {
507
- name: "base",
532
+ activeSet: activeBreakpointsForWidth(sizeBreakpoints, baseWidth),
508
533
  displayName: mediaDisplayName("--"),
534
+ name: "base",
509
535
  width: baseWidth,
510
- activeSet: activeBreakpointsForWidth(sizeBreakpoints, baseWidth),
511
536
  },
512
537
  ];
513
538
  for (const bp of sizeBreakpoints) {
514
539
  allPanelDefs.push({
515
- name: bp.name,
540
+ activeSet: activeBreakpointsForWidth(sizeBreakpoints, bp.width),
516
541
  displayName: mediaDisplayName(bp.name),
542
+ name: bp.name,
517
543
  width: bp.width,
518
- activeSet: activeBreakpointsForWidth(sizeBreakpoints, bp.width),
519
544
  });
520
545
  }
521
546
 
522
547
  const panelEntries = allPanelDefs.map((def) => {
523
548
  const label = `${def.displayName} (${def.width}px)`;
524
549
  const { tpl, panel } = canvasPanelTemplate(def.name, label, false, def.width);
525
- return { tpl, panel, activeSet: def.activeSet };
550
+ return { activeSet: def.activeSet, panel, tpl };
526
551
  });
527
552
 
528
553
  litRender(
@@ -531,7 +556,9 @@ export function renderCanvas() {
531
556
  class="panzoom-wrap"
532
557
  style="transform-origin:0 0"
533
558
  ${ref((el) => {
534
- if (el) view.panzoomWrap = el as HTMLDivElement;
559
+ if (el) {
560
+ view.panzoomWrap = el as HTMLDivElement;
561
+ }
535
562
  })}
536
563
  >
537
564
  ${panelEntries.map((e) => e.tpl)}
@@ -591,9 +618,11 @@ function renderCanvasIntoPanel(
591
618
  renderCanvasLive(gen, docToRender, canvas)
592
619
  .then((scope: Record<string, unknown> | null) => {
593
620
  // Skip post-render setup if a newer render has started
594
- if (gen !== view.renderGeneration) return;
621
+ if (gen !== view.renderGeneration) {
622
+ return;
623
+ }
595
624
  if (scope) {
596
- updateCanvas({ status: "ready", scope, error: null });
625
+ updateCanvas({ error: null, scope, status: "ready" });
597
626
  applyCanvasMediaOverrides(canvas, activeBreakpoints);
598
627
  statusMessage("Runtime render OK", 1500);
599
628
 
@@ -615,14 +644,14 @@ function renderCanvasIntoPanel(
615
644
  }
616
645
  } else {
617
646
  // Fallback to structural preview
618
- updateCanvas({ status: "ready", scope: null, error: null });
647
+ updateCanvas({ error: null, scope: null, status: "ready" });
619
648
  canvas.innerHTML = "";
620
649
  renderCanvasNode(docToRender, [], canvas, activeBreakpoints, featureToggles);
621
650
  }
622
651
  try {
623
652
  registerPanelDnD(panel as unknown as CanvasPanel);
624
- } catch (e) {
625
- console.warn("registerPanelDnD failed:", (e as Error).message);
653
+ } catch (dndError) {
654
+ console.warn("registerPanelDnD failed:", errorMessage(dndError));
626
655
  }
627
656
  registerPanelEvents(panel as unknown as CanvasPanel);
628
657
  renderOverlays();
@@ -636,20 +665,24 @@ function renderCanvasIntoPanel(
636
665
  const targetPanel = canvasPanels.find((p) => p.mediaName === mn) || canvasPanels[0];
637
666
  if (targetPanel) {
638
667
  const el = findCanvasElement(path, targetPanel.canvas);
639
- if (el) enterComponentInlineEdit(el, path);
668
+ if (el) {
669
+ enterComponentInlineEdit(el, path);
670
+ }
640
671
  }
641
672
  }
642
673
  })
643
- .catch((err: unknown) => {
644
- if (gen !== view.renderGeneration) return;
645
- console.warn("renderCanvasLive rejected:", err instanceof Error ? err.message : err);
646
- updateCanvas({ status: "ready", scope: null, error: null });
674
+ .catch((error: unknown) => {
675
+ if (gen !== view.renderGeneration) {
676
+ return;
677
+ }
678
+ console.warn("renderCanvasLive rejected:", error instanceof Error ? error.message : error);
679
+ updateCanvas({ error: null, scope: null, status: "ready" });
647
680
  canvas.innerHTML = "";
648
681
  renderCanvasNode(docToRender, [], canvas, activeBreakpoints, featureToggles);
649
682
  try {
650
683
  registerPanelDnD(panel as unknown as CanvasPanel);
651
- } catch (e) {
652
- console.warn("registerPanelDnD failed:", (e as Error).message);
684
+ } catch (dndError) {
685
+ console.warn("registerPanelDnD failed:", errorMessage(dndError));
653
686
  }
654
687
  registerPanelEvents(panel as unknown as CanvasPanel);
655
688
  renderOverlays();
@@ -668,7 +701,9 @@ function applyDiffHighlightToCanvas(
668
701
  canvas: HTMLElement,
669
702
  diffMap: Map<string, "added" | "removed" | "modified">,
670
703
  ) {
671
- if (!diffMap || diffMap.size === 0) return;
704
+ if (!diffMap || diffMap.size === 0) {
705
+ return;
706
+ }
672
707
 
673
708
  // Walk all elements in canvas and check their data attributes or other markers
674
709
  const walkCanvas = (el: HTMLElement, /** @type {string} */ path = "") => {
@@ -676,9 +711,13 @@ function applyDiffHighlightToCanvas(
676
711
 
677
712
  if (diffMap.has(pathKey)) {
678
713
  const status = diffMap.get(pathKey);
679
- if (status === "added") el.classList.add("element-diff-added");
680
- else if (status === "removed") el.classList.add("element-diff-removed");
681
- else if (status === "modified") el.classList.add("element-diff-modified");
714
+ if (status === "added") {
715
+ el.classList.add("element-diff-added");
716
+ } else if (status === "removed") {
717
+ el.classList.add("element-diff-removed");
718
+ } else if (status === "modified") {
719
+ el.classList.add("element-diff-modified");
720
+ }
682
721
  }
683
722
 
684
723
  // Check for child elements (heuristic: children array markers)
@@ -687,7 +726,7 @@ function applyDiffHighlightToCanvas(
687
726
  const childPath =
688
727
  pathKey === "/" ? `children/${childIdx}` : `${pathKey}/children/${childIdx}`;
689
728
  walkCanvas(child as HTMLElement, childPath);
690
- childIdx++;
729
+ childIdx += 1;
691
730
  }
692
731
  };
693
732
 
@@ -703,14 +742,20 @@ function applyDiffHighlightToCanvas(
703
742
  * @param {Set<string>} activeBreakpoints
704
743
  */
705
744
  function applyCanvasMediaOverrides(canvasEl: Element, activeBreakpoints: Set<string>) {
706
- if (!activeBreakpoints.size) return;
745
+ if (activeBreakpoints.size === 0) {
746
+ return;
747
+ }
707
748
  const tab = activeTab.value;
708
- if (!tab) return;
749
+ if (!tab) {
750
+ return;
751
+ }
709
752
  const docMedia = getEffectiveMedia(tab.doc.document.$media || {});
710
753
  // Build a set of CSS condition texts that match active breakpoints
711
754
  const activeConditions = new Set<string>();
712
755
  for (const name of activeBreakpoints) {
713
- if (docMedia[name]) activeConditions.add(docMedia[name]);
756
+ if (docMedia[name]) {
757
+ activeConditions.add(docMedia[name]);
758
+ }
714
759
  }
715
760
  const overrides = collectMediaOverrides(document.styleSheets, activeConditions);
716
761
  applyOverridesToCanvas(canvasEl, overrides);