@jxsuite/studio 5.0.0 → 5.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.
- package/dist/chunks/{cf-account-picker-fyf63pmy.js → cf-account-picker-qwkm9zrf.js} +5 -5
- package/dist/chunks/{components-2w1f35qs.js → components-vj72gqtq.js} +6 -4
- package/dist/chunks/{content-source-3wqeas45.js → content-source-5mex4x1h.js} +20 -20
- package/dist/chunks/{convert-to-repeater-d0cgnb6d.js → convert-to-repeater-n64h8znc.js} +20 -20
- package/dist/chunks/entry-commands-234hjnqw.js +42 -0
- package/dist/chunks/{extension-sections-fy7q4fe9.js → extension-sections-3hpcambp.js} +22 -22
- package/dist/chunks/{file-ops-3btj6st0.js → file-ops-8yhd83xr.js} +19 -19
- package/dist/chunks/{files-ef8fgs3w.js → files-hnxqxtsg.js} +26 -26
- package/dist/chunks/{format-host-3c33nwr8.js → format-host-779sfp2n.js} +2 -2
- package/dist/chunks/{fs-events-13tj6sex.js → fs-events-b88z7g4h.js} +4 -4
- package/dist/chunks/{git-panel-4wp2rn9b.js → git-panel-xwj8fpab.js} +18 -18
- package/dist/chunks/{iframe-host-jgesd924.js → iframe-host-syr0f9sb.js} +9 -9
- package/dist/chunks/{layers-1nq5evsh.js → layers-ff81wthn.js} +5 -4
- package/dist/chunks/layers-panel-pnnghw12.js +40 -0
- package/dist/chunks/{media-picker-2qbetq73.js → media-picker-cqhhxhhe.js} +10 -10
- package/dist/chunks/menu-9p2w46gj.js +15 -0
- package/dist/chunks/{publish-panel-98f7sr3g.js → publish-panel-5zt840sy.js} +9 -9
- package/dist/chunks/quick-search-3748564r.js +45 -0
- package/dist/chunks/{references-n553f0e1.js → references-qvvxej7h.js} +3 -3
- package/dist/chunks/right-panel-eccz92qt.js +45 -0
- package/dist/chunks/{studio-x3rty6tr.js → studio-1knfqgy7.js} +6 -6
- package/dist/chunks/{studio-rkstz940.js → studio-1z1kcjv4.js} +17 -8
- package/dist/chunks/{studio-qahxvnan.js → studio-3773086z.js} +2 -2
- package/dist/chunks/{studio-arfpe389.js → studio-3rz5md06.js} +4 -4
- package/dist/chunks/{studio-pm6hgy7r.js → studio-5wt9nvhd.js} +16 -16
- package/dist/chunks/{studio-akbww0j5.js → studio-7xnmgv66.js} +19 -19
- package/dist/chunks/{studio-26d7myd1.js → studio-8scdew0e.js} +3 -3
- package/dist/chunks/{studio-3hxkd92t.js → studio-93zafjcm.js} +8 -8
- package/dist/chunks/{studio-5rt7sqa0.js → studio-a79k90n9.js} +11 -17
- package/dist/chunks/{studio-3gxv60tn.js → studio-aj37eer3.js} +3 -3
- package/dist/chunks/{studio-18avnhjp.js → studio-b0k5tqgp.js} +23 -21
- package/dist/chunks/{studio-fabxk6r9.js → studio-b4s9eykd.js} +13 -13
- package/dist/chunks/{studio-8qtrpzr3.js → studio-e1pgga3k.js} +5 -5
- package/dist/chunks/{studio-8jn5yhyn.js → studio-g3jevyjt.js} +2 -2
- package/dist/chunks/{studio-rpyah2ew.js → studio-gb13095a.js} +18 -17
- package/dist/chunks/{studio-57mf66w2.js → studio-js8nv53n.js} +5 -5
- package/dist/chunks/{studio-vz68yn9e.js → studio-jxp12rnj.js} +4 -4
- package/dist/chunks/{studio-az3zd49t.js → studio-k75n7vhp.js} +56 -4
- package/dist/chunks/{studio-5jy81t3s.js → studio-kkqkcp3d.js} +221 -16
- package/dist/chunks/{studio-tb3ev01j.js → studio-mhhyamxf.js} +4 -4
- package/dist/chunks/{studio-81390n6f.js → studio-nq5yf5mp.js} +1 -1
- package/dist/chunks/{studio-4m0haseg.js → studio-nzzpvzx9.js} +3 -3
- package/dist/chunks/{studio-em9nw8eh.js → studio-pxbgypmg.js} +12 -12
- package/dist/chunks/{studio-sgdc5pfc.js → studio-qhhm6hbt.js} +3 -3
- package/dist/chunks/{studio-d1ja2x9r.js → studio-qk4bfsq2.js} +3 -3
- package/dist/chunks/{studio-25hkdrkw.js → studio-qk90fgqm.js} +9 -9
- package/dist/chunks/{studio-s6z853aq.js → studio-r6nvacqs.js} +18 -17
- package/dist/chunks/{studio-7xptrg0j.js → studio-sbjyc8bt.js} +6 -6
- package/dist/chunks/{studio-5523bpzt.js → studio-syg2tadj.js} +3 -3
- package/dist/chunks/{studio-cpvb2ed1.js → studio-tqgryxwg.js} +7 -7
- package/dist/chunks/{studio-xsykpvsh.js → studio-try13h8c.js} +11 -11
- package/dist/chunks/{studio-erdatzvp.js → studio-vfq8qpjt.js} +34 -5
- package/dist/chunks/{studio-79y5sz51.js → studio-vw7mzp6z.js} +2 -2
- package/dist/chunks/{studio-9xxz1q69.js → studio-wqz6dggm.js} +5 -5
- package/dist/chunks/studio-zdn2g6dt.js +148 -0
- package/dist/chunks/{workspace-wrew8zk4.js → workspace-t55jdjbx.js} +3 -3
- package/dist/iframe-entry.js +97 -10
- package/dist/studio.js +127 -244
- package/package.json +6 -6
- 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 +3 -2
- package/src/i18n/i18n-commands.ts +10 -0
- 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/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-strip.ts +3 -3
- package/src/services/ai-command-tools.ts +78 -8
- 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 +14 -2
- package/src/surfaces/commandbar.json +13 -12
- package/src/surfaces/commandbar.ts +6 -5
- package/src/surfaces/statusbar.ts +2 -1
- package/src/tabs/project-config.ts +45 -11
- package/src/ui/layers.ts +4 -1
- package/src/workspace/workspace.ts +60 -0
- package/dist/chunks/entry-commands-d6g50j7s.js +0 -42
- package/dist/chunks/layers-panel-ar2gh90g.js +0 -40
- package/dist/chunks/menu-rf7n72mf.js +0 -14
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -62,14 +62,14 @@
|
|
|
62
62
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.2.0",
|
|
63
63
|
"@jxsuite/ai": "^0.37.1",
|
|
64
64
|
"@jxsuite/collab": "^0.9.0",
|
|
65
|
-
"@jxsuite/create": "^1.3.
|
|
66
|
-
"@jxsuite/formulas": "^0.0.
|
|
65
|
+
"@jxsuite/create": "^1.3.11",
|
|
66
|
+
"@jxsuite/formulas": "^0.0.18",
|
|
67
67
|
"@jxsuite/markup": "^0.4.10",
|
|
68
68
|
"@jxsuite/protocol": "^2.3.0",
|
|
69
|
-
"@jxsuite/runtime": "^4.0.
|
|
69
|
+
"@jxsuite/runtime": "^4.0.1",
|
|
70
70
|
"@jxsuite/schema": "^2.2.0",
|
|
71
|
-
"@jxsuite/site": "^2.0.
|
|
72
|
-
"@jxsuite/ui": "^0.
|
|
71
|
+
"@jxsuite/site": "^2.0.1",
|
|
72
|
+
"@jxsuite/ui": "^0.2.0",
|
|
73
73
|
"@vue/reactivity": "3.5.42",
|
|
74
74
|
"ajv": "^8.20.0",
|
|
75
75
|
"ajv-formats": "^3.0.1",
|
|
@@ -1024,8 +1024,35 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
|
|
|
1024
1024
|
group: "3_canvas",
|
|
1025
1025
|
requires: "an open document",
|
|
1026
1026
|
when: documentOpen,
|
|
1027
|
-
/*
|
|
1028
|
-
|
|
1027
|
+
/*
|
|
1028
|
+
* Projected, and it was deleted once under §12.4's first rule (chrome). The rule is about a
|
|
1029
|
+
* verb whose WHOLE effect is what the person is looking at, and this one's is not: the tree
|
|
1030
|
+
* tools are gated on `editor.kind`, which is derived from the mode (`editorKindForMode`), so
|
|
1031
|
+
* a document the person left in Code — or the model itself moved — refuses every tree write
|
|
1032
|
+
* with "requires an open document whose element tree the canvas is editing", and without this
|
|
1033
|
+
* verb the model had no way back to a state it can read. `run` acts on the active tab and
|
|
1034
|
+
* the model reads the result: `after.editor.kind` is the same fact the gate reads.
|
|
1035
|
+
*/
|
|
1036
|
+
aiTool: {
|
|
1037
|
+
description:
|
|
1038
|
+
"Switch the active document's editor mode. edit and design put its element tree on " +
|
|
1039
|
+
"the canvas, where the document tools work; preview renders it as a visitor sees it; " +
|
|
1040
|
+
"source shows the JSON; stylebook, grid and git-diff open other editors. Use it to " +
|
|
1041
|
+
"return a document to edit or design when the tree tools are refused.",
|
|
1042
|
+
name: "set_canvas_mode",
|
|
1043
|
+
/* `after.editor.kind` rather than the tab's own field: it is the gate's read (`treeEditable`
|
|
1044
|
+
in `document-assistant.ts` is `editor.kind === "canvas"`), so the sentence and the gate
|
|
1045
|
+
cannot disagree. The mode named is the one requested — `run` resolved, so it is the mode
|
|
1046
|
+
the tab is in. */
|
|
1047
|
+
report: ({ after, args }) => {
|
|
1048
|
+
const mode = enumArg("canvas.setMode", args, "mode", CANVAS_MODES);
|
|
1049
|
+
const reach =
|
|
1050
|
+
after.editor.kind === "canvas"
|
|
1051
|
+
? "the document tools address its element tree"
|
|
1052
|
+
: "the tree-editing tools are unavailable until it returns to edit or design";
|
|
1053
|
+
return `The active document is in ${mode} mode; ${reach}.`;
|
|
1054
|
+
},
|
|
1055
|
+
},
|
|
1029
1056
|
run: (_commandCtx, args) => {
|
|
1030
1057
|
const mode = enumArg("canvas.setMode", args, "mode", CANVAS_MODES);
|
|
1031
1058
|
const tab = requireTab("canvas.setMode");
|
|
@@ -72,6 +72,23 @@ export function numberArg(commandId: string, args: CommandArgValues, key: string
|
|
|
72
72
|
return value;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* A required integer argument. `1.5` is a refusal, not a value to round: a caller naming an index
|
|
77
|
+
* or a count with a fraction in it has said something the app cannot mean.
|
|
78
|
+
*
|
|
79
|
+
* Its own reader rather than `numberArg` plus a check in `run`, because `coerceArgs` routes a bare
|
|
80
|
+
* `type: "integer"` here — without it the row read as a plain number and `1.5` passed the schema
|
|
81
|
+
* pass to fail, or not, in whatever `run` did with it. `typedArg` already held a type LIST to
|
|
82
|
+
* integrality; this is the same rule for the bare form.
|
|
83
|
+
*/
|
|
84
|
+
export function integerArg(commandId: string, args: CommandArgValues, key: string): number {
|
|
85
|
+
const value = args[key];
|
|
86
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
87
|
+
throw refuse(commandId, key, `expected an integer, got ${describe(value)}`);
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
75
92
|
/**
|
|
76
93
|
* A required number argument inside a closed interval.
|
|
77
94
|
*
|
|
@@ -330,6 +347,7 @@ export type CoercionRow =
|
|
|
330
347
|
| "boolean"
|
|
331
348
|
| "bounded-number"
|
|
332
349
|
| "number"
|
|
350
|
+
| "integer"
|
|
333
351
|
| "string"
|
|
334
352
|
| "path"
|
|
335
353
|
| "path-list"
|
|
@@ -373,8 +391,13 @@ export function describeShape(property: object): CoercionRow {
|
|
|
373
391
|
case "number":
|
|
374
392
|
case "integer": {
|
|
375
393
|
// Both bounds, as `numberProperty` writes them. A half-open interval is not a shape any
|
|
376
|
-
// Record declares, so it reads as a plain number and `run` owns the bound.
|
|
377
|
-
|
|
394
|
+
// Record declares, so it reads as a plain number and `run` owns the bound. A bounded integer
|
|
395
|
+
// Is the `bounded-number` row too, which holds it to integrality before the interval; only
|
|
396
|
+
// The BARE integer goes to `integerArg`, whose whole rule is integrality.
|
|
397
|
+
if (p.minimum !== undefined && p.maximum !== undefined) {
|
|
398
|
+
return "bounded-number";
|
|
399
|
+
}
|
|
400
|
+
return p.type === "integer" ? "integer" : "number";
|
|
378
401
|
}
|
|
379
402
|
case "string": {
|
|
380
403
|
return "string";
|
|
@@ -486,6 +509,9 @@ function shapeOf(property: PropertySchema): string {
|
|
|
486
509
|
case "number": {
|
|
487
510
|
return "a finite number";
|
|
488
511
|
}
|
|
512
|
+
case "integer": {
|
|
513
|
+
return "an integer";
|
|
514
|
+
}
|
|
489
515
|
case "string": {
|
|
490
516
|
return "a non-empty string";
|
|
491
517
|
}
|
|
@@ -577,11 +603,20 @@ function coerceProperty(
|
|
|
577
603
|
return booleanArg(commandId, args, key);
|
|
578
604
|
}
|
|
579
605
|
case "bounded-number": {
|
|
606
|
+
// Integrality first, so `{ type: "integer", minimum: 1, maximum: 9 }` refuses `1.5` by name
|
|
607
|
+
// Rather than admitting it as a number inside the interval — the static check's `type` row
|
|
608
|
+
// Already refused it, and the two passes must agree.
|
|
609
|
+
if (property.type === "integer") {
|
|
610
|
+
integerArg(commandId, args, key);
|
|
611
|
+
}
|
|
580
612
|
return boundedNumberArg(commandId, args, key, property.minimum!, property.maximum!);
|
|
581
613
|
}
|
|
582
614
|
case "number": {
|
|
583
615
|
return numberArg(commandId, args, key);
|
|
584
616
|
}
|
|
617
|
+
case "integer": {
|
|
618
|
+
return integerArg(commandId, args, key);
|
|
619
|
+
}
|
|
585
620
|
case "string": {
|
|
586
621
|
return stringArg(commandId, args, key);
|
|
587
622
|
}
|
package/src/commands/registry.ts
CHANGED
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
*
|
|
28
28
|
* The registry takes its context by injection (`getContext`), so nothing here imports a state
|
|
29
29
|
* module. That is what lets the CI checks import the command set in a bare Bun process and lets
|
|
30
|
-
* every test build the exact state it wants to assert against.
|
|
30
|
+
* every test build the exact state it wants to assert against. The one store it reaches is the
|
|
31
|
+
* notification store, through `run-reported.ts`, and only from `handleKeyEvent`: a chord's refusal
|
|
32
|
+
* has no caller to hand it to, so the registry files it where every other surface's refusal goes.
|
|
31
33
|
*/
|
|
32
34
|
|
|
33
35
|
import { createKeymap } from "./keymap";
|
|
@@ -35,6 +37,7 @@ import type { KeyChordEvent, Keymap, KeymapMatch } from "./keymap";
|
|
|
35
37
|
import { checkRecordPlacements } from "./levels";
|
|
36
38
|
import type { Category, KeyScope, Level, Placement } from "./levels";
|
|
37
39
|
import { coerceArgs } from "./command-args";
|
|
40
|
+
import { runReported } from "./run-reported";
|
|
38
41
|
import type { CommandContext } from "./context";
|
|
39
42
|
|
|
40
43
|
/** Arguments as they arrive from a palette prompt, an automation manifest step or an AI tool call. */
|
|
@@ -435,13 +438,20 @@ export function createCommandRegistry(options: CommandRegistryOptions): CommandR
|
|
|
435
438
|
if (!registry.isEnabled(hit.commandId)) {
|
|
436
439
|
return hit.commandId;
|
|
437
440
|
}
|
|
438
|
-
/* An ARGUMENT refusal is the one
|
|
439
|
-
record with `{}`, and `run` coerces that against the
|
|
440
|
-
schema requires a key cannot be a chord — every shipped
|
|
441
|
-
`required: []` by the sweep in `tests/command-args.test.ts`, and a
|
|
442
|
-
override that binds one anyway hears about it as a
|
|
443
|
-
key, rather than as a chord that silently does nothing.
|
|
444
|
-
|
|
441
|
+
/* An ARGUMENT refusal is the one refusal that can still happen here, and it is REPORTED
|
|
442
|
+
rather than thrown. A chord runs its record with `{}`, and `run` coerces that against the
|
|
443
|
+
schema first, so a record whose schema requires a key cannot be a chord — every shipped
|
|
444
|
+
keybinding is held to `required: []` by the sweep in `tests/command-args.test.ts`, and a
|
|
445
|
+
user's own keymap override that binds one anyway hears about it as a Problems row naming
|
|
446
|
+
the command and the key, rather than as a chord that silently does nothing. It used to
|
|
447
|
+
leave as a `RangeError` out of the keydown listener, which named the same things to the
|
|
448
|
+
console and to nobody; `run-reported.ts` is what catches that shape, and the rejection a
|
|
449
|
+
`run` body may produce later, for every surface alike. A gate that closes BETWEEN the
|
|
450
|
+
`isEnabled` above and `run`'s own re-read is reported the same way, and that is a choice:
|
|
451
|
+
the silent claim above is for a chord the person can see is disabled, while a gate that
|
|
452
|
+
moved under the chord is state the person cannot see, and the `requires` sentence in
|
|
453
|
+
Problems is the one account of why nothing happened. */
|
|
454
|
+
void runReported(registry, hit.commandId, undefined, "Keyboard");
|
|
445
455
|
return hit.commandId;
|
|
446
456
|
},
|
|
447
457
|
context: options.getContext,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run-reported.ts — run a command from a surface, and let a refusal land in Problems.
|
|
3
|
+
*
|
|
4
|
+
* `registry.run` refuses in two shapes, and a caller that catches one and not the other still lets
|
|
5
|
+
* a refusal out. It throws SYNCHRONOUSLY — `CommandUnavailableError` when the gate refuses, a
|
|
6
|
+
* `RangeError` when `coerceArgs` refuses the received record BEFORE `run` is entered, and whatever
|
|
7
|
+
* a synchronous `run` body throws — and it REJECTS when an async `run` body does. A bare `void
|
|
8
|
+
* registry.run(id, args)` lets the first shape out of the click, keydown or notice action it came
|
|
9
|
+
* from, and a `.catch` on the promise alone catches nothing when `run` has already thrown; a
|
|
10
|
+
* `Promise.resolve(registry.run(...))` around it is the same mistake with more characters, because
|
|
11
|
+
* the argument is evaluated first. Three surfaces had grown the same six-line try/catch in reply
|
|
12
|
+
* (the palette's `runCommand`, the Settings menu's `runRow` and the Languages panel's `runByName`)
|
|
13
|
+
* while ten others still ran bare, which is the drift issue 333 names: a refusal reached Problems
|
|
14
|
+
* or the console depending on which surface the person happened to be on.
|
|
15
|
+
*
|
|
16
|
+
* This is the one spelling. It catches both shapes, files the sentence in Problems — the tier
|
|
17
|
+
* `notify.error` chooses, and the right one: a refusal names something the person has to fix or a
|
|
18
|
+
* schema that drifted from its caller, and neither is a toast that may be taken away — and returns
|
|
19
|
+
* a promise that never rejects, so a caller may `void` it or `await` it without a `.catch`.
|
|
20
|
+
*
|
|
21
|
+
* A refusal is the whole message; a CRASH is not. `CommandUnavailableError` and `RangeError` are
|
|
22
|
+
* the two shapes the registry and `coerceArgs` refuse with, and each is a sentence written to be
|
|
23
|
+
* read, so the Problems row is complete. Anything else out of a `run` body — a `TypeError` on a
|
|
24
|
+
* node that was not there — is a bug, and its stack is the part that matters; the row still names
|
|
25
|
+
* it, and the error object also goes to the console, where the palette used to send everything and
|
|
26
|
+
* where a trace can be opened. Without that the helper would have made every surface quieter than
|
|
27
|
+
* the keydown listener it replaced, which reached `window.onerror`.
|
|
28
|
+
*
|
|
29
|
+
* Two spellings, one rule. {@link runReported} takes the registry as a PARAMETER: `editor/
|
|
30
|
+
* shortcuts.ts`, the block action bar and the registry's own `handleKeyEvent` are handed theirs,
|
|
31
|
+
* and a helper that resolved the active registry for them would silently do nothing wherever a
|
|
32
|
+
* test, or a second window kind, ran a registry that is not the published one.
|
|
33
|
+
* {@link runActiveReported} is the same call for a surface that was spelled `void
|
|
34
|
+
* activeRegistry()?.run(id, args)` — a footer row, a palette hit, a tab-strip chip — and it keeps
|
|
35
|
+
* that spelling's one property: before the bootstrap has published a registry there is nothing to
|
|
36
|
+
* run and nothing to report, exactly as the `?.` was silent.
|
|
37
|
+
*
|
|
38
|
+
* The sweep in `tests/run-reported.test.ts` walks `packages/studio/src` for any other `.run(` on a
|
|
39
|
+
* registry. Two callers own their refusal and are named there as such — `services/automation.ts`
|
|
40
|
+
* and `services/ai-command-tools.ts`, where the script or the model is the one who reads it — and
|
|
41
|
+
* the `?.run(` spelling issue 333 did not list survives in a RATCHET of files the sweep names one
|
|
42
|
+
* by one, each still holding a bare call; the list can only shrink, so a new surface cannot spell
|
|
43
|
+
* it bare and an existing one converted must drop its entry.
|
|
44
|
+
*
|
|
45
|
+
* `runReported` does not pre-check `isEnabled`. A surface that renders a disabled control for a
|
|
46
|
+
* refused record has already shown the `requires` sentence, and a click that arrives on one anyway
|
|
47
|
+
* (state moved between the render and the click) is a refusal the person should read, not a
|
|
48
|
+
* swallowed no-op. The callers that DO pre-check keep doing so for their own reasons — a chord on a
|
|
49
|
+
* disabled command is claimed silently by `handleKeyEvent`, by design — and route what remains
|
|
50
|
+
* through here.
|
|
51
|
+
*
|
|
52
|
+
* @docs studio/interface/problems-and-progress
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
import { errorMessage } from "@jxsuite/schema/parse";
|
|
56
|
+
import { notify } from "../services/notify";
|
|
57
|
+
import { activeRegistry } from "./active-registry";
|
|
58
|
+
import type { CommandArgs, CommandRegistry } from "./registry";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether an error is one of the two REFUSAL shapes, whose message is written to be read whole.
|
|
62
|
+
*
|
|
63
|
+
* By name rather than `instanceof CommandUnavailableError`: `registry.ts` imports this module for
|
|
64
|
+
* `handleKeyEvent`, and a value import back would close a cycle for one class whose constructor
|
|
65
|
+
* sets exactly this name. `RangeError` is the shape `coerceArgs` and a `run` body refuse with.
|
|
66
|
+
*/
|
|
67
|
+
function isRefusal(error: unknown): boolean {
|
|
68
|
+
return (
|
|
69
|
+
error instanceof RangeError ||
|
|
70
|
+
(error instanceof Error && error.name === "CommandUnavailableError")
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run `id` with `args` on `registry`, and file whatever refuses it in Problems under `source`.
|
|
76
|
+
*
|
|
77
|
+
* `source` is the SURFACE the run came from ("Palette", "Keyboard", "Languages"), the column the
|
|
78
|
+
* Problems panel groups by; it defaults to the command id, which is what the Settings menu filed
|
|
79
|
+
* under before this helper existed and is still a true answer to "who reported it". The Problems
|
|
80
|
+
* row is keyed by the command id, so a chord held down on a refusing record is one problem rather
|
|
81
|
+
* than sixty — the file-watcher rule `services/notify.ts` writes down for `key`.
|
|
82
|
+
*
|
|
83
|
+
* The returned promise settles when the command does, and never rejects: an `await` on it is a
|
|
84
|
+
* wait, not a second place the refusal could escape from.
|
|
85
|
+
*/
|
|
86
|
+
export function runReported(
|
|
87
|
+
registry: CommandRegistry,
|
|
88
|
+
id: string,
|
|
89
|
+
args?: CommandArgs,
|
|
90
|
+
source: string = id,
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
const report = (error: unknown) => {
|
|
93
|
+
if (!isRefusal(error)) {
|
|
94
|
+
// A crash, not a refusal: the row below carries the sentence, the console keeps the stack.
|
|
95
|
+
console.error(`${source}: command "${id}" failed`, error);
|
|
96
|
+
}
|
|
97
|
+
notify.error(errorMessage(error), { key: `command.run:${id}`, source });
|
|
98
|
+
};
|
|
99
|
+
let result: void | Promise<void>;
|
|
100
|
+
try {
|
|
101
|
+
result = registry.run(id, args);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
report(error);
|
|
104
|
+
return Promise.resolve();
|
|
105
|
+
}
|
|
106
|
+
return Promise.resolve(result).catch(report);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* {@link runReported} on this window's registry, or nothing before the bootstrap has published one.
|
|
111
|
+
*
|
|
112
|
+
* The `?.` this replaces was the surfaces' answer to `active-registry.ts` returning `null` for the
|
|
113
|
+
* frame before the bootstrap composes the registry; a click that lands in that frame has no
|
|
114
|
+
* registry to refuse it, so there is no refusal to file. The resolved promise keeps the caller's
|
|
115
|
+
* `void` honest either way.
|
|
116
|
+
*/
|
|
117
|
+
export function runActiveReported(id: string, args?: CommandArgs, source?: string): Promise<void> {
|
|
118
|
+
const registry = activeRegistry();
|
|
119
|
+
return registry ? runReported(registry, id, args, source) : Promise.resolve();
|
|
120
|
+
}
|
package/src/editor/shortcuts.ts
CHANGED
|
@@ -77,7 +77,7 @@ import { clipboardCommands, pasteNode } from "./context-menu";
|
|
|
77
77
|
import { hasElementSelection, hasSelection, inCanvas, keyScopeStack } from "../commands/context";
|
|
78
78
|
import { defaultCommands } from "../commands/defaults";
|
|
79
79
|
import { setActiveRegistry } from "../commands/active-registry";
|
|
80
|
-
import {
|
|
80
|
+
import { runReported } from "../commands/run-reported";
|
|
81
81
|
import type { CommandContext } from "../commands/context";
|
|
82
82
|
import type { DockId as CommandDockId } from "../commands/defaults";
|
|
83
83
|
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
@@ -388,11 +388,12 @@ function toggleShellDock(registry: CommandRegistry, dock: CommandDockId): void {
|
|
|
388
388
|
*
|
|
389
389
|
* `view.setAssistant` and `view.setRightTab` are `shell.ts`'s, composed into the app's registry by
|
|
390
390
|
* the bootstrap — a reduced registry (a test, a future second window kind) may not carry them, and
|
|
391
|
-
* a missing id must be inert rather than an exception thrown out of a keydown handler.
|
|
391
|
+
* a missing id must be inert rather than an exception thrown out of a keydown handler. A present
|
|
392
|
+
* one that refuses its arguments is not inert: `commands/run-reported.ts` files it in Problems.
|
|
392
393
|
*/
|
|
393
394
|
function runIfPresent(registry: CommandRegistry, id: string, args: Record<string, unknown>): void {
|
|
394
395
|
if (registry.get(id) && registry.isEnabled(id)) {
|
|
395
|
-
void registry
|
|
396
|
+
void runReported(registry, id, args, "Keyboard");
|
|
396
397
|
}
|
|
397
398
|
}
|
|
398
399
|
|
|
@@ -901,34 +902,21 @@ export function registerStudioCommands(
|
|
|
901
902
|
*
|
|
902
903
|
* `preventDefault()` iff a command claimed the chord. A command that is VISIBLE but disabled — ⌘Z
|
|
903
904
|
* with nothing to undo, Delete on the document element — still counts as claiming it: the registry
|
|
904
|
-
*
|
|
905
|
-
* because the chord is spoken for and letting the browser act on it instead would
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
905
|
+
* consults `isEnabled` itself and returns the id without running, and swallowing the key is the
|
|
906
|
+
* honest outcome, because the chord is spoken for and letting the browser act on it instead would
|
|
907
|
+
* be a surprise. Nothing is caught here, because nothing leaves: `handleKeyEvent` runs the hit
|
|
908
|
+
* through `commands/run-reported.ts`, so a record that refuses its arguments, a gate that closed
|
|
909
|
+
* between the check and the run, or a `run` body that throws is a Problems row under "Keyboard"
|
|
910
|
+
* rather than an exception out of the keydown listener.
|
|
910
911
|
*/
|
|
911
912
|
function dispatchKey(registry: CommandRegistry, event: KeyboardEvent): string | undefined {
|
|
912
|
-
const commandId =
|
|
913
|
+
const commandId = registry.handleKeyEvent(event, keyScopeStack(registry.context()));
|
|
913
914
|
if (commandId) {
|
|
914
915
|
event.preventDefault();
|
|
915
916
|
}
|
|
916
917
|
return commandId;
|
|
917
918
|
}
|
|
918
919
|
|
|
919
|
-
/** The id of the command that claimed the chord, whether it ran or refused. */
|
|
920
|
-
function claimChord(registry: CommandRegistry, event: KeyboardEvent): string | undefined {
|
|
921
|
-
const stack = keyScopeStack(registry.context());
|
|
922
|
-
try {
|
|
923
|
-
return registry.handleKeyEvent(event, stack);
|
|
924
|
-
} catch (error) {
|
|
925
|
-
if (error instanceof CommandUnavailableError) {
|
|
926
|
-
return error.commandId;
|
|
927
|
-
}
|
|
928
|
-
throw error;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
|
|
932
920
|
// ─── Pointer and wheel gestures ───────────────────────────────────────────────
|
|
933
921
|
|
|
934
922
|
/**
|
package/src/files/components.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { getPlatform } from "../platform";
|
|
4
4
|
import { projectState } from "../store";
|
|
5
5
|
import type { ComponentMeta } from "../types";
|
|
6
|
+
import { componentMetaFrom } from "@jxsuite/schema/component-meta";
|
|
6
7
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
7
8
|
|
|
8
9
|
/** A discovered component: project components carry a file path; package components may not. */
|
|
@@ -26,6 +27,44 @@ export async function loadComponentRegistry() {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Bring the registry up to date with a document that was just saved.
|
|
32
|
+
*
|
|
33
|
+
* The registry is the snapshot the platform's scan took when the project opened, and a saved
|
|
34
|
+
* component definition used to leave it behind: give a prop `format: "image"` in the definition and
|
|
35
|
+
* every instance's Component Settings kept drawing the text box the snapshot knew, until the
|
|
36
|
+
* project was reopened. The saved document is the same input the scan reads, through the same
|
|
37
|
+
* extractor (`componentMetaFrom`, shared by all three backends), so the entry is rebuilt from it
|
|
38
|
+
* here rather than by a rescan of every JSON file in the project. A definition that stopped being a
|
|
39
|
+
* component (its `tagName` lost its hyphen) leaves the registry by the same rule the scan applies.
|
|
40
|
+
*
|
|
41
|
+
* @param {string | null} path - The saved document's project-relative path; `null` for an unsaved
|
|
42
|
+
* draft, which no scan would have seen either.
|
|
43
|
+
* @param {unknown} doc - The saved document.
|
|
44
|
+
* @returns {boolean} Whether the registry changed, so a caller knows to repaint what reads it.
|
|
45
|
+
*/
|
|
46
|
+
export function noteComponentSaved(path: string | null, doc: unknown): boolean {
|
|
47
|
+
if (!path) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const index = componentRegistry.findIndex((c) => c.path === path);
|
|
51
|
+
const meta = componentMetaFrom(doc, path);
|
|
52
|
+
if (!meta) {
|
|
53
|
+
if (index === -1) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
componentRegistry.splice(index, 1);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
const entry = meta as ComponentEntry;
|
|
60
|
+
if (index === -1) {
|
|
61
|
+
componentRegistry.push(entry);
|
|
62
|
+
} else {
|
|
63
|
+
componentRegistry[index] = entry;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
29
68
|
/**
|
|
30
69
|
* Build a fresh instance definition for a component: $props from prop defaults, plus the
|
|
31
70
|
* component's slot fallback children cloned in as starting content so editors can edit from the
|
package/src/files/files.ts
CHANGED
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
import { openCsvGridTab, openPagesGrid } from "../grid/grid-open";
|
|
74
74
|
import { isViewableMedia, openMediaTab } from "../media/media-open";
|
|
75
75
|
import { activeRegistry } from "../commands/active-registry";
|
|
76
|
+
import { runActiveReported, runReported } from "../commands/run-reported";
|
|
76
77
|
import { collectionOfPath } from "../content/entry-model";
|
|
77
78
|
import { confirmFileDelete, parseSourceForPath, renamePromptMessage } from "./file-ops";
|
|
78
79
|
import { parseJsonDocument } from "./json-layout";
|
|
@@ -1582,7 +1583,7 @@ function placedFileRows(entry: { path: string; type: string }): MenuRowProjectio
|
|
|
1582
1583
|
rows.push(
|
|
1583
1584
|
reason === undefined
|
|
1584
1585
|
? treeMenuRow(command.id, command.title, () => {
|
|
1585
|
-
void registry
|
|
1586
|
+
void runReported(registry, command.id, args, "Files");
|
|
1586
1587
|
})
|
|
1587
1588
|
: {
|
|
1588
1589
|
destructive: false,
|
|
@@ -2498,7 +2499,7 @@ const FILE_ACTIONS: FilesPanelActions = {
|
|
|
2498
2499
|
/* The DECLARED command, run through the registry — never a second opener of this surface's own
|
|
2499
2500
|
(§12.5). It is the one the ⌘O chord, the palette and the status bar's PROJECT field already
|
|
2500
2501
|
run, and it is the only one that knows about the picker modal and the "new window" target. */
|
|
2501
|
-
void
|
|
2502
|
+
void runActiveReported("project.open", undefined, "Files");
|
|
2502
2503
|
},
|
|
2503
2504
|
refresh: () => {
|
|
2504
2505
|
void refreshFileTree();
|
|
@@ -195,6 +195,14 @@ export function i18nCommands(): AnyCommand[] {
|
|
|
195
195
|
menus: ["context/tab", "palette"],
|
|
196
196
|
group: "2_navigate",
|
|
197
197
|
requires: "a document open in a project that declares more than one language",
|
|
198
|
+
/* `ctx.document.open` stays in the gate although `path` no longer needs a document open
|
|
199
|
+
(issue 333). `registry.run` evaluates `when` BEFORE `run` and refuses with
|
|
200
|
+
`CommandUnavailableError`, so relaxing it is the only way `{ locale, path }` could run with
|
|
201
|
+
no document open — and `when` reads the context, not the arguments, so the same relaxation
|
|
202
|
+
would light the palette row too, where the record runs with no `path` and would then throw
|
|
203
|
+
`addressedDocument`'s "needs a document open" out of a click instead of standing disabled
|
|
204
|
+
with its `requires` sentence. The palette's availability is the person's contract; an
|
|
205
|
+
argument-only gate needs a shape the registry does not have. Same on `createTranslation`. */
|
|
198
206
|
when: (ctx) => ctx.project.isMultilingual && ctx.document.open,
|
|
199
207
|
/* No `aiTool`, by §12.4's first deletion rule: this opens a surface for a person, and the
|
|
200
208
|
model has `open_document` for the file it names. */
|
|
@@ -247,6 +255,8 @@ export function i18nCommands(): AnyCommand[] {
|
|
|
247
255
|
menus: ["palette"],
|
|
248
256
|
group: "2_navigate",
|
|
249
257
|
requires: "a document open in a project that declares more than one language",
|
|
258
|
+
// `document.open` stays for the reason `i18n.openTranslation` gives: the gate runs before
|
|
259
|
+
// `run`, and it cannot see `path`.
|
|
250
260
|
when: (ctx) => ctx.project.isMultilingual && ctx.document.open,
|
|
251
261
|
/* The document history cannot hold this: the change is a file that did not exist, and
|
|
252
262
|
`undo: "project"` would claim `project.json` moved, which it did not. */
|
|
@@ -19,7 +19,11 @@ import type { GitStatusResult } from "../types";
|
|
|
19
19
|
|
|
20
20
|
const PKG_PATHS = ["package.json", "bun.lock", "bun.lockb"];
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Untracked markers across backends. Every backend says `U` now — the desktop used to pass
|
|
24
|
+
* porcelain v1's raw `??` through, and this set is what kept package sync working while it did.
|
|
25
|
+
* `??` stays accepted so a Studio bundle ahead of its desktop host keeps working.
|
|
26
|
+
*/
|
|
23
27
|
const UNTRACKED_STATUSES = new Set(["??", "U"]);
|
|
24
28
|
|
|
25
29
|
/** Above this many dirty files, skip the per-file upstream comparison and leave the error as-is. */
|
package/src/panels/ai-panel.ts
CHANGED
|
@@ -44,6 +44,7 @@ import { onCredentialsChanged } from "../settings/preferences-accounts";
|
|
|
44
44
|
import { setDockCollapsed } from "../shell";
|
|
45
45
|
import { hasSelection } from "../commands/context";
|
|
46
46
|
import { activeRegistry } from "../commands/active-registry";
|
|
47
|
+
import { runReported } from "../commands/run-reported";
|
|
47
48
|
import { clearMarkdownCache } from "./ai-chat/chat-markdown";
|
|
48
49
|
import {
|
|
49
50
|
formatErrorAdvice,
|
|
@@ -158,12 +159,14 @@ export function renderAiPanel(): void {
|
|
|
158
159
|
* Re-asked at click time, not trusted from the projection: state moves between the two, and
|
|
159
160
|
* `registry.run` THROWS on a refusal. Same bargain `registry.handleKeyEvent` strikes for a chord
|
|
160
161
|
* bound to a disabled command — swallow it here rather than make every surface wrap a dispatch in
|
|
161
|
-
* try/catch.
|
|
162
|
+
* try/catch. What the gate lets through still runs through `commands/run-reported.ts`: a `run` body
|
|
163
|
+
* that rejects, or a schema the record has drifted from, is a sentence in Problems rather than an
|
|
164
|
+
* exception out of the click.
|
|
162
165
|
*/
|
|
163
166
|
function runCommand(id: string): void {
|
|
164
167
|
const registry = activeRegistry();
|
|
165
168
|
if (registry?.isEnabled(id)) {
|
|
166
|
-
void registry
|
|
169
|
+
void runReported(registry, id, undefined, "Assistant");
|
|
167
170
|
}
|
|
168
171
|
}
|
|
169
172
|
|
|
@@ -70,6 +70,7 @@ import { showSlashMenu } from "../editor/slash-menu";
|
|
|
70
70
|
import { getConvertTargets } from "../editor/convert-targets";
|
|
71
71
|
import { rectOf } from "../utils/geometry";
|
|
72
72
|
import { createCommandRegistry } from "../commands/registry";
|
|
73
|
+
import { runReported } from "../commands/run-reported";
|
|
73
74
|
import { editorKindForMode, makeContext } from "../commands/context";
|
|
74
75
|
import { defaultCommands, noopCommandDeps } from "../commands/defaults";
|
|
75
76
|
import { emptyBlockBarView, mountBlockActionBar } from "../surfaces/block-action-bar";
|
|
@@ -845,11 +846,15 @@ export function commandTooltip(registry: CommandRegistry, command: AnyCommand):
|
|
|
845
846
|
return chord ? `${command.title} (${chord})` : command.title;
|
|
846
847
|
}
|
|
847
848
|
|
|
848
|
-
/**
|
|
849
|
+
/**
|
|
850
|
+
* Run a command, swallowing the refusal a disabled control should never have produced — and
|
|
851
|
+
* reporting, through `commands/run-reported.ts`, the one an enabled control can still meet: a `run`
|
|
852
|
+
* body that throws or rejects.
|
|
853
|
+
*/
|
|
849
854
|
export function runCommand(registry: CommandRegistry, id: string, target?: JxPath | null): void {
|
|
850
855
|
const invoke = () => {
|
|
851
856
|
if (registry.isEnabled(id)) {
|
|
852
|
-
void registry
|
|
857
|
+
void runReported(registry, id, undefined, "Editor");
|
|
853
858
|
}
|
|
854
859
|
};
|
|
855
860
|
if (target) {
|
package/src/panels/i18n-panel.ts
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
43
|
import { activeRegistry } from "../commands/active-registry";
|
|
44
|
+
import { runReported } from "../commands/run-reported";
|
|
44
45
|
import { errorMessage } from "@jxsuite/schema/parse";
|
|
45
46
|
import { getEffectiveLocales } from "../site-context";
|
|
46
47
|
import { getPlatform } from "../platform";
|
|
@@ -51,7 +52,6 @@ import {
|
|
|
51
52
|
translationPathFor,
|
|
52
53
|
} from "@jxsuite/schema/locale";
|
|
53
54
|
import { mountI18nSurface } from "../surfaces/panel-i18n";
|
|
54
|
-
import { notify } from "../services/notify";
|
|
55
55
|
import { nothing } from "lit-html";
|
|
56
56
|
import { projectState } from "../store";
|
|
57
57
|
import { registerPanel } from "./panel-registry";
|
|
@@ -386,26 +386,19 @@ function cellTitle(cell: ParityCell, locale: string, refusal: string | undefined
|
|
|
386
386
|
* already there, and a click that produced nothing and printed nothing would leave the author
|
|
387
387
|
* looking at a grid that disagrees with the disk.
|
|
388
388
|
*
|
|
389
|
-
* Both shapes,
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
389
|
+
* Both shapes, through `commands/run-reported.ts`: `registry.run` throws SYNCHRONOUSLY when the
|
|
390
|
+
* record refuses the arguments — it coerces `{ locale, path }` against the record's schema before
|
|
391
|
+
* `run` is entered — and a `Promise.resolve(...)` around a call that has already thrown catches
|
|
392
|
+
* nothing. That refusal was every cell click for as long as the records did not declare `path`; the
|
|
393
|
+
* report is what makes the next such drift a sentence in the Problems panel rather than an
|
|
394
|
+
* exception out of a click.
|
|
395
395
|
*/
|
|
396
396
|
function runByName(id: string, args: Record<string, unknown> = {}): void {
|
|
397
397
|
const registry = activeRegistry();
|
|
398
398
|
if (!registry?.get(id)) {
|
|
399
399
|
return;
|
|
400
400
|
}
|
|
401
|
-
|
|
402
|
-
notify("error", errorMessage(error), { source: "Languages" });
|
|
403
|
-
};
|
|
404
|
-
try {
|
|
405
|
-
void Promise.resolve(registry.run(id, args)).catch(report);
|
|
406
|
-
} catch (error) {
|
|
407
|
-
report(error);
|
|
408
|
-
}
|
|
401
|
+
void runReported(registry, id, args, "Languages");
|
|
409
402
|
}
|
|
410
403
|
|
|
411
404
|
/**
|
package/src/panels/jump-bar.ts
CHANGED
|
@@ -58,6 +58,7 @@ import { derivationOfPane, tabOfPane } from "../canvas/canvas-surface";
|
|
|
58
58
|
import { paneRegion } from "../ui/regions";
|
|
59
59
|
import { primarySelection } from "../tabs/selection";
|
|
60
60
|
import { activeRegistry } from "../commands/active-registry";
|
|
61
|
+
import { runReported } from "../commands/run-reported";
|
|
61
62
|
import { openMenu } from "../surfaces/menu";
|
|
62
63
|
import { mountJumpBarSurface } from "../surfaces/jump-bar";
|
|
63
64
|
import { PANE_SELECTOR } from "../surfaces/pane-grid";
|
|
@@ -427,7 +428,7 @@ function openChoices(segment: JumpSegment, anchor: HTMLElement | null): void {
|
|
|
427
428
|
dividerAbove: false,
|
|
428
429
|
id: String(i),
|
|
429
430
|
run: () => {
|
|
430
|
-
void registry
|
|
431
|
+
void runReported(registry, choice.command, choice.args, "Jump Bar");
|
|
431
432
|
},
|
|
432
433
|
title: choice.label,
|
|
433
434
|
}));
|
|
@@ -544,7 +545,7 @@ export function attachJumpBarHost(paneId: string, host: HTMLElement | null): voi
|
|
|
544
545
|
const segment = _bars.get(paneId)?.segments.get(key);
|
|
545
546
|
const registry = activeRegistry();
|
|
546
547
|
if (segment?.command && registry) {
|
|
547
|
-
void registry
|
|
548
|
+
void runReported(registry, segment.command, segment.args, "Jump Bar");
|
|
548
549
|
}
|
|
549
550
|
},
|
|
550
551
|
}),
|