@jxsuite/studio 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/iframe-entry.js +622 -49
  2. package/dist/iframe-entry.js.map +15 -12
  3. package/dist/studio.css +1333 -0
  4. package/dist/studio.js +53069 -31746
  5. package/dist/studio.js.map +109 -74
  6. package/package.json +11 -9
  7. package/src/canvas/canvas-live-render.ts +21 -2
  8. package/src/canvas/canvas-render.ts +35 -16
  9. package/src/canvas/canvas-utils.ts +118 -72
  10. package/src/canvas/iframe-entry.ts +20 -0
  11. package/src/canvas/iframe-eval.ts +155 -0
  12. package/src/canvas/iframe-host.ts +131 -5
  13. package/src/canvas/iframe-inline-edit.ts +107 -1
  14. package/src/canvas/iframe-protocol.ts +43 -3
  15. package/src/canvas/iframe-render.ts +18 -0
  16. package/src/collab/collab-session.ts +23 -8
  17. package/src/collab/collab-state.ts +6 -3
  18. package/src/component-props.ts +104 -0
  19. package/src/editor/inline-edit-apply.ts +36 -1
  20. package/src/editor/inline-edit.ts +58 -1
  21. package/src/editor/shortcuts.ts +41 -12
  22. package/src/files/file-ops.ts +14 -0
  23. package/src/files/files.ts +53 -1
  24. package/src/grid/cell-editors.ts +288 -0
  25. package/src/grid/cell-popovers.ts +108 -0
  26. package/src/grid/csv-codec.ts +122 -0
  27. package/src/grid/edit-buffer.ts +490 -0
  28. package/src/grid/grid-controller.ts +417 -0
  29. package/src/grid/grid-layout.ts +83 -0
  30. package/src/grid/grid-open.ts +167 -0
  31. package/src/grid/grid-panel.ts +302 -0
  32. package/src/grid/grid-source.ts +210 -0
  33. package/src/grid/grid-view.ts +367 -0
  34. package/src/grid/schema-columns.ts +261 -0
  35. package/src/grid/sources/connector-source.ts +217 -0
  36. package/src/grid/sources/content-source.ts +542 -0
  37. package/src/grid/sources/csv-file-source.ts +247 -0
  38. package/src/grid/tabulator-tables.d.ts +120 -0
  39. package/src/panels/block-action-bar.ts +8 -3
  40. package/src/panels/chat-panel.ts +98 -0
  41. package/src/panels/data-grid.ts +9 -387
  42. package/src/panels/editors.ts +43 -17
  43. package/src/panels/events-panel.ts +137 -44
  44. package/src/panels/formula-workspace.ts +379 -0
  45. package/src/panels/frontmatter-fields.ts +231 -0
  46. package/src/panels/frontmatter-panel.ts +132 -0
  47. package/src/panels/git-panel.ts +1 -1
  48. package/src/panels/head-panel.ts +11 -175
  49. package/src/panels/properties-panel.ts +154 -144
  50. package/src/panels/right-panel.ts +9 -47
  51. package/src/panels/signals-panel.ts +115 -23
  52. package/src/panels/statement-editor.ts +710 -0
  53. package/src/panels/style-panel.ts +25 -1
  54. package/src/panels/stylebook-panel.ts +0 -2
  55. package/src/panels/tab-bar.ts +175 -6
  56. package/src/panels/tab-strip.ts +62 -6
  57. package/src/panels/toolbar.ts +37 -3
  58. package/src/services/ai-project-tools.ts +541 -0
  59. package/src/services/ai-session-store.ts +28 -0
  60. package/src/services/ai-system-prompt.ts +194 -24
  61. package/src/services/ai-tools.ts +88 -21
  62. package/src/services/automation.ts +16 -0
  63. package/src/services/document-assistant.ts +81 -11
  64. package/src/services/gated-registry.ts +72 -0
  65. package/src/services/live-preview.ts +0 -0
  66. package/src/services/preview-eval.ts +68 -0
  67. package/src/services/project-adoption.ts +31 -0
  68. package/src/site-context.ts +2 -0
  69. package/src/store.ts +4 -0
  70. package/src/studio.ts +83 -52
  71. package/src/tabs/patch-ops.ts +25 -0
  72. package/src/tabs/tab.ts +39 -1
  73. package/src/tabs/transact.ts +60 -13
  74. package/src/types.ts +12 -0
  75. package/src/ui/dynamic-slot.ts +272 -0
  76. package/src/ui/expression-editor.ts +423 -125
  77. package/src/ui/field-row.ts +5 -0
  78. package/src/ui/formula-catalog.ts +557 -0
  79. package/src/ui/formula-chips.ts +216 -0
  80. package/src/ui/formula-palette.ts +211 -0
  81. package/src/ui/layers.ts +40 -0
  82. package/src/ui/media-picker.ts +1 -1
  83. package/src/ui/panel-resize.ts +15 -1
  84. package/src/utils/preview-format.ts +26 -0
  85. package/src/view.ts +4 -4
  86. package/src/workspace/workspace.ts +14 -0
