@jxsuite/studio 5.0.0 → 5.2.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.
- package/dist/chunks/{cf-account-picker-fyf63pmy.js → cf-account-picker-h4dab9bq.js} +10 -10
- package/dist/chunks/{cf-settings-71bv5zn9.js → cf-settings-adg74qsk.js} +4 -4
- package/dist/chunks/components-5y78e05d.js +20 -0
- package/dist/chunks/content-source-sgg1sv6n.js +46 -0
- package/dist/chunks/{convert-to-repeater-d0cgnb6d.js → convert-to-repeater-mf1cxgfn.js} +25 -25
- package/dist/chunks/entry-commands-w9qsxa6q.js +42 -0
- package/dist/chunks/{extension-sections-fy7q4fe9.js → extension-sections-c6zwzbre.js} +27 -27
- package/dist/chunks/file-ops-vrpq1jjk.js +47 -0
- package/dist/chunks/{files-ef8fgs3w.js → files-msaf95y6.js} +33 -32
- package/dist/chunks/{format-host-3c33nwr8.js → format-host-f64fnesp.js} +2 -2
- package/dist/chunks/{fs-events-13tj6sex.js → fs-events-3y6y0nrm.js} +6 -6
- package/dist/chunks/git-panel-bv5e22j0.js +50 -0
- package/dist/chunks/grid-view-1p6hsyjx.css +8406 -0
- package/dist/chunks/grid-view-paj67nr5.js +15322 -0
- package/dist/chunks/{iframe-host-jgesd924.js → iframe-host-dh5np8vk.js} +14 -14
- package/dist/chunks/{layers-1nq5evsh.js → layers-1vcfdbet.js} +7 -6
- package/dist/chunks/layers-panel-fksbdg4q.js +40 -0
- package/dist/chunks/{media-picker-2qbetq73.js → media-picker-p49nhqhs.js} +14 -14
- package/dist/chunks/menu-qht48vnc.js +15 -0
- package/dist/chunks/{monaco-buffer-bhy3hb6m.js → monaco-buffer-pykwbm92.js} +3 -3
- package/dist/chunks/{notify-628t0anh.js → notify-42gk8ha1.js} +2 -2
- package/dist/chunks/{parse-0zx14y15.js → parse-reg9eevt.js} +2 -2
- package/dist/chunks/publish-panel-vz23wwmg.js +27 -0
- package/dist/chunks/quick-search-e9rp74m8.js +46 -0
- package/dist/chunks/{references-n553f0e1.js → references-h69xp9zz.js} +4 -4
- package/dist/chunks/right-panel-j97t2yvy.js +45 -0
- package/dist/chunks/{studio-18avnhjp.js → studio-01jeck2y.js} +36 -30
- package/dist/chunks/{studio-9xxz1q69.js → studio-14d9tq4d.js} +9 -9
- package/dist/chunks/{studio-xsykpvsh.js → studio-15f3kxqe.js} +19 -19
- package/dist/chunks/studio-1tbjfqv4.js +146 -0
- package/dist/chunks/{studio-79y5sz51.js → studio-28vs5k2y.js} +2 -2
- package/dist/chunks/{studio-3hxkd92t.js → studio-2mdbbc76.js} +10 -10
- package/dist/chunks/{studio-7r42c6ca.js → studio-3ntvq6cg.js} +5 -5
- package/dist/chunks/{studio-8jn5yhyn.js → studio-4c041r8y.js} +3 -3
- package/dist/chunks/{studio-qahxvnan.js → studio-4r14yzyy.js} +4 -4
- package/dist/chunks/{studio-arfpe389.js → studio-4xmjcrg3.js} +6 -6
- package/dist/chunks/{studio-5523bpzt.js → studio-5nz00bhh.js} +3 -3
- package/dist/chunks/{studio-x3rty6tr.js → studio-5t7ena5z.js} +8 -8
- package/dist/chunks/{studio-5rt7sqa0.js → studio-66arbpc2.js} +13 -19
- package/dist/chunks/{studio-fabxk6r9.js → studio-68fydsrj.js} +15 -15
- package/dist/chunks/{studio-tb3ev01j.js → studio-9j6sfeba.js} +6 -6
- package/dist/chunks/{studio-akbww0j5.js → studio-a1pswps3.js} +38 -38
- package/dist/chunks/{studio-6xa21ej9.js → studio-c04nzbby.js} +2 -2
- package/dist/chunks/{studio-4m0haseg.js → studio-c07s5ckd.js} +4 -4
- package/dist/chunks/{studio-57mf66w2.js → studio-ctm7bhx5.js} +14 -14
- package/dist/chunks/{studio-p1dw8wfw.js → studio-d8z5bajd.js} +14 -10
- package/dist/chunks/{studio-vz68yn9e.js → studio-en82c639.js} +6 -6
- package/dist/chunks/{studio-rkstz940.js → studio-etvx9s2g.js} +21 -12
- package/dist/chunks/{studio-az3zd49t.js → studio-ez11dmdh.js} +127 -37
- package/dist/chunks/{studio-sgdc5pfc.js → studio-hdc2jt3z.js} +4 -4
- package/dist/chunks/{studio-s6z853aq.js → studio-hhskq2mr.js} +24 -23
- package/dist/chunks/{studio-6ewqv390.js → studio-m1p9b6c3.js} +5 -5
- package/dist/chunks/{studio-25hkdrkw.js → studio-mpzwze9x.js} +12 -12
- package/dist/chunks/{studio-8qtrpzr3.js → studio-ncn9e189.js} +7 -7
- package/dist/chunks/{studio-81390n6f.js → studio-nq5yf5mp.js} +1 -1
- package/dist/chunks/{studio-5jy81t3s.js → studio-p6pmwkvh.js} +229 -24
- package/dist/chunks/{studio-3gxv60tn.js → studio-qa7fzxan.js} +4 -4
- package/dist/chunks/{studio-7xptrg0j.js → studio-sar8g54p.js} +8 -8
- package/dist/chunks/studio-t171hxhe.js +23 -0
- package/dist/chunks/{studio-em9nw8eh.js → studio-t3adrr56.js} +20 -17
- package/dist/chunks/{studio-hhvs4sdg.js → studio-vn5hske5.js} +10 -4
- package/dist/chunks/{studio-pm6hgy7r.js → studio-vt0tj32j.js} +29 -42
- package/dist/chunks/{studio-d1ja2x9r.js → studio-wjpgxywa.js} +4 -4
- package/dist/chunks/{studio-q3x06vmj.js → studio-wnsyffg1.js} +2 -2
- package/dist/chunks/{studio-rpyah2ew.js → studio-x10fp4tp.js} +64 -24
- package/dist/chunks/{studio-26d7myd1.js → studio-x72j58vw.js} +4 -4
- package/dist/chunks/{studio-cpvb2ed1.js → studio-y4f9fdge.js} +8 -8
- package/dist/chunks/{studio-erdatzvp.js → studio-ympe0n7a.js} +35 -6
- package/dist/chunks/{studio-y9zedyjn.js → studio-z2smx5ce.js} +2 -2
- package/dist/chunks/studio-zjjde71a.js +148 -0
- package/dist/chunks/{workspace-wrew8zk4.js → workspace-bnw54cha.js} +18 -10
- package/dist/iframe-entry.js +102 -11
- package/dist/studio.css +1 -2
- package/dist/studio.js +1113 -16062
- package/package.json +11 -11
- package/src/canvas/canvas-utils.ts +29 -2
- package/src/commands/command-args.ts +37 -2
- package/src/commands/registry.ts +18 -8
- package/src/commands/run-reported.ts +120 -0
- package/src/editor/shortcuts.ts +11 -23
- package/src/files/components.ts +39 -0
- package/src/files/files.ts +41 -17
- package/src/grid/grid-lazy.ts +59 -0
- package/src/grid/grid-open.ts +20 -3
- package/src/grid/grid-panel.ts +43 -9
- package/src/i18n/i18n-commands.ts +10 -0
- package/src/media/media-open.ts +14 -2
- package/src/packages/pull-package-sync.ts +5 -1
- package/src/panels/ai-panel.ts +5 -2
- package/src/panels/block-action-bar.ts +7 -2
- package/src/panels/i18n-panel.ts +8 -15
- package/src/panels/jump-bar.ts +3 -2
- package/src/panels/pane-context.ts +5 -4
- package/src/panels/pane-grid.ts +100 -10
- package/src/panels/problems-panel.ts +6 -2
- package/src/panels/quick-search.ts +6 -12
- package/src/panels/settings-menu.ts +5 -15
- package/src/panels/tab-drop.ts +220 -0
- package/src/panels/tab-strip.ts +238 -33
- package/src/services/ai-command-tools.ts +78 -8
- package/src/services/ai-models.ts +13 -0
- package/src/services/ai-project-tools.ts +8 -2
- package/src/services/ai-system-prompt.ts +4 -8
- package/src/services/ai-tools.ts +8 -69
- package/src/services/document-assistant.ts +9 -6
- package/src/studio.ts +24 -8
- package/src/surfaces/commandbar.json +35 -24
- package/src/surfaces/commandbar.ts +6 -5
- package/src/surfaces/pane-grid.json +35 -3
- package/src/surfaces/pane-grid.ts +26 -4
- package/src/surfaces/statusbar.ts +2 -1
- package/src/surfaces/tab-strip.json +16 -39
- package/src/surfaces/tab-strip.ts +76 -12
- package/src/tabs/project-config.ts +45 -11
- package/src/ui/ai-credentials-form.ts +12 -1
- package/src/ui/layers.ts +4 -1
- package/src/workspace/pane-derive.ts +7 -5
- package/src/workspace/session.ts +3 -3
- package/src/workspace/workspace.ts +220 -43
- package/styles/shell-frame.css +8 -0
- package/styles/shell-frame.json +4 -0
- package/dist/chunks/components-2w1f35qs.js +0 -18
- package/dist/chunks/content-source-3wqeas45.js +0 -46
- package/dist/chunks/entry-commands-d6g50j7s.js +0 -42
- package/dist/chunks/file-ops-3btj6st0.js +0 -47
- package/dist/chunks/git-panel-4wp2rn9b.js +0 -49
- package/dist/chunks/layers-panel-ar2gh90g.js +0 -40
- package/dist/chunks/menu-rf7n72mf.js +0 -14
- package/dist/chunks/publish-panel-98f7sr3g.js +0 -27
- package/dist/chunks/quick-search-srzz83k2.js +0 -45
- package/dist/chunks/right-panel-rrgx2dn3.js +0 -45
- package/dist/chunks/studio-d055c37e.js +0 -62
|
@@ -32,6 +32,25 @@
|
|
|
32
32
|
* the model is told "changed nothing" rather than congratulated. This is what closes the
|
|
33
33
|
* phantom-success finding for every bridged document write, not just the one tool it was found on.
|
|
34
34
|
*
|
|
35
|
+
* **The project witness** is the same comparison over `project.json`. Every configuration write is
|
|
36
|
+
* a transaction on the configuration document (`tabs/project-config.ts`), whose bind effect keeps
|
|
37
|
+
* `projectState.projectConfig` equal to `toRaw(document.doc.document)` — so a record with `undo:
|
|
38
|
+
* "project"` that left that reference alone wrote nothing, whatever its report says. The ledger
|
|
39
|
+
* then files nothing (`wrote: []`, mechanically, for every project record), and a report that
|
|
40
|
+
* named no `wrote` of its own — one with no latch to know it was a no-op — has its sentence
|
|
41
|
+
* replaced by "changed nothing", because "Wrote project.json" over an untouched file is the
|
|
42
|
+
* phantom the ledger exists to keep out. A record whose report answered `wrote: []` itself is one
|
|
43
|
+
* that KNEW, and its sentence ("already enabled") is the one the witness cannot write. That is why
|
|
44
|
+
* `add_project_locale` keeps its latch: the sentence, not the ledger, is what the latch is for.
|
|
45
|
+
*
|
|
46
|
+
* **A tool with nothing it could be called with is withheld.** A `derivedEnumProperty` on a
|
|
47
|
+
* REQUIRED argument serialises `enum: []` when the list behind it is empty — `enable_extension`
|
|
48
|
+
* with no catalogue — and `@jxsuite/ai`'s validator tolerates that only because every projected
|
|
49
|
+
* tool is `strict: false`. The record's gate cannot see its own argument list, so the bridge reads
|
|
50
|
+
* it: an enabled record whose required enum is empty is not advertised this round, and comes back
|
|
51
|
+
* the round the list does. `getDefinition` still answers, so a call the model makes anyway meets
|
|
52
|
+
* the coercion's own refusal (`is not declared — declared: none`) rather than `Unknown tool`.
|
|
53
|
+
*
|
|
35
54
|
* **`validate()` is a no-op, on purpose.** `@jxsuite/ai`'s validator never checks `enum` and treats
|
|
36
55
|
* a required `null` as missing (`packages/ai/src/tools.ts`), which would refuse `select_node
|
|
37
56
|
* { path: null }`. The registry's coercion is the single validator, so every projected tool is
|
|
@@ -46,6 +65,7 @@ import { activeRegistry } from "../commands/active-registry";
|
|
|
46
65
|
import { argsSchema } from "../commands/command-args";
|
|
47
66
|
import { inCanvas } from "../commands/context";
|
|
48
67
|
import { toRaw } from "../reactivity";
|
|
68
|
+
import { projectState } from "../state";
|
|
49
69
|
import { recordWrite } from "./ai-writes";
|
|
50
70
|
import { reportDocumentWrite, snapshotBeforeWrite } from "./ai-write-report";
|
|
51
71
|
import type { WriteReportDeps, WriteSnapshot } from "./ai-write-report";
|
|
@@ -76,10 +96,25 @@ const EMPTY_ARGS = argsSchema({});
|
|
|
76
96
|
|
|
77
97
|
/** The loosely-typed view of a record's `args` this module reads. */
|
|
78
98
|
interface ArgsShape {
|
|
79
|
-
properties?: Record<string,
|
|
99
|
+
properties?: Record<string, { enum?: unknown }>;
|
|
80
100
|
required?: readonly string[];
|
|
81
101
|
}
|
|
82
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Whether a required argument's choice list is empty right now.
|
|
105
|
+
*
|
|
106
|
+
* Reading `enum` is what serialises a `derivedEnumProperty` — the getter runs here exactly as it
|
|
107
|
+
* runs on the wire — so this is the same answer the model would have been shown. Only a REQUIRED
|
|
108
|
+
* property counts: an optional enum that is empty is an argument the model can leave out.
|
|
109
|
+
*/
|
|
110
|
+
function hasEmptyRequiredEnum(args: object | undefined): boolean {
|
|
111
|
+
const shape = (args ?? EMPTY_ARGS) as ArgsShape;
|
|
112
|
+
return (shape.required ?? []).some((key) => {
|
|
113
|
+
const declared = shape.properties?.[key]?.enum;
|
|
114
|
+
return Array.isArray(declared) && declared.length === 0;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
83
118
|
/**
|
|
84
119
|
* The selector's own `paths` property object, taken BY REFERENCE from the live record so the
|
|
85
120
|
* description the model reads is the selector's. `undefined` when the selector is not registered,
|
|
@@ -141,12 +176,14 @@ function selectionAdvertised(registry: CommandRegistry): boolean {
|
|
|
141
176
|
/**
|
|
142
177
|
* Whether one record's tool is advertised this round. For a project- or document-level record it is
|
|
143
178
|
* the record's own gate, evaluated as the palette evaluates it — a module-state `enablement`
|
|
144
|
-
* closure included. Selection-level records are the one derived case, above.
|
|
179
|
+
* closure included. Selection-level records are the one derived case, above. And a record the gate
|
|
180
|
+
* admits is still withheld while a required argument has an empty choice list: there is nothing it
|
|
181
|
+
* could be called with, and a tool advertised with `enum: []` costs a round to discover that.
|
|
145
182
|
*/
|
|
146
183
|
function advertised(registry: CommandRegistry, command: AnyCommand): boolean {
|
|
147
|
-
|
|
148
|
-
? selectionAdvertised(registry)
|
|
149
|
-
|
|
184
|
+
const open =
|
|
185
|
+
command.level === "selection" ? selectionAdvertised(registry) : registry.isEnabled(command.id);
|
|
186
|
+
return open && !hasEmptyRequiredEnum(command.args);
|
|
150
187
|
}
|
|
151
188
|
|
|
152
189
|
/** Every record carrying a projection, enabled or not. */
|
|
@@ -251,6 +288,19 @@ function normaliseReport(report: string | AiToolReport): AiToolReport {
|
|
|
251
288
|
return typeof report === "string" ? { summary: report } : report;
|
|
252
289
|
}
|
|
253
290
|
|
|
291
|
+
/**
|
|
292
|
+
* The configuration object the app is live on, unwrapped, or `null` before a project has one.
|
|
293
|
+
*
|
|
294
|
+
* The same read `tabs/project-config.ts`'s `liveConfig()` makes: `projectState.projectConfig` is
|
|
295
|
+
* held to `toRaw(document.doc.document)` by the bind effect, so a `transactDoc` on the
|
|
296
|
+
* configuration document is a new reference here — and an unchanged reference is a run that never
|
|
297
|
+
* transacted, however the record's report reads.
|
|
298
|
+
*/
|
|
299
|
+
function projectConfigRef(): object | null {
|
|
300
|
+
const config = projectState?.projectConfig;
|
|
301
|
+
return config ? toRaw(config as unknown as object) : null;
|
|
302
|
+
}
|
|
303
|
+
|
|
254
304
|
/**
|
|
255
305
|
* The ledger paths a record's write defaults to when `report` names none, by undo scope. `"none"`
|
|
256
306
|
* has no default on purpose — see {@link AiToolReport.wrote}.
|
|
@@ -322,6 +372,10 @@ export function createCommandToolRegistry(deps: CommandToolDeps): ToolRegistry {
|
|
|
322
372
|
what `transactDoc` replaces, and the snapshot is what the verdict compares against. */
|
|
323
373
|
const tab = command.undo === "document" ? deps.getTab() : null;
|
|
324
374
|
const rootBefore = tab ? toRaw(tab.doc.document) : null;
|
|
375
|
+
/* The project witness's half: the configuration object the chokepoint holds, which every
|
|
376
|
+
applied `project.json` transaction replaces. Read for `undo: "project"` records only — the
|
|
377
|
+
comparison is meaningless for a record that does not claim the file. */
|
|
378
|
+
const configBefore = command.undo === "project" ? projectConfigRef() : null;
|
|
325
379
|
let snapshot: WriteSnapshot | null = null;
|
|
326
380
|
let before: CommandContext;
|
|
327
381
|
try {
|
|
@@ -357,6 +411,12 @@ export function createCommandToolRegistry(deps: CommandToolDeps): ToolRegistry {
|
|
|
357
411
|
};
|
|
358
412
|
}
|
|
359
413
|
|
|
414
|
+
/* The project witness's verdict, decided before the report so a report that throws is filed
|
|
415
|
+
against it too. Unlike the document witness this is not a refusal: an idempotent project verb
|
|
416
|
+
asked for a state it already had is an ordinary answer, and the record's own sentence may
|
|
417
|
+
say why. What the witness owns is the LEDGER — nothing landed, so nothing is filed. */
|
|
418
|
+
const projectUnchanged = command.undo === "project" && projectConfigRef() === configBefore;
|
|
419
|
+
|
|
360
420
|
const after = registry.context();
|
|
361
421
|
let report: AiToolReport;
|
|
362
422
|
try {
|
|
@@ -366,13 +426,23 @@ export function createCommandToolRegistry(deps: CommandToolDeps): ToolRegistry {
|
|
|
366
426
|
Left to the loop, the throw would be labelled "Failed to parse arguments" and the ledger
|
|
367
427
|
would never be filed. So the ledger is filed from `undo`'s defaults — the report that would
|
|
368
428
|
have named the paths is the thing that failed — and the model is told both halves. */
|
|
369
|
-
fileLedger(command, tab);
|
|
429
|
+
fileLedger(command, tab, projectUnchanged ? [] : undefined);
|
|
370
430
|
const message = error instanceof Error ? error.message : String(error);
|
|
371
431
|
return { error: `${command.title} ran, but its report failed: ${message}`, success: false };
|
|
372
432
|
}
|
|
373
|
-
fileLedger(command, tab, report.wrote);
|
|
374
433
|
|
|
375
|
-
let { summary } = report;
|
|
434
|
+
let { summary, wrote } = report;
|
|
435
|
+
if (projectUnchanged) {
|
|
436
|
+
/* The ledger files nothing whatever the report named. A report that did not itself know it
|
|
437
|
+
was a no-op has its sentence replaced — a claim of a write over an untouched file is
|
|
438
|
+
exactly the phantom this exists to catch. A report that answered `wrote: []` knew, and its
|
|
439
|
+
sentence is kept: it says WHY nothing changed, which the witness cannot. */
|
|
440
|
+
wrote = [];
|
|
441
|
+
if (report.wrote === undefined || report.wrote.length > 0) {
|
|
442
|
+
summary = `${command.title} changed nothing: project.json is exactly as it was.`;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
fileLedger(command, tab, wrote);
|
|
376
446
|
if (tab && snapshot) {
|
|
377
447
|
const verdict = await reportDocumentWrite(tab, snapshot, summary, deps);
|
|
378
448
|
if (!verdict.success) {
|
|
@@ -75,6 +75,7 @@ let proxyConfigured = false;
|
|
|
75
75
|
let proxyManaged = false;
|
|
76
76
|
let proxyDefaultModel = "";
|
|
77
77
|
let proxyCode: AiModelsResponse["code"];
|
|
78
|
+
let proxyModelsError = "";
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
81
|
* One-shot capability probe shared by every credentials gate, plus the hosts to repaint when it
|
|
@@ -116,6 +117,7 @@ export function resetModelCache() {
|
|
|
116
117
|
proxyManaged = false;
|
|
117
118
|
proxyDefaultModel = "";
|
|
118
119
|
proxyCode = undefined;
|
|
120
|
+
proxyModelsError = "";
|
|
119
121
|
/* The probe's result IS the flags above. Clearing them while keeping the settled promise
|
|
120
122
|
would strand every gate on a permanent "unconfigured, unmanaged" reading — ensureProxyProbe
|
|
121
123
|
would no-op forever and the managed option would vanish until a full reload. */
|
|
@@ -266,6 +268,16 @@ export function proxyStateCode(): AiModelsResponse["code"] {
|
|
|
266
268
|
return proxyCode;
|
|
267
269
|
}
|
|
268
270
|
|
|
271
|
+
/**
|
|
272
|
+
* The upstream's own error message from the last fetch, when it reported one (e.g. Cloudflare's "No
|
|
273
|
+
* route for that URI" for a base URL that doesn't serve `/models`). Empty when the last fetch had
|
|
274
|
+
* no upstream error to report — a stale message from an earlier failure must not survive a fetch
|
|
275
|
+
* that succeeded.
|
|
276
|
+
*/
|
|
277
|
+
export function proxyModelsErrorMessage(): string {
|
|
278
|
+
return proxyModelsError;
|
|
279
|
+
}
|
|
280
|
+
|
|
269
281
|
/** The proxy's preferred model id ("" when it does not declare one). */
|
|
270
282
|
export function getProxyDefaultModel(): string {
|
|
271
283
|
return proxyDefaultModel;
|
|
@@ -356,6 +368,7 @@ export async function fetchAvailableModels(
|
|
|
356
368
|
proxyManaged = data.managed === true;
|
|
357
369
|
proxyDefaultModel = data.defaultModel ?? "";
|
|
358
370
|
proxyCode = data.code;
|
|
371
|
+
proxyModelsError = data.upstreamError !== undefined ? (data.upstreamMessage ?? "") : "";
|
|
359
372
|
/* Capabilities are kept, not dropped. The backend has reported `toolSupport` all along and the
|
|
360
373
|
ingest mapped `{id, name}` only, so a Workers AI model that cannot call tools looked exactly
|
|
361
374
|
like one that can — the agent loop ran, called nothing, and answered as if that were normal.
|
|
@@ -115,8 +115,14 @@ export interface ProjectToolsCtx {
|
|
|
115
115
|
* tab happened to be re-read from disk afterwards ({@link ProjectToolsCtx.reloadTab}); that
|
|
116
116
|
* re-read is gone, because a second parse of `project.json` is the very rival object the
|
|
117
117
|
* configuration document exists to prevent.
|
|
118
|
+
*
|
|
119
|
+
* `text` is the bytes that reached the file, of which `config` is the parse. The document keeps a
|
|
120
|
+
* layout record for the file it holds (§9.4), and the assistant's write replaces the file — so
|
|
121
|
+
* the record has to travel with the write, or the next settings commit re-lays the file the model
|
|
122
|
+
* just formatted with the record of the file it had read before (issue 331). Handing the text
|
|
123
|
+
* over is what lets the document derive that record without reading the file back.
|
|
118
124
|
*/
|
|
119
|
-
onProjectConfigWritten?: (config: ProjectConfig) => void | Promise<void>;
|
|
125
|
+
onProjectConfigWritten?: (config: ProjectConfig, text: string) => void | Promise<void>;
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
/**
|
|
@@ -399,7 +405,7 @@ export function registerProjectTools(
|
|
|
399
405
|
that tab, which is the split the adoption just closed. Every other path is a plain file
|
|
400
406
|
and its tab has to be told from disk. */
|
|
401
407
|
if (projectConfig) {
|
|
402
|
-
await onProjectConfigWritten?.(projectConfig);
|
|
408
|
+
await onProjectConfigWritten?.(projectConfig, content);
|
|
403
409
|
} else if (openTab) {
|
|
404
410
|
await reloadTab(relPath);
|
|
405
411
|
}
|
|
@@ -117,8 +117,8 @@ export interface AiToolInfo {
|
|
|
117
117
|
*
|
|
118
118
|
* The other kind of tool is not here and must not be added here: a command record that declares
|
|
119
119
|
* `aiTool` is projected by `services/ai-command-tools.ts`, gated by the record's own `when` /
|
|
120
|
-
* `enablement`, and described by the record. `enable_extension`, `disable_extension`
|
|
121
|
-
*
|
|
120
|
+
* `enablement`, and described by the record. `enable_extension`, `disable_extension`, `delete_node`
|
|
121
|
+
* (né `remove_node`) and `open_document` left this table for that one.
|
|
122
122
|
*/
|
|
123
123
|
export const AI_TOOL_TIERS: AiToolInfo[] = [
|
|
124
124
|
// Always — a question is not gated on what happens to be open
|
|
@@ -185,12 +185,8 @@ export const AI_TOOL_TIERS: AiToolInfo[] = [
|
|
|
185
185
|
tier: "project",
|
|
186
186
|
blurb: "create_page(path, content) — create a new .json page file on disk.",
|
|
187
187
|
},
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
tier: "project",
|
|
191
|
-
blurb:
|
|
192
|
-
"open_document(path) — open a file on the canvas as the active document; the document tools then operate on it. Use when the user should SEE the page, or for iterative visual refinement.",
|
|
193
|
-
},
|
|
188
|
+
/* `open_document` is not a row: it is the projection of the `document.open` record
|
|
189
|
+
(`workspace/workspace.ts`), advertised by the record's own gate and described by the record. */
|
|
194
190
|
// Document (an active document on the canvas)
|
|
195
191
|
{
|
|
196
192
|
name: "read_document",
|
package/src/services/ai-tools.ts
CHANGED
|
@@ -14,9 +14,6 @@ import type { JxMutableNode, JxPath, JxStateDefinition } from "@jxsuite/schema/t
|
|
|
14
14
|
import { getNodeAtPath } from "../state";
|
|
15
15
|
import type { Tab } from "../tabs/tab";
|
|
16
16
|
import {
|
|
17
|
-
beginBatch,
|
|
18
|
-
endBatch,
|
|
19
|
-
isBatching,
|
|
20
17
|
mutateInsertNode,
|
|
21
18
|
mutateMoveNode,
|
|
22
19
|
mutateUpdateProperty,
|
|
@@ -110,7 +107,6 @@ async function applyAndValidate(
|
|
|
110
107
|
* validate?: (doc: unknown) => Promise<string[]>;
|
|
111
108
|
* saveFile?: (relPath: string, content: string) => Promise<void>;
|
|
112
109
|
* renderCheck?: (doc: unknown) => Promise<{ ok: true } | { ok: false; error: string }>;
|
|
113
|
-
* openDocument?: (path: string) => Promise<void>;
|
|
114
110
|
* projectStyle?: Record<string, string>;
|
|
115
111
|
* getProjectStyle?: () => Record<string, string> | undefined;
|
|
116
112
|
* findOpenTab?: (path: string) => import("../tabs/tab").Tab | null;
|
|
@@ -124,7 +120,6 @@ export function registerAiTools(
|
|
|
124
120
|
validate = validateDoc,
|
|
125
121
|
saveFile,
|
|
126
122
|
renderCheck,
|
|
127
|
-
openDocument,
|
|
128
123
|
projectStyle,
|
|
129
124
|
getProjectStyle,
|
|
130
125
|
findOpenTab,
|
|
@@ -134,7 +129,6 @@ export function registerAiTools(
|
|
|
134
129
|
validate?: (doc: unknown) => Promise<string[]>;
|
|
135
130
|
saveFile?: (relPath: string, content: string) => Promise<void>;
|
|
136
131
|
renderCheck?: (doc: unknown) => Promise<{ ok: true } | { ok: false; error: string }>;
|
|
137
|
-
openDocument?: (path: string) => Promise<void>;
|
|
138
132
|
projectStyle?: Record<string, string> | undefined;
|
|
139
133
|
/**
|
|
140
134
|
* Live variant of `projectStyle` — takes precedence; re-read per tool call so a project
|
|
@@ -776,67 +770,12 @@ export function registerAiTools(
|
|
|
776
770
|
}),
|
|
777
771
|
);
|
|
778
772
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
"document. Use this to iteratively refine pages or components after creating them " +
|
|
788
|
-
"with create_page or create_component.",
|
|
789
|
-
parameters: {
|
|
790
|
-
type: "object",
|
|
791
|
-
properties: {
|
|
792
|
-
path: {
|
|
793
|
-
type: "string",
|
|
794
|
-
description:
|
|
795
|
-
'File path relative to the project root, e.g. "pages/about.json" or ' +
|
|
796
|
-
'"components/nav-bar.json". Must be an existing file.',
|
|
797
|
-
},
|
|
798
|
-
},
|
|
799
|
-
required: ["path"],
|
|
800
|
-
},
|
|
801
|
-
async execute(args) {
|
|
802
|
-
if (!openDocument) {
|
|
803
|
-
return {
|
|
804
|
-
success: false,
|
|
805
|
-
error: "File navigation is not available in this environment.",
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
const { path: relPath } = args as { path: string };
|
|
809
|
-
try {
|
|
810
|
-
await openDocument(relPath);
|
|
811
|
-
const tab = getTab();
|
|
812
|
-
if (!tab) {
|
|
813
|
-
return {
|
|
814
|
-
success: false,
|
|
815
|
-
error: `File "${relPath}" could not be opened — no active tab after navigation.`,
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
/*
|
|
819
|
-
* The agent loop opens a single undo batch on the tab that was active at loop start
|
|
820
|
-
* (tool-executor.js → beginBatch). Switching the active document mid-loop would strand
|
|
821
|
-
* the new tab's edits with no history snapshot — undo would have nothing to roll back.
|
|
822
|
-
* Flush the previous tab's batch and re-open one on the newly-active tab so edits in
|
|
823
|
-
* each document remain individually undoable.
|
|
824
|
-
*/
|
|
825
|
-
if (isBatching()) {
|
|
826
|
-
endBatch();
|
|
827
|
-
beginBatch(tab);
|
|
828
|
-
}
|
|
829
|
-
return {
|
|
830
|
-
success: true,
|
|
831
|
-
summary: `Switched to "${relPath}". All tools now operate on this document.`,
|
|
832
|
-
};
|
|
833
|
-
} catch (error) {
|
|
834
|
-
return {
|
|
835
|
-
success: false,
|
|
836
|
-
error: `Failed to open "${relPath}": ${error instanceof Error ? error.message : String(error)}`,
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
},
|
|
840
|
-
}),
|
|
841
|
-
);
|
|
773
|
+
/*
|
|
774
|
+
* `open_document` is NOT here any more. It is the projection of the `document.open` record
|
|
775
|
+
* (`workspace/workspace.ts`'s `tabCommands`), so the person's palette row and the model's tool
|
|
776
|
+
* are one `run` and one gate (§12.4). The hand tool read `getTab()` after the open and called
|
|
777
|
+
* whatever it found a success — a missing file left the previous document active and reported
|
|
778
|
+
* "Switched to" — and it re-anchored the undo batch itself, which `tool-executor.ts` has done
|
|
779
|
+
* after EVERY tool since project adoption started replacing tabs mid-loop.
|
|
780
|
+
*/
|
|
842
781
|
}
|
|
@@ -40,7 +40,7 @@ import { preferredModel } from "./ai-models";
|
|
|
40
40
|
import { pruneOrphanToolMessages, trimContext } from "./context-manager";
|
|
41
41
|
import { renderCheck } from "./render-critic";
|
|
42
42
|
import { validateDoc } from "./jx-validate";
|
|
43
|
-
import {
|
|
43
|
+
import { reloadFileInTab } from "../files/files";
|
|
44
44
|
import { getExtensionCatalog, refreshExtensionUi } from "../format/format-host";
|
|
45
45
|
import * as sessionStore from "./ai-session-store";
|
|
46
46
|
|
|
@@ -98,7 +98,6 @@ export function createDocumentAssistant() {
|
|
|
98
98
|
renderCheck: renderCheck as (
|
|
99
99
|
doc: unknown,
|
|
100
100
|
) => Promise<{ ok: true } | { ok: false; error: string }>,
|
|
101
|
-
openDocument: openFileInTab,
|
|
102
101
|
getProjectStyle,
|
|
103
102
|
findOpenTab,
|
|
104
103
|
reloadTab: reloadFileInTab,
|
|
@@ -130,12 +129,15 @@ export function createDocumentAssistant() {
|
|
|
130
129
|
sessionStore.moveSession("", root, sessionId);
|
|
131
130
|
}
|
|
132
131
|
},
|
|
133
|
-
onProjectConfigWritten: async (config: ProjectConfig) => {
|
|
132
|
+
onProjectConfigWritten: async (config: ProjectConfig, text: string) => {
|
|
134
133
|
/* Into the configuration document, not beside it. This used to assign a fresh object to
|
|
135
134
|
`projectState.projectConfig`, leaving the document holding the previous configuration —
|
|
136
135
|
and the next settings commit wrote that stale document back over the assistant's file.
|
|
137
|
-
`adoptProjectConfig` is the one door for a config that reached disk another way.
|
|
138
|
-
|
|
136
|
+
`adoptProjectConfig` is the one door for a config that reached disk another way. The text
|
|
137
|
+
goes with it: the document derives the file's layout record from the bytes the tool wrote,
|
|
138
|
+
so the next settings commit is a one-line diff on the file as the model laid it out, not a
|
|
139
|
+
re-layout of it with the record of the file it read before (issue 331). */
|
|
140
|
+
await adoptProjectConfig(config, text);
|
|
139
141
|
/* An `extensions` edit changes what the generated entry documents compose, and the backends
|
|
140
142
|
regenerate them from project.json on demand — so without this the editor and the assistant
|
|
141
143
|
both keep judging by the PREVIOUS project's schemas until the window reopens. */
|
|
@@ -157,7 +159,8 @@ export function createDocumentAssistant() {
|
|
|
157
159
|
document: "an open document (use open_document first)",
|
|
158
160
|
"document-tree":
|
|
159
161
|
"an open document whose element tree the canvas is editing — Project Settings and the " +
|
|
160
|
-
"grid editors are documents, but not trees to restructure"
|
|
162
|
+
"grid editors are documents, but not trees to restructure, and a page left in Code view " +
|
|
163
|
+
"returns to the canvas with set_canvas_mode",
|
|
161
164
|
} as const;
|
|
162
165
|
|
|
163
166
|
/**
|
package/src/studio.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
|
|
28
28
|
import {
|
|
29
29
|
PRIMARY_PANE,
|
|
30
|
+
activePane,
|
|
30
31
|
activeTab,
|
|
31
32
|
closeAllTabs,
|
|
32
33
|
openTab,
|
|
@@ -160,7 +161,7 @@ import {
|
|
|
160
161
|
mountSignalsPanel,
|
|
161
162
|
registerSignalsCommands,
|
|
162
163
|
} from "./panels/signals-panel";
|
|
163
|
-
import { loadComponentRegistry } from "./files/components";
|
|
164
|
+
import { loadComponentRegistry, noteComponentSaved } from "./files/components";
|
|
164
165
|
import { ensureDependenciesInstalled } from "./packages/ensure-deps";
|
|
165
166
|
import { maybePromptJxsuiteUpdate } from "./packages/jxsuite-update";
|
|
166
167
|
import { autoSyncProjectOnOpen } from "./packages/pull-package-sync";
|
|
@@ -234,7 +235,11 @@ import {
|
|
|
234
235
|
platformUsesRepoPicker,
|
|
235
236
|
} from "./new-project/add-repo-modal";
|
|
236
237
|
import { openNewProjectModal, registerNewProjectCommands } from "./new-project/new-project-modal";
|
|
237
|
-
import {
|
|
238
|
+
import {
|
|
239
|
+
invalidatePageRouteCache,
|
|
240
|
+
registerInspectorCommands,
|
|
241
|
+
renderPropertiesPanel,
|
|
242
|
+
} from "./panels/properties-panel";
|
|
238
243
|
import { liveElementCommands, setContextMenuNavigate } from "./editor/context-menu";
|
|
239
244
|
import { registerSeoCommands, renderSeoModal } from "./panels/seo-modal";
|
|
240
245
|
import { ensurePopoverRevealWatch, setOpenPopover } from "./canvas/popover-state";
|
|
@@ -380,9 +385,11 @@ function setCanvasMode(tab: Tab | null, mode: string) {
|
|
|
380
385
|
* 1. **To the side.** §8.2 has promised this since P3 and it never shipped — the chain ran
|
|
381
386
|
* `openFileInTab` → `openTab` → `activePane()`, so "open the layout that wraps this page" opened
|
|
382
387
|
* it ON TOP of the page it was teaching about.
|
|
383
|
-
* 2. **
|
|
384
|
-
*
|
|
385
|
-
* pane
|
|
388
|
+
* 2. **The focus FOLLOWS.** The open is a gesture, not a read: the author asked to edit the component,
|
|
389
|
+
* so the pane it lands in takes the keyboard. The failure it replaces is the opposite one — an
|
|
390
|
+
* assistant pane that takes the keyboard means the author's next keystroke edits the definition
|
|
391
|
+
* instead of the document they are looking at, which is why the READS (`pane.compareWith`, the
|
|
392
|
+
* derivation follow, session restore) browse with `focus: false`.
|
|
386
393
|
* 3. **A PREVIEW tab**, because drilling in is browsing: the second drill-in takes the same slot
|
|
387
394
|
* instead of littering the side strip, and an edit promotes it (`promoteDirtyPreviewTabs`).
|
|
388
395
|
* 4. **An ordinary tab, not a derivation.** "Edit definition" is a commitment to edit one component; a
|
|
@@ -392,16 +399,17 @@ function setCanvasMode(tab: Tab | null, mode: string) {
|
|
|
392
399
|
* `openedFrom` is unchanged — §14.2's relationship, which nothing pops and nothing restores from.
|
|
393
400
|
*
|
|
394
401
|
* @param {string} componentPath
|
|
402
|
+
* @docs studio/interface/tabs
|
|
395
403
|
*/
|
|
396
404
|
async function navigateToComponent(componentPath: string) {
|
|
397
405
|
const from = activeTab.value;
|
|
398
|
-
/* {@link receivingPane}, not `
|
|
406
|
+
/* {@link receivingPane}, not `paneBeside`: the pane beside this one may be a LENS, which owns no
|
|
399
407
|
tab. The open then landed in a `tabOrder` `tabOfPane` hops straight past, so the read below got
|
|
400
408
|
the SOURCE tab back, `opened.documentPath !== componentPath`, and the one relationship this
|
|
401
409
|
function exists to record (§14.2) was skipped without a sound. */
|
|
402
|
-
const target = receivingPane();
|
|
410
|
+
const target = receivingPane(activePane().id);
|
|
403
411
|
const alreadyOpen = [...workspace.tabs.values()].some((t) => t.documentPath === componentPath);
|
|
404
|
-
await openFileInTab(componentPath, {
|
|
412
|
+
await openFileInTab(componentPath, { paneId: target.id, preview: true });
|
|
405
413
|
const opened = tabOfPane(target.id);
|
|
406
414
|
if (!alreadyOpen && from && opened && opened.documentPath === componentPath) {
|
|
407
415
|
opened.session.openedFrom = { documentPath: from.documentPath, tabId: from.id };
|
|
@@ -1497,9 +1505,17 @@ const liveChrome = createLiveSurfaceSaver({
|
|
|
1497
1505
|
redefineOnCanvases: redefineElementOnCanvases,
|
|
1498
1506
|
workspace,
|
|
1499
1507
|
});
|
|
1508
|
+
/* The third is the component registry: a saved definition rewrites its own entry, so an instance's
|
|
1509
|
+
Component Settings and the Library read the props the file has now rather than the ones the scan
|
|
1510
|
+
saw when the project opened. The Content tab and the Library are told, because neither reads the
|
|
1511
|
+
registry reactively. */
|
|
1500
1512
|
setDocumentSavedListener((path, doc) => {
|
|
1501
1513
|
void noteFileSaved(path);
|
|
1502
1514
|
void liveChrome(path, doc as unknown as JxDocument);
|
|
1515
|
+
if (noteComponentSaved(path, doc)) {
|
|
1516
|
+
renderPropertiesPanel();
|
|
1517
|
+
renderLeftPanel();
|
|
1518
|
+
}
|
|
1503
1519
|
});
|
|
1504
1520
|
/* Walking a comparison. No deps: the stepper reads the pane-keyed diff store directly and the
|
|
1505
1521
|
toolbar redraws itself, so there is nothing for the bootstrap to inject. */
|
|
@@ -21,9 +21,6 @@
|
|
|
21
21
|
"flex": "1",
|
|
22
22
|
"flexShrink": "999"
|
|
23
23
|
},
|
|
24
|
-
"& jx-button, & jx-action-button, & button": {
|
|
25
|
-
"WebkitAppRegion": "no-drag"
|
|
26
|
-
},
|
|
27
24
|
"& [part=\"csd-lead\"], & [part=\"csd-trail\"], & [part=\"menu-slot\"], & [part=\"layouts-slot\"], & [part=\"presence-slot\"]": {
|
|
28
25
|
"display": "contents"
|
|
29
26
|
},
|
|
@@ -78,7 +75,7 @@
|
|
|
78
75
|
"gap": "2px",
|
|
79
76
|
"flexShrink": "0"
|
|
80
77
|
},
|
|
81
|
-
"& [part=\"layout\"]
|
|
78
|
+
"& [part=\"layout\"]": {
|
|
82
79
|
"padding": "2px var(--jx-space-3)",
|
|
83
80
|
"border": "none",
|
|
84
81
|
"borderRadius": "var(--jx-radius-sm)",
|
|
@@ -101,9 +98,6 @@
|
|
|
101
98
|
"color": "var(--jx-fg)",
|
|
102
99
|
"background": "var(--jx-hover-bg)"
|
|
103
100
|
},
|
|
104
|
-
"& [part=\"layout-add\"]": {
|
|
105
|
-
"padding": "2px var(--jx-space-2)"
|
|
106
|
-
},
|
|
107
101
|
"& [part=\"center\"]": {
|
|
108
102
|
"display": "flex",
|
|
109
103
|
"alignItems": "center",
|
|
@@ -267,7 +261,8 @@
|
|
|
267
261
|
"size": "sm"
|
|
268
262
|
},
|
|
269
263
|
"attributes": {
|
|
270
|
-
"data-control": "close"
|
|
264
|
+
"data-control": "close",
|
|
265
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
271
266
|
},
|
|
272
267
|
"onclick": {
|
|
273
268
|
"$prototype": "Function",
|
|
@@ -291,7 +286,8 @@
|
|
|
291
286
|
"size": "sm"
|
|
292
287
|
},
|
|
293
288
|
"attributes": {
|
|
294
|
-
"data-control": "minimize"
|
|
289
|
+
"data-control": "minimize",
|
|
290
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
295
291
|
},
|
|
296
292
|
"onclick": {
|
|
297
293
|
"$prototype": "Function",
|
|
@@ -315,7 +311,8 @@
|
|
|
315
311
|
"size": "sm"
|
|
316
312
|
},
|
|
317
313
|
"attributes": {
|
|
318
|
-
"data-control": "maximize"
|
|
314
|
+
"data-control": "maximize",
|
|
315
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
319
316
|
},
|
|
320
317
|
"onclick": {
|
|
321
318
|
"$prototype": "Function",
|
|
@@ -357,7 +354,8 @@
|
|
|
357
354
|
}
|
|
358
355
|
},
|
|
359
356
|
"attributes": {
|
|
360
|
-
"data-menu": "studio"
|
|
357
|
+
"data-menu": "studio",
|
|
358
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
361
359
|
},
|
|
362
360
|
"onclick": {
|
|
363
361
|
"$prototype": "Function",
|
|
@@ -441,7 +439,8 @@
|
|
|
441
439
|
"aria-selected": "${$map.item.active ? 'true' : 'false'}",
|
|
442
440
|
"data-layout": "${$map.item.id}",
|
|
443
441
|
"data-active": "${$map.item.active ? '' : null}",
|
|
444
|
-
"title": "${$map.item.title}"
|
|
442
|
+
"title": "${$map.item.title}",
|
|
443
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
445
444
|
},
|
|
446
445
|
"textContent": "${$map.item.name}",
|
|
447
446
|
"onclick": {
|
|
@@ -479,14 +478,17 @@
|
|
|
479
478
|
}
|
|
480
479
|
},
|
|
481
480
|
{
|
|
482
|
-
"tagName": "button",
|
|
481
|
+
"tagName": "jx-action-button",
|
|
482
|
+
"$props": {
|
|
483
|
+
"label": "Save layout",
|
|
484
|
+
"hint": "Save the current arrangement as a layout",
|
|
485
|
+
"icon": "plus",
|
|
486
|
+
"size": "sm"
|
|
487
|
+
},
|
|
483
488
|
"attributes": {
|
|
484
489
|
"part": "layout-add",
|
|
485
|
-
"
|
|
486
|
-
"title": "Save the current arrangement as a layout",
|
|
487
|
-
"aria-label": "Save layout"
|
|
490
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
488
491
|
},
|
|
489
|
-
"textContent": "+",
|
|
490
492
|
"onclick": {
|
|
491
493
|
"$prototype": "Function",
|
|
492
494
|
"body": [
|
|
@@ -515,7 +517,8 @@
|
|
|
515
517
|
"attributes": {
|
|
516
518
|
"part": "center",
|
|
517
519
|
"role": "group",
|
|
518
|
-
"aria-label": "Command Center"
|
|
520
|
+
"aria-label": "Command Center",
|
|
521
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
519
522
|
},
|
|
520
523
|
"onclick": {
|
|
521
524
|
"$prototype": "Function",
|
|
@@ -582,7 +585,8 @@
|
|
|
582
585
|
"part": "segment",
|
|
583
586
|
"type": "button",
|
|
584
587
|
"data-segment": "${$map.item.key}",
|
|
585
|
-
"title": "${$map.item.title}"
|
|
588
|
+
"title": "${$map.item.title}",
|
|
589
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
586
590
|
},
|
|
587
591
|
"textContent": "${$map.item.label}",
|
|
588
592
|
"onclick": {
|
|
@@ -657,6 +661,9 @@
|
|
|
657
661
|
"label": {
|
|
658
662
|
"$ref": "$map/item/title"
|
|
659
663
|
},
|
|
664
|
+
"hint": {
|
|
665
|
+
"$ref": "$map/item/tooltip"
|
|
666
|
+
},
|
|
660
667
|
"disabled": {
|
|
661
668
|
"$ref": "$map/item/disabled"
|
|
662
669
|
},
|
|
@@ -665,7 +672,7 @@
|
|
|
665
672
|
},
|
|
666
673
|
"attributes": {
|
|
667
674
|
"data-command-id": "${$map.item.id}",
|
|
668
|
-
"
|
|
675
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
669
676
|
},
|
|
670
677
|
"onclick": {
|
|
671
678
|
"$prototype": "Function",
|
|
@@ -864,7 +871,8 @@
|
|
|
864
871
|
"size": "sm"
|
|
865
872
|
},
|
|
866
873
|
"attributes": {
|
|
867
|
-
"data-command-id": "${$map.item.id}"
|
|
874
|
+
"data-command-id": "${$map.item.id}",
|
|
875
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
868
876
|
},
|
|
869
877
|
"onchange": {
|
|
870
878
|
"$prototype": "Function",
|
|
@@ -913,7 +921,8 @@
|
|
|
913
921
|
"size": "sm"
|
|
914
922
|
},
|
|
915
923
|
"attributes": {
|
|
916
|
-
"data-control": "minimize"
|
|
924
|
+
"data-control": "minimize",
|
|
925
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
917
926
|
},
|
|
918
927
|
"onclick": {
|
|
919
928
|
"$prototype": "Function",
|
|
@@ -937,7 +946,8 @@
|
|
|
937
946
|
"size": "sm"
|
|
938
947
|
},
|
|
939
948
|
"attributes": {
|
|
940
|
-
"data-control": "maximize"
|
|
949
|
+
"data-control": "maximize",
|
|
950
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
941
951
|
},
|
|
942
952
|
"onclick": {
|
|
943
953
|
"$prototype": "Function",
|
|
@@ -961,7 +971,8 @@
|
|
|
961
971
|
"size": "sm"
|
|
962
972
|
},
|
|
963
973
|
"attributes": {
|
|
964
|
-
"data-control": "close"
|
|
974
|
+
"data-control": "close",
|
|
975
|
+
"class": "electrobun-webkit-app-region-no-drag"
|
|
965
976
|
},
|
|
966
977
|
"onclick": {
|
|
967
978
|
"$prototype": "Function",
|