@jxsuite/studio 4.0.0 → 4.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/{2020-xp8vnwma.js → 2020-n6tfwnda.js} +2 -2
- package/dist/chunks/{Overlay.dev-723bv0df.js → Overlay.dev-etqjwyf9.js} +4 -4
- package/dist/chunks/cf-account-picker-en86v7x7.js +163 -0
- package/dist/chunks/{content-source-t88vb2dr.js → content-source-48xg86wj.js} +17 -17
- package/dist/chunks/{convert-to-repeater-dhw1nrz9.js → convert-to-repeater-43tfn7cs.js} +24 -24
- package/dist/chunks/{entry-commands-6z6bhxg0.js → entry-commands-e63f1xc0.js} +20 -20
- package/dist/chunks/{extension-sections-3ydgjbdy.js → extension-sections-txv08xxg.js} +23 -23
- package/dist/chunks/{file-ops-rskmhxyq.js → file-ops-wbh4hhsx.js} +16 -16
- package/dist/chunks/{files-0cratsr0.js → files-3kfkwhk4.js} +25 -24
- package/dist/chunks/{focus-trap.esm-ggawf1hy.js → focus-trap.esm-9t2drp0g.js} +6 -5
- package/dist/chunks/{format-host-tgcmfs4p.js → format-host-w16q81tf.js} +2 -2
- package/dist/chunks/fs-events-hrrg328g.js +1 -1
- package/dist/chunks/{git-panel-0yywyk76.js → git-panel-532whr1y.js} +16 -15
- package/dist/chunks/{index-nj44jpzj.js → index-wcnt3r9g.js} +2 -2
- package/dist/chunks/{jx-validate-8q2g05x1.js → jx-validate-8er6gbxy.js} +3 -3
- package/dist/chunks/{layers-panel-z3tqe5cd.js → layers-panel-07hm7g5j.js} +19 -19
- package/dist/chunks/{monaco-setup-rws7k4br.js → monaco-setup-vx9bhp1d.js} +2 -2
- package/dist/chunks/{publish-panel-54jvhskq.js → publish-panel-v9st96ja.js} +11 -10
- package/dist/chunks/{quick-search-cgfmjwq5.js → quick-search-zccjp6gv.js} +26 -25
- package/dist/chunks/{right-panel-cqp8bc4v.js → right-panel-vm3b6kwy.js} +17 -17
- package/dist/chunks/{sp-overlay.dev-yztmtrs1.js → sp-overlay.dev-qgvhnxf4.js} +5 -5
- package/dist/chunks/{sp-tooltip.dev-xcvpnqr0.js → sp-tooltip.dev-4sastdnw.js} +3 -3
- package/dist/chunks/{studio-txwkm6p9.js → studio-0bq6eh7v.js} +2 -2
- package/dist/chunks/studio-1ngbejft.js +181 -0
- package/dist/chunks/{studio-gv8vvshc.js → studio-1wagb550.js} +3 -3
- package/dist/chunks/{studio-kjjsjak4.js → studio-2b946z26.js} +58 -57
- package/dist/chunks/{studio-4pqqxbrt.js → studio-3bqpr0zh.js} +16 -16
- package/dist/chunks/{studio-v38hmvx1.js → studio-4b5z1x43.js} +42 -8
- package/dist/chunks/{studio-9c4k4vhv.js → studio-4n2b5gzb.js} +4 -4
- package/dist/chunks/{studio-p8j86wag.js → studio-4r1pv6zf.js} +2 -2
- package/dist/chunks/{studio-mmv72a27.js → studio-5fx3g9jg.js} +29 -48
- package/dist/chunks/{studio-5rywrkxz.js → studio-5myknm0a.js} +42 -42
- package/dist/chunks/{studio-sf7zjgv1.js → studio-6yw4y3my.js} +3 -3
- package/dist/chunks/{studio-pdfjntbk.js → studio-950k0dx6.js} +35 -35
- package/dist/chunks/{studio-eenfkt4e.js → studio-ahn3pdgx.js} +5 -5
- package/dist/chunks/{studio-eesa1myv.js → studio-b55y6ewx.js} +9 -9
- package/dist/chunks/{studio-cb12yz9w.js → studio-cmb03bg5.js} +99 -1264
- package/dist/chunks/studio-dm19q43r.js +195 -0
- package/dist/chunks/{studio-ahjfdy6e.js → studio-ef49pnzx.js} +6 -6
- package/dist/chunks/{studio-wx5609xd.js → studio-ew0djmhg.js} +10 -10
- package/dist/chunks/{studio-9r6j5ev1.js → studio-f3wqjtk5.js} +4 -3
- package/dist/chunks/{studio-8swrjbny.js → studio-fbf92ckf.js} +14 -14
- package/dist/chunks/{studio-4q0wznzf.js → studio-gcye7qka.js} +442 -126
- package/dist/chunks/{studio-ex8zyp98.js → studio-gfgb8pe6.js} +23 -184
- package/dist/chunks/{studio-r1r7afxx.js → studio-jc994dnr.js} +24 -24
- package/dist/chunks/{studio-yxrkyv8a.js → studio-jkftxyvz.js} +52 -3416
- package/dist/chunks/{studio-hbtp0eh1.js → studio-msd847zn.js} +13 -13
- package/dist/chunks/{studio-kyk2v92a.js → studio-mtfnaqs9.js} +14 -14
- package/dist/chunks/{studio-vadzgkrz.js → studio-n6j9t10q.js} +2 -2
- package/dist/chunks/{studio-68n67094.js → studio-q9jd1a60.js} +4 -4
- package/dist/chunks/studio-qcy05dp8.js +1743 -0
- package/dist/chunks/{studio-htsf0w6m.js → studio-qjy2z9bv.js} +12 -30
- package/dist/chunks/{studio-2zkz8evn.js → studio-trxrabqf.js} +12 -12
- package/dist/chunks/{studio-5q69gfag.js → studio-x4qkdat5.js} +12 -11
- package/dist/chunks/studio-x813zp48.js +195 -0
- package/dist/chunks/{studio-1hbqnt19.js → studio-zqazsct2.js} +29 -29
- package/dist/chunks/{workspace-xpres40v.js → workspace-fjx52ryv.js} +4 -4
- package/dist/iframe-entry.js +34 -33
- package/dist/manifest.json +7 -0
- package/dist/studio.js +534 -546
- package/favicon.ico +0 -0
- package/index.html +1 -0
- package/package.json +15 -14
- package/src/about/about-modal.ts +4 -3
- package/src/files/files.ts +6 -4
- package/src/files/media-paths.ts +13 -42
- package/src/files/media-usage.ts +41 -131
- package/src/hosting/document.ts +2 -0
- package/src/hosting/layout.ts +8 -0
- package/src/new-project/import-tab.ts +37 -15
- package/src/new-project/location-fields.ts +8 -5
- package/src/panels/ai-chat/composer.ts +10 -0
- package/src/platforms/cloud.ts +241 -70
- package/src/publish/publish-panel.ts +62 -2
- package/src/services/ai-import-tools.ts +50 -12
- package/src/services/ai-models.ts +108 -1
- package/src/services/context-manager.ts +12 -1
- package/src/services/tool-executor.ts +11 -0
- package/src/settings/preferences-accounts.ts +193 -12
- package/src/settings/preferences-dialog.ts +75 -18
- package/src/types.ts +27 -3
- package/src/ui/ai-managed-connect.ts +62 -13
- package/src/ui/ai-model-picker.ts +29 -2
- package/src/ui/cf-account-picker.ts +200 -0
- package/styles/shell.css +36 -0
- package/dist/chunks/studio-bw5dz6km.js +0 -608
package/favicon.ico
ADDED
|
Binary file
|
package/index.html
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Jx Studio</title>
|
|
7
|
+
<link rel="icon" href="./favicon.ico" />
|
|
7
8
|
<link rel="stylesheet" href="./dist/studio.css" />
|
|
8
9
|
<link rel="stylesheet" href="./styles/tokens.css" />
|
|
9
10
|
<link rel="stylesheet" href="./styles/shell.css" />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"dist/workers",
|
|
22
22
|
"index.html",
|
|
23
23
|
"canvas.html",
|
|
24
|
+
"favicon.ico",
|
|
24
25
|
"styles",
|
|
25
26
|
"fonts"
|
|
26
27
|
],
|
|
@@ -55,17 +56,17 @@
|
|
|
55
56
|
"upgrade": "bun update --latest && bun install"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
|
-
"@atlaskit/pragmatic-drag-and-drop": "^3.
|
|
59
|
-
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.
|
|
60
|
-
"@jxsuite/ai": "^0.
|
|
61
|
-
"@jxsuite/collab": "^0.8.
|
|
62
|
-
"@jxsuite/create": "^1.3.
|
|
63
|
-
"@jxsuite/formulas": "^0.0.
|
|
64
|
-
"@jxsuite/markup": "^0.4.
|
|
65
|
-
"@jxsuite/protocol": "^2.
|
|
66
|
-
"@jxsuite/runtime": "^3.
|
|
67
|
-
"@jxsuite/schema": "^2.
|
|
68
|
-
"@jxsuite/site": "^1.
|
|
59
|
+
"@atlaskit/pragmatic-drag-and-drop": "^3.1.0",
|
|
60
|
+
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.2.0",
|
|
61
|
+
"@jxsuite/ai": "^0.37.0",
|
|
62
|
+
"@jxsuite/collab": "^0.8.5",
|
|
63
|
+
"@jxsuite/create": "^1.3.9",
|
|
64
|
+
"@jxsuite/formulas": "^0.0.16",
|
|
65
|
+
"@jxsuite/markup": "^0.4.9",
|
|
66
|
+
"@jxsuite/protocol": "^2.2.0",
|
|
67
|
+
"@jxsuite/runtime": "^3.1.0",
|
|
68
|
+
"@jxsuite/schema": "^2.1.0",
|
|
69
|
+
"@jxsuite/site": "^1.1.0",
|
|
69
70
|
"@spectrum-web-components/accordion": "^1.12.2",
|
|
70
71
|
"@spectrum-web-components/action-bar": "1.12.2",
|
|
71
72
|
"@spectrum-web-components/action-button": "^1.12.2",
|
|
@@ -102,7 +103,7 @@
|
|
|
102
103
|
"@spectrum-web-components/toast": "^1.12.2",
|
|
103
104
|
"@spectrum-web-components/tooltip": "^1.12.2",
|
|
104
105
|
"@spectrum-web-components/underlay": "^1.12.2",
|
|
105
|
-
"@vue/reactivity": "3.5.
|
|
106
|
+
"@vue/reactivity": "3.5.42",
|
|
106
107
|
"ajv": "^8.20.0",
|
|
107
108
|
"ajv-formats": "^3.0.1",
|
|
108
109
|
"lit": "^3.3.3",
|
|
@@ -112,7 +113,7 @@
|
|
|
112
113
|
"yaml": "^2.9.0"
|
|
113
114
|
},
|
|
114
115
|
"devDependencies": {
|
|
115
|
-
"@happy-dom/global-registrator": "^20.
|
|
116
|
+
"@happy-dom/global-registrator": "^20.12.0",
|
|
116
117
|
"@types/mdast": "^4.0.4",
|
|
117
118
|
"@webref/css": "^8.7.3",
|
|
118
119
|
"@webref/elements": "^2.8.0",
|
package/src/about/about-modal.ts
CHANGED
|
@@ -152,8 +152,9 @@ function renderModal() {
|
|
|
152
152
|
*
|
|
153
153
|
* The rail foot carries **Preferences** and nothing else (plan §3.2 ②). About is not a view you
|
|
154
154
|
* switch to and it is opened roughly once in an app's lifetime, so it costs a rail slot it cannot
|
|
155
|
-
* repay;
|
|
156
|
-
*
|
|
155
|
+
* repay; instead it sits at the bottom of the ⬢ Studio menu (`commandbar/overflow`, `group:
|
|
156
|
+
* "9_help"` sorts after every other group there) and stays reachable by name from the palette
|
|
157
|
+
* besides.
|
|
157
158
|
*/
|
|
158
159
|
export function aboutCommands(): Command[] {
|
|
159
160
|
return [
|
|
@@ -162,7 +163,7 @@ export function aboutCommands(): Command[] {
|
|
|
162
163
|
title: `About ${APP_NAME}`,
|
|
163
164
|
category: "Help",
|
|
164
165
|
level: "application",
|
|
165
|
-
menus: ["palette"],
|
|
166
|
+
menus: ["commandbar/overflow", "palette"],
|
|
166
167
|
group: "9_help",
|
|
167
168
|
run: () => {
|
|
168
169
|
openAboutModal();
|
package/src/files/files.ts
CHANGED
|
@@ -1910,10 +1910,12 @@ function renameStatus(newName: string, report: RenameResult): string {
|
|
|
1910
1910
|
*
|
|
1911
1911
|
* The dialog above a rename states, in a modal the user has to accept, that N references "will be
|
|
1912
1912
|
* updated automatically. Nothing else changes." The engine keeps that promise for every document it
|
|
1913
|
-
* can write
|
|
1914
|
-
* deliberately no serializer,
|
|
1915
|
-
*
|
|
1916
|
-
*
|
|
1913
|
+
* can write, and it can write more than it round-trips: a `.csv` collection has a parser and
|
|
1914
|
+
* deliberately no serializer, but declares the narrower `rewrite` capability, so a reference in one
|
|
1915
|
+
* is repaired cell by cell. What is left over is a document that fails to parse, and a tag rename
|
|
1916
|
+
* inside a format with no serializer. In those `applyRename` names the file rather than dropping
|
|
1917
|
+
* it, and this is the half that makes the naming reach a person. Silence here is what turns a
|
|
1918
|
+
* stated promise into a false one.
|
|
1917
1919
|
*
|
|
1918
1920
|
* A drag-move makes no promise at all — it has no dialog — which is exactly why it needs this more,
|
|
1919
1921
|
* not less. A format CONVERSION makes the strongest promise of the three, and shares this for that
|
package/src/files/media-paths.ts
CHANGED
|
@@ -15,14 +15,19 @@
|
|
|
15
15
|
* mount (specs/site-architecture.md §9.3), so the same file can also be named
|
|
16
16
|
* `/content/blog/images/hero.png` when the collection's `source` is not already `content/blog`.
|
|
17
17
|
*
|
|
18
|
-
* **Why this module exists at all.**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
18
|
+
* **Why this module exists at all.** A surface that has a FILE and needs a URL cannot get one by
|
|
19
|
+
* prefixing a slash. The library grid did exactly that, so every `public/` image asked for
|
|
20
|
+
* `/public/hero.jpg` — a URL the site does not publish — and every content-collection image skipped
|
|
21
|
+
* its mount entirely. {@link mediaSiteUrl} and {@link previewFileSrc} are the answer, and they take
|
|
22
|
+
* a FILE PATH: an authored reference is a different thing and resolves through
|
|
23
|
+
* `canvas/asset-refs`.
|
|
24
|
+
*
|
|
25
|
+
* This module used to carry a third function, `authoredRefTargets`, enumerating every path an
|
|
26
|
+
* authored reference to a file could resolve to, so a usage query could ask about all of them and
|
|
27
|
+
* union the answers. That existed because `findReferences` resolved a rooted reference against the
|
|
28
|
+
* project root alone; the engine now resolves every lane itself (`site-architecture.md` §9.3), on
|
|
29
|
+
* the write side as well as the read side, which a client-side union never could. Asking once is
|
|
30
|
+
* both simpler and more correct, so the helper is gone.
|
|
26
31
|
*
|
|
27
32
|
* Everything here is pure string math over project-relative, forward-slashed paths. The only input
|
|
28
33
|
* from outside is the open project's content sections, read the same way `canvas/asset-refs.ts`
|
|
@@ -101,40 +106,6 @@ export function mediaSiteUrl(path: string): string {
|
|
|
101
106
|
return mount ? `/${mountedPath(normalized, mount)}` : `/${normalized}`;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
/**
|
|
105
|
-
* Every path an authored reference to this media file resolves to — the keys a usage query must ask
|
|
106
|
-
* about, most literal first.
|
|
107
|
-
*
|
|
108
|
-
* 1. The file itself. A sibling's `./images/hero.png`, a rooted `/content/blog/images/hero.png` and a
|
|
109
|
-
* bare `assets/logo.svg` all land here.
|
|
110
|
-
* 2. Its served path, for `public/` media. `/hero.jpg` resolves to `hero.jpg`, which is not a file on
|
|
111
|
-
* disk and does not need to be — the sweep compares resolved strings, and this is the string
|
|
112
|
-
* every reference to a public asset produces.
|
|
113
|
-
* 3. Its asset-mount path, when a collection's `source` differs from its mount prefix. Identical to
|
|
114
|
-
* (1) for the ordinary `content/<type>/` layout, and deduplicated when it is.
|
|
115
|
-
*
|
|
116
|
-
* The list is a UNION and is deliberately generous: over-counting a reference makes a delete dialog
|
|
117
|
-
* more alarming than it needs to be, while missing one makes it lie.
|
|
118
|
-
*/
|
|
119
|
-
export function authoredRefTargets(path: string): string[] {
|
|
120
|
-
const normalized = normalizeProjectPath(path);
|
|
121
|
-
if (normalized === "") {
|
|
122
|
-
return [];
|
|
123
|
-
}
|
|
124
|
-
const targets = [normalized];
|
|
125
|
-
if (normalized.startsWith(`${PUBLIC_DIR}/`)) {
|
|
126
|
-
targets.push(normalized.slice(PUBLIC_DIR.length + 1));
|
|
127
|
-
}
|
|
128
|
-
const mount = mountOf(normalized);
|
|
129
|
-
if (mount) {
|
|
130
|
-
const mounted = mountedPath(normalized, mount);
|
|
131
|
-
if (!targets.includes(mounted)) {
|
|
132
|
-
targets.push(mounted);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return targets;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
109
|
/**
|
|
139
110
|
* The src a PARENT-REALM preview should load for a PROJECT FILE PATH — a library thumbnail, a
|
|
140
111
|
* media-browser tile.
|
package/src/files/media-usage.ts
CHANGED
|
@@ -1,164 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Media usage — "which pages use this image?", asked
|
|
2
|
+
* Media usage — "which pages use this image?", asked once.
|
|
3
3
|
*
|
|
4
|
-
* `services/references.ts`
|
|
5
|
-
* what every destructive confirmation says out loud. Media
|
|
6
|
-
*
|
|
7
|
-
* somewhere else.** `public/hero.jpg` is written `/hero.jpg`, which the compiler — and therefore
|
|
8
|
-
* the sweep — resolves to `hero.jpg`. Ask about the file and every reference to it is invisible;
|
|
9
|
-
* the query succeeds, returns zero, and the delete dialog says nothing else refers to it while
|
|
10
|
-
* seven pages break. A confident zero is the one answer a destructive dialog must never invent.
|
|
4
|
+
* `services/references.ts` answers "where is this used?" for every kind of file, and its answer is
|
|
5
|
+
* what every destructive confirmation says out loud. Media needs three things on top of it, and no
|
|
6
|
+
* longer needs the fourth this module was built for.
|
|
11
7
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* round trip), the same `invalidateUsages` on any filesystem event, the same `usageWarning`
|
|
17
|
-
* sentence in the confirmation, and the same `usageHeadline` wording on a panel.
|
|
8
|
+
* **What it still does.** A media path is normalized before it becomes a query (a leading `./` is
|
|
9
|
+
* not part of a file's identity), an empty path is a FAILURE rather than "nothing uses it", and
|
|
10
|
+
* {@link mediaUsageHeadline} renders the one line a media surface shows beside a file — where a
|
|
11
|
+
* failed query says **unknown**, which is a different fact from "unused" and has to read like one.
|
|
18
12
|
*
|
|
19
|
-
* **
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
13
|
+
* **What it no longer does, and why that matters.** A media file's authored reference usually
|
|
14
|
+
* resolves somewhere else: `public/hero.jpg` is written `/hero.jpg`, and a content asset is written
|
|
15
|
+
* `./images/hero.png` and republished at its collection's mount. `findReferences` used to resolve a
|
|
16
|
+
* rooted reference against the project root ALONE, so asking it about the file matched none of
|
|
17
|
+
* those and returned a confident zero — the one answer a destructive dialog must never invent. This
|
|
18
|
+
* module's answer was to enumerate every authored spelling of the file and union the results.
|
|
19
|
+
*
|
|
20
|
+
* That was a workaround in the wrong place. A client-side union can make a COUNT come out right and
|
|
21
|
+
* can do nothing at all about the REWRITE, which happens inside the engine — so the engine learned
|
|
22
|
+
* to resolve every lane itself (issue 239/241, `site-architecture.md` §9.3), and the union became a
|
|
23
|
+
* second implementation of a rule that now has one. It is gone: one path in, one query, one answer,
|
|
24
|
+
* and the lanes are the engine's business.
|
|
23
25
|
*
|
|
24
26
|
* @docs studio/projects/media
|
|
25
27
|
*/
|
|
26
28
|
|
|
27
|
-
import {
|
|
29
|
+
import { normalizeProjectPath } from "./media-paths";
|
|
28
30
|
import { loadUsages, peekUsages, retryUsages, usageHeadline } from "../services/references";
|
|
29
|
-
import type {
|
|
30
|
-
import type { ReferenceFile, ReferenceHit, ReferencesResult } from "../types";
|
|
31
|
+
import type { UsageState } from "../services/references";
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
* One per authored form. An empty list means there is no question to ask (an empty path), which
|
|
36
|
-
* {@link loadMediaUsages} reports as a failure rather than as "nothing uses it".
|
|
37
|
-
*/
|
|
38
|
-
export function mediaUsageQueries(path: string): UsageQuery[] {
|
|
39
|
-
return authoredRefTargets(path).map((target) => ({ path: target }));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Merge one file's hits into the accumulator, summing counts per `(refType, ref)`. */
|
|
43
|
-
function mergeFile(into: Map<string, ReferenceFile>, file: ReferenceFile): void {
|
|
44
|
-
const existing = into.get(file.path);
|
|
45
|
-
if (!existing) {
|
|
46
|
-
into.set(file.path, { count: file.count, path: file.path, refs: [...file.refs] });
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
for (const hit of file.refs) {
|
|
50
|
-
const same = existing.refs.find(
|
|
51
|
-
(candidate: ReferenceHit) => candidate.refType === hit.refType && candidate.ref === hit.ref,
|
|
52
|
-
);
|
|
53
|
-
if (same) {
|
|
54
|
-
same.count += hit.count;
|
|
55
|
-
} else {
|
|
56
|
-
existing.refs.push({ ...hit });
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
existing.count += file.count;
|
|
60
|
-
}
|
|
33
|
+
/** The state a caller gets for a path that names no file. */
|
|
34
|
+
const NO_FILE: UsageState = { message: "no media file to look for", status: "failed" };
|
|
61
35
|
|
|
62
36
|
/**
|
|
63
|
-
*
|
|
37
|
+
* The query for a media file, or null when the path names nothing.
|
|
64
38
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* whichever lane happened to be first.
|
|
39
|
+
* Normalizing here rather than at each call site is the whole of what this wrapper adds over
|
|
40
|
+
* `loadUsages`: the file tree, a drop handler and a library tile do not agree on whether a path
|
|
41
|
+
* carries a leading `./`, and two spellings of one file must not become two cache entries.
|
|
69
42
|
*/
|
|
70
|
-
function
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
for (const result of results) {
|
|
74
|
-
for (const file of result.files) {
|
|
75
|
-
mergeFile(files, file);
|
|
76
|
-
}
|
|
77
|
-
for (const error of result.errors) {
|
|
78
|
-
if (!errors.has(error.path)) {
|
|
79
|
-
errors.set(error.path, error);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const merged = [...files.values()].toSorted((a, b) => a.path.localeCompare(b.path));
|
|
84
|
-
return {
|
|
85
|
-
errors: [...errors.values()],
|
|
86
|
-
files: merged,
|
|
87
|
-
filesReferencing: merged.length,
|
|
88
|
-
path,
|
|
89
|
-
refsTotal: merged.reduce((sum, file) => sum + file.count, 0),
|
|
90
|
-
tagName: null,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Reduce the lanes to one state.
|
|
96
|
-
*
|
|
97
|
-
* Precedence is strictly least-informative-wins, because every step down is a step away from a
|
|
98
|
-
* number the caller could act on: unsupported (the host cannot answer at all) beats failed (it
|
|
99
|
-
* tried and could not) beats pending beats a real union.
|
|
100
|
-
*/
|
|
101
|
-
function combine(path: string, states: readonly UsageState[]): UsageState {
|
|
102
|
-
if (states.some((state) => state.status === "unsupported")) {
|
|
103
|
-
return { status: "unsupported" };
|
|
104
|
-
}
|
|
105
|
-
const failure = states.find((state) => state.status === "failed");
|
|
106
|
-
if (failure) {
|
|
107
|
-
return failure;
|
|
108
|
-
}
|
|
109
|
-
if (states.some((state) => state.status === "pending")) {
|
|
110
|
-
return { status: "pending" };
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
result: unionResults(
|
|
114
|
-
path,
|
|
115
|
-
states.map((state) => (state as { result: ReferencesResult }).result),
|
|
116
|
-
),
|
|
117
|
-
status: "ready",
|
|
118
|
-
};
|
|
43
|
+
function mediaQuery(path: string): { path: string } | null {
|
|
44
|
+
const normalized = normalizeProjectPath(path);
|
|
45
|
+
return normalized === "" ? null : { path: normalized };
|
|
119
46
|
}
|
|
120
47
|
|
|
121
48
|
/**
|
|
122
49
|
* The answer right now, WITHOUT starting a request — the synchronous read a lit template needs.
|
|
123
50
|
*
|
|
124
|
-
* `null` means
|
|
125
|
-
*
|
|
51
|
+
* `null` means the question has never been asked, and the caller decides whether this paint is the
|
|
52
|
+
* one that asks (see {@link loadMediaUsages}).
|
|
126
53
|
*/
|
|
127
54
|
export function peekMediaUsages(path: string): UsageState | null {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
const states: UsageState[] = [];
|
|
133
|
-
for (const query of queries) {
|
|
134
|
-
const state = peekUsages(query);
|
|
135
|
-
if (state === null) {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
states.push(state);
|
|
139
|
-
}
|
|
140
|
-
return combine(path, states);
|
|
55
|
+
const query = mediaQuery(path);
|
|
56
|
+
return query === null ? null : peekUsages(query);
|
|
141
57
|
}
|
|
142
58
|
|
|
143
59
|
/**
|
|
144
|
-
* Ask
|
|
60
|
+
* Ask, or join the ask already in flight. Never rejects — a failure is a STATE, because the caller
|
|
145
61
|
* has to render something and "unknown" is the honest something.
|
|
146
62
|
*/
|
|
147
63
|
export async function loadMediaUsages(path: string): Promise<UsageState> {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
return { message: "no media file to look for", status: "failed" };
|
|
151
|
-
}
|
|
152
|
-
return combine(path, await Promise.all(queries.map((query) => loadUsages(query))));
|
|
64
|
+
const query = mediaQuery(path);
|
|
65
|
+
return query === null ? NO_FILE : loadUsages(query);
|
|
153
66
|
}
|
|
154
67
|
|
|
155
|
-
/** Forget
|
|
68
|
+
/** Forget the answer and ask again — the Retry behind a failed media count. */
|
|
156
69
|
export async function retryMediaUsages(path: string): Promise<UsageState> {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
return { message: "no media file to look for", status: "failed" };
|
|
160
|
-
}
|
|
161
|
-
return combine(path, await Promise.all(queries.map((query) => retryUsages(query))));
|
|
70
|
+
const query = mediaQuery(path);
|
|
71
|
+
return query === null ? NO_FILE : retryUsages(query);
|
|
162
72
|
}
|
|
163
73
|
|
|
164
74
|
/**
|
package/src/hosting/document.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
assetUrl,
|
|
16
16
|
STUDIO_BUNDLE_CSS,
|
|
17
17
|
STUDIO_ENTRY,
|
|
18
|
+
STUDIO_FAVICON,
|
|
18
19
|
STUDIO_IFRAME_ENTRY,
|
|
19
20
|
STUDIO_STYLESHEETS,
|
|
20
21
|
} from "./layout";
|
|
@@ -71,6 +72,7 @@ export function studioShellHtml(options: DocumentOptions = {}): string {
|
|
|
71
72
|
<meta charset="utf-8" />
|
|
72
73
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
73
74
|
<title>${options.title ?? "Jx Studio"}</title>
|
|
75
|
+
<link rel="icon" href="${url(STUDIO_FAVICON)}" />
|
|
74
76
|
${links}
|
|
75
77
|
</head>
|
|
76
78
|
<body>
|
package/src/hosting/layout.ts
CHANGED
|
@@ -66,6 +66,13 @@ export const STUDIO_ASSETS: readonly StudioAsset[] = [
|
|
|
66
66
|
required: true,
|
|
67
67
|
why: "the canvas iframe document. Without it every canvas frame 404s at boot.",
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
dir: false,
|
|
71
|
+
kind: "asset",
|
|
72
|
+
path: "favicon.ico",
|
|
73
|
+
required: true,
|
|
74
|
+
why: "the shell document's <link rel=\"icon\">. A Chromium --app window (the NixOS desktop install) has no native title-bar icon of its own and falls back to the page's favicon, so without this the window chrome shows a generic icon even though the taskbar/dock entry is already branded via the .desktop file.",
|
|
75
|
+
},
|
|
69
76
|
{
|
|
70
77
|
dir: false,
|
|
71
78
|
kind: "entry",
|
|
@@ -143,6 +150,7 @@ export const PUBLISHED_EXTRAS: readonly string[] = ["src", "data"];
|
|
|
143
150
|
|
|
144
151
|
export const STUDIO_SHELL = "index.html";
|
|
145
152
|
export const STUDIO_CANVAS = "canvas.html";
|
|
153
|
+
export const STUDIO_FAVICON = "favicon.ico";
|
|
146
154
|
export const STUDIO_ENTRY = "dist/studio.js";
|
|
147
155
|
export const STUDIO_IFRAME_ENTRY = "dist/iframe-entry.js";
|
|
148
156
|
export const STUDIO_BUNDLE_CSS = "dist/studio.css";
|
|
@@ -58,8 +58,8 @@ export interface ImportTabCtx {
|
|
|
58
58
|
form: { name: string; directory: string };
|
|
59
59
|
/**
|
|
60
60
|
* Validate the shared Parameters fields and return where to write, or null when something is
|
|
61
|
-
* missing (the modal has already rendered the reason inline).
|
|
62
|
-
*
|
|
61
|
+
* missing (the modal has already rendered the reason inline). Either destination shape: a hosted
|
|
62
|
+
* backend imports into a repository it creates, so `"repo"` reaches here too.
|
|
63
63
|
*/
|
|
64
64
|
resolveDestination: () => CreateProjectDestination | null;
|
|
65
65
|
rerender: () => void;
|
|
@@ -201,6 +201,22 @@ export function breakpointPolicyFromForm(): ImportBreakpointPolicy {
|
|
|
201
201
|
return { count: _breakpointCount, mode: "limit", rounding: _breakpointRounding };
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Whether this backend can answer the fidelity question at all.
|
|
206
|
+
*
|
|
207
|
+
* `verify` builds the emitted project with the compiler and serves it to screenshot every page
|
|
208
|
+
* against the original — it executes the project's own JavaScript, so `@jxsuite/import` keeps that
|
|
209
|
+
* phase out of the portable pipeline entirely and a hosted backend deliberately never runs it.
|
|
210
|
+
*
|
|
211
|
+
* There is no PAL member for "this backend compiles", so the nearest DECLARED fact stands in:
|
|
212
|
+
* `createDestination`. A `"repo"` platform commits the emitted project into a git tree it never
|
|
213
|
+
* checks out, which is the same set of hosts and one the tab can read without naming a platform by
|
|
214
|
+
* id. Offering the switch there would collect an answer nothing acts on.
|
|
215
|
+
*/
|
|
216
|
+
function canVerify(): boolean {
|
|
217
|
+
return getPlatform().createDestination !== "repo";
|
|
218
|
+
}
|
|
219
|
+
|
|
204
220
|
function slugOf(name: string): string {
|
|
205
221
|
return name
|
|
206
222
|
.toLowerCase()
|
|
@@ -252,7 +268,7 @@ export function importBriefFor(ctx: ImportTabCtx): ImportBrief | null {
|
|
|
252
268
|
return null;
|
|
253
269
|
}
|
|
254
270
|
const destination = ctx.resolveDestination();
|
|
255
|
-
if (!destination
|
|
271
|
+
if (!destination) {
|
|
256
272
|
return null;
|
|
257
273
|
}
|
|
258
274
|
return {
|
|
@@ -266,7 +282,9 @@ export function importBriefFor(ctx: ImportTabCtx): ImportBrief | null {
|
|
|
266
282
|
prompt: _prompt.trim(),
|
|
267
283
|
minFidelity: _minFidelity,
|
|
268
284
|
url: parsed.href,
|
|
269
|
-
|
|
285
|
+
/* Never asked for where it cannot run: the switch is not rendered there, and a value left over
|
|
286
|
+
from a session on another platform must not travel as a request the backend would ignore. */
|
|
287
|
+
verify: canVerify() && _verify,
|
|
270
288
|
};
|
|
271
289
|
}
|
|
272
290
|
|
|
@@ -472,18 +490,22 @@ export function renderImportSource(ctx: ImportTabCtx): TemplateResult {
|
|
|
472
490
|
>
|
|
473
491
|
AI component naming
|
|
474
492
|
</sp-switch>
|
|
475
|
-
<sp-switch
|
|
476
|
-
.checked=${live(_verify)}
|
|
477
|
-
@change=${(e: Event) => {
|
|
478
|
-
_verify = (e.target as HTMLInputElement).checked;
|
|
479
|
-
// The bar below it only means anything while the check runs, so it appears with it.
|
|
480
|
-
ctx.rerender();
|
|
481
|
-
}}
|
|
482
|
-
>
|
|
483
|
-
Check fidelity against the original (slower)
|
|
484
|
-
</sp-switch>
|
|
485
493
|
${
|
|
486
|
-
|
|
494
|
+
canVerify()
|
|
495
|
+
? html`<sp-switch
|
|
496
|
+
.checked=${live(_verify)}
|
|
497
|
+
@change=${(e: Event) => {
|
|
498
|
+
_verify = (e.target as HTMLInputElement).checked;
|
|
499
|
+
// The bar below it only means anything while the check runs, so it appears with it.
|
|
500
|
+
ctx.rerender();
|
|
501
|
+
}}
|
|
502
|
+
>
|
|
503
|
+
Check fidelity against the original (slower)
|
|
504
|
+
</sp-switch>`
|
|
505
|
+
: nothing
|
|
506
|
+
}
|
|
507
|
+
${
|
|
508
|
+
canVerify() && _verify
|
|
487
509
|
? html`
|
|
488
510
|
<label class="new-project-field">
|
|
489
511
|
<span class="new-project-label">Minimum fidelity</span>
|
|
@@ -140,13 +140,16 @@ function joinPath(parent: string, name: string): string {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
|
-
* The
|
|
144
|
-
*
|
|
145
|
-
*
|
|
143
|
+
* The destination as the import pipeline names it: `importSite` takes a plain `directory` string
|
|
144
|
+
* rather than a destination object, so the modal flattens one here.
|
|
145
|
+
*
|
|
146
|
+
* Both shapes, because both platforms import now. A `"path"` destination flattens to the absolute
|
|
147
|
+
* folder it writes; a `"repo"` one to `owner/repo`, which is what a backend that commits the
|
|
148
|
+
* emitted project into a git tree can act on — there is no directory for it to name.
|
|
146
149
|
*/
|
|
147
150
|
export function destinationPath(destination: CreateProjectDestination, slug: string): string {
|
|
148
|
-
if (destination.kind
|
|
149
|
-
|
|
151
|
+
if (destination.kind === "repo") {
|
|
152
|
+
return `${destination.owner}/${destination.repo || slug.trim()}`;
|
|
150
153
|
}
|
|
151
154
|
return joinPath(destination.parent, slug.trim());
|
|
152
155
|
}
|
|
@@ -280,6 +280,16 @@ export function createComposer(opts: ComposerOptions): Composer {
|
|
|
280
280
|
`
|
|
281
281
|
}
|
|
282
282
|
</div>
|
|
283
|
+
${
|
|
284
|
+
/* Said once, quietly, under the picker that caused it. A chat-only model still answers,
|
|
285
|
+
so this is not a gate — but the agent loop it silently disables is the whole reason
|
|
286
|
+
the panel exists, and nothing else on screen would have mentioned it. */
|
|
287
|
+
modelPicker.selectedLacksTools()
|
|
288
|
+
? html`<div class="ai-composer-note">
|
|
289
|
+
This model can't use editing tools — the assistant will answer but not edit.
|
|
290
|
+
</div>`
|
|
291
|
+
: nothing
|
|
292
|
+
}
|
|
283
293
|
</div>
|
|
284
294
|
`;
|
|
285
295
|
}
|