@@ -21,6 +21,7 @@ import { view } from "../view";
21
21
  import { componentRegistry } from "../files/components";
22
22
  import { widgetForType } from "./style-inputs";
23
23
  import { renderFieldRow } from "../ui/field-row";
24
+ import { renderDynamicSlot, slotMode } from "../ui/dynamic-slot";
24
25
  import { spTextArea, spTextField } from "../ui/field-input";
25
26
  import {
26
27
  attrLabel,
@@ -41,11 +42,13 @@ import { getPlatform } from "../platform";
41
42
  import htmlMeta from "../../data/html-meta.json";
42
43
 
43
44
  import type {
45
+ JxAttributeValue,
44
46
  JxMutableNode,
45
47
  JxPrototypeDef,
46
48
  JxStateDefinition,
47
49
  JxStateObject,
48
50
  } from "@jxsuite/schema/types";
51
+ import type { SlotMode } from "../ui/dynamic-slot";
49
52
  import type { JxPath } from "../state";
50
53
  import type { SignalDef } from "./signals-panel.js";
51
54
 
@@ -113,11 +116,13 @@ function bindableFieldRow(
113
116
  onChange: (v?: JsonValue) => void,
114
117
  filterFn: ((d: SignalDef) => boolean) | null = null,
115
118
  extraSignals: SignalOption[] | null = null,
119
+ fieldKey = `prop:${label}`,
120
+ caps: SlotMode[] = ["literal", "ref"],
116
121
  ) {
117
122
  const tab = activeTab.value;
118
123
  const defs = tab!.doc.document.state || {};
119
124
  const boundRef = isRef(rawValue) ? rawValue.$ref : null;
120
- const isBound = boundRef !== null;
125
+ const isDynamic = slotMode(rawValue) !== "literal";
121
126
 
122
127
  const signalDefs = Object.entries(defs).filter(([, d]) =>
123
128
  filterFn
@@ -126,70 +131,38 @@ function bindableFieldRow(
126
131
  (d as JxPrototypeDef)?.$prototype !== "Function",
127
132
  );
128
133
 
129
- const staticVal = isBound ? "" : (rawValue ?? "");
130
- const fieldKey = `prop:${label}`;
134
+ const staticVal = isDynamic ? "" : (rawValue ?? "");
135
+ const draftKey = `prop:${label}`;
131
136
  const staticTpl =
132
137
  type === "textarea"
133
- ? spTextArea(fieldKey, String(staticVal), (v: string) => onChange(v))
138
+ ? spTextArea(draftKey, String(staticVal), (v: string) => onChange(v))
134
139
  : type === "checkbox"
135
140
  ? html`<sp-checkbox
136
141
  ?checked=${Boolean(staticVal)}
137
142
  @change=${(e: Event) => onChange((e.target as HTMLInputElement).checked)}
138
143
  ></sp-checkbox>`
139
- : spTextField(fieldKey, String(staticVal), (v: string) => onChange(v));
140
-
141
- const boundTpl = html`
142
- <sp-picker
143
- size="s"
144
- quiet
145
- placeholder="— select signal —"
146
- value=${boundRef ?? nothing}
147
- @change=${(e: Event) => {
148
- if ((e.target as HTMLInputElement).value) {
149
- onChange({ $ref: (e.target as HTMLInputElement).value });
150
- } else {
151
- onChange();
152
- }
153
- }}
154
- >
155
- ${signalDefs.map(
156
- ([defName]) => html`<sp-menu-item value=${`#/state/${defName}`}>${defName}</sp-menu-item>`,
157
- )}
158
- ${extraSignals
159
- ? html`
160
- <sp-menu-divider></sp-menu-divider>
161
- ${extraSignals.map(
162
- (sig: SignalOption) =>
163
- html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
164
- )}
165
- `
166
- : nothing}
167
- </sp-picker>
168
- `;
169
-
170
- const onToggle = () => {
171
- if (boundRef !== null) {
172
- const defName = boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef;
173
- const staticDefault = defaultAsString(defs[defName]);
174
- onChange(staticDefault || undefined);
175
- } else if (signalDefs.length > 0) {
176
- onChange({ $ref: `#/state/${signalDefs[0]![0]}` });
177
- } else if (extraSignals && extraSignals.length > 0) {
178
- onChange({ $ref: extraSignals[0]!.value });
179
- }
180
- };
181
-
144
+ : spTextField(draftKey, String(staticVal), (v: string) => onChange(v));
145
+
146
+ // De-escalating to literal restores the bound signal's declared default (old unbind behavior).
147
+ const literalDefault = boundRef
148
+ ? defaultAsString(defs[boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef]) ||
149
+ undefined
150
+ : undefined;
151
+
152
+ const slot = renderDynamicSlot({
153
+ caps,
154
+ extraSignals,
155
+ fieldKey,
156
+ literalDefault,
157
+ onChange,
158
+ staticWidget: staticTpl,
159
+ stateDefs: signalDefs.map(([defName]) => defName),
160
+ value: rawValue,
161
+ });
182
162
  return html`
183
163
  <div class="field-row">
184
164
  <sp-field-label size="s">${label}</sp-field-label>
185
- ${isBound ? boundTpl : staticTpl}
186
- <sp-action-button
187
- size="xs"
188
- quiet
189
- title=${isBound ? "Unbind (switch to static)" : "Bind to signal"}
190
- @click=${onToggle}
191
- >${isBound ? "\u26A1" : "\u2194"}</sp-action-button
192
- >
165
+ ${slot.modeButton} ${slot.widget}
193
166
  </div>
194
167
  `;
195
168
  }
@@ -261,21 +234,42 @@ function renderRepeaterFieldsTemplate(
261
234
  _mapSignals: SignalOption[] | null,
262
235
  ) {
263
236
  return html`
264
- ${bindableFieldRow("Items", "text", node.items, (v: JsonValue) =>
265
- transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "items", v)),
237
+ ${bindableFieldRow(
238
+ "Items",
239
+ "text",
240
+ node.items,
241
+ (v: JsonValue) =>
242
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "items", v)),
243
+ null,
244
+ null,
245
+ `prop|${path.join("/")}|items`,
266
246
  )}
267
247
  ${node.filter
268
- ? bindableFieldRow("Filter", "text", node.filter, (v: JsonValue) =>
269
- transactDoc(activeTab.value, (t) =>
270
- mutateUpdateProperty(t, path, "filter", v || undefined),
271
- ),
248
+ ? bindableFieldRow(
249
+ "Filter",
250
+ "text",
251
+ node.filter,
252
+ (v: JsonValue) =>
253
+ transactDoc(activeTab.value, (t) =>
254
+ mutateUpdateProperty(t, path, "filter", v || undefined),
255
+ ),
256
+ null,
257
+ null,
258
+ `prop|${path.join("/")}|filter`,
272
259
  )
273
260
  : nothing}
274
261
  ${node.sort
275
- ? bindableFieldRow("Sort", "text", node.sort, (v: JsonValue) =>
276
- transactDoc(activeTab.value, (t) =>
277
- mutateUpdateProperty(t, path, "sort", v || undefined),
278
- ),
262
+ ? bindableFieldRow(
263
+ "Sort",
264
+ "text",
265
+ node.sort,
266
+ (v: JsonValue) =>
267
+ transactDoc(activeTab.value, (t) =>
268
+ mutateUpdateProperty(t, path, "sort", v || undefined),
269
+ ),
270
+ null,
271
+ null,
272
+ `prop|${path.join("/")}|sort`,
279
273
  )
280
274
  : nothing}
281
275
  <div style="display:flex;gap:8px;margin-top:4px">
@@ -331,6 +325,10 @@ function renderSwitchFieldsTemplate(
331
325
  transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "$switch", v)),
332
326
  null,
333
327
  mapSignals,
328
+ `prop|${path.join("/")}|$switch`,
329
+ // No literal rung: a $switch is inherently dynamic.
330
+ // De-escalating to literal would delete the key and demote the node mid-cycle.
331
+ ["ref", "template"],
334
332
  )}
335
333
  <div
336
334
  style="font-size:var(--spectrum-font-size-50, 11px);font-weight:600;color:var(--fg-dim);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.05em"
@@ -410,7 +408,12 @@ function renderComponentPropsFieldsTemplate(
410
408
  const updateFn = isNpm
411
409
  ? (name: string, v?: JsonValue) =>
412
410
  transactDoc(activeTab.value, (t) =>
413
- mutateUpdateAttribute(t, path, name, v === "" ? undefined : (v as string | undefined)),
411
+ mutateUpdateAttribute(
412
+ t,
413
+ path,
414
+ name,
415
+ v === "" ? undefined : (v as JxAttributeValue | undefined),
416
+ ),
414
417
  )
415
418
  : (name: string, v?: JsonValue) =>
416
419
  transactDoc(activeTab.value, (t) => mutateUpdateProp(t, path, name, v));
@@ -429,58 +432,21 @@ function renderComponentPropsFieldsTemplate(
429
432
  ) => {
430
433
  const rawValue = currentVals[prop.name];
431
434
  const boundRef = isRef(rawValue) ? rawValue.$ref : null;
432
- const isBound = boundRef !== null;
433
435
  const hasVal = rawValue !== undefined && rawValue !== null;
434
436
  const parsed = parseCemType(prop.type);
435
437
  const onChange = (v?: JsonValue) => updateFn(prop.name, v);
436
- const staticVal = isBound ? "" : String(rawValue ?? "");
438
+ const staticVal = slotMode(rawValue) === "literal" ? String(rawValue ?? "") : "";
437
439
 
438
440
  const clearProp = (e: Event) => {
439
441
  e.stopPropagation();
440
442
  updateFn(prop.name);
441
443
  };
442
444
 
443
- const onToggleBind = () => {
444
- if (boundRef !== null) {
445
- const defName = boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef;
446
- const staticDefault = defaultAsString(defs[defName]);
447
- onChange(staticDefault || undefined);
448
- } else if (signalDefs.length > 0) {
449
- onChange({ $ref: `#/state/${signalDefs[0]![0]}` });
450
- } else if (extraSignals && extraSignals.length > 0) {
451
- onChange({ $ref: extraSignals[0]!.value });
452
- }
453
- };
454
-
455
- const boundTpl = html`
456
- <sp-picker
457
- size="s"
458
- quiet
459
- placeholder="— select signal —"
460
- value=${boundRef ?? nothing}
461
- @change=${(e: Event) => {
462
- if ((e.target as HTMLInputElement).value) {
463
- onChange({ $ref: (e.target as HTMLInputElement).value });
464
- } else {
465
- onChange();
466
- }
467
- }}
468
- >
469
- ${signalDefs.map(
470
- ([defName]) =>
471
- html`<sp-menu-item value=${`#/state/${defName}`}>${defName}</sp-menu-item>`,
472
- )}
473
- ${extraSignals
474
- ? html`
475
- <sp-menu-divider></sp-menu-divider>
476
- ${extraSignals.map(
477
- (sig: SignalOption) =>
478
- html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
479
- )}
480
- `
481
- : nothing}
482
- </sp-picker>
483
- `;
445
+ // De-escalating to literal restores the bound signal's declared default (old unbind behavior).
446
+ const literalDefault = boundRef
447
+ ? defaultAsString(defs[boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef]) ||
448
+ undefined
449
+ : undefined;
484
450
 
485
451
  /** @type {ReturnType<typeof setTimeout> | undefined} */
486
452
  let debounce: ReturnType<typeof setTimeout> | undefined;
@@ -532,6 +498,16 @@ function renderComponentPropsFieldsTemplate(
532
498
  );
533
499
  }
