@urbicon-ui/design 6.3.1 → 6.3.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/README.md CHANGED
@@ -20,6 +20,17 @@ bun add -d @urbicon-ui/design # dev tooling — not a runtime dependency
20
20
  This exposes the `urbicon` command (a self-contained, Node-runnable bundle — no
21
21
  Bun required at the consumer side).
22
22
 
23
+ > **Running it standalone (no local install).** The bin is `urbicon` but the package
24
+ > is `@urbicon-ui/design`, so a bare `bunx urbicon …` from a project that hasn't
25
+ > installed it fails with `GET …/urbicon 404` (it looks for a package literally named
26
+ > `urbicon`). To run the CLI without a local install, name both the package and the bin:
27
+ >
28
+ > ```bash
29
+ > bunx --package @urbicon-ui/design urbicon validate src/ # or: npx --package @urbicon-ui/design urbicon …
30
+ > ```
31
+ >
32
+ > Inside a project that already has `@urbicon-ui/design` installed, plain `bunx urbicon …` resolves fine.
33
+
23
34
  ## Onboarding a consumer project
24
35
 
25
36
  ```bash
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  import { readFile as readFile10 } from "node:fs/promises";
5
- import { resolve as resolve9 } from "node:path";
5
+ import { resolve as resolve10 } from "node:path";
6
6
 
7
7
  // src/cli/args.ts
8
8
  var BOOLEAN_FLAGS = new Set([
@@ -168,12 +168,22 @@ function parseIntent(body) {
168
168
  return emptyIntent();
169
169
  const lines = section.split(`
170
170
  `);
171
+ const isFieldOrHeading = (l) => /^\*\*[^*]+:\*\*/.test(l) || /^#{1,6}\s/.test(l);
171
172
  const inlineField = (label) => {
172
- const re = new RegExp(`^\\*\\*${label}:\\*\\*\\s*(.+)$`);
173
- for (const l of lines) {
174
- const m = l.match(re);
175
- if (m)
176
- return m[1].trim();
173
+ const labelRe = new RegExp(`^\\*\\*${label}:\\*\\*\\s*(.*)$`);
174
+ for (let i = 0;i < lines.length; i++) {
175
+ const m = lines[i].match(labelRe);
176
+ if (!m)
177
+ continue;
178
+ const parts = m[1].trim() ? [m[1].trim()] : [];
179
+ for (let j = i + 1;j < lines.length; j++) {
180
+ const l = lines[j];
181
+ if (l.trim() === "" || isFieldOrHeading(l))
182
+ break;
183
+ parts.push(l.trim());
184
+ }
185
+ const value = parts.join(" ").trim();
186
+ return value === "" ? undefined : value;
177
187
  }
178
188
  return;
179
189
  };
@@ -181,22 +191,33 @@ function parseIntent(body) {
181
191
  const items = [];
182
192
  const labelRe = new RegExp(`^\\*\\*${label}:\\*\\*\\s*(.*)$`);
183
193
  let capturing = false;
194
+ let inInlineRun = false;
184
195
  for (const l of lines) {
185
196
  if (!capturing) {
186
197
  const m = l.match(labelRe);
187
198
  if (m) {
188
199
  capturing = true;
200
+ inInlineRun = true;
189
201
  const inline = m[1].trim();
190
202
  if (inline)
191
203
  items.push(...splitList(inline));
192
204
  }
193
205
  continue;
194
206
  }
195
- if (/^\*\*[^*]+:\*\*/.test(l))
207
+ if (isFieldOrHeading(l))
196
208
  break;
197
209
  const bullet = l.match(/^\s*[-*]\s+(.+)$/);
198
- if (bullet)
210
+ if (bullet) {
199
211
  items.push(bullet[1].trim());
212
+ inInlineRun = false;
213
+ continue;
214
+ }
215
+ if (l.trim() === "") {
216
+ inInlineRun = false;
217
+ continue;
218
+ }
219
+ if (inInlineRun)
220
+ items.push(...splitList(l.trim()));
200
221
  }
201
222
  return items;
202
223
  };
@@ -811,6 +832,51 @@ async function loadComponentLlm(slug) {
811
832
  return null;
812
833
  }
813
834
 
835
+ // src/cli/installed.ts
836
+ import { readFileSync } from "node:fs";
837
+ import { dirname as dirname3, join as join2, parse, resolve as resolve4 } from "node:path";
838
+ var DEP_FIELDS = [
839
+ "dependencies",
840
+ "devDependencies",
841
+ "peerDependencies",
842
+ "optionalDependencies"
843
+ ];
844
+ function readConsumerDependencies(cwd = process.cwd()) {
845
+ let dir = resolve4(cwd);
846
+ const { root } = parse(dir);
847
+ for (;; ) {
848
+ try {
849
+ const pkg = JSON.parse(readFileSync(join2(dir, "package.json"), "utf-8"));
850
+ const names = new Set;
851
+ for (const field of DEP_FIELDS) {
852
+ const deps = pkg[field];
853
+ if (deps && typeof deps === "object") {
854
+ for (const name of Object.keys(deps))
855
+ names.add(name);
856
+ }
857
+ }
858
+ return names;
859
+ } catch {}
860
+ if (dir === root)
861
+ return null;
862
+ dir = dirname3(dir);
863
+ }
864
+ }
865
+ function installStateFor(pkg, deps) {
866
+ if (!deps)
867
+ return "unknown";
868
+ let hasUrbiconContext = false;
869
+ for (const d of deps) {
870
+ if (d.startsWith("@urbicon-ui/")) {
871
+ hasUrbiconContext = true;
872
+ break;
873
+ }
874
+ }
875
+ if (!hasUrbiconContext)
876
+ return "unknown";
877
+ return deps.has(pkg) ? "installed" : "missing";
878
+ }
879
+
814
880
  // src/cli/commands/find.ts
815
881
  function variantSummary(entry) {
816
882
  return entry.variants.filter((v) => !v.values.every((x) => x === "true" || x === "false")).map((v) => `${v.name}: ${v.values.join("/")}`).join(" · ");
@@ -820,8 +886,14 @@ function shortDescription(description) {
820
886
  `)[0]?.trim() ?? "";
