@jxsuite/studio 2.4.3 → 4.0.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/README.md +5 -5
- package/dist/chunks/Overlay.dev-723bv0df.js +23 -0
- package/dist/chunks/cf-settings-45fgyx8y.js +21 -0
- package/dist/chunks/{components-z6cnkyy1.js → components-zmx10t62.js} +6 -5
- package/dist/chunks/content-source-t88vb2dr.js +49 -0
- package/dist/chunks/{convert-to-repeater-k10dvcc7.js → convert-to-repeater-dhw1nrz9.js} +27 -18
- package/dist/chunks/entry-commands-6z6bhxg0.js +46 -0
- package/dist/chunks/{extension-sections-0ada0xcw.js → extension-sections-3ydgjbdy.js} +32 -23
- package/dist/chunks/file-ops-rskmhxyq.js +48 -0
- package/dist/chunks/files-0cratsr0.js +92 -0
- package/dist/chunks/{format-host-wewba4nq.js → format-host-tgcmfs4p.js} +3 -3
- package/dist/chunks/fs-events-hrrg328g.js +21 -0
- package/dist/chunks/{git-panel-2dh1aw50.js → git-panel-0yywyk76.js} +21 -15
- package/dist/chunks/{jx-validate-jj9rnt4n.js → jx-validate-8q2g05x1.js} +6 -4
- package/dist/chunks/layers-panel-z3tqe5cd.js +67 -0
- package/dist/chunks/layers-q75tj2mf.js +45 -0
- package/dist/chunks/lit-html-j055mkh8.js +22 -0
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +25 -0
- package/dist/chunks/{monaco-setup-a5wkmbgh.js → monaco-setup-rws7k4br.js} +4 -4
- package/dist/chunks/notify-yjm79n4s.js +30 -0
- package/dist/chunks/parse-24d0gay3.js +19 -0
- package/dist/chunks/platform-ba1j1gbh.js +16 -0
- package/dist/chunks/publish-panel-54jvhskq.js +28 -0
- package/dist/chunks/quick-search-cgfmjwq5.js +65 -0
- package/dist/chunks/references-3qx8fkyp.js +29 -0
- package/dist/chunks/right-panel-cqp8bc4v.js +46 -0
- package/dist/chunks/sp-overlay.dev-yztmtrs1.js +18 -0
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +9 -0
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +15 -0
- package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +15 -0
- package/dist/chunks/{sp-tray.dev-64v4pxw8.js → sp-tray.dev-tkbscm06.js} +5 -5
- package/dist/chunks/{studio-f5yyvqxv.js → studio-1hbqnt19.js} +37 -31
- package/dist/chunks/studio-2zkz8evn.js +3163 -0
- package/dist/chunks/{studio-0b6fs05q.js → studio-4pqqxbrt.js} +23 -15
- package/dist/chunks/{studio-kvfx9z2c.js → studio-4rzyrtjz.js} +70 -31
- package/dist/chunks/{studio-ef8t53vc.js → studio-5q69gfag.js} +60 -56
- package/dist/chunks/{studio-v2c5pmjj.js → studio-5rywrkxz.js} +565 -133
- package/dist/chunks/{studio-6kv3apa9.js → studio-68n67094.js} +25 -9
- package/dist/chunks/{studio-0rqqhrdy.js → studio-7nte2hj4.js} +2 -2
- package/dist/chunks/{studio-hyn3vhwr.js → studio-8swrjbny.js} +31 -18
- package/dist/chunks/studio-92ab2jqe.js +282 -0
- package/dist/chunks/{studio-sas7mt3k.js → studio-9ab8gqsm.js} +2 -2
- package/dist/chunks/{studio-28xc8ws4.js → studio-9c4k4vhv.js} +14 -10
- package/dist/chunks/studio-9n4mrmnb.js +184 -0
- package/dist/chunks/{studio-tr20m8g8.js → studio-9r6j5ev1.js} +37 -2
- package/dist/chunks/{studio-aweyr4v1.js → studio-ahjfdy6e.js} +9 -7
- package/dist/chunks/{studio-s519zc99.js → studio-bgxt93ga.js} +3 -3
- package/dist/chunks/{studio-26sv44br.js → studio-cb12yz9w.js} +8543 -8784
- package/dist/chunks/studio-cft9r4r7.js +119 -0
- package/dist/chunks/{studio-rqemfhz6.js → studio-eenfkt4e.js} +6 -6
- package/dist/chunks/studio-eesa1myv.js +498 -0
- package/dist/chunks/{studio-ay8wcr2b.js → studio-eqq1w638.js} +7 -9
- package/dist/chunks/{studio-ke244pam.js → studio-ex8zyp98.js} +51 -25
- package/dist/chunks/studio-gv8vvshc.js +118 -0
- package/dist/chunks/studio-hbtp0eh1.js +1315 -0
- package/dist/chunks/{studio-cz2j1c0g.js → studio-htsf0w6m.js} +75 -123
- package/dist/chunks/{studio-pyrw8h0w.js → studio-kjjsjak4.js} +528 -212
- package/dist/chunks/{studio-5x5774ak.js → studio-kryvsw6j.js} +3 -3
- package/dist/chunks/studio-kyh9avvv.js +28 -0
- package/dist/chunks/{studio-jnakhg87.js → studio-kyk2v92a.js} +14 -14
- package/dist/chunks/{studio-twsafr1e.js → studio-m3hwn7y1.js} +3 -3
- package/dist/chunks/{studio-tzx3xmvx.js → studio-mmv72a27.js} +8 -8
- package/dist/chunks/{studio-vhk5fmfv.js → studio-n8b9wqwy.js} +5 -5
- package/dist/chunks/{studio-cjm7fdjf.js → studio-p4ngc046.js} +8 -104
- package/dist/chunks/{studio-sccge2wf.js → studio-p8j86wag.js} +2 -2
- package/dist/chunks/{studio-v1va0vhd.js → studio-pdfjntbk.js} +75 -139
- package/dist/chunks/studio-q43a5y9h.js +394 -0
- package/dist/chunks/studio-q7v10cg2.js +106 -0
- package/dist/chunks/{studio-2w11grav.js → studio-qq6jym04.js} +3 -3
- package/dist/chunks/studio-r1r7afxx.js +1066 -0
- package/dist/chunks/{studio-3fk1ac9z.js → studio-rdx8jcxy.js} +4 -4
- package/dist/chunks/{studio-4mqb9a0j.js → studio-rn9ta2mz.js} +4 -4
- package/dist/chunks/studio-sf7zjgv1.js +91 -0
- package/dist/chunks/studio-t7qb9db8.js +665 -0
- package/dist/chunks/studio-tmc5emrw.js +8 -0
- package/dist/chunks/{studio-wdt9y92a.js → studio-txwkm6p9.js} +2 -2
- package/dist/chunks/{studio-dgj7gq44.js → studio-v38hmvx1.js} +7 -7
- package/dist/chunks/{studio-7ejemhej.js → studio-vadzgkrz.js} +6 -6
- package/dist/chunks/studio-wcy8trfx.js +263 -0
- package/dist/chunks/{studio-hb7c46jy.js → studio-wx5609xd.js} +21 -15
- package/dist/chunks/studio-xrapxwwc.js +1539 -0
- package/dist/chunks/{studio-ws6ef6js.js → studio-yxrkyv8a.js} +41 -29
- package/dist/chunks/{studio-xm3dbxvn.js → studio-zczps70q.js} +2 -2
- package/dist/chunks/workspace-xpres40v.js +89 -0
- package/dist/iframe-entry.js +450 -48
- package/dist/studio.js +2455 -1761
- package/package.json +16 -15
- package/src/browse/library-layouts.ts +3 -3
- package/src/browse/library-pane.ts +59 -5
- package/src/browse/library-preview.ts +19 -3
- package/src/canvas/asset-refs.ts +119 -0
- package/src/canvas/asset-resolve.ts +256 -0
- package/src/canvas/canvas-live-render.ts +23 -15
- package/src/canvas/canvas-render.ts +23 -1
- package/src/canvas/iframe-entry.ts +1 -0
- package/src/canvas/iframe-host.ts +4 -0
- package/src/canvas/iframe-patch.ts +47 -21
- package/src/canvas/iframe-protocol.ts +9 -0
- package/src/canvas/iframe-render.ts +50 -16
- package/src/commands/app-commands.ts +4 -0
- package/src/commands/command-args.ts +29 -0
- package/src/commands/context.ts +15 -1
- package/src/commands/defaults.ts +29 -1
- package/src/commands/levels.ts +16 -0
- package/src/commands/live-context.ts +3 -0
- package/src/content/collection-match.ts +267 -0
- package/src/content/entry-commands.ts +38 -29
- package/src/content/entry-fields.ts +6 -0
- package/src/editor/file-drop-action.ts +2 -1
- package/src/editor/shortcuts.ts +5 -0
- package/src/files/file-ops.ts +10 -42
- package/src/files/files.ts +487 -61
- package/src/files/fs-events.ts +9 -0
- package/src/files/gitignore.ts +432 -0
- package/src/files/media-paths.ts +37 -14
- package/src/files/media-upload.ts +47 -6
- package/src/files/serialize-document.ts +60 -0
- package/src/format/convert-file.ts +511 -0
- package/src/format/format-choices.ts +199 -0
- package/src/grid/grid-idle.ts +45 -0
- package/src/grid/grid-view.ts +35 -0
- package/src/grid/schema-columns.ts +2 -1
- package/src/grid/sources/content-source.ts +24 -3
- package/src/media/media-open.ts +58 -0
- package/src/media/media-pane.ts +310 -0
- package/src/new-project/import-tab.ts +333 -160
- package/src/new-project/new-project-modal.ts +27 -34
- package/src/packages/ensure-deps.ts +14 -7
- package/src/packages/jxsuite-update.ts +11 -11
- package/src/page-params.ts +10 -46
- package/src/panels/activity-bar.ts +45 -16
- package/src/panels/ai-chat/attached-context.ts +6 -1
- package/src/panels/ai-chat/chat-view.ts +255 -13
- package/src/panels/ai-chat/composer.ts +21 -80
- package/src/panels/ai-panel.ts +116 -2
- package/src/panels/frontmatter-fields.ts +6 -2
- package/src/panels/navigator-panels.ts +7 -6
- package/src/panels/properties-panel.ts +8 -2
- package/src/panels/seo-modal.ts +5 -6
- package/src/panels/settings-menu.ts +799 -0
- package/src/panels/signals-panel.ts +2 -1
- package/src/panels/toolbar.ts +135 -15
- package/src/platforms/cloud.ts +34 -3
- package/src/platforms/devserver.ts +72 -34
- package/src/preview/preview-overlay.ts +226 -0
- package/src/services/ai-ask.ts +247 -0
- package/src/services/ai-import-tools.ts +494 -0
- package/src/services/ai-models.ts +101 -11
- package/src/services/ai-project-tools.ts +20 -26
- package/src/services/ai-settings.ts +69 -71
- package/src/services/ai-system-prompt.ts +88 -2
- package/src/services/ai-turn-signal.ts +70 -0
- package/src/services/cf-settings.ts +19 -37
- package/src/services/connection.ts +74 -0
- package/src/services/context-manager.ts +89 -0
- package/src/services/document-assistant.ts +49 -7
- package/src/services/idle.ts +6 -1
- package/src/services/import-client.ts +33 -6
- package/src/services/import-run.ts +158 -0
- package/src/services/import-seed.ts +120 -0
- package/src/services/jx-validate.ts +58 -6
- package/src/services/project-adoption.ts +71 -0
- package/src/services/references.ts +0 -0
- package/src/services/settings/definitions.ts +181 -0
- package/src/services/settings/kernel.ts +335 -0
- package/src/services/settings/write-queue.ts +116 -0
- package/src/services/tool-executor.ts +34 -2
- package/src/settings/contexts-section.ts +45 -3
- package/src/settings/dependencies-editor.ts +43 -23
- package/src/settings/general-settings.ts +30 -12
- package/src/settings/preferences-accounts.ts +6 -19
- package/src/settings/preferences-dialog.ts +18 -41
- package/src/settings/preferences-keymap.ts +8 -18
- package/src/settings/preferences-sections.ts +57 -0
- package/src/settings/schema-field-ui.ts +2 -2
- package/src/settings/settings-document.ts +65 -9
- package/src/shell.ts +6 -14
- package/src/studio.ts +65 -7
- package/src/tabs/transact.ts +42 -19
- package/src/types.ts +164 -8
- package/src/ui/ai-credentials-form.ts +52 -27
- package/src/ui/ai-model-picker.ts +162 -0
- package/src/ui/choice-field.ts +72 -0
- package/src/ui/layers.ts +83 -5
- package/src/ui/media-picker.ts +6 -11
- package/src/utils/studio-utils.ts +43 -31
- package/styles/forced-colors.css +7 -0
- package/styles/overlays.css +256 -0
- package/styles/panels.css +13 -1
- package/styles/shell.css +111 -0
- package/dist/chunks/Overlay.dev-d2ajy6sj.js +0 -23
- package/dist/chunks/cf-settings-g5fbf2zp.js +0 -17
- package/dist/chunks/content-source-k9724vcv.js +0 -39
- package/dist/chunks/files-yew27sn9.js +0 -69
- package/dist/chunks/layers-panel-qnsghwwd.js +0 -59
- package/dist/chunks/publish-panel-a0pdft5w.js +0 -21
- package/dist/chunks/quick-search-9avyc4pc.js +0 -54
- package/dist/chunks/right-panel-kcnhtc5j.js +0 -38
- package/dist/chunks/sp-overlay.dev-s18hymn9.js +0 -18
- package/dist/chunks/sp-popover.dev-dj9x7e7a.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +0 -15
- package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +0 -15
- package/dist/chunks/studio-a7pzveq1.js +0 -110
- package/dist/chunks/studio-c31yb4t6.js +0 -55
- package/dist/chunks/studio-ra7ar50f.js +0 -6682
- package/src/canvas/content-assets.ts +0 -263
- package/src/canvas/site-style-css.ts +0 -121
- package/src/services/settings-store.ts +0 -99
|
@@ -21,7 +21,7 @@ import coreDocumentSchema from "@jxsuite/schema/schema.json";
|
|
|
21
21
|
import coreProjectSchema from "@jxsuite/schema/project-schema.json";
|
|
22
22
|
|
|
23
23
|
type ValidateFn = ((doc: unknown) => boolean) & {
|
|
24
|
-
errors?: { instancePath?: string; message?: string }[] | null;
|
|
24
|
+
errors?: { instancePath?: string; message?: string; params?: Record<string, unknown> }[] | null;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
/** Which of the two entry documents a validator covers. */
|
|
@@ -78,6 +78,16 @@ function getValidator(kind: SchemaKind): Promise<ValidateFn | null> {
|
|
|
78
78
|
return compiling[kind];
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/** A payload half that is usable as a JSON Schema — a non-empty, non-array object. */
|
|
82
|
+
function asSchema(value: unknown): object | undefined {
|
|
83
|
+
return typeof value === "object" &&
|
|
84
|
+
value !== null &&
|
|
85
|
+
!Array.isArray(value) &&
|
|
86
|
+
Object.keys(value).length > 0
|
|
87
|
+
? (value as object)
|
|
88
|
+
: undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
81
91
|
/**
|
|
82
92
|
* Swap in the active project's pre-bundled entry documents (the payload behind the platform's
|
|
83
93
|
* `fetchProjectSchemas` member). Each half falls back to its core schema when absent, so a partial
|
|
@@ -90,16 +100,24 @@ function getValidator(kind: SchemaKind): Promise<ValidateFn | null> {
|
|
|
90
100
|
export function applyProjectSchemas(
|
|
91
101
|
schemas: { project?: unknown; document?: unknown } | null,
|
|
92
102
|
): boolean {
|
|
93
|
-
|
|
94
|
-
|
|
103
|
+
/*
|
|
104
|
+
* A payload half that is not a schema OBJECT falls back to core.
|
|
105
|
+
*
|
|
106
|
+
* `ajv.compile({})` succeeds and accepts everything, so a backend answering `null`, a string, or
|
|
107
|
+
* an error envelope under `document` would not fail loudly — it would install a validator that
|
|
108
|
+
* calls every document valid, and every consumer would go on trusting it.
|
|
109
|
+
*/
|
|
110
|
+
const document = asSchema(schemas?.document) ?? CORE.document;
|
|
111
|
+
const project = asSchema(schemas?.project) ?? CORE.project;
|
|
112
|
+
const applied = document !== CORE.document || project !== CORE.project;
|
|
95
113
|
if (document === active.document && project === active.project) {
|
|
96
|
-
return
|
|
114
|
+
return applied;
|
|
97
115
|
}
|
|
98
116
|
active.document = document;
|
|
99
117
|
active.project = project;
|
|
100
118
|
compiling.document = null;
|
|
101
119
|
compiling.project = null;
|
|
102
|
-
return
|
|
120
|
+
return applied;
|
|
103
121
|
}
|
|
104
122
|
|
|
105
123
|
/** Restore the core schemas (project closed / tests). */
|
|
@@ -107,6 +125,17 @@ export function resetProjectSchemas(): void {
|
|
|
107
125
|
applyProjectSchemas(null);
|
|
108
126
|
}
|
|
109
127
|
|
|
128
|
+
/**
|
|
129
|
+
* The keys ajv uses to name the property an error is ABOUT, in the order they can appear.
|
|
130
|
+
*
|
|
131
|
+
* `message` alone is not a diagnosis for any of these three. "must NOT have unevaluated properties"
|
|
132
|
+
* says a key is unrecognised without saying which key — and at the root of a `project.json` that is
|
|
133
|
+
* the difference between a fixable problem and a mystery. An imported project once produced that
|
|
134
|
+
* line three times over, naming nothing, and the only way to learn it meant `title`, `description`
|
|
135
|
+
* and `$style` was to compile the schema by hand.
|
|
136
|
+
*/
|
|
137
|
+
const ERROR_PROPERTY_KEYS = ["unevaluatedProperty", "additionalProperty", "missingProperty"];
|
|
138
|
+
|
|
110
139
|
/**
|
|
111
140
|
* Format ajv errors the way the agent loop consumes them (see ai-tools.ts
|
|
112
141
|
* `translateValidationError`).
|
|
@@ -115,7 +144,11 @@ export function resetProjectSchemas(): void {
|
|
|
115
144
|
* @returns {string[]} One formatted string per error
|
|
116
145
|
*/
|
|
117
146
|
function formatErrors(validate: ValidateFn): string[] {
|
|
118
|
-
return (validate.errors || []).map((e) =>
|
|
147
|
+
return (validate.errors || []).map((e) => {
|
|
148
|
+
const key = ERROR_PROPERTY_KEYS.find((name) => typeof e.params?.[name] === "string");
|
|
149
|
+
const property = key ? ` (${String(e.params?.[key])})` : "";
|
|
150
|
+
return `${e.instancePath || "(root)"}: ${e.message}${property}`;
|
|
151
|
+
});
|
|
119
152
|
}
|
|
120
153
|
|
|
121
154
|
/**
|
|
@@ -132,6 +165,25 @@ export async function validateDoc(doc: unknown): Promise<string[]> {
|
|
|
132
165
|
return formatErrors(validate);
|
|
133
166
|
}
|
|
134
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Validate a Jx document, distinguishing "valid" from "could not be checked".
|
|
170
|
+
*
|
|
171
|
+
* {@link validateDoc} fails OPEN — an unavailable validator answers `[]`, which is the same answer
|
|
172
|
+
* as "no errors". That is right where validation decorates an editor and wrong where it GATES a
|
|
173
|
+
* destructive step: a conversion that says "this will be a valid JSON document" on the strength of
|
|
174
|
+
* a schema it never compiled is the "never render 0" defect §9.1.1 forbids, one layer down.
|
|
175
|
+
*
|
|
176
|
+
* @param doc - Parsed document
|
|
177
|
+
* @returns The errors, `[]` when valid, or `null` when no validator could be compiled
|
|
178
|
+
*/
|
|
179
|
+
export async function validateDocOrNull(doc: unknown): Promise<string[] | null> {
|
|
180
|
+
const validate = await getValidator("document");
|
|
181
|
+
if (!validate) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return validate(doc) ? [] : formatErrors(validate);
|
|
185
|
+
}
|
|
186
|
+
|
|
135
187
|
/**
|
|
136
188
|
* Validate a `project.json` config against the active project schema. The per-project entry
|
|
137
189
|
* document is what closes the composition (`unevaluatedProperties: false` over core + every enabled
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { initProjectRepo } from "../files/files";
|
|
13
|
+
import { beginBatch, endBatch, isBatching } from "../tabs/transact";
|
|
14
|
+
import { workspace } from "../workspace/workspace";
|
|
15
|
+
import type { Tab } from "../tabs/tab";
|
|
16
|
+
|
|
12
17
|
let _adopter: ((root: string) => Promise<void>) | null = null;
|
|
13
18
|
|
|
14
19
|
/** Register the project-open flow (studio.ts boot). */
|
|
@@ -29,3 +34,69 @@ export async function adoptProject(root: string): Promise<void> {
|
|
|
29
34
|
}
|
|
30
35
|
await _adopter(root);
|
|
31
36
|
}
|
|
37
|
+
|
|
38
|
+
/** What {@link adoptCreatedProject} observed, so a tool reports the outcome instead of the intent. */
|
|
39
|
+
export interface AdoptOutcome {
|
|
40
|
+
/** The workspace is now on this root. False means the project exists but this window is not on it. */
|
|
41
|
+
adopted: boolean;
|
|
42
|
+
/** Why adoption failed, when it threw. Null when it did not (including when it silently did not). */
|
|
43
|
+
error: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The seams {@link adoptCreatedProject} runs through, so each caller keeps the one it already had. */
|
|
47
|
+
export interface AdoptCreatedOptions {
|
|
48
|
+
/** The active tab, read AFTER adoption to re-anchor the agent loop's undo batch. */
|
|
49
|
+
getTab: () => Tab | null;
|
|
50
|
+
/** The project-open flow. Defaults to this module's late-bound {@link adoptProject}. */
|
|
51
|
+
adopt?: (root: string) => Promise<void>;
|
|
52
|
+
/** Version-control init. Defaults to `files/files.ts`'s `initProjectRepo`. */
|
|
53
|
+
initRepo?: (root: string) => Promise<boolean>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Everything that must happen to a project the moment it exists: version control, then adoption.
|
|
58
|
+
*
|
|
59
|
+
* Extracted because `create_project` and `import_site` are the same act with different backends,
|
|
60
|
+
* and the wizard is a third caller of half of it. Two things it does are easy to leave out, and
|
|
61
|
+
* both were:
|
|
62
|
+
*
|
|
63
|
+
* - **Git init.** `specs/desktop.md` §4.5 is normative — _"On the create path, every source,
|
|
64
|
+
* including Import and Agent, Studio therefore binds the backend to the new root (`activate`),
|
|
65
|
+
* reads `gitStatus`, and runs `gitInit`."_ `initProjectRepo` had exactly two call sites, both in
|
|
66
|
+
* the New Project modal, so a project the ASSISTANT bootstrapped was not a repository and nothing
|
|
67
|
+
* said so. It runs first because it is what calls `platform.activate(root)`, which the open flow
|
|
68
|
+
* below needs.
|
|
69
|
+
* - **Verifying.** `openRecentProject` swallows its failures into a status message, so a resolved
|
|
70
|
+
* promise is not proof of adoption. The caller must be told which of the two happened.
|
|
71
|
+
*
|
|
72
|
+
* The batch dance is the third: the agent loop may hold an undo batch on the pre-adoption tab, and
|
|
73
|
+
* adoption replaces every tab in the workspace. Flush it, adopt, re-open one on whatever tab
|
|
74
|
+
* adoption left active.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} root - Absolute root of the project that was just created.
|
|
77
|
+
* @param {AdoptCreatedOptions} opts
|
|
78
|
+
* @returns {Promise<AdoptOutcome>}
|
|
79
|
+
*/
|
|
80
|
+
export async function adoptCreatedProject(
|
|
81
|
+
root: string,
|
|
82
|
+
{ getTab, adopt = adoptProject, initRepo = initProjectRepo }: AdoptCreatedOptions,
|
|
83
|
+
): Promise<AdoptOutcome> {
|
|
84
|
+
// Never fails the create: a project that was written stays written (initProjectRepo notifies).
|
|
85
|
+
await initRepo(root);
|
|
86
|
+
|
|
87
|
+
const wasBatching = isBatching();
|
|
88
|
+
if (wasBatching) {
|
|
89
|
+
endBatch();
|
|
90
|
+
}
|
|
91
|
+
let adoptionError: string | null = null;
|
|
92
|
+
try {
|
|
93
|
+
await adopt(root);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
adoptionError = error instanceof Error ? error.message : String(error);
|
|
96
|
+
}
|
|
97
|
+
if (wasBatching) {
|
|
98
|
+
beginBatch(getTab());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { adopted: workspace.projectRoot === root, error: adoptionError };
|
|
102
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings registry — every application setting Studio holds, declared once.
|
|
3
|
+
*
|
|
4
|
+
* Before this table the answer to "which settings roam, and what is each one's default" lived in
|
|
5
|
+
* four places: a `PERSISTED_SETTINGS_KEYS` array, a private `const` per key in each `*-settings.ts`,
|
|
6
|
+
* a bespoke getter/setter pair beside it, and whatever the getter's `||` happened to fall back to.
|
|
7
|
+
* Adding a roaming setting meant editing all four and hoping; the key strings themselves were
|
|
8
|
+
* duplicated between the array and the module that owned them.
|
|
9
|
+
*
|
|
10
|
+
* Two properties of this table are load-bearing rather than tidy:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Scope is declared, not inferred.** {@link USER_SETTINGS} is DERIVED from it, so a setting
|
|
13
|
+
* roams because it says `scope: "user"` — not because someone remembered to add its key to a
|
|
14
|
+
* second list. `device` is the honest name for the rest: panel geometry and a palette's MRU
|
|
15
|
+
* describe *this window*, and a user with two windows open wants them to differ.
|
|
16
|
+
* 2. **A default is not a value.** The default is what a *reader* sees when nothing is stored; it
|
|
17
|
+
* is never itself stored. `getModel()` used to mask unset as `"gpt-4o"` with `||`, a form
|
|
18
|
+
* prefilled from it, and Save wrote the mask back — which is how an install whose owner had
|
|
19
|
+
* configured an entirely different provider was left holding `jx.ai.model: "gpt-4o"` and
|
|
20
|
+
* nothing else. {@link readSetting} and {@link readStoredSetting} in `kernel.ts` are two
|
|
21
|
+
* functions for exactly this reason.
|
|
22
|
+
*
|
|
23
|
+
* @license MIT
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Where a setting lives.
|
|
28
|
+
*
|
|
29
|
+
* `user` follows the author — every window, every project, and (where the platform has a backend
|
|
30
|
+
* store) every browser profile the launcher hands out. `device` stays where it was written.
|
|
31
|
+
*/
|
|
32
|
+
export type SettingScope = "user" | "device";
|
|
33
|
+
|
|
34
|
+
/** One setting, as the registry declares it. */
|
|
35
|
+
export interface SettingDefinition {
|
|
36
|
+
/** The storage key, in both localStorage and the backend map. */
|
|
37
|
+
key: string;
|
|
38
|
+
scope: SettingScope;
|
|
39
|
+
/** What a reader sees when nothing is stored. Never written — see the module docblock. */
|
|
40
|
+
default: string;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the value is a credential.
|
|
43
|
+
*
|
|
44
|
+
* Nothing in the kernel treats a secret differently; the flag is what lets a surface refuse to
|
|
45
|
+
* print one, a diagnostic dump refuse to include one, and the Accounts list report _that_ one is
|
|
46
|
+
* held rather than what it is (`specs/studio.md` §15 rule 1).
|
|
47
|
+
*/
|
|
48
|
+
secret?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Canonicalise an incoming value before it is stored. Total: it is handed raw user input.
|
|
51
|
+
*
|
|
52
|
+
* Applied on write only. A value already on disk is returned as it was found, so a normalizer
|
|
53
|
+
* that tightens later cannot silently rewrite what someone else stored.
|
|
54
|
+
*/
|
|
55
|
+
normalize?: (raw: string) => string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Trim, and drop trailing slashes — an endpoint is a base, not a path. */
|
|
59
|
+
function normalizeBaseUrl(raw: string): string {
|
|
60
|
+
return raw.trim().replace(/\/+$/, "");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Trim. Credentials are pasted, and a paste carries whitespace. */
|
|
64
|
+
function trimmed(raw: string): string {
|
|
65
|
+
return raw.trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Every setting, keyed by the name the rest of the app refers to it by.
|
|
70
|
+
*
|
|
71
|
+
* Declared `as const satisfies` so each entry keeps its literal `key` for callers while still being
|
|
72
|
+
* checked against {@link SettingDefinition}.
|
|
73
|
+
*/
|
|
74
|
+
export const SETTINGS = {
|
|
75
|
+
aiBaseUrl: {
|
|
76
|
+
default: "",
|
|
77
|
+
key: "jx.ai.baseUrl",
|
|
78
|
+
normalize: normalizeBaseUrl,
|
|
79
|
+
scope: "user",
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* The provider key. Stays in the settings store rather than moving to the launcher's credential
|
|
83
|
+
* store: the webview is its legitimate consumer — it sends the key itself as `X-Api-Key` — so
|
|
84
|
+
* withholding it and then transmitting it would be the same trust boundary with more parts. See
|
|
85
|
+
* `specs/desktop.md` §3.6, which draws that line.
|
|
86
|
+
*/
|
|
87
|
+
aiOpenAiKey: {
|
|
88
|
+
default: "",
|
|
89
|
+
key: "jx.ai.openaiKey",
|
|
90
|
+
normalize: trimmed,
|
|
91
|
+
scope: "user",
|
|
92
|
+
secret: true,
|
|
93
|
+
},
|
|
94
|
+
/**
|
|
95
|
+
* `"gpt-4o"` is what a sender falls back to, NOT a stored choice. A surface that writes back must
|
|
96
|
+
* read through `readStoredSetting`, or it persists a model the user never picked.
|
|
97
|
+
*/
|
|
98
|
+
aiModel: {
|
|
99
|
+
default: "gpt-4o",
|
|
100
|
+
key: "jx.ai.model",
|
|
101
|
+
normalize: trimmed,
|
|
102
|
+
scope: "user",
|
|
103
|
+
},
|
|
104
|
+
/**
|
|
105
|
+
* The author's rebindings, as a JSON object of `command id → chords`.
|
|
106
|
+
*
|
|
107
|
+
* Roams, so a keyboard learned once is the keyboard everywhere. `normalize` is why the field
|
|
108
|
+
* exists on {@link SettingDefinition}: an empty override map is the absence of an override, and
|
|
109
|
+
* storing `"{}"` would make every window think a layer had been authored.
|
|
110
|
+
*/
|
|
111
|
+
keybindings: {
|
|
112
|
+
default: "",
|
|
113
|
+
key: "jx.keybindings",
|
|
114
|
+
normalize: (raw) => (raw.trim() === "{}" ? "" : raw.trim()),
|
|
115
|
+
scope: "user",
|
|
116
|
+
},
|
|
117
|
+
/**
|
|
118
|
+
* The chrome theme.
|
|
119
|
+
*
|
|
120
|
+
* Read at module evaluation, before the first paint — `shell.ts` says a mismatch with the theme
|
|
121
|
+
* `index.html` hard-codes flashes the shell. That is the reason the kernel keeps a synchronous
|
|
122
|
+
* cache rather than awaiting the backend.
|
|
123
|
+
*/
|
|
124
|
+
theme: {
|
|
125
|
+
default: "",
|
|
126
|
+
key: "jx-studio-theme",
|
|
127
|
+
normalize: trimmed,
|
|
128
|
+
scope: "user",
|
|
129
|
+
},
|
|
130
|
+
/**
|
|
131
|
+
* Whether the Files tree draws what `.gitignore` masks.
|
|
132
|
+
*
|
|
133
|
+
* Unset — the default — HIDES them, which is the whole point: a project root holds the author's
|
|
134
|
+
* forty documents and a tool's forty thousand, and the author already said which is which. The
|
|
135
|
+
* setting exists so that answer is not a trap. `.env`, a built `dist/` an author wants to read, a
|
|
136
|
+
* generated file being debugged: each is a legitimate reason to look at an ignored file, and a
|
|
137
|
+
* sidebar that cannot show one sends the author to a different program.
|
|
138
|
+
*
|
|
139
|
+
* Roams, because "show me everything" is a way of working rather than a property of a window.
|
|
140
|
+
*/
|
|
141
|
+
showIgnoredFiles: {
|
|
142
|
+
default: "",
|
|
143
|
+
key: "jx.files.showIgnored",
|
|
144
|
+
normalize: trimmed,
|
|
145
|
+
scope: "user",
|
|
146
|
+
},
|
|
147
|
+
cfAccountId: {
|
|
148
|
+
default: "",
|
|
149
|
+
key: "jx.cf.accountId",
|
|
150
|
+
normalize: trimmed,
|
|
151
|
+
scope: "user",
|
|
152
|
+
},
|
|
153
|
+
cfToken: {
|
|
154
|
+
default: "",
|
|
155
|
+
key: "jx.cf.token",
|
|
156
|
+
normalize: trimmed,
|
|
157
|
+
scope: "user",
|
|
158
|
+
secret: true,
|
|
159
|
+
},
|
|
160
|
+
} as const satisfies Record<string, SettingDefinition>;
|
|
161
|
+
|
|
162
|
+
/** The registry's own names — `SETTINGS.aiModel`, not `"jx.ai.model"`. */
|
|
163
|
+
export type SettingName = keyof typeof SETTINGS;
|
|
164
|
+
|
|
165
|
+
/** Every declared setting, in declaration order. */
|
|
166
|
+
export const ALL_SETTINGS: readonly SettingDefinition[] = Object.values(SETTINGS);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The settings that roam, derived from {@link SETTINGS}.
|
|
170
|
+
*
|
|
171
|
+
* This replaces the hand-maintained `PERSISTED_SETTINGS_KEYS`. A setting joins it by declaring
|
|
172
|
+
* `scope: "user"` and by no other means, so the list cannot fall behind the table it describes.
|
|
173
|
+
*/
|
|
174
|
+
export const USER_SETTINGS: readonly SettingDefinition[] = ALL_SETTINGS.filter(
|
|
175
|
+
(definition) => definition.scope === "user",
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
/** The roaming keys as strings — what a backend map is keyed by. */
|
|
179
|
+
export const USER_SETTING_KEYS: readonly string[] = USER_SETTINGS.map(
|
|
180
|
+
(definition) => definition.key,
|
|
181
|
+
);
|