534
500
 
501
+ const slot = renderDynamicSlot({
502
+ caps: ["literal", "ref", "template"],
503
+ extraSignals,
504
+ fieldKey: `cprop|${path.join("/")}|${prop.name}`,
505
+ literalDefault,
506
+ onChange,
507
+ staticWidget: widgetTpl,
508
+ stateDefs: signalDefs.map(([defName]) => defName),
509
+ value: rawValue,
510
+ });
535
511
  return html`
536
512
  <div class="style-row" data-prop=${prop.name}>
537
513
  <div class="style-row-label">
@@ -541,15 +517,9 @@ function renderComponentPropsFieldsTemplate(
541
517
  <sp-field-label size="s" title=${prop.description || prop.name}
542
518
  >${camelToLabel(prop.name)}</sp-field-label
543
519
  >
544
- <sp-action-button
545
- size="xs"
546
- quiet
547
- title=${isBound ? "Unbind (switch to static)" : "Bind to signal"}
548
- @click=${onToggleBind}
549
- >${isBound ? "\u26A1" : "\u2194"}</sp-action-button
550
- >
520
+ ${slot.modeButton}
551
521
  </div>
552
- ${isBound ? boundTpl : widgetTpl}
522
+ ${slot.widget}
553
523
  </div>
554
524
  `;
