@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
package/src/state.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { ProjectState } from "./types";
13
+ import { isRef } from "@jxsuite/schema/guards";
13
14
  import type { JxMutableNode } from "@jxsuite/schema/types";
14
15
 
15
16
  export type JxPath = (string | number)[];
@@ -63,12 +64,26 @@ interface StudioStackFrame {
63
64
  export function getNodeAtPath(doc: JxMutableNode, path: JxPath) {
64
65
  let node = doc;
65
66
  for (const key of path) {
66
- if (node == null) return undefined as unknown as JxMutableNode;
67
- node = node[key];
67
+ if (node == null) {
68
+ return undefined as unknown as JxMutableNode;
69
+ }
70
+ // Paths address nodes by construction; non-node leaves surface as undefined above.
71
+ node = node[key] as JxMutableNode;
68
72
  }
69
73
  return node;
70
74
  }
71
75
 
76
+ /**
77
+ * The node's children when they are a static array (the edit-mode invariant); an empty array for
78
+ * mapped-array or absent children.
79
+ *
80
+ * @param {JxMutableNode | null | undefined} node
81
+ * @returns {(JxMutableNode | string)[]}
82
+ */
83
+ export function childList(node: JxMutableNode | null | undefined): (JxMutableNode | string)[] {
84
+ return Array.isArray(node?.children) ? node.children : [];
85
+ }
86
+
72
87
  /**
73
88
  * Return the path to the parent element (strips trailing 'children' + index).
74
89
  *
@@ -86,7 +101,7 @@ export function parentElementPath(path: JxPath) {
86
101
  * @returns {string | number}
87
102
  */
88
103
  export function childIndex(path: JxPath) {
89
- return path[path.length - 1];
104
+ return path.at(-1);
90
105
  }
91
106
 
92
107
  /**
@@ -107,8 +122,12 @@ export function pathKey(path: JxPath) {
107
122
  * @returns {boolean}
108
123
  */
109
124
  export function pathsEqual(a: JxPath | null, b: JxPath | null) {
110
- if (a === b) return true;
111
- if (!a || !b || a.length !== b.length) return false;
125
+ if (a === b) {
126
+ return true;
127
+ }
128
+ if (!a || !b || a.length !== b.length) {
129
+ return false;
130
+ }
112
131
  return a.every((v, i) => v === b[i]);
113
132
  }
114
133
 
@@ -120,7 +139,9 @@ export function pathsEqual(a: JxPath | null, b: JxPath | null) {
120
139
  * @returns {boolean}
121
140
  */
122
141
  export function isAncestor(path: JxPath, descendant: JxPath) {
123
- if (path.length > descendant.length) return false;
142
+ if (path.length > descendant.length) {
143
+ return false;
144
+ }
124
145
  return path.every((v, i) => v === descendant[i]);
125
146
  }
126
147
 
@@ -142,31 +163,31 @@ export function isAncestor(path: JxPath, descendant: JxPath) {
142
163
  * nodeType: string;
143
164
  * }[]}
144
165
  */
145
- export type FlatRow = {
166
+ export interface FlatRow {
146
167
  node: JxMutableNode | string | number | boolean;
147
168
  path: JxPath;
148
169
  depth: number;
149
170
  nodeType: string;
150
- };
171
+ }
151
172
 
152
173
  export function flattenTree(
153
174
  doc: JxMutableNode | string | number | boolean,
154
175
  path: JxPath = [],
155
- depth: number = 0,
176
+ depth = 0,
156
177
  ): FlatRow[] {
157
178
  // Text node children: bare primitives get a "text" row
158
179
  if (typeof doc === "string" || typeof doc === "number" || typeof doc === "boolean") {
159
- return [{ node: doc, path, depth, nodeType: "text" }];
180
+ return [{ depth, node: doc, nodeType: "text", path }];
160
181
  }
161
182
 
162
- const rows: FlatRow[] = [{ node: doc, path, depth, nodeType: "element" }];
183
+ const rows: FlatRow[] = [{ depth, node: doc, nodeType: "element", path }];
163
184
 
164
185
  // Custom component instances without user-authored children are atomic in the layer tree
165
186
  if (doc.$props && (doc.tagName || "").includes("-") && !Array.isArray(doc.children)) {
166
187
  return rows;
167
188
  }
168
189
 
169
- const children = doc.children;
190
+ const { children } = doc;
170
191
 
171
192
  if (Array.isArray(children)) {
172
193
  for (let i = 0; i < children.length; i++) {
@@ -180,10 +201,10 @@ export function flattenTree(
180
201
  ) {
181
202
  // $map — emit the map container, then recurse into the template
182
203
  rows.push({
183
- node: children as JxMutableNode,
184
- path: [...path, "children"],
185
204
  depth: depth + 1,
205
+ node: children as JxMutableNode,
186
206
  nodeType: "map",
207
+ path: [...path, "children"],
187
208
  });
188
209
  const mapDef = (children as JxMutableNode).map;
189
210
  if (mapDef && typeof mapDef === "object") {
@@ -197,17 +218,17 @@ export function flattenTree(
197
218
  const casePath = [...path, "cases", caseName];
198
219
  if (caseDef && typeof caseDef === "object" && (caseDef as JxMutableNode).$ref) {
199
220
  rows.push({
200
- node: caseDef as JxMutableNode,
201
- path: casePath,
202
221
  depth: depth + 1,
222
+ node: caseDef as JxMutableNode,
203
223
  nodeType: "case-ref",
224
+ path: casePath,
204
225
  });
205
226
  } else if (caseDef && typeof caseDef === "object") {
206
227
  rows.push({
207
- node: caseDef as JxMutableNode,
208
- path: casePath,
209
228
  depth: depth + 1,
229
+ node: caseDef as JxMutableNode,
210
230
  nodeType: "case",
231
+ path: casePath,
211
232
  });
212
233
  // Recurse into case children (skip the case node itself — already emitted)
213
234
  const caseChildren = flattenTree(caseDef as JxMutableNode, casePath, depth + 2);
@@ -226,14 +247,21 @@ export function flattenTree(
226
247
  * @returns {string}
227
248
  */
228
249
  export function nodeLabel(node: JxMutableNode | null) {
229
- if (!node) return "?";
250
+ if (!node) {
251
+ return "?";
252
+ }
230
253
  // $map container (Repeater)
231
254
  if (node.$prototype === "Array") {
232
- const ref = node.items?.$ref || "items";
255
+ const { items } = node;
256
+ const ref = (isRef(items) ? items.$ref : undefined) || "items";
233
257
  return `Repeater → ${ref}`;
234
258
  }
235
- if (node.$title) return node.$title;
236
- if (node.$id) return node.$id;
259
+ if (node.$title) {
260
+ return node.$title;
261
+ }
262
+ if (node.$id) {
263
+ return node.$id;
264
+ }
237
265
  const tag = node.tagName ?? "div";
238
266
  const suffix = node.$switch ? " ⇆" : "";
239
267
  if (typeof node.textContent === "string" && node.textContent.length > 0) {
@@ -251,47 +279,47 @@ export function nodeLabel(node: JxMutableNode | null) {
251
279
  export function createState(doc: JxMutableNode): StudioState {
252
280
  const initial = { document: doc, selection: null };
253
281
  return {
254
- document: doc,
255
- selection: null,
256
- hover: null,
257
- history: [initial],
258
- historyIndex: 0,
282
+ canvas: {
283
+ error: null, // Error message on failure
284
+ scope: null, // $defs scope from runtime buildScope
285
+ status: "idle", // "idle" | "loading" | "ready" | "error"
286
+ },
287
+ content: { frontmatter: {} }, // Frontmatter metadata for .md files
259
288
  dirty: false,
289
+ document: doc,
290
+ documentPath: null, // Root-relative path, e.g. "examples/markdown/blog.json"
291
+ documentStack: [], // Frames for component navigation
260
292
  fileHandle: null,
261
- documentPath: null, // root-relative path, e.g. "examples/markdown/blog.json"
262
- documentStack: [], // frames for component navigation
263
293
  handlersSource: null,
294
+ history: [initial],
295
+ historyIndex: 0,
296
+ hover: null,
264
297
  mode: "component", // 'component' | 'content'
265
- content: { frontmatter: {} }, // frontmatter metadata for .md files
298
+ selection: null,
266
299
  ui: {
267
- rightTab: "properties", // 'properties' | 'events' | 'style'
268
- zoom: 1,
269
300
  activeMedia: null, // '--md' | null (base) — focused canvas/breakpoint
270
301
  activeSelector: null, // ':hover' | '.child' | null (base) — nested selector context
302
+ editingFunction: null, // Null | { type: 'def', defName } | { type: 'event', path, eventKey }
271
303
  featureToggles: {}, // { '--dark': true } — non-size media toggles
272
- styleSections: {}, // { layout: true, ... } — section open/closed state
304
+ gitBranches: null, // { current, branches: [] }
305
+ gitCommitMessage: "", // Commit message input
306
+ gitDiffState: null,
307
+ gitError: null, // Error message string
308
+ gitLoading: false, // Loading indicator during async ops
309
+ gitStatus: null, // { branch, ahead, behind, files: [] }
273
310
  inspectorSections: {}, // { identity: true, ... } — properties panel section open/closed state
311
+ pendingInlineEdit: null, // Null | { path, mediaName } — deferred inline edit awaiting canvas readiness
312
+ rightTab: "properties", // 'properties' | 'events' | 'style'
313
+ settingsTab: "stylebook", // "stylebook" | "definitions" | "contentTypes"
314
+ styleFilter: "", // Free-text filter for CSS property names
315
+ styleFilterActive: false, // True = show only props with values set
316
+ styleSections: {}, // { layout: true, ... } — section open/closed state
274
317
  styleShorthands: {}, // { padding: true, ... } — shorthand expand/collapse state
275
- styleFilter: "", // free-text filter for CSS property names
276
- styleFilterActive: false, // true = show only props with values set
277
- editingFunction: null, // null | { type: 'def', defName } | { type: 'event', path, eventKey }
278
- stylebookSelection: null, // tag name string, e.g. "h1"
318
+ stylebookCustomizedOnly: false, // Show only customized elements
319
+ stylebookFilter: "", // Search filter text
320
+ stylebookSelection: null, // Tag name string, e.g. "h1"
279
321
  stylebookTab: "elements", // "elements" | "variables"
280
- stylebookFilter: "", // search filter text
281
- stylebookCustomizedOnly: false, // show only customized elements
282
- settingsTab: "stylebook", // "stylebook" | "definitions" | "contentTypes"
283
- gitStatus: null, // { branch, ahead, behind, files: [] }
284
- gitBranches: null, // { current, branches: [] }
285
- gitCommitMessage: "", // commit message input
286
- gitLoading: false, // loading indicator during async ops
287
- gitError: null, // error message string
288
- gitDiffState: null,
289
- pendingInlineEdit: null, // null | { path, mediaName } — deferred inline edit awaiting canvas readiness
290
- },
291
- canvas: {
292
- status: "idle", // "idle" | "loading" | "ready" | "error"
293
- scope: null, // $defs scope from runtime buildScope
294
- error: null, // error message on failure
322
+ zoom: 1,
295
323
  },
296
324
  };
297
325
  }
@@ -334,26 +362,26 @@ export function fromFlat(S: StudioState) {
334
362
  } = S;
335
363
  return {
336
364
  doc: {
337
- document,
365
+ content,
338
366
  dirty,
339
- fileHandle,
367
+ document,
340
368
  documentPath,
341
369
  documentStack,
370
+ fileHandle,
342
371
  handlersSource,
343
- mode,
344
- content,
345
372
  history,
346
373
  historyIndex,
374
+ mode,
347
375
  },
348
- session: { selection, hover, ui, canvas },
376
+ session: { canvas, hover, selection, ui },
349
377
  };
350
378
  }
351
379
 
352
380
  // ─── Project state (persists across document switches) ────────────────────────
353
381
  //
354
382
  // Shape: { root, name, projectRoot, isSiteProject, projectConfig,
355
- // dirs: Map<string, DirEntry[]>, expanded: Set<string>,
356
- // selectedPath: string|null, searchQuery: string }
383
+ // Dirs: Map<string, DirEntry[]>, expanded: Set<string>,
384
+ // SelectedPath: string|null, searchQuery: string }
357
385
  // DirEntry: { name, path, type: "file"|"directory", size, modified }
358
386
 
359
387
  export let projectState: ProjectState | null = null;
@@ -386,8 +414,11 @@ export function requireProjectState() {
386
414
  */
387
415
  export function updateFrontmatter(state: StudioState, field: string, value: unknown) {
388
416
  const fm = { ...state.content?.frontmatter };
389
- if (value === undefined || value === null || value === "") delete fm[field];
390
- else fm[field] = value;
417
+ if (value === undefined || value === null || value === "") {
418
+ delete fm[field];
419
+ } else {
420
+ fm[field] = value;
421
+ }
391
422
  return {
392
423
  ...state,
393
424
  content: { ...state.content, frontmatter: fm },
@@ -427,14 +458,14 @@ export function hoverNode(state: StudioState, path: JxPath | null) {
427
458
  */
428
459
  export function pushDocument(state: StudioState, doc: JxMutableNode, documentPath: string | null) {
429
460
  const frame = {
461
+ dirty: state.dirty,
430
462
  document: state.document,
431
- selection: state.selection,
432
- fileHandle: state.fileHandle,
433
463
  documentPath: state.documentPath,
434
- dirty: state.dirty,
464
+ fileHandle: state.fileHandle,
435
465
  history: state.history,
436
466
  historyIndex: state.historyIndex,
437
467
  mode: state.mode,
468
+ selection: state.selection,
438
469
  };
439
470
  const newState = createState(doc);
440
471
  newState.documentStack = [...(state.documentStack || []), frame];
@@ -450,7 +481,9 @@ export function pushDocument(state: StudioState, doc: JxMutableNode, documentPat
450
481
  * @returns {StudioState}
451
482
  */
452
483
  export function popDocument(state: StudioState) {
453
- if (!state.documentStack || state.documentStack.length === 0) return state;
484
+ if (!state.documentStack || state.documentStack.length === 0) {
485
+ return state;
486
+ }
454
487
  const stack = [...state.documentStack];
455
488
  const frame = stack.pop();
456
489
  return {
package/src/store.ts CHANGED
@@ -10,12 +10,15 @@
10
10
  // ─── Re-exports from state.js ────────────────────────────────────────────────
11
11
 
12
12
  import { activeTab } from "./workspace/workspace";
13
+ import { isEventBinding } from "@jxsuite/schema/guards";
13
14
  import type { JxPath } from "./state";
14
15
  import type { JxMutableNode } from "@jxsuite/schema/types";
16
+ import type { CanvasPanel } from "./panels/canvas-dnd.js";
15
17
 
16
18
  export {
17
19
  createState,
18
20
  getNodeAtPath,
21
+ childList,
19
22
  flattenTree,
20
23
  nodeLabel,
21
24
  pathKey,
@@ -49,9 +52,9 @@ export function initShellRefs() {
49
52
 
50
53
  // ─── Shared containers (mutated in place by owner modules) ───────────────────
51
54
 
52
- export const elToPath: WeakMap<Element, JxPath> = new WeakMap();
55
+ export const elToPath = new WeakMap<Element, JxPath>();
53
56
 
54
- export const canvasPanels: import("./panels/canvas-dnd.js").CanvasPanel[] = [];
57
+ export const canvasPanels: CanvasPanel[] = [];
55
58
 
56
59
  // ─── Shared constants ────────────────────────────────────────────────────────
57
60
 
@@ -101,8 +104,12 @@ const _styleDebounceTimers = new Map();
101
104
  * @param {number} ms
102
105
  * @param {(...args: unknown[]) => void} fn
103
106
  */
104
- export function debouncedStyleCommit(prop: string, ms: number, fn: (...args: any[]) => void) {
105
- return (...args: unknown[]) => {
107
+ export function debouncedStyleCommit<A extends unknown[]>(
108
+ prop: string,
109
+ ms: number,
110
+ fn: (...args: A) => void,
111
+ ) {
112
+ return (...args: A) => {
106
113
  clearTimeout(_styleDebounceTimers.get(prop));
107
114
  _styleDebounceTimers.set(
108
115
  prop,
@@ -127,30 +134,34 @@ export function cancelStyleDebounce(prop: string) {
127
134
  * @returns {JxMutableNode}
128
135
  */
129
136
  export function stripEventHandlers(node: JxMutableNode): JxMutableNode {
130
- if (!node || typeof node !== "object") return node;
131
- if (Array.isArray(node)) return node.map(stripEventHandlers);
137
+ if (!node || typeof node !== "object") {
138
+ return node;
139
+ }
140
+ if (Array.isArray(node)) {
141
+ // Arrays of nodes round-trip element-wise; the array itself is not a node.
142
+ return node.map((n) => stripEventHandlers(n)) as unknown as JxMutableNode;
143
+ }
132
144
  const out: Record<string, unknown> = {};
133
145
  for (const [k, v] of Object.entries(node)) {
134
- if (
135
- k.startsWith("on") &&
136
- typeof v === "object" &&
137
- (v?.$ref || v?.$prototype === "Function" || v?.$expression)
138
- )
146
+ if (k.startsWith("on") && isEventBinding(v)) {
139
147
  continue;
148
+ }
140
149
  if (k === "children") {
141
150
  out.children = Array.isArray(v)
142
- ? v.map(stripEventHandlers)
151
+ ? v.map((c) => stripEventHandlers(c))
143
152
  : stripEventHandlers(v as JxMutableNode);
144
153
  } else if (k === "cases" && typeof v === "object") {
145
154
  const cases: Record<string, unknown> = {};
146
- for (const [ck, cv] of Object.entries(v as Record<string, unknown>))
155
+ for (const [ck, cv] of Object.entries(v as Record<string, unknown>)) {
147
156
  cases[ck] = stripEventHandlers(cv as JxMutableNode);
157
+ }
148
158
  out.cases = cases;
149
159
  } else if (k === "state" && typeof v === "object" && v !== null) {
150
160
  const state: Record<string, unknown> = {};
151
161
  for (const [sk, sv] of Object.entries(v as Record<string, unknown>)) {
152
- if (sv && typeof sv === "object" && (sv as Record<string, unknown>).timing === "server")
162
+ if (sv && typeof sv === "object" && (sv as Record<string, unknown>).timing === "server") {
153
163
  continue;
164
+ }
154
165
  state[sk] = sv;
155
166
  }
156
167
  out.state = state;
@@ -165,7 +176,7 @@ export function stripEventHandlers(node: JxMutableNode): JxMutableNode {
165
176
 
166
177
  // ─── Render orchestration ────────────────────────────────────────────────────
167
178
 
168
- const _renderers: Map<string, Function> = new Map();
179
+ const _renderers = new Map<string, () => void>();
169
180
 
170
181
  /**
171
182
  * Register a named renderer. Called at module import time by each module.
@@ -182,8 +193,8 @@ export function render() {
182
193
  for (const [name, fn] of _renderers.entries()) {
183
194
  try {
184
195
  fn();
185
- } catch (e) {
186
- console.error(`Renderer "${name}" failed:`, e);
196
+ } catch (error) {
197
+ console.error(`Renderer "${name}" failed:`, error);
187
198
  }
188
199
  }
189
200
  }
@@ -196,11 +207,13 @@ export function render() {
196
207
  export function renderOnly(...names: string[]) {
197
208
  for (const name of names) {
198
209
  const fn = _renderers.get(name);
199
- if (!fn) continue;
210
+ if (!fn) {
211
+ continue;
212
+ }
200
213
  try {
201
214
  fn();
202
- } catch (e) {
203
- console.error(`Renderer "${name}" failed:`, e);
215
+ } catch (error) {
216
+ console.error(`Renderer "${name}" failed:`, error);
204
217
  }
205
218
  }
206
219
  }
@@ -221,9 +234,15 @@ export function updateSession(patch: {
221
234
  }) {
222
235
  const tab = activeTab.value;
223
236
  if (tab) {
224
- if (patch.selection !== undefined) tab.session.selection = patch.selection as JxPath | null;
225
- if (patch.hover !== undefined) tab.session.hover = patch.hover as JxPath | null;
226
- if (patch.clipboard !== undefined) tab.session.clipboard = patch.clipboard!;
237
+ if (patch.selection !== undefined) {
238
+ tab.session.selection = patch.selection as JxPath | null;
239
+ }
240
+ if (patch.hover !== undefined) {
241
+ tab.session.hover = patch.hover as JxPath | null;
242
+ }
243
+ if (patch.clipboard !== undefined) {
244
+ tab.session.clipboard = patch.clipboard!;
245
+ }
227
246
  if (patch.ui) {
228
247
  for (const [k, v] of Object.entries(patch.ui)) {
229
248
  (tab.session.ui as unknown as Record<string, unknown>)[k] = v;