@jxsuite/protocol 2.0.0 → 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 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 — the editor
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` — the request/response shapes (`DirEntry`,
9
+ - `@jxsuite/protocol/types`: the request/response shapes (`DirEntry`,
10
10
  `GitStatusResult`, `StarterInfo`, `AiModelsResponse`, `CfConnection`, …).
11
- Environment-agnostic: no DOM, no node — importable in browsers, Bun, and
12
- Cloudflare Workers alike.
13
- - `@jxsuite/protocol/routes` — `STUDIO_ROUTES`, the canonical endpoint table
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 — the reference implementation |
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 — Studio degrades exactly as each entry's `degradation` describes (the
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
- a header-borne user token on the dev server, a vaulted OAuth token on cloud
56
- platforms — and must never be persisted by stateless implementations.
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": "2.0.0",
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": "^1.9.0"
30
+ "@jxsuite/schema": "^2.0.0"
31
31
  }
32
32
  }
package/src/routes.ts CHANGED
@@ -107,7 +107,8 @@ 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 files project-wide",
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"),
@@ -140,13 +141,20 @@ export const STUDIO_ROUTES = {
140
141
  "The canvas cannot address project files by URL, so a host whose origin does not serve the " +
141
142
  "site URL space renders no component $refs and no images.",
142
143
  ),
143
- fileRename: route("POST", "/__studio/file/rename", "Rename/move (+ refactor report)"),
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
+ ),
144
150
  references: route(
145
151
  "GET",
146
152
  "/__studio/references",
147
153
  "Where a file or a component tag is used (?path=&tag=, at least one) → ReferencesResult " +
148
154
  "{files, filesReferencing, refsTotal} — the read side of the rename refactor's own walker, " +
149
- "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)",
150
158
  'Usage counts are hidden: no "Used on N pages" in the inspector, no Selection: Find Usages, ' +
151
159
  "and delete/rename confirmations state no reference count.",
152
160
  ),
@@ -235,12 +243,49 @@ export const STUDIO_ROUTES = {
235
243
  * these paths mean the project's SOURCES, and a built page addresses its own OUTPUT by the same
236
244
  * paths (`/components/x.js` is the formula module here and the custom element there), so a
237
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.
238
251
  */
239
252
  buildSite: route(
240
253
  "POST",
241
254
  "/__studio/build",
242
- "Build the site to its output directory → {routes, files, errors, url}",
243
- "Open in Browser reports that this target cannot build a preview.",
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.",
244
289
  ),
245
290
 
246
291
  // ─── Packages ─────────────────────────────────────────────────────────────
@@ -259,11 +304,11 @@ export const STUDIO_ROUTES = {
259
304
  "Whether node_modules is stale",
260
305
  "The install-on-open prompt never shows.",
261
306
  ),
262
- packagesOutdated: route(
307
+ packagesVersions: route(
263
308
  "GET",
264
- "/__studio/packages/outdated",
265
- "Dependencies with newer versions (OutdatedInfo[])",
266
- "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.",
267
312
  ),
268
313
  packagesSetVersions: route(
269
314
  "POST",
package/src/types.ts CHANGED
@@ -453,15 +453,20 @@ export interface PackageInfo {
453
453
  dev?: boolean;
454
454
  }
455
455
 
456
- /** A dependency with a newer version available, as reported by `bun outdated` / the npm registry. */
457
- export interface OutdatedInfo {
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 {
458
465
  name: string;
459
466
  /** The version range pinned in package.json (e.g. "^0.19.0"). */
460
467
  current: string;
461
- /** The newest published version. */
468
+ /** The newest version published for this package on the registry. */
462
469
  latest: string;
463
- /** The newest version satisfying the current range, if known. */
464
- wanted?: string;
465
470
  dev?: boolean;
466
471
  }
467
472
 
@@ -531,6 +536,40 @@ export interface ImportProgressEvent {
531
536
  total?: number;
532
537
  }
533
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
+
534
573
  /** Options for the import-site pipeline (StudioPlatform.importSite). */
535
574
  export interface ImportSiteOptions {
536
575
  /** The live site to clone; must be http(s). */
@@ -543,14 +582,89 @@ export interface ImportSiteOptions {
543
582
  depth: number;
544
583
  /** Max pages to capture. */
545
584
  maxPages: number;
585
+ /** Which of the site's declared breakpoints the project keeps. Omitted means the default limit. */
586
+ breakpoints?: ImportBreakpointPolicy;
546
587
  /** Refine component/prop names with the LLM (requires a key). */
547
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;
548
612
  /** OpenAI-compatible credentials, from the user's AI settings. */
549
613
  apiKey?: string;
550
614
  baseUrl?: string;
551
615
  model?: string;
552
616
  }
553
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
+
554
668
  // ─── AI proxy ────────────────────────────────────────────────────────────────
555
669
 
556
670
  /** One entry of the AI proxy's model catalogue. */