555
525
  },
@@ -1162,9 +1132,22 @@ export function renderPropertiesPanelTemplate(ctx: {
1162
1132
  ]
1163
1133
  : null;
1164
1134
 
1135
+ // Signals offered to attribute/textContent bindings (handlers and Functions excluded).
1136
+ const bindableSignals = Object.entries(tab.doc.document.state || {})
1137
+ .filter(
1138
+ ([, d]) =>
1139
+ !(d as Record<string, unknown>)?.$handler &&
1140
+ (d as JxPrototypeDef)?.$prototype !== "Function",
1141
+ )
1142
+ .map(([defName]) => defName);
1143
+
1165
1144
  function renderAttrRow(attr: string, entry: HtmlMetaEntry, value: unknown) {
1166
1145
  const type = inferInputType(entry);
1167
1146
  const hasVal = value !== undefined && value !== "";
1147
+ const commitAttr = (v?: JsonValue) =>
1148
+ transactDoc(activeTab.value!, (t) =>
1149
+ mutateUpdateAttribute(t, path, attr, v as JxAttributeValue | undefined),
1150
+ );
1168
1151
 
1169
1152
  // Enhanced Link handling: only for anchors (a/area) with a plain (non-binding) value. Bindings
1170
1153
  // ($ref objects or ${…} template strings) fall through to the raw widget to stay editable.
@@ -1178,39 +1161,54 @@ export function renderPropertiesPanelTemplate(ctx: {
1178
1161
  }
1179
1162
  }
1180
1163
 
1164
+ // Attribute strings are schema-legal at three rungs: literal, $ref binding, ${} template.
1165
+ const attrSlot = (staticWidget: unknown) =>
1166
+ renderDynamicSlot({
1167
+ caps: ["literal", "ref", "template"],
1168
+ extraSignals: mapSignals,
1169
+ fieldKey: `attr|${path.join("/")}|${attr}`,
1170
+ onChange: commitAttr,
1171
+ staticWidget,
1172
+ stateDefs: bindableSignals,
1173
+ value,
1174
+ });
1175
+
1181
1176
  if (entry.type === "boolean") {
1177
+ const checkboxWidget = html`
1178
+ <sp-checkbox
1179
+ size="s"
1180
+ .checked=${live(Boolean(value))}
1181
+ @change=${(e: Event) =>
1182
+ commitAttr((e.target as HTMLInputElement).checked ? "" : undefined)}
1183
+ >
1184
+ </sp-checkbox>
1185
+ `;
1186
+ const slot = attrSlot(checkboxWidget);
1182
1187
  return renderFieldRow({
1183
1188
  hasValue: hasVal,
1184
1189
  label: attrLabel(entry, attr),
1190
+ labelExtra: slot.modeButton,
1185
1191
  onClear: () => transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr)),
1186
1192
  prop: attr,
1187
- widget: html`
1188
- <sp-checkbox
1189
- size="s"
1190
- .checked=${live(Boolean(value))}
1191
- @change=${(e: Event) =>
1192
- transactDoc(activeTab.value, (t) =>
1193
- mutateUpdateAttribute(
1194
- t,
1195
- path,
1196
- attr,
1197
- (e.target as HTMLInputElement).checked ? "" : undefined,
1198
- ),
1199
- )}
1200
- >
1201
- </sp-checkbox>
1202
- `,
1193
+ widget: slot.widget,
1203
1194
  });
1204
1195
  }
