@jxsuite/studio 2.4.2 → 2.4.3

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/studio.js CHANGED
@@ -28687,6 +28687,71 @@ function renderAiPanelTemplate() {
28687
28687
  `;
28688
28688
  }
28689
28689
 
28690
+ // ../schema/src/component-meta.ts
28691
+ function collectSlotDefs(node, out = []) {
28692
+ if (!node || typeof node !== "object" || Array.isArray(node)) {
28693
+ return out;
28694
+ }
28695
+ const el = node;
28696
+ if (el.tagName === "slot") {
28697
+ const attrs = el.attributes;
28698
+ const rawName = attrs?.name;
28699
+ const name = typeof rawName === "string" ? rawName.trim() : "";
28700
+ const { children } = el;
28701
+ out.push({
28702
+ name,
28703
+ ...Array.isArray(children) && children.length > 0 ? { fallback: children } : {}
28704
+ });
28705
+ }
28706
+ if (Array.isArray(el.children)) {
28707
+ for (const c of el.children) {
28708
+ collectSlotDefs(c, out);
28709
+ }
28710
+ }
28711
+ return out;
28712
+ }
28713
+ function isPropEntry(def) {
28714
+ if (def == null) {
28715
+ return false;
28716
+ }
28717
+ if (typeof def !== "object") {
28718
+ return true;
28719
+ }
28720
+ const obj = def;
28721
+ return !obj.$prototype && !obj.$handler && !obj.$compute;
28722
+ }
28723
+ function componentMetaFrom(doc, path) {
28724
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
28725
+ return null;
28726
+ }
28727
+ const content = doc;
28728
+ const { tagName } = content;
28729
+ if (typeof tagName !== "string" || !tagName.includes("-")) {
28730
+ return null;
28731
+ }
28732
+ const slots = collectSlotDefs(content);
28733
+ const state2 = content.state ?? {};
28734
+ return {
28735
+ $id: typeof content.$id === "string" && content.$id ? content.$id : null,
28736
+ hasElements: Array.isArray(content.$elements) && content.$elements.length > 0,
28737
+ path,
28738
+ props: Object.entries(state2).filter(([, def]) => isPropEntry(def)).map(([name, def]) => {
28739
+ if (typeof def !== "object" || def === null) {
28740
+ return { default: def, name, type: typeof def };
28741
+ }
28742
+ const obj = def;
28743
+ return {
28744
+ default: obj.default,
28745
+ format: obj.format,
28746
+ name,
28747
+ type: obj.type
28748
+ };
28749
+ }),
28750
+ ...slots.length > 0 ? { slots } : {},
28751
+ tagName
28752
+ };
28753
+ }
28754
+
28690
28755
  // ../protocol/src/problem.ts
28691
28756
  var PROBLEM_TYPE_BASE = "https://jxsuite.com/problems/";
28692
28757
  function problemSlug(type) {
@@ -29098,7 +29163,41 @@ function createCloudPlatform(project) {
29098
29163
  return data.result;
29099
29164
  },
29100
29165
  async discoverComponents() {
29101
- return [];
29166
+ const SKIP = new Set(["node_modules", "dist", ".git", ".claude", ".obsidian", "build"]);
29167
+ const MAX_FILES = 400;
29168
+ const MAX_DEPTH = 6;
29169
+ const jsonPaths = [];
29170
+ const walk = async (dir, depth) => {
29171
+ if (depth > MAX_DEPTH || jsonPaths.length >= MAX_FILES) {
29172
+ return;
29173
+ }
29174
+ let entries;
29175
+ try {
29176
+ entries = await platform.listDirectory(dir);
29177
+ } catch {
29178
+ return;
29179
+ }
29180
+ const dirs = [];
29181
+ for (const entry of entries) {
29182
+ if (entry.type === "directory") {
29183
+ if (!SKIP.has(entry.name) && !entry.name.startsWith(".")) {
29184
+ dirs.push(entry.path);
29185
+ }
29186
+ } else if (entry.name.endsWith(".json") && jsonPaths.length < MAX_FILES) {
29187
+ jsonPaths.push(entry.path);
29188
+ }
29189
+ }
29190
+ await Promise.all(dirs.map(async (d) => walk(d, depth + 1)));
29191
+ };
29192
+ await walk("", 0);
29193
+ const metas = await Promise.all(jsonPaths.map(async (path) => {
29194
+ try {
29195
+ return componentMetaFrom(JSON.parse(await platform.readFile(path)), path);
29196
+ } catch {
29197
+ return null;
29198
+ }
29199
+ }));
29200
+ return metas.filter((m) => m !== null);
29102
29201
  },
29103
29202
  async codeService() {
29104
29203
  return null;
@@ -58591,9 +58690,9 @@ function renderDefsEditor(container) {
58591
58690
 
58592
58691
  // src/version.ts
58593
58692
  var APP_NAME = "Jx Studio";
58594
- var VERSION = "2.4.2";
58595
- var BUILD_DATE = "2026-08-24T19:48:51.173Z";
58596
- var GIT_COMMIT = "7859073";
58693
+ var VERSION = "2.4.3";
58694
+ var BUILD_DATE = "2026-08-25T03:20:06.206Z";
58695
+ var GIT_COMMIT = "624ef53";
58597
58696
  var LINKS = {
58598
58697
  github: "https://github.com/jxsuite/jx",
58599
58698
  docs: "https://github.com/jxsuite/jx#readme",
@@ -61876,5 +61975,5 @@ export {
61876
61975
  hasUnsavedTabs
61877
61976
  };
61878
61977
 
61879
- //# debugId=31462A53C2ABCD7964756E2164756E21
61978
+ //# debugId=980E02AF5DBA79D264756E2164756E21
61880
61979
  //# sourceMappingURL=studio.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/studio",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "description": "Jx Studio — visual builder for Jx documents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -57,14 +57,14 @@
57
57
  "dependencies": {
58
58
  "@atlaskit/pragmatic-drag-and-drop": "^3.0.0",
59
59
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.1.0",
60
- "@jxsuite/ai": "^0.36.2",
61
- "@jxsuite/collab": "^0.8.1",
62
- "@jxsuite/create": "^1.3.5",
63
- "@jxsuite/formulas": "^0.0.12",
64
- "@jxsuite/markup": "^0.4.5",
65
- "@jxsuite/protocol": "^1.2.0",
66
- "@jxsuite/runtime": "^2.0.4",
67
- "@jxsuite/schema": "^1.8.0",
60
+ "@jxsuite/ai": "^0.36.3",
61
+ "@jxsuite/collab": "^0.8.2",
62
+ "@jxsuite/create": "^1.3.6",
63
+ "@jxsuite/formulas": "^0.0.13",
64
+ "@jxsuite/markup": "^0.4.6",
65
+ "@jxsuite/protocol": "^1.2.1",
66
+ "@jxsuite/runtime": "^2.0.5",
67
+ "@jxsuite/schema": "^1.8.1",
68
68
  "@spectrum-web-components/accordion": "^1.12.2",
69
69
  "@spectrum-web-components/action-bar": "1.12.2",
70
70
  "@spectrum-web-components/action-button": "^1.12.2",
@@ -15,8 +15,10 @@ import { negotiateCollab } from "@jxsuite/collab/negotiate";
15
15
  import type { CollabNegotiation } from "@jxsuite/collab/negotiate";
16
16
  import type { WsCollabConnection } from "@jxsuite/collab/client";
17
17
  import type { ProjectConfig } from "@jxsuite/schema/types";
18
+ import { componentMetaFrom } from "@jxsuite/schema/component-meta";
18
19
  import type {
19
20
  AccountStatus,
21
+ ComponentMeta,
20
22
  CreateProjectDestination,
21
23
  DirEntry,
22
24
  ExtensionsInfo,
@@ -518,9 +520,66 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
518
520
 
519
521
  // ─── Components / code services (cloud: static-only posture) ──────────
520
522
 
523
+ /**
524
+ * Component discovery, by READING rather than executing.
525
+ *
526
+ * This returned `[]` under "no execution of project JS in the cloud", and the cost was not the
527
+ * loss of discovery — it was the canvas. `canvas-live-render` injects the `$elements` a
528
+ * document's tags need only when the registry is non-empty, so an empty registry meant nothing
529
+ * was ever registered, no component was ever fetched, and every component instance rendered as
530
+ * an unregistered custom element: an empty inline box. A page built from components came up as
531
+ * blank space between the layout's chrome.
532
+ *
533
+ * Discovery does not need to execute anything. For a JSON component it is a file read and a
534
+ * property lookup — `componentMetaFrom` is that function, shared with the two Bun backends so
535
+ * "which `state` entries are props" has one answer. Non-JSON component formats DO need a
536
+ * project-supplied parser, and those stay out: the posture is kept where it actually applies
537
+ * rather than across the whole feature.
538
+ *
539
+ * Bounded rather than unbounded: the walk skips the directories a scan has no business in and
540
+ * stops at a depth and a file count, because this runs against a remote session over HTTP and a
541
+ * pathological tree must not turn opening a project into thousands of requests.
542
+ */
521
543
  async discoverComponents() {
522
- // No execution of project JS in the cloud (MVP security posture).
523
- return [];
544
+ const SKIP = new Set(["node_modules", "dist", ".git", ".claude", ".obsidian", "build"]);
545
+ const MAX_FILES = 400;
546
+ const MAX_DEPTH = 6;
547
+ const jsonPaths: string[] = [];
548
+
549
+ const walk = async (dir: string, depth: number): Promise<void> => {
550
+ if (depth > MAX_DEPTH || jsonPaths.length >= MAX_FILES) {
551
+ return;
552
+ }
553
+ let entries: DirEntry[];
554
+ try {
555
+ entries = await platform.listDirectory(dir);
556
+ } catch {
557
+ return; // A directory that vanished mid-walk is not a discovery failure.
558
+ }
559
+ const dirs: string[] = [];
560
+ for (const entry of entries) {
561
+ if (entry.type === "directory") {
562
+ if (!SKIP.has(entry.name) && !entry.name.startsWith(".")) {
563
+ dirs.push(entry.path);
564
+ }
565
+ } else if (entry.name.endsWith(".json") && jsonPaths.length < MAX_FILES) {
566
+ jsonPaths.push(entry.path);
567
+ }
568
+ }
569
+ await Promise.all(dirs.map(async (d) => walk(d, depth + 1)));
570
+ };
571
+ await walk("", 0);
572
+
573
+ const metas = await Promise.all(
574
+ jsonPaths.map(async (path) => {
575
+ try {
576
+ return componentMetaFrom(JSON.parse(await platform.readFile(path)), path);
577
+ } catch {
578
+ return null; // Not JSON, not readable, or not a component — all the same answer here.
579
+ }
580
+ }),
581
+ );
582
+ return metas.filter((m): m is NonNullable<typeof m> => m !== null) as ComponentMeta[];
524
583
  },
525
584
 
526
585
  async codeService() {