@unoverse-platform/studio 0.1.73 → 0.1.75

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/bin/studio.mjs CHANGED
@@ -167,6 +167,11 @@ process.env.UNOVERSE_HOME = projectRoot;
167
167
  // topUpSamples explains the record that makes that distinction safe.
168
168
  {
169
169
  const { topUpSamples } = await import("../local/scaffold.mjs");
170
+ // NO PROJECT ARGUMENT ON PURPOSE. Since skills and blocks became org-scoped
171
+ // (2026-08-24) their samples need a project to land in, and a workspace may hold
172
+ // several — picking one here would drop a sample into an arbitrary org. Those
173
+ // samples arrive with `createFirstProject`, which is the only moment a destination
174
+ // is unambiguous; workspace-level samples (nodes) still top up on every launch.
170
175
  try { topUpSamples(projectRoot); } catch { /* a read-only folder must not block launch */ }
171
176
  }
172
177
  console.log(`\n Studio reading: ${designHome(projectRoot)}\n`);
@@ -73,7 +73,11 @@ export function createProject(home, name) {
73
73
  const root = join(designHome(home), name);
74
74
  if (existsSync(root)) throw new Error(`${name} already exists in this workspace`);
75
75
 
76
- for (const d of ["components/welcome", "apps", "styles"]) mkdirSync(join(root, d), { recursive: true });
76
+ // EVERY AUTHORED KIND LIVES UNDER THE ORG (2026-08-24). skills/ and blocks/ moved in
77
+ // beside components/apps/styles so a skill is org-scoped exactly like a component —
78
+ // two orgs may each ship an `hr-education-coach` without colliding on one flat name
79
+ // (items/collect.ts qualifies a skill's row `<org>/<name>`).
80
+ for (const d of ["components/welcome", "apps", "styles", "skills", "blocks"]) mkdirSync(join(root, d), { recursive: true });
77
81
  writeFileSync(join(root, "components/welcome/welcome.yaml"), WELCOME);
78
82
 
79
83
  // Worked example styles (sample-styles: brand ingredients + the override rules), so
@@ -111,9 +115,14 @@ export function createProject(home, name) {
111
115
  * makes that safe.
112
116
  */
113
117
  const SAMPLES = [
114
- { id: "nodes/samples", dir: "sample-nodes/samples", to: "nodes/samples" },
115
- { id: "prompts/skills/sample-complaint-handling", dir: "sample-prompts/skills/sample-complaint-handling", to: "prompts/skills/sample-complaint-handling" },
116
- { id: "prompts/blocks/sample-concise-answers", dir: "sample-prompts/blocks/formatting/sample-concise-answers.md", to: "prompts/blocks/formatting/sample-concise-answers.md" },
118
+ { id: "nodes/samples", dir: "sample-nodes/samples", to: () => "nodes/samples" },
119
+ // ORG-SCOPED since 2026-08-24: a sample skill/block lands in the FIRST project's own
120
+ // folder, because that is where authored skills and blocks now live. `to` is a
121
+ // function of the project for exactly that reason — the id stays the old flat string
122
+ // so a workspace that already installed these is not offered them a second time in a
123
+ // new location (the record is keyed by id, and re-copying is what it exists to stop).
124
+ { id: "prompts/skills/sample-complaint-handling", dir: "sample-prompts/skills/sample-complaint-handling", to: (p) => `design/${p}/skills/sample-complaint-handling` },
125
+ { id: "prompts/blocks/sample-concise-answers", dir: "sample-prompts/blocks/formatting/sample-concise-answers.md", to: (p) => `design/${p}/blocks/formatting/sample-concise-answers.md` },
117
126
  ];
118
127
 
119
128
  /**
@@ -127,25 +136,39 @@ const SAMPLES = [
127
136
  * That is what lets `unoverse studio` top up new samples on every launch without ever
128
137
  * resurrecting deleted work or overwriting changed files.
129
138
  */
130
- export function topUpSamples(home) {
139
+ export function topUpSamples(home, project) {
131
140
  const record = join(home, ".unoverse", "samples.json");
141
+ // An org-scoped sample has nowhere to go without a project. Called at launch (where
142
+ // the project is not known) the org samples are simply skipped — they arrive with the
143
+ // next `createFirstProject`, which is the only moment their destination exists.
144
+ const destOf = (s) => (typeof s.to === "function" ? s.to(project) : s.to);
145
+ const placeable = (s) => typeof s.to !== "function" || !!project;
132
146
  let seen = [];
133
147
  try {
134
148
  seen = JSON.parse(readFileSync(record, "utf8")).installed ?? [];
135
149
  } catch {
136
150
  // No record: a workspace from before records existed. Treat every sample already
137
151
  // on disk as seen (it was installed by an old scaffold), so nothing is re-copied.
138
- seen = SAMPLES.filter((s) => existsSync(join(home, s.to))).map((s) => s.id);
152
+ // The OLD flat paths count too: a workspace that installed these before the org
153
+ // move still holds them at prompts/…, and must not be handed a second copy.
154
+ const legacy = {
155
+ "prompts/skills/sample-complaint-handling": "prompts/skills/sample-complaint-handling",
156
+ "prompts/blocks/sample-concise-answers": "prompts/blocks/formatting/sample-concise-answers.md",
157
+ };
158
+ seen = SAMPLES.filter(
159
+ (s) => (placeable(s) && existsSync(join(home, destOf(s)))) || (legacy[s.id] && existsSync(join(home, legacy[s.id]))),
160
+ ).map((s) => s.id);
139
161
  }
140
162
  const here = dirname(fileURLToPath(import.meta.url));
141
163
  let changed = false;
142
164
  for (const s of SAMPLES) {
143
- if (seen.includes(s.id)) continue;
165
+ if (seen.includes(s.id) || !placeable(s)) continue;
144
166
  const src = join(here, s.dir);
145
167
  if (!existsSync(src)) continue;
146
- if (!existsSync(join(home, s.to))) {
147
- mkdirSync(dirname(join(home, s.to)), { recursive: true });
148
- cpSync(src, join(home, s.to), { recursive: true });
168
+ const dest = join(home, destOf(s));
169
+ if (!existsSync(dest)) {
170
+ mkdirSync(dirname(dest), { recursive: true });
171
+ cpSync(src, dest, { recursive: true });
149
172
  }
150
173
  seen.push(s.id);
151
174
  changed = true;
@@ -164,9 +187,11 @@ export function topUpSamples(home) {
164
187
  */
165
188
  export function createFirstProject(home, name) {
166
189
  const root = createProject(home, name);
167
- for (const d of ["prompts/skills", "prompts/blocks", "nodes"]) mkdirSync(join(home, d), { recursive: true });
190
+ // Only nodes/ remains beside the design tree; skills/ and blocks/ moved INTO the org
191
+ // (createProject makes them) so every authored kind sits in one place.
192
+ for (const d of ["nodes"]) mkdirSync(join(home, d), { recursive: true });
168
193
 
169
- topUpSamples(home);
194
+ topUpSamples(home, name);
170
195
 
171
196
  // SCAFFOLD WRITES NO `.claude/skills` (2026-08-21). It used to copy the Claude Code
172
197
  // authoring skills out of `vendor/skills` and into `<home>/.claude/skills`, which meant
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unoverse-platform/studio",
3
- "version": "0.1.73",
3
+ "version": "0.1.75",
4
4
  "type": "module",
5
5
  "description": "Unoverse Studio: the local authoring app. Reads your own rx/ folder, no backend.",
6
6
  "scripts": {