1205
1196
 
1197
+ const literalWidget = widgetForType(
1198
+ type,
1199
+ entry,
1200
+ attr,
1201
+ isRef(value) ? "" : String(value || ""),
1202
+ (v: string) => commitAttr(v || undefined),
1203
+ );
1204
+ const slot = attrSlot(literalWidget);
1206
1205
  return renderFieldRow({
1207
1206
  hasValue: hasVal,
1208
1207
  label: attrLabel(entry, attr),
1208
+ labelExtra: slot.modeButton,
1209
1209
  onClear: () => transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr)),
1210
1210
  prop: attr,
1211
- widget: widgetForType(type, entry, attr, String(value || ""), (v: string) =>
1212
- transactDoc(activeTab.value!, (t) => mutateUpdateAttribute(t, path, attr, v || undefined)),
1213
- ),
1211
+ widget: slot.widget,
1214
1212
  });
1215
1213
  }
1216
1214
 
@@ -1280,6 +1278,24 @@ export function renderPropertiesPanelTemplate(ctx: {
1280
1278
 
1281
1279
  // ── Build section templates ─────────────────────────────────────────
1282
1280
 
1281
+ const textSlot = renderDynamicSlot({
1282
+ caps: ["literal", "ref", "template"],
1283
+ extraSignals: mapSignals,
1284
+ fieldKey: `prop|${path.join("/")}|textContent`,
1285
+ onChange: (v?: JsonValue) =>
1286
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "textContent", v)),
1287
+ staticWidget: spTextArea(
1288
+ "prop:textContent",
1289
+ typeof node.textContent === "string" ? node.textContent : "",
1290
+ (v: string) =>
1291
+ transactDoc(activeTab.value, (t) =>
1292
+ mutateUpdateProperty(t, path, "textContent", v || undefined),
1293
+ ),
1294
+ ),
1295
+ stateDefs: bindableSignals,
1296
+ value: node.textContent,
1297
+ });
1298
+
1283
1299
  const elemT = html`
1284
1300
  <sp-accordion-item
1285
1301
  label="Element"
@@ -1365,15 +1381,9 @@ export function renderPropertiesPanelTemplate(ctx: {
1365
1381
  ></span>`
