@jxsuite/protocol 1.2.1 → 2.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/README.md +11 -10
- package/package.json +2 -2
- package/src/routes.ts +82 -10
- package/src/types.ts +174 -5
package/README.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
# @jxsuite/protocol
|
|
2
2
|
|
|
3
3
|
The **Studio Backend Protocol**: the wire types and canonical route table that
|
|
4
|
-
every Jx Studio backend implements. Jx Studio is backend-agnostic
|
|
4
|
+
every Jx Studio backend implements. Jx Studio is backend-agnostic. The editor
|
|
5
5
|
talks to whatever registered a `StudioPlatform` adapter (see
|
|
6
6
|
`@jxsuite/studio/platform`), and every adapter ultimately speaks the contract
|
|
7
7
|
defined here. This package is the single source of truth for that contract:
|
|
8
8
|
|
|
9
|
-
- `@jxsuite/protocol/types
|
|
9
|
+
- `@jxsuite/protocol/types`: the request/response shapes (`DirEntry`,
|
|
10
10
|
`GitStatusResult`, `StarterInfo`, `AiModelsResponse`, `CfConnection`, …).
|
|
11
|
-
Environment-agnostic
|
|
12
|
-
Cloudflare Workers alike.
|
|
13
|
-
- `@jxsuite/protocol/routes
|
|
11
|
+
Environment-agnostic, with no DOM and no node, so the types are importable
|
|
12
|
+
in browsers, Bun, and Cloudflare Workers alike.
|
|
13
|
+
- `@jxsuite/protocol/routes`: `STUDIO_ROUTES`, the canonical endpoint table
|
|
14
14
|
with methods, one-line contract summaries, and per-route optionality.
|
|
15
15
|
|
|
16
16
|
## Who implements this
|
|
17
17
|
|
|
18
18
|
| Backend | Transport |
|
|
19
19
|
| ------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
20
|
-
| `@jxsuite/server` (dev server) | The literal `/__studio/*` HTTP routes
|
|
20
|
+
| `@jxsuite/server` (dev server) | The literal `/__studio/*` HTTP routes (the reference implementation) |
|
|
21
21
|
| Desktop (Electrobun / Chromium) | Typed RPC / WebSocket JSON-RPC dispatching into the same handlers |
|
|
22
22
|
| Cloud platforms | HTTP behind a session gateway (e.g. `/api/v1/p/:owner/:repo/:branch/studio/*`), same sub-paths and shapes |
|
|
23
23
|
|
|
@@ -28,7 +28,7 @@ when any shape changes incompatibly.
|
|
|
28
28
|
## Core vs optional
|
|
29
29
|
|
|
30
30
|
Optional routes back optional `StudioPlatform` members. Omitting one is not an
|
|
31
|
-
error
|
|
31
|
+
error. Studio degrades exactly as each entry's `degradation` describes (the
|
|
32
32
|
starter picker empties, the Projects catalogue hides, the Publish panel
|
|
33
33
|
explains git-push publishing, and so on). `coreRouteNames()` /
|
|
34
34
|
`optionalRouteNames()` split the table programmatically; a conformance test
|
|
@@ -51,9 +51,10 @@ doc?, options? }` through the project's format registry; without it only
|
|
|
51
51
|
backend holds credentials (Studio then unlocks the assistant without a
|
|
52
52
|
locally stored key) and `managed: true` when the platform brokers them.
|
|
53
53
|
- **`cf/proxy`** is an allowlisted Cloudflare API passthrough (accounts and
|
|
54
|
-
Pages projects/deployments only). Credentials are injected by the backend
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
Pages projects/deployments only). Credentials are injected by the backend
|
|
55
|
+
and must never be persisted by stateless implementations. On the dev server
|
|
56
|
+
the credential is a header-borne user token, and on cloud platforms a
|
|
57
|
+
vaulted OAuth token.
|
|
57
58
|
- **Errors** are `{ error, code?, detail? }` (`ErrorBody`) with a meaningful
|
|
58
59
|
HTTP status; `code` is the machine-readable discriminator Studio switches
|
|
59
60
|
on (e.g. `remote_moved`, `cf_not_connected`, `needs_installation_access`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/protocol",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "The Studio Backend Protocol — wire types and the canonical route table every Jx Studio backend implements",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,6 @@
|
|
|
27
27
|
"test:coverage": "bun test --isolate --coverage"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@jxsuite/schema": "^
|
|
30
|
+
"@jxsuite/schema": "^2.0.0"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/src/routes.ts
CHANGED
|
@@ -107,19 +107,54 @@ export const STUDIO_ROUTES = {
|
|
|
107
107
|
files: route(
|
|
108
108
|
"GET",
|
|
109
109
|
"/__studio/files",
|
|
110
|
-
"List a directory (DirEntry[]); with ?glob=<pattern>, search matching
|
|
110
|
+
"List a directory (DirEntry[]) in stable path order; with ?glob=<pattern>, search matching " +
|
|
111
|
+
"files project-wide",
|
|
111
112
|
),
|
|
112
113
|
fileRead: route("GET", "/__studio/file", "Read a file's text content"),
|
|
113
114
|
fileWrite: route("PUT", "/__studio/file", "Write a file's text content"),
|
|
114
115
|
fileDelete: route("DELETE", "/__studio/file", "Delete a file"),
|
|
115
|
-
fileUpload: route(
|
|
116
|
-
|
|
116
|
+
fileUpload: route(
|
|
117
|
+
"POST",
|
|
118
|
+
"/__studio/file/upload",
|
|
119
|
+
"Upload binary content to a path → UploadResult {path, size?}. `path` is the answer, not an " +
|
|
120
|
+
"echo: a store that de-duplicates, suffixes, or normalizes names reports what it really wrote",
|
|
121
|
+
),
|
|
122
|
+
/**
|
|
123
|
+
* A project file as its own BYTES, at its own URL.
|
|
124
|
+
*
|
|
125
|
+
* `fileRead` answers `{ path, content }`, which is right for the editor and useless to the
|
|
126
|
+
* renderer: `@jxsuite/runtime` resolves a component `$ref` with `fetch(url).then(r => r.json())`
|
|
127
|
+
* and expects the DOCUMENT, and an `<img src>` cannot be a JSON envelope at all. So a backend
|
|
128
|
+
* whose origin does not serve the site's own URL space serves this instead, declares `assetSpace:
|
|
129
|
+
* "repo"`, and Studio addresses every project file under it.
|
|
130
|
+
*
|
|
131
|
+
* The path is appended to the prefix, so this is the one route whose `path` is a PREFIX rather
|
|
132
|
+
* than a whole path. It was a textual assertion on both sides of the cloud seam before it was a
|
|
133
|
+
* route; naming it here is what makes that assertion a contract.
|
|
134
|
+
*/
|
|
135
|
+
documentRaw: route(
|
|
136
|
+
"GET",
|
|
137
|
+
"/__studio/raw/",
|
|
138
|
+
"A project file as its own bytes, at its own URL — the mount a canvas renders against when " +
|
|
139
|
+
"the editor origin does not serve the site's URL space. The project-relative path is " +
|
|
140
|
+
"appended to this prefix",
|
|
141
|
+
"The canvas cannot address project files by URL, so a host whose origin does not serve the " +
|
|
142
|
+
"site URL space renders no component $refs and no images.",
|
|
143
|
+
),
|
|
144
|
+
fileRename: route(
|
|
145
|
+
"POST",
|
|
146
|
+
"/__studio/file/rename",
|
|
147
|
+
"Rename/move (+ refactor report). `from`/`to` are server-root-relative; every path in the " +
|
|
148
|
+
"report is relative to the active project (specs/server.md §4.1)",
|
|
149
|
+
),
|
|
117
150
|
references: route(
|
|
118
151
|
"GET",
|
|
119
152
|
"/__studio/references",
|
|
120
153
|
"Where a file or a component tag is used (?path=&tag=, at least one) → ReferencesResult " +
|
|
121
154
|
"{files, filesReferencing, refsTotal} — the read side of the rename refactor's own walker, " +
|
|
122
|
-
"cached until the backend's watcher sees the tree move (backs findReferences)"
|
|
155
|
+
"cached until the backend's watcher sees the tree move (backs findReferences). `?path=` is " +
|
|
156
|
+
"server-root-relative and every path in the answer is relative to the active project; a " +
|
|
157
|
+
"target outside that project is a 400, never a zero-result 200 (specs/server.md §4.1)",
|
|
123
158
|
'Usage counts are hidden: no "Used on N pages" in the inspector, no Selection: Find Usages, ' +
|
|
124
159
|
"and delete/rename confirmations state no reference count.",
|
|
125
160
|
),
|
|
@@ -208,12 +243,49 @@ export const STUDIO_ROUTES = {
|
|
|
208
243
|
* these paths mean the project's SOURCES, and a built page addresses its own OUTPUT by the same
|
|
209
244
|
* paths (`/components/x.js` is the formula module here and the custom element there), so a
|
|
210
245
|
* backend serves the built site somewhere of its own and says where.
|
|
246
|
+
*
|
|
247
|
+
* A backend that cannot run the compiler at all — a hosted one executes no project JS and has no
|
|
248
|
+
* bundler, image pipeline or filesystem — may instead serve the working tree as a site and let
|
|
249
|
+
* the runtime assemble each page in the reader's browser. `mode` reports which happened, and an
|
|
250
|
+
* absent `mode` means `built`, so a backend predating the field keeps its meaning.
|
|
211
251
|
*/
|
|
212
252
|
buildSite: route(
|
|
213
253
|
"POST",
|
|
214
254
|
"/__studio/build",
|
|
215
|
-
"Build the site
|
|
216
|
-
"
|
|
255
|
+
"Build or render the site → {routes, files, errors, mode?, url}",
|
|
256
|
+
"Build Site reports that this target cannot build.",
|
|
257
|
+
),
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Preview the site LIVE at a route, with no build on the path.
|
|
261
|
+
*
|
|
262
|
+
* The sibling of `buildSite` and what `Open in Browser` reaches first. Nothing is compiled: the
|
|
263
|
+
* working tree is composed on demand and the runtime assembles each page in the reader's browser,
|
|
264
|
+
* so what opens carries edits nobody has saved (see `previewOverlay`).
|
|
265
|
+
*
|
|
266
|
+
* `reused` is the answer a caller must honour: `true` means a client already holding this
|
|
267
|
+
* project's preview took the route, and opening a tab anyway would leave the author with two on
|
|
268
|
+
* one project.
|
|
269
|
+
*/
|
|
270
|
+
previewSite: route(
|
|
271
|
+
"POST",
|
|
272
|
+
"/__studio/preview",
|
|
273
|
+
"Preview the working tree at a route → {routes, files, errors, mode, url, reused}",
|
|
274
|
+
"Open in Browser falls back to buildSite.",
|
|
275
|
+
),
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The unsaved bytes a live preview reads before the disk.
|
|
279
|
+
*
|
|
280
|
+
* POST publishes one document's bytes, DELETE retracts one (or all, with no path). The editor
|
|
281
|
+
* owns the lifecycle — it knows which documents are dirty and when a save, a close or a discard
|
|
282
|
+
* ends that — so a backend stores what it is told and decides nothing.
|
|
283
|
+
*/
|
|
284
|
+
previewOverlay: route(
|
|
285
|
+
"POST",
|
|
286
|
+
"/__studio/preview/overlay",
|
|
287
|
+
"Publish (POST) or retract (DELETE) a document's unsaved bytes → 204",
|
|
288
|
+
"A live preview shows the last saved state instead of the editor's.",
|
|
217
289
|
),
|
|
218
290
|
|
|
219
291
|
// ─── Packages ─────────────────────────────────────────────────────────────
|
|
@@ -232,11 +304,11 @@ export const STUDIO_ROUTES = {
|
|
|
232
304
|
"Whether node_modules is stale",
|
|
233
305
|
"The install-on-open prompt never shows.",
|
|
234
306
|
),
|
|
235
|
-
|
|
307
|
+
packagesVersions: route(
|
|
236
308
|
"GET",
|
|
237
|
-
"/__studio/packages/
|
|
238
|
-
"
|
|
239
|
-
"The update affordances are hidden.",
|
|
309
|
+
"/__studio/packages/versions",
|
|
310
|
+
"Newest published version of each dependency (PackageVersionInfo[])",
|
|
311
|
+
"The Latest column stays empty and the update affordances are hidden.",
|
|
240
312
|
),
|
|
241
313
|
packagesSetVersions: route(
|
|
242
314
|
"POST",
|
package/src/types.ts
CHANGED
|
@@ -68,6 +68,43 @@ export interface RenameResult {
|
|
|
68
68
|
error?: string;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* What an upload actually stored.
|
|
73
|
+
*
|
|
74
|
+
* `path` is the ANSWER, not an echo. A backend may store the bytes somewhere other than the path
|
|
75
|
+
* asked for — a store that de-duplicates by content hash, one that appends a collision suffix, one
|
|
76
|
+
* that normalizes a name — and the client has to write a reference to what was really written. The
|
|
77
|
+
* upload response used to be untyped (`Promise<unknown>`), and every caller ignored it and used the
|
|
78
|
+
* requested path, so any such backend produced a document pointing at a file that is not there.
|
|
79
|
+
*
|
|
80
|
+
* `size` is what the backend recorded, when it says: the client already knows the bytes it sent, so
|
|
81
|
+
* a differing value is the backend telling you it transformed them.
|
|
82
|
+
*/
|
|
83
|
+
export interface UploadResult {
|
|
84
|
+
/** Project-relative path the bytes were stored at. */
|
|
85
|
+
path: string;
|
|
86
|
+
/** Stored size in bytes, when the backend reports one. */
|
|
87
|
+
size?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* What a backend will accept as an upload, when it says.
|
|
92
|
+
*
|
|
93
|
+
* Every field is optional and absence means "no declared limit" — a client must not invent one,
|
|
94
|
+
* because a limit it made up is a file the user cannot upload for no reason it can name. A DECLARED
|
|
95
|
+
* limit is different: it lets the client refuse before spending the round trip, and name the number
|
|
96
|
+
* in the refusal.
|
|
97
|
+
*/
|
|
98
|
+
export interface AssetCapabilities {
|
|
99
|
+
/** Largest single upload the backend accepts, in bytes. */
|
|
100
|
+
maxUploadBytes?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Media types and extensions the backend accepts, in `<input accept>` syntax (`"image/*,.pdf"`).
|
|
103
|
+
* Narrows the client's own default; it never widens it.
|
|
104
|
+
*/
|
|
105
|
+
accept?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
71
108
|
/** One kind of reference in one file, with how many times it occurs there. */
|
|
72
109
|
export interface ReferenceHit {
|
|
73
110
|
/**
|
|
@@ -389,6 +426,24 @@ export interface SecretsSetResponse {
|
|
|
389
426
|
names: string[];
|
|
390
427
|
}
|
|
391
428
|
|
|
429
|
+
// ─── User settings (desktop launchers; specs/desktop.md §3.1) ───────────────
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* A set of changes to the user-level settings store.
|
|
433
|
+
*
|
|
434
|
+
* A patch rather than the whole map, and for the same reason {@link SecretsSetRequest} is one: a key
|
|
435
|
+
* named by neither `set` nor `remove` must be left exactly as it was found. Writing the whole map
|
|
436
|
+
* let any window overwrite the shared store with its own view of it — and on the chromium launcher
|
|
437
|
+
* every window is a separate process with a separate browser profile, so a window holding no
|
|
438
|
+
* settings could clear the credentials another had just stored.
|
|
439
|
+
*/
|
|
440
|
+
export interface SettingsPatch {
|
|
441
|
+
/** Keys to store, with their new values. */
|
|
442
|
+
set?: Record<string, string>;
|
|
443
|
+
/** Keys to forget. */
|
|
444
|
+
remove?: string[];
|
|
445
|
+
}
|
|
446
|
+
|
|
392
447
|
// ─── Packages ────────────────────────────────────────────────────────────────
|
|
393
448
|
|
|
394
449
|
export interface PackageInfo {
|
|
@@ -398,15 +453,20 @@ export interface PackageInfo {
|
|
|
398
453
|
dev?: boolean;
|
|
399
454
|
}
|
|
400
455
|
|
|
401
|
-
/**
|
|
402
|
-
|
|
456
|
+
/**
|
|
457
|
+
* A dependency's pinned range beside the newest version published for it on the npm registry.
|
|
458
|
+
*
|
|
459
|
+
* Reported for EVERY dependency whose spec is a registry range, whether or not it is behind — "is
|
|
460
|
+
* this outdated?" is a comparison the caller makes, not a filter the backend applies. That matters
|
|
461
|
+
* for the Packages table, which shows a Latest column for every row: a backend that only answered
|
|
462
|
+
* about outdated packages could describe the up-to-date ones only as a blank.
|
|
463
|
+
*/
|
|
464
|
+
export interface PackageVersionInfo {
|
|
403
465
|
name: string;
|
|
404
466
|
/** The version range pinned in package.json (e.g. "^0.19.0"). */
|
|
405
467
|
current: string;
|
|
406
|
-
/** The newest published
|
|
468
|
+
/** The newest version published for this package on the registry. */
|
|
407
469
|
latest: string;
|
|
408
|
-
/** The newest version satisfying the current range, if known. */
|
|
409
|
-
wanted?: string;
|
|
410
470
|
dev?: boolean;
|
|
411
471
|
}
|
|
412
472
|
|
|
@@ -476,6 +536,40 @@ export interface ImportProgressEvent {
|
|
|
476
536
|
total?: number;
|
|
477
537
|
}
|
|
478
538
|
|
|
539
|
+
/**
|
|
540
|
+
* How many of a site's declared breakpoints the imported project keeps.
|
|
541
|
+
*
|
|
542
|
+
* A real site declares as many as it has accumulated frameworks — nine is ordinary — and each one
|
|
543
|
+
* becomes a canvas size in Studio and a column in every style editor. `limit` is the default
|
|
544
|
+
* because that is the complaint it answers; `explicit` is for an author who already knows the
|
|
545
|
+
* widths their project should have. `rounding` decides which DECLARED width backs a kept one, since
|
|
546
|
+
* the styles flip where the site says they do, not where the author wishes they did.
|
|
547
|
+
*/
|
|
548
|
+
export type BreakpointRounding = "nearest" | "down" | "up";
|
|
549
|
+
|
|
550
|
+
export interface ImportBreakpointPolicy {
|
|
551
|
+
mode: "all" | "limit" | "explicit";
|
|
552
|
+
/** `limit` only — how many to keep, evenly spaced across the declared range. */
|
|
553
|
+
count?: number;
|
|
554
|
+
/** `explicit` only — the widths the project should have, in CSS pixels. */
|
|
555
|
+
widths?: number[];
|
|
556
|
+
/** `limit` and `explicit` — how a kept width matches a declared one. */
|
|
557
|
+
rounding?: BreakpointRounding;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* The line the import stream sends the moment the destination exists and holds a `project.json`.
|
|
562
|
+
*
|
|
563
|
+
* It is what makes an import watchable. The emit phase rewrites that file completely, so nothing
|
|
564
|
+
* here is a guess about the result — it says the directory is now a PROJECT, so the caller can open
|
|
565
|
+
* it and see the pages, components and assets arrive in its own file tree over the several minutes
|
|
566
|
+
* a crawl takes, rather than facing a welcome screen until the run ends.
|
|
567
|
+
*/
|
|
568
|
+
export interface ImportReadyEvent {
|
|
569
|
+
/** The project root, in the platform's own form. */
|
|
570
|
+
root: string;
|
|
571
|
+
}
|
|
572
|
+
|
|
479
573
|
/** Options for the import-site pipeline (StudioPlatform.importSite). */
|
|
480
574
|
export interface ImportSiteOptions {
|
|
481
575
|
/** The live site to clone; must be http(s). */
|
|
@@ -488,14 +582,89 @@ export interface ImportSiteOptions {
|
|
|
488
582
|
depth: number;
|
|
489
583
|
/** Max pages to capture. */
|
|
490
584
|
maxPages: number;
|
|
585
|
+
/** Which of the site's declared breakpoints the project keeps. Omitted means the default limit. */
|
|
586
|
+
breakpoints?: ImportBreakpointPolicy;
|
|
491
587
|
/** Refine component/prop names with the LLM (requires a key). */
|
|
492
588
|
aiComponents: boolean;
|
|
589
|
+
/**
|
|
590
|
+
* Build the emitted project and screenshot-diff every page against the original.
|
|
591
|
+
*
|
|
592
|
+
* Off by default: it runs a full compile and a second browser pass, so it roughly doubles the
|
|
593
|
+
* run. What it buys is the one number the assistant can act on — a page at 61% fidelity is a
|
|
594
|
+
* question worth asking, and every other finding is a count of things that were skipped.
|
|
595
|
+
*/
|
|
596
|
+
verify?: boolean;
|
|
597
|
+
/**
|
|
598
|
+
* Pixelmatch's per-pixel COLOUR tolerance, 0..1, for `verify` (default 0.15).
|
|
599
|
+
*
|
|
600
|
+
* It decides when two pixels count as the same colour, so it moves the score and never the
|
|
601
|
+
* outcome. `verifyMinFidelity` is the bar.
|
|
602
|
+
*/
|
|
603
|
+
verifyThreshold?: number;
|
|
604
|
+
/**
|
|
605
|
+
* The average fidelity, 0..100, this run has to reach for `verify` to report `passed`.
|
|
606
|
+
*
|
|
607
|
+
* A finding rather than a failure on this transport: the project is written and opened either
|
|
608
|
+
* way, and the summary says the bar was missed. `jx-import` uses the same number to decide an
|
|
609
|
+
* exit code, because a script in a pipeline has no reader to tell.
|
|
610
|
+
*/
|
|
611
|
+
verifyMinFidelity?: number;
|
|
493
612
|
/** OpenAI-compatible credentials, from the user's AI settings. */
|
|
494
613
|
apiKey?: string;
|
|
495
614
|
baseUrl?: string;
|
|
496
615
|
model?: string;
|
|
497
616
|
}
|
|
498
617
|
|
|
618
|
+
/** Per-page fidelity from the `verify` pass. */
|
|
619
|
+
export interface ImportVerifyPage {
|
|
620
|
+
route: string;
|
|
621
|
+
/** 0..100 — the percentage of pixels that matched the original. */
|
|
622
|
+
fidelity: number;
|
|
623
|
+
/**
|
|
624
|
+
* Console errors and uncaught exceptions the rendered page produced.
|
|
625
|
+
*
|
|
626
|
+
* What a percentage cannot say. A page that 404s on fifteen images scores badly, and only this
|
|
627
|
+
* and `failedRequests` name the reason (issue #232).
|
|
628
|
+
*/
|
|
629
|
+
consoleErrors?: number;
|
|
630
|
+
/** Requests the rendered page made that failed or answered 4xx/5xx. */
|
|
631
|
+
failedRequests?: number;
|
|
632
|
+
/** Why this page has no score at all — it could not be rendered. */
|
|
633
|
+
error?: string;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* What the run produced, carried on the import stream's terminal `done` line.
|
|
638
|
+
*
|
|
639
|
+
* It exists because the pipeline computed all of this and the HTTP layer threw it away: the `done`
|
|
640
|
+
* line carried `{root, config}` and nothing else, so the caller could report that an import had
|
|
641
|
+
* happened and nothing whatever about what it found. Every field is optional — a backend that does
|
|
642
|
+
* not send one is not broken, and an older client ignores them.
|
|
643
|
+
*/
|
|
644
|
+
export interface ImportSiteSummary {
|
|
645
|
+
pages?: { route: string; title: string; nodeCount: number }[];
|
|
646
|
+
fileCount?: number;
|
|
647
|
+
/** Soft failures the pipeline recorded: assets that would not download, pages it skipped. */
|
|
648
|
+
warnings?: string[];
|
|
649
|
+
/** Present only when `verify` was requested and reference screenshots were captured. */
|
|
650
|
+
verify?: {
|
|
651
|
+
averageFidelity: number;
|
|
652
|
+
reportDir: string;
|
|
653
|
+
pages?: ImportVerifyPage[];
|
|
654
|
+
/**
|
|
655
|
+
* Whether the run met its bar: the project built cleanly, every page rendered, and the average
|
|
656
|
+
* fidelity reached `minFidelity`. Over HTTP that bar is 0 by default, so `passed: false` from
|
|
657
|
+
* this backend means the project did not build or a page did not render — not that the clone
|
|
658
|
+
* merely scored low. The `jx-import` CLI sets a real floor and exits non-zero.
|
|
659
|
+
*/
|
|
660
|
+
passed?: boolean;
|
|
661
|
+
/** The bar `passed` was measured against, 0..100. */
|
|
662
|
+
minFidelity?: number;
|
|
663
|
+
/** Errors the compiler reported building the emitted project. */
|
|
664
|
+
buildErrors?: string[];
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
499
668
|
// ─── AI proxy ────────────────────────────────────────────────────────────────
|
|
500
669
|
|
|
501
670
|
/** One entry of the AI proxy's model catalogue. */
|