821
887
  return firstLine.length > 140 ? `${firstLine.slice(0, 139)}…` : firstLine;
822
888
  }
823
- function formatEntry(entry) {
824
- const lines = [` ${entry.name} · ${entry.slug}`, ` ${shortDescription(entry.description)}`];
889
+ function packageTag(entry, state) {
890
+ return state === "missing" ? `${entry.package} · not installed` : entry.package;
891
+ }
892
+ function formatEntry(entry, state) {
893
+ const lines = [
894
+ ` ${entry.name} · ${entry.slug} · ${packageTag(entry, state)}`,
895
+ ` ${shortDescription(entry.description)}`
896
+ ];
825
897
  const variants = variantSummary(entry);
826
898
  if (variants)
827
899
  lines.push(` ${variants}`);
@@ -850,8 +922,14 @@ async function runFind(positionals, flags) {
850
922
  return EXIT.FAIL;
851
923
  }
852
924
  const results = query ? matchComponents(components, query, tags, limit) : components.filter((c) => !tags || c.tags.some((t) => tags.includes(t)));
925
+ const deps = readConsumerDependencies();
926
+ const stateOf = (entry) => installStateFor(entry.package, deps);
853
927
  if (asJson) {
854
- console.log(JSON.stringify(results, null, 2));
928
+ const annotated = results.map((entry) => {
929
+ const state = stateOf(entry);
930
+ return { ...entry, installed: state === "unknown" ? null : state === "installed" };
931
+ });
932
+ console.log(JSON.stringify(annotated, null, 2));
855
933
  return EXIT.OK;
856
934
  }
857
935
  if (results.length === 0) {
@@ -862,15 +940,31 @@ async function runFind(positionals, flags) {
862
940
  console.log(`${header}
863
941
  `);
864
942
  for (const entry of results) {
865
- console.log(`${formatEntry(entry)}
943
+ console.log(`${formatEntry(entry, stateOf(entry))}
866
944
  `);
867
945
  }
946
+ const missing = [
947
+ ...new Set(results.filter((e) => stateOf(e) === "missing").map((e) => e.package))
948
+ ];
949
+ if (missing.length > 0) {
950
+ console.log(`⚠ Not in your dependencies: ${missing.join(", ")} — install before importing (e.g. \`bun add ${missing[0]}\`).`);
951
+ }
868
952
  console.log("→ `urbicon get-component <slug>` for the full API · `get_css_reference` for tokens.");
869
953
  return EXIT.OK;
870
954
  }
871
955
 
872
956
  // src/cli/commands/get-component.ts
873
957
  var SECTIONS = ["overview", "examples", "variants", "api", "slots"];
958
+ async function warnIfNotInstalled(slug) {
959
+ try {
960
+ const entry = (await loadCatalog()).components.find((c) => c.slug === slug);
961
+ if (!entry)
962
+ return;
963
+ if (installStateFor(entry.package, readConsumerDependencies()) === "missing") {
964
+ console.error(`⚠ ${entry.name} ships from ${entry.package}, which isn't in your dependencies — ` + `install it before importing (e.g. \`bun add ${entry.package}\`).`);
965
+ }
966
+ } catch {}
967
+ }
874
968
  async function runGetComponent(positionals, flags) {
875
969
  const slug = positionals[0];
876
970
  if (!slug) {
@@ -893,6 +987,7 @@ async function runGetComponent(positionals, flags) {
893
987
  printError(`component "${slug}" not found. Run \`urbicon find <query>\` to discover the slug.`);
894
988
  return EXIT.FAIL;
895
989
  }
990
+ await warnIfNotInstalled(slug);
896
991
  if (!section || section === "full") {
897
992
  console.log(content.trim());
898
993
  return EXIT.OK;
@@ -908,7 +1003,7 @@ async function runGetComponent(positionals, flags) {
908
1003
 
909
1004
  // src/cli/commands/hook.ts
910
1005
  import { readFile as readFile5 } from "node:fs/promises";
911
- import { resolve as resolve4 } from "node:path";
1006
+ import { resolve as resolve5 } from "node:path";
912
1007
  // ../design-engine/src/linter/heuristics.ts
913
1008
  var CHROMATIC_INTENTS = ["primary", "secondary", "success", "warning", "danger", "info"];
914
1009
  var HEURISTIC_THRESHOLDS = {
@@ -1740,6 +1835,53 @@ var KNOWN_BAD_NAMESPACES = {
1740
1835
  "status-": "Use a `feedback-*` token (feedback-success, feedback-error, …) or a bare intent (`success`, `danger`).",
1741
1836
  "-fg": "Use `text-on-primary` / `text-on-surface` for foreground-on-intent text."
1742
1837
  };
1838
+ function isSingleEditApart(a, b) {
1839
+ if (a === b)
1840
+ return false;
1841
+ if (a.length === b.length) {
1842
+ let diffs = 0;
1843
+ let at = -1;
1844
+ for (let i2 = 0;i2 < a.length; i2++) {
1845
+ if (a[i2] !== b[i2]) {
1846
+ diffs++;
1847
+ if (at === -1)
1848
+ at = i2;
1849
+ }
1850
+ }
1851
+ if (diffs === 1)
1852
+ return true;
1853
+ return diffs === 2 && at >= 0 && a[at] === b[at + 1] && a[at + 1] === b[at];
1854
+ }
1855
+ if (Math.abs(a.length - b.length) !== 1)
1856
+ return false;
1857
+ const [short, long] = a.length < b.length ? [a, b] : [b, a];
1858
+ let i = 0;
1859
+ let j = 0;
1860
+ let skipped = false;
1861
+ while (i < short.length && j < long.length) {
1862
+ if (short[i] === long[j]) {
1863
+ i++;
1864
+ j++;
1865
+ } else if (!skipped) {
1866
+ skipped = true;
1867
+ j++;
1868
+ } else {
1869
+ return false;
1870
+ }
1871
+ }
1872
+ return true;
1873
+ }
1874
+ function suggestIntentTypo(core) {
1875
+ if (core.includes("-"))
1876
+ return null;
1877
+ if (INTENT_NAMES.includes(core))
1878
+ return null;
1879
+ for (const intent of INTENT_NAMES) {
1880
+ if (isSingleEditApart(core, intent))
1881
+ return intent;
1882
+ }
1883
+ return null;
1884
+ }
1743
1885
 
1744
1886
  // ../design-engine/src/linter/rules.ts
1745
1887
  var SHADCN_FIX = "This is shadcn/ui vocabulary, not Urbicon UI. Use surface tokens (`bg-surface-base`/`-elevated`), text tokens (`text-text-primary`/`-secondary`), or intents (`bg-primary`, `text-success`).";
@@ -1951,6 +2093,81 @@ var dynamicClassInterpolation = {
1951
2093
  return dedupeByLine(findings);
1952
2094
  }
1953
2095
  };
2096
+ var INTERNAL_SUBPATH_SEGMENTS = new Set([
2097
+ "primitives",
2098
+ "components",
2099
+ "lib",
2100
+ "dist",
2101
+ "src",
2102
+ "icons"
2103
+ ]);
2104
+ function isDeepInternalSubpath(subpath) {
2105
+ if (/\.svelte(\.[jt]s)?$|\.[jt]s$/.test(subpath))
2106
+ return true;
2107
+ return subpath.split("/").some((seg) => INTERNAL_SUBPATH_SEGMENTS.has(seg));
2108
+ }
2109
+ var deepInternalImport = {
2110
+ id: "deep-internal-import",
2111
+ severity: "error",
2112
+ description: "Deep/internal import into an `@urbicon-ui` package instead of its public root.",
2113
+ check(lines) {
2114
+ const re = /['"](@urbicon-ui\/[a-z-]+)\/([^'"]+)['"]/g;
2115
+ const findings = [];
2116
+ lines.forEach((line, i) => {
2117
+ for (const m of line.matchAll(re)) {
2118
+ const pkg = m[1];
2119
+ const subpath = m[2];
2120
+ if (!isDeepInternalSubpath(subpath))
2121
+ continue;
2122
+ findings.push({
2123
+ ruleId: this.id,
2124
+ severity: this.severity,
2125
+ kind: "deterministic",
2126
+ message: `Deep import \`${pkg}/${subpath}\` reaches into ${pkg}'s internals — they can move between releases.`,
2127
+ fix: `Import from the package root: \`import { … } from '${pkg}'\`.`,
2128
+ line: i + 1,
2129
+ match: `${pkg}/${subpath}`
2130
+ });
2131
+ }
2132
+ });
2133
+ return dedupeByLine(findings);
2134
+ }
2135
+ };
2136
+ var hardcodedMotion = {
2137
+ id: "hardcoded-motion",
2138
+ severity: "error",
2139
+ description: "Hardcoded transition duration or `cubic-bezier()` easing instead of a motion token.",
2140
+ check(lines) {
2141
+ const duration = /\bduration-\[\d+(?:\.\d+)?m?s\]/g;
2142
+ const easing = /\bease-\[cubic-bezier\([^\]]*\)\]/g;
2143
+ const findings = [];
2144
+ lines.forEach((line, i) => {
2145
+ for (const m of line.matchAll(duration)) {
2146
+ findings.push({
2147
+ ruleId: this.id,
2148
+ severity: this.severity,
2149
+ kind: "deterministic",
2150
+ message: `Hardcoded transition duration \`${m[0]}\` bypasses the motion scale (no global speed / reduced-motion control).`,
2151
+ fix: "Use a duration token: `duration-[var(--blocks-duration-fast)]` / `-normal` / `-slow`.",
2152
+ line: i + 1,
2153
+ match: m[0]
2154
+ });
2155
+ }
2156
+ for (const m of line.matchAll(easing)) {
2157
+ findings.push({
2158
+ ruleId: this.id,
2159
+ severity: this.severity,
2160
+ kind: "deterministic",
2161
+ message: `Hardcoded \`cubic-bezier()\` easing \`${m[0]}\` bypasses the motion system's easing tokens.`,
2162
+ fix: "Use an easing token: `ease-[var(--blocks-ease-smooth)]` / `-snappy` / `-gentle`, or a named Tailwind ease (`ease-out`).",
2163
+ line: i + 1,
2164
+ match: m[0]
2165
+ });
2166
+ }
2167
+ });
2168
+ return dedupeByLine(findings);
2169
+ }
2170
+ };
1954
2171
  var tokenHallucination = {
1955
2172
  id: "token-hallucination",
1956
2173
  severity: "warning",
@@ -1963,8 +2180,21 @@ var tokenHallucination = {
1963
2180
  lines.forEach((line, i) => {
1964
2181
  for (const m of line.matchAll(re)) {
1965
2182
  const core = m[2];
1966
- if (!looksSemantic(core))
2183
+ if (!looksSemantic(core)) {
2184
+ const intended = suggestIntentTypo(core);
2185
+ if (intended) {
2186
+ findings.push({
2187
+ ruleId: this.id,
2188
+ severity: this.severity,
2189
+ kind: "deterministic",
2190
+ message: `\`${m[1]}-${core}\` looks like a typo of \`${m[1]}-${intended}\`.`,
2191
+ fix: `Did you mean \`${m[1]}-${intended}\`? Valid intents: ${INTENT_NAMES.join(", ")}.`,
2192
+ line: i + 1,
2193
+ match: m[0]
2194
+ });
2195
+ }
1967
2196
  continue;
2197
+ }
1968
2198
  if (validCores.has(core))
1969
2199
  continue;
1970
2200
  findings.push({
@@ -2022,6 +2252,8 @@ var RULES = [
2022
2252
  darkModeOverride,
2023
2253
  focusNotVisible,
2024
2254
  hardcodedZIndex,
2255
+ hardcodedMotion,
2256
+ deepInternalImport,
2025
2257
  dynamicClassInterpolation,
2026
2258
  tokenHallucination,
2027
2259
  ...MARKUP_RULES
@@ -2146,7 +2378,7 @@ async function runHook(_positionals, flags) {
2146
2378
  for (const p of paths) {
2147
2379
  let code;
2148
2380
  try {
2149
- code = await readFile5(resolve4(p), "utf-8");
2381
+ code = await readFile5(resolve5(p), "utf-8");
2150
2382
  } catch {
2151
2383
  continue;
2152
2384
  }
@@ -2171,21 +2403,21 @@ Fix the issues above and re-save. — urbicon design gate`);
2171
2403
 
2172
2404
  // src/cli/commands/init.ts
2173
2405
  import { mkdir, readFile as readFile7, writeFile as writeFile2 } from "node:fs/promises";
2174
- import { dirname as dirname4, join as join2, relative as relative2, resolve as resolve6 } from "node:path";
2406
+ import { dirname as dirname5, join as join3, relative as relative2, resolve as resolve7 } from "node:path";
2175
2407
 
2176
2408
  // src/cli/package-root.ts
2177
2409
  import { readFile as readFile6 } from "node:fs/promises";
2178
- import { dirname as dirname3, resolve as resolve5 } from "node:path";
2410
+ import { dirname as dirname4, resolve as resolve6 } from "node:path";
2179
2411
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2180
2412
  async function findPackageRoot() {
2181
- let dir = dirname3(fileURLToPath2(import.meta.url));
2413
+ let dir = dirname4(fileURLToPath2(import.meta.url));
2182
2414
  for (let i = 0;i < 6; i++) {
2183
2415
  try {
2184
- const pkg = JSON.parse(await readFile6(resolve5(dir, "package.json"), "utf-8"));
2416
+ const pkg = JSON.parse(await readFile6(resolve6(dir, "package.json"), "utf-8"));
2185
2417
  if (pkg.name === "@urbicon-ui/design")
2186
2418
  return dir;
2187
2419
  } catch {}
2188
- const parent = dirname3(dir);
2420
+ const parent = dirname4(dir);
2189
2421
  if (parent === dir)
2190
2422
  break;
2191
2423
  dir = parent;
@@ -2196,11 +2428,31 @@ async function findPackageRoot() {
2196
2428
  // src/cli/commands/init.ts
2197
2429
  var BLOCK_START = "<!-- urbicon:start";
2198
2430
  var BLOCK_END = "<!-- urbicon:end -->";
2431
+ function tailwindSteps(deps) {
2432
+ const has = (p) => deps?.has(p) ?? false;
2433
+ const tailwindWired = has("@tailwindcss/vite") || has("tailwindcss");
2434
+ if (tailwindWired) {
2435
+ return [
2436
+ " • Tailwind is installed — ensure your `app.css` has `@source '../node_modules/@urbicon-ui/blocks/dist';`",
2437
+ " (relative to app.css) so the components' utility classes are generated. Easy to miss."
2438
+ ];
2439
+ }
2440
+ return [
2441
+ " • Wire up Tailwind 4 — REQUIRED, or components render unstyled (they emit Tailwind classes):",
2442
+ " 1. bun add -D tailwindcss @tailwindcss/vite",
2443
+ " 2. vite.config.ts → add the `tailwindcss()` plugin",
2444
+ " 3. src/app.css →",
2445
+ " @import 'tailwindcss';",
2446
+ " @import '@urbicon-ui/blocks/style/index.css';",
2447
+ " @source '../node_modules/@urbicon-ui/blocks/dist'; /* generates component classes */",
2448
+ " 4. import './app.css' in your root +layout.svelte"
2449
+ ];
2450
+ }
2199
2451
  async function readTemplate(name) {
2200
2452
  const root = await findPackageRoot();
2201
2453
  if (!root)
2202
2454
  throw new Error("could not locate the @urbicon-ui/design package root");
2203
- return readFile7(join2(root, "templates", name), "utf-8");
2455
+ return readFile7(join3(root, "templates", name), "utf-8");
2204
2456
  }
2205
2457
  async function readOrNull(path) {
2206
2458
  try {
@@ -2248,7 +2500,7 @@ async function mergeHook(settingsPath) {
2248
2500
  matcher: "Edit|MultiEdit|Write",
2249
2501
  hooks: [{ type: "command", command: "urbicon hook" }]
2250
2502
  });
2251
- await mkdir(dirname4(settingsPath), { recursive: true });
2503
+ await mkdir(dirname5(settingsPath), { recursive: true });
2252
2504
  await writeFile2(settingsPath, `${JSON.stringify(settings, null, 2)}
2253
2505
  `, "utf-8");
2254
2506
  return "added";
@@ -2265,7 +2517,7 @@ async function runInit(_positionals, flags) {
2265
2517
  printError(err.message);
2266
2518
  return EXIT.FAIL;
2267
2519
  }
2268
- const agentsPath = resolve6(stringFlag(flags, "agents-file") ?? "AGENTS.md");
2520
+ const agentsPath = resolve7(stringFlag(flags, "agents-file") ?? "AGENTS.md");
2269
2521
  const existingAgents = await readOrNull(agentsPath) ?? "";
2270
2522
  let upserted;
2271
2523
  try {
@@ -2285,7 +2537,7 @@ async function runInit(_positionals, flags) {
2285
2537
  done.push(`${rel(manifestPath)} — scaffolded`);
2286
2538
  }
2287
2539
  if (boolFlag(flags, "hook")) {
2288
- const settingsPath = resolve6(".claude", "settings.json");
2540
+ const settingsPath = resolve7(".claude", "settings.json");
2289
2541
  try {
2290
2542
  const result = await mergeHook(settingsPath);
2291
2543
  (result === "added" ? done : skipped).push(`${rel(settingsPath)} — ${result === "added" ? "wired" : "already has"} the PostToolUse \`urbicon hook\``);
@@ -2294,12 +2546,12 @@ async function runInit(_positionals, flags) {
2294
2546
  }
2295
2547
  }
2296
2548
  if (boolFlag(flags, "ci")) {
2297
- const ciPath = resolve6(".github", "workflows", "design-gate.yml");
2549
+ const ciPath = resolve7(".github", "workflows", "design-gate.yml");
2298
2550
  if (await readOrNull(ciPath)) {
2299
2551
  skipped.push(`${rel(ciPath)} — already present`);
2300
2552
  } else {
2301
2553
  const ci = await readTemplate("ci-github.yml");
2302
- await mkdir(dirname4(ciPath), { recursive: true });
2554
+ await mkdir(dirname5(ciPath), { recursive: true });
2303
2555
  await writeFile2(ciPath, ci, "utf-8");
2304
2556
  done.push(`${rel(ciPath)} — wrote the design-gate workflow`);
2305
2557
  }
@@ -2312,6 +2564,8 @@ async function runInit(_positionals, flags) {
2312
2564
  console.log(` · ${s}`);
2313
2565
  console.log(`
2314
2566
  Next steps:`);
2567
+ for (const line of tailwindSteps(readConsumerDependencies()))
2568
+ console.log(line);
2315
2569
  console.log(" • Make sure your agent reads AGENTS.md (or paste the block into CLAUDE.md / .cursorrules).");
2316
2570
  console.log(" • Seed the design memory: `bunx urbicon verb adopt` (brownfield) or `onboard` (greenfield) — the guided intake.");
2317
2571
  if (!boolFlag(flags, "hook")) {
@@ -2368,7 +2622,7 @@ async function runRecordDecision(_positionals, flags) {
2368
2622
  }
2369
2623
 
2370
2624
  // src/cli/commands/sync-manifest.ts
2371
- import { dirname as dirname5 } from "node:path";
2625
+ import { dirname as dirname6 } from "node:path";
2372
2626
  async function runSyncManifest(_positionals, flags) {
2373
2627
  const path = resolveManifestPath(stringFlag(flags, "manifest"));
2374
2628
  if (!path.endsWith(".md")) {
@@ -2376,7 +2630,7 @@ async function runSyncManifest(_positionals, flags) {
2376
2630
  return EXIT.USAGE;
2377
2631
  }
2378
2632
  const src = resolveSourceDir(stringFlag(flags, "src"));
2379
- const usages = await scanMarkers(src, dirname5(path));
2633
+ const usages = await scanMarkers(src, dirname6(path));
2380
2634
  const { content, created } = await readOrCreateManifest(path);
2381
2635
  const updated = upsertUsagesSection(content, usages);
2382
2636
  try {
@@ -2405,7 +2659,7 @@ async function runSyncManifest(_positionals, flags) {
2405
2659
 
2406
2660
  // src/cli/commands/validate.ts
2407
2661
  import { readdir as readdir2, readFile as readFile8, stat as stat2 } from "node:fs/promises";
2408
- import { join as join3, relative as relative3, resolve as resolve7, sep as sep2 } from "node:path";
2662
+ import { join as join4, relative as relative3, resolve as resolve8, sep as sep2 } from "node:path";
2409
2663
  var SKIP_DIRS2 = new Set([
2410
2664
  "node_modules",
2411
2665
  ".svelte-kit",
@@ -2434,9 +2688,9 @@ async function collectSvelte(dir, depth = 0) {
2434
2688
  if (entry.isDirectory()) {
2435
2689
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith("."))
2436
2690
  continue;
2437
- files.push(...await collectSvelte(join3(dir, entry.name), depth + 1));
2691
+ files.push(...await collectSvelte(join4(dir, entry.name), depth + 1));
2438
2692
  } else if (entry.isFile() && entry.name.endsWith(".svelte")) {
2439
- files.push(join3(dir, entry.name));
2693
+ files.push(join4(dir, entry.name));
2440
2694
  }
2441
2695
  }
2442
2696
  return files;
@@ -2457,7 +2711,7 @@ async function gather(positionals) {
2457
2711
  units.push({ label: "<stdin>", code: await readStdin2() });
2458
2712
  continue;
2459
2713
  }
2460
- const abs = resolve7(p);
2714
+ const abs = resolve8(p);
2461
2715
  let info;
2462
2716
  try {
2463
2717
  info = await stat2(abs);
@@ -2549,11 +2803,11 @@ FAIL — ${reason}.`);
2549
2803
 
2550
2804
  // src/cli/commands/verb.ts
2551
2805
  import { readdir as readdir3, readFile as readFile9 } from "node:fs/promises";
2552
- import { resolve as resolve8 } from "node:path";
2806
+ import { resolve as resolve9 } from "node:path";
2553
2807
  var SAFE_VERB = /^[a-z][a-z0-9-]*$/;
2554
2808
  async function resolveVerbsDir() {
2555
2809
  const root = await findPackageRoot();
2556
- return root ? resolve8(root, "skill", "verbs") : null;
2810
+ return root ? resolve9(root, "skill", "verbs") : null;
2557
2811
  }
2558
2812
  function purposeOf(body) {
2559
2813
  const heading = body.split(`
@@ -2579,7 +2833,7 @@ async function runVerbList(_positionals, _flags) {
2579
2833
  const name = file.replace(/\.md$/, "");
2580
2834
  let purpose = "";
2581
2835
  try {
2582
- purpose = purposeOf(await readFile9(resolve8(dir, file), "utf-8"));
2836
+ purpose = purposeOf(await readFile9(resolve9(dir, file), "utf-8"));
2583
2837
  } catch {}
2584
2838
  console.log(` ${name.padEnd(10)} ${purpose}`);
2585
2839
  }
@@ -2601,7 +2855,7 @@ async function runVerb(positionals, _flags) {
2601
2855
  return EXIT.FAIL;
2602
2856
  }
2603
2857
  try {
2604
- console.log(await readFile9(resolve8(dir, `${name}.md`), "utf-8"));
2858
+ console.log(await readFile9(resolve9(dir, `${name}.md`), "utf-8"));
2605
2859
  return EXIT.OK;
2606
2860
  } catch {
2607
2861
  printError(`unknown verb "${name}" — list the available verbs with \`urbicon verbs\``);
@@ -2684,7 +2938,7 @@ async function readVersion() {
2684
2938
  if (!root)
2685
2939
  return "unknown";
2686
2940
  try {
2687
- const pkg = JSON.parse(await readFile10(resolve9(root, "package.json"), "utf-8"));
2941
+ const pkg = JSON.parse(await readFile10(resolve10(root, "package.json"), "utf-8"));
2688
2942
  return pkg.version ?? "unknown";
2689
2943
  } catch {
2690
2944
  return "unknown";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@urbicon-ui/design",
3
- "version": "6.3.1",
3
+ "version": "6.3.3",
4
4
  "description": "The urbicon CLI — version-pinned design validation and design-manifest tooling for projects built with Urbicon UI. Wraps @urbicon-ui/design-engine for editor hooks and CI.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,8 +38,8 @@
38
38
  "test:run": "vitest run"
39
39
  },
40
40
  "dependencies": {
41
- "@urbicon-ui/design-content": "6.3.1",
42
- "@urbicon-ui/design-engine": "6.3.1"
41
+ "@urbicon-ui/design-content": "6.3.3",
42
+ "@urbicon-ui/design-engine": "6.3.3"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^6.0.3",
@@ -78,6 +78,8 @@ app root and reference it by name; never force colours with inline `!` overrides
78
78
  </BlocksProvider>
79
79
  ```
80
80
 
81
+ The full override ladder (weakest → strongest): `class` (root slot only) → instance `slotClasses={{ <slot>: … }}` → `BlocksProvider` `defaults`/`presets` → prop-conditional `overrides` (one variant/intent/state) → `unstyled` + `slotClasses` (strip & rebuild).
82
+
81
83
  **Svelte 5** — `$props()` not `export let`; `{#snippet}` / `{@render}` not `<slot>`; callback props
82
84
  (`onValueChange`) not `createEventDispatcher`; lowercase DOM events (`onclick`).
83
85