1366
1382
  : nothing}
1367
1383
  <sp-field-label size="s">Text Content</sp-field-label>
1384
+ ${textSlot.modeButton}
1368
1385
  </div>
1369
- ${spTextArea(
1370
- "prop:textContent",
1371
- typeof node.textContent === "string" ? node.textContent : "",
1372
- (v: string) =>
1373
- transactDoc(activeTab.value, (t) =>
1374
- mutateUpdateProperty(t, path, "textContent", v || undefined),
1375
- ),
1376
- )}
1386
+ ${textSlot.widget}
1377
1387
  </div>
1378
1388
  `
1379
1389
  : nothing}
@@ -10,8 +10,7 @@ import { rightPanel, updateUi } from "../store";
10
10
  import { effect, effectScope } from "../reactivity";
11
11
  import { createPanelScheduler } from "./panel-scheduler";
12
12
  import type { PanelScheduler } from "./panel-scheduler";
13
- import { activeTab, workspace } from "../workspace/workspace";
14
- import { consumePendingAgentPrompt, hasPendingAgentPrompt } from "../services/agent-seed";
13
+ import { activeTab } from "../workspace/workspace";
15
14
  import { tabIcon } from "./activity-bar";
16
15
  import { eventsSidebarTemplate } from "./events-panel";
17
16
  import { isCustomElementDoc } from "./signals-panel";
@@ -21,12 +20,6 @@ import { renderStylePanelTemplate } from "./style-panel";
21
20
  import { renderPropertiesPanelTemplate } from "./properties-panel";
22
21
 
23
22
  import type { EffectScope } from "@vue/reactivity";
24
- import {
25
- renderAiPanelTemplate,
26
- bindAiPanelHost,
27
- mountAiPanel,
28
- seedAssistantPrompt,
29
- } from "./ai-panel";
30
23
 
31
24
  interface RightPanelCtx {
32
25
  navigateToComponent: (path: string) => void;
@@ -47,7 +40,6 @@ let _scheduler: PanelScheduler | null = null;
47
40
  */
48
41
  export function mount(ctx: RightPanelCtx) {
49
42
  _ctx = ctx;
50
- mountAiPanel();
51
43
  _scheduler = createPanelScheduler({
52
44
  blockWhile: isColorPopoverOpen,
53
45
  render: _doRender,
@@ -87,7 +79,6 @@ export function unmount() {
87
79
  _propsContainer = null;
88
80
  _eventsContainer = null;
89
81
  _styleContainer = null;
90
- _assistantContainer = null;
91
82
  }
92
83
 
93
84
  /**
@@ -101,7 +92,6 @@ export function render() {
101
92
  let _propsContainer: HTMLElement | null = null;
102
93
  let _eventsContainer: HTMLElement | null = null;
103
94
  let _styleContainer: HTMLElement | null = null;
104
- let _assistantContainer: HTMLElement | null = null;
105
95
 
106
96
  function _ensureContainers() {
107
97
  if (_propsContainer) {
@@ -113,12 +103,6 @@ function _ensureContainers() {
113
103
  _eventsContainer.className = "panel-body";
114
104
  _styleContainer = document.createElement("div");
115
105
  _styleContainer.className = "panel-body";
116
- _assistantContainer = document.createElement("div");
117
- _assistantContainer.className = "panel-body";
118
- _assistantContainer.style.cssText = "display:flex;flex-direction:column;overflow:hidden";
119
- // The AI panel owns a focus-guard-free rAF render loop into this container so
120
- // Streaming repaints while the composer is focused (see ai-panel.ts).
121
- bindAiPanelHost(_assistantContainer);
122
106
  }
123
107
 
124
108
  function _doRender() {
@@ -138,20 +122,17 @@ function _doRender() {
138
122
  selection: aTab.session.selection,
139
123
  ui: aTab.session.ui,
140
124
  };
141
- // A pending agent prompt (stored by the New Project flow, possibly from another window)
142
- // Forces the Assistant tab open — same updateUi mechanism the automation hook uses.
143
- const root = workspace.projectRoot;
144
- if (root && S.ui.rightTab !== "assistant" && hasPendingAgentPrompt(root)) {
145
- updateUi("rightTab", "assistant");
146
- }
147
- const tab = S.ui.rightTab;
125
+ // Coerce stale values ("assistant" moved to the persistent chat sidebar; automation or a
126
+ // Restored session may still carry it).
127
+ const tab = ["properties", "events", "style"].includes(S.ui.rightTab)
128
+ ? S.ui.rightTab
129
+ : "properties";
148
130
 
149
131
  // Render tabs header
150
132
  const panelTabs = [
151
133
  { icon: "sp-icon-properties", label: "Properties", value: "properties" },
152
134
  { icon: "sp-icon-event", label: "Events", value: "events" },
153
135
  { icon: "sp-icon-brush", label: "Style", value: "style" },
154
- { icon: "sp-icon-chat", label: "Assistant", value: "assistant" },
155
136
  ];
156
137
  const tabsT = html`
157
138
  <div class="panel-tabs">
@@ -178,21 +159,12 @@ function _doRender() {
178
159
  `;
179
160
 
180
161
  _ensureContainers();
181
- const containers = [
182
- _propsContainer,
183
- _eventsContainer,
184
- _styleContainer,
185
- _assistantContainer,
186
- ] as HTMLElement[];
187
- const tabKeys = ["properties", "events", "style", "assistant"];
162
+ const containers = [_propsContainer, _eventsContainer, _styleContainer] as HTMLElement[];
163
+ const tabKeys = ["properties", "events", "style"];
188
164
 
189
165
  // Show/hide containers
190
166
  for (let i = 0; i < containers.length; i++) {
191
- if (tabKeys[i] === tab) {
192
- containers[i]!.style.display = tabKeys[i] === "assistant" ? "flex" : "";
193
- } else {
194
- containers[i]!.style.display = "none";
195
- }
167
+ containers[i]!.style.display = tabKeys[i] === tab ? "" : "none";
196
168
  }
197
169
 
198
170
  // Render tabs into the right panel, append containers
@@ -224,16 +196,6 @@ function _doRender() {
224
196
  } catch (error) {
225
197
  console.error("[renderStylePanelTemplate]", error);
226
198
  }
227
- } else if (tab === "assistant") {
228
- litRender(renderAiPanelTemplate(), _assistantContainer!);
229
- if (root) {
230
- // Consume-on-read keeps repeated renders idempotent; seed after the panel template
231
- // Has rendered so the assistant machinery is in place.
232
- const prompt = consumePendingAgentPrompt(root);
233
- if (prompt) {
234
- requestAnimationFrame(() => void seedAssistantPrompt(prompt));
235
- }
236
- }
237
199
  }
238
200
  } catch (error) {
239
201
  console.error("right-panel render error:", error);