onsystem 0.8.0 → 0.9.1
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 +20 -2
- package/dist/cli.js +122 -21
- package/dist/{config-kYGiD5oW.d.ts → config-B98w8bcg.d.ts} +5 -0
- package/dist/{design-system-Dmcv7eHR.js → design-system-DZm2aP1z.js} +1105 -189
- package/dist/eslint-worker.d.ts +1 -1
- package/dist/eslint-worker.js +2 -2
- package/dist/eslint.js +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.js +2 -2
- package/dist/{stdio-MTS6QAgo.js → stdio-BJvjEnKW.js} +338 -87
- package/dist/{workspace-CM1RyqZA.js → workspace-Dqu3nkYM.js} +1 -1
- package/package.json +6 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-
|
|
1
|
+
import { $ as LEGACY_CONFIG_FILES, At as relativePath, Ct as closest, Dt as SCRIPT_FILE, Et as unique, Mt as defaultProjectConfig, Ot as isInside, Tt as truncate, X as DEFAULT_SEVERITY, _t as detectProject, at as loadConfig, ct as formatPx, d as descriptionOf, dt as LIBRARY_IDS, et as RULE_IDS, f as searchComponents, ft as libraryTitle, h as uncheckedNotice, n as DesignSystemHost, pt as notReadYet, q as CONFIG_FILES, rt as excludedBy, u as buildSearchIndex, ut as LIBRARIES, v as NAME, vt as findDesignSystemPackage, wt as plural, y as VERSION, z as TOKEN_CATEGORIES } from "./design-system-DZm2aP1z.js";
|
|
2
|
+
import { n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-Dqu3nkYM.js";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -232,19 +232,127 @@ function importStatement(component) {
|
|
|
232
232
|
function variantSummary(component) {
|
|
233
233
|
return component.variants.map((v) => `${v.name}: ${v.values.join(" | ")}${v.default ? ` (default ${v.default})` : ""}`);
|
|
234
234
|
}
|
|
235
|
+
/** An icon: a component that renders an <svg>. */
|
|
236
|
+
function isIcon(component) {
|
|
237
|
+
return component.element === "svg";
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Icons from one import, or from the modules of one folder, are listed by name on one line from
|
|
241
|
+
* this many on: a spinner or a logo is listed like the rest.
|
|
242
|
+
*/
|
|
243
|
+
const ICON_LINE = 5;
|
|
244
|
+
/**
|
|
245
|
+
* What list_components shows of `items`, after `filter`. `packageOf`: at a workspace root, the
|
|
246
|
+
* package an item comes from, so two apps' `@/components/icons` are not taken for one.
|
|
247
|
+
*/
|
|
248
|
+
function listComponents(items, component, filter = {}, packageOf = () => "") {
|
|
249
|
+
const query = filter.query?.trim().toLowerCase();
|
|
250
|
+
const matching = items.filter((item) => {
|
|
251
|
+
const c = component(item);
|
|
252
|
+
if (filter.kind && filter.kind === "icon" !== isIcon(c)) return false;
|
|
253
|
+
const texts = [
|
|
254
|
+
c.name,
|
|
255
|
+
...c.aliases,
|
|
256
|
+
...c.subcomponents,
|
|
257
|
+
descriptionOf(c) ?? ""
|
|
258
|
+
];
|
|
259
|
+
return !query || texts.some((text) => text.toLowerCase().includes(query));
|
|
260
|
+
});
|
|
261
|
+
const icons = [];
|
|
262
|
+
if (!filter.kind) {
|
|
263
|
+
const group = (groups, item, from, folder) => {
|
|
264
|
+
const key = `${packageOf(item)}\0${from}`;
|
|
265
|
+
const known = groups.get(key) ?? {
|
|
266
|
+
from,
|
|
267
|
+
items: [],
|
|
268
|
+
...folder ? { folder } : {}
|
|
269
|
+
};
|
|
270
|
+
known.items.push(item);
|
|
271
|
+
groups.set(key, known);
|
|
272
|
+
};
|
|
273
|
+
const byImport = /* @__PURE__ */ new Map();
|
|
274
|
+
for (const item of matching) {
|
|
275
|
+
const c = component(item);
|
|
276
|
+
if (isIcon(c)) group(byImport, item, c.library ?? c.importPath);
|
|
277
|
+
}
|
|
278
|
+
const byFolder = /* @__PURE__ */ new Map();
|
|
279
|
+
for (const found of byImport.values()) {
|
|
280
|
+
if (found.items.length >= ICON_LINE) {
|
|
281
|
+
icons.push(found);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
for (const item of found.items) {
|
|
285
|
+
const c = component(item);
|
|
286
|
+
const folder = c.importPath.slice(0, Math.max(0, c.importPath.lastIndexOf("/")));
|
|
287
|
+
if (!c.library && folder) group(byFolder, item, folder, true);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
icons.push(...[...byFolder.values()].filter((found) => found.items.length >= ICON_LINE));
|
|
291
|
+
}
|
|
292
|
+
const inLine = new Set(icons.flatMap((found) => found.items));
|
|
293
|
+
const rest = matching.filter((item) => !inLine.has(item));
|
|
294
|
+
const shown = rest.slice(0, filter.limit ?? rest.length);
|
|
295
|
+
return {
|
|
296
|
+
shown,
|
|
297
|
+
icons,
|
|
298
|
+
matched: matching.length,
|
|
299
|
+
omitted: rest.length - shown.length
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/** `query "chart" and kind "icon"`, or '' without filters. */
|
|
303
|
+
function filterText(filter) {
|
|
304
|
+
return [filter.query?.trim() ? `query "${filter.query.trim()}"` : "", filter.kind ? `kind "${filter.kind}"` : ""].filter(Boolean).join(" and ");
|
|
305
|
+
}
|
|
306
|
+
/** The first line of a list: how many components there are, or how many match the filter. */
|
|
307
|
+
function listHead(roots, parts, listed, filter) {
|
|
308
|
+
const filtered = filterText(filter);
|
|
309
|
+
if (filtered) return `${listed.matched} of ${plural(roots, "component")} match ${filtered}`;
|
|
310
|
+
return `${plural(roots, "component")}${parts ? ` (+${plural(parts, "part")})` : ""}`;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* `128 icons from "@dub/ui" (each renders <svg>; import { Name } from "@dub/ui"): Apple, Bolt`,
|
|
314
|
+
* or for a module per icon, `6 icons in "@/components/icons" (each renders <svg>; import { Name }
|
|
315
|
+
* from "@/components/icons/<module>"): CheckIcon (check), PlusIcon (plus)`.
|
|
316
|
+
*/
|
|
317
|
+
function iconLine(group) {
|
|
318
|
+
const { from, items } = group;
|
|
319
|
+
const count = plural(items.length, "icon");
|
|
320
|
+
if (!group.folder) return `${count} from "${from}" (each renders <svg>; import { Name } from "${from}"): ${items.map((c) => c.name).join(", ")}`;
|
|
321
|
+
return `${count} in "${from}" (each renders <svg>; import { Name } from "${from}/<module>"): ${items.map((c) => `${c.name} (${c.importPath.slice(from.length + 1)})`).join(", ")}`;
|
|
322
|
+
}
|
|
323
|
+
/** What a list says at its end about the icons it named and the components `limit` left out. */
|
|
324
|
+
function listTail(listed) {
|
|
325
|
+
const lines = [];
|
|
326
|
+
if (listed.icons.length) lines.push("", "Pass kind \"icon\" to list the icons one by one.");
|
|
327
|
+
if (listed.omitted) lines.push("", `${listed.omitted.toLocaleString("en-US")} more not shown: pass a higher \`limit\`, or a \`query\`.`);
|
|
328
|
+
return lines;
|
|
329
|
+
}
|
|
330
|
+
/** The shared explanation when a project has no design-system components to show. */
|
|
331
|
+
function noComponentsMessage(root, project, monorepo = false) {
|
|
332
|
+
if (monorepo) return `No design-system components found in any project of this monorepo (root: ${root}). Check the "components" globs in the config. Run \`onsystem inspect\` there to see what was looked at.`;
|
|
333
|
+
return `No design-system components found${project ? ` in ${project}` : ""}. Check the "components" globs in the config (root: ${root}).`;
|
|
334
|
+
}
|
|
235
335
|
/** `project`: the folder of a workspace root's project the list is for. */
|
|
236
|
-
function renderComponentList(ds, project) {
|
|
336
|
+
function renderComponentList(ds, project, filter = {}) {
|
|
237
337
|
const roots = ds.roots();
|
|
238
|
-
|
|
338
|
+
const where = project ? ` in ${project}` : "";
|
|
339
|
+
if (!roots.length) return noComponentsMessage(ds.root, project);
|
|
340
|
+
const listed = listComponents(roots, (c) => c, filter);
|
|
341
|
+
if (!listed.matched) return `No components match ${filterText(filter)}${where} (${roots.length} in all). Call list_components without filters to see them.`;
|
|
239
342
|
const parts = ds.components.length - roots.length;
|
|
240
|
-
const lines = [`${
|
|
241
|
-
const own =
|
|
343
|
+
const lines = [`${listHead(roots.length, parts, listed, filter)}${where}. Call get_component before using one.`, ""];
|
|
344
|
+
const own = listed.shown.filter((c) => !c.library);
|
|
242
345
|
for (const component of own) lines.push(...listItem(component, registryNote(ds, component)));
|
|
243
|
-
for (const library of new Set(
|
|
346
|
+
for (const library of new Set(listed.shown.flatMap((c) => c.library ? [c.library] : []))) {
|
|
244
347
|
if (own.length) lines.push("");
|
|
245
348
|
lines.push(`From ${library} (import { Name } from "${library}"):`, "");
|
|
246
|
-
for (const component of
|
|
349
|
+
for (const component of listed.shown.filter((c) => c.library === library)) lines.push(...listItem(component, "", false));
|
|
350
|
+
}
|
|
351
|
+
for (const group of listed.icons) {
|
|
352
|
+
if (lines.at(-1) !== "") lines.push("");
|
|
353
|
+
lines.push(iconLine(group));
|
|
247
354
|
}
|
|
355
|
+
lines.push(...listTail(listed));
|
|
248
356
|
return lines.join("\n");
|
|
249
357
|
}
|
|
250
358
|
/** ` (from @signoff-ui, read-only)` for a component copied from a shadcn registry. */
|
|
@@ -254,7 +362,8 @@ function registryNote(ds, component) {
|
|
|
254
362
|
}
|
|
255
363
|
function listItem(component, note = "", withImport = true) {
|
|
256
364
|
const element = component.element ? ` <${component.element}>` : "";
|
|
257
|
-
const
|
|
365
|
+
const description = firstSentence(descriptionOf(component));
|
|
366
|
+
const lines = [`${component.name}${element}${note}${description ? ` — ${description}` : ""}`];
|
|
258
367
|
for (const variant of variantSummary(component)) lines.push(` ${variant}`);
|
|
259
368
|
if (component.subcomponents.length) lines.push(` parts: ${component.subcomponents.join(", ")}`);
|
|
260
369
|
if (withImport) lines.push(` ${importStatement(component)}`);
|
|
@@ -268,22 +377,44 @@ function listOf$1(items, max = 3) {
|
|
|
268
377
|
}
|
|
269
378
|
const PICK_PROJECT = "This is a monorepo root: pass `path`, the file you are editing, to get_component, get_tokens and check_ui so they answer for its project.";
|
|
270
379
|
/** Every project's components at a workspace root, grouped by the package they come from. */
|
|
271
|
-
function renderCatalogList(catalog) {
|
|
380
|
+
function renderCatalogList(catalog, filter = {}) {
|
|
272
381
|
const roots = catalog.roots();
|
|
273
|
-
if (!roots.length) return
|
|
274
|
-
const
|
|
275
|
-
|
|
382
|
+
if (!roots.length) return noComponentsMessage(catalog.root, void 0, true);
|
|
383
|
+
const listed = listComponents(roots, (e) => e.component, filter, (e) => e.package);
|
|
384
|
+
if (!listed.matched) return `No components match ${filterText(filter)} in any project (${roots.length} in all). Call list_components without filters to see them.`;
|
|
385
|
+
const packages = new Set(roots.map((e) => e.package));
|
|
276
386
|
const parts = catalog.entries.length - roots.length;
|
|
277
|
-
const lines = [`${
|
|
278
|
-
|
|
279
|
-
|
|
387
|
+
const lines = [`${listHead(roots.length, parts, listed, filter)} from ${plural(packages.size, "package")}, used by ${plural(catalog.projects.length, "project")}. ${PICK_PROJECT}`];
|
|
388
|
+
const byPackage = /* @__PURE__ */ new Map();
|
|
389
|
+
const section = (pkg) => {
|
|
390
|
+
const known = byPackage.get(pkg) ?? {
|
|
391
|
+
entries: [],
|
|
392
|
+
icons: []
|
|
393
|
+
};
|
|
394
|
+
byPackage.set(pkg, known);
|
|
395
|
+
return known;
|
|
396
|
+
};
|
|
397
|
+
for (const entry of listed.shown) section(entry.package).entries.push(entry);
|
|
398
|
+
for (const group of listed.icons) {
|
|
399
|
+
const [first] = group.items;
|
|
400
|
+
const items = group.items.map((e) => e.component);
|
|
401
|
+
if (first) section(first.package).icons.push(iconLine({
|
|
402
|
+
...group,
|
|
403
|
+
items
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
for (const [pkg, { entries, icons }] of byPackage) {
|
|
407
|
+
const projects = [...new Set(roots.filter((e) => e.package === pkg).flatMap((e) => e.projects))];
|
|
280
408
|
lines.push("", `## ${pkg} (used by ${listOf$1(projects)})`);
|
|
281
409
|
for (const entry of entries) {
|
|
282
|
-
const others = entry.ambiguous ?
|
|
410
|
+
const others = entry.ambiguous ? roots.filter((e) => e !== entry && e.component.name === entry.component.name) : [];
|
|
283
411
|
const note = others.length ? ` (also in ${[...new Set(others.map((e) => e.package))].join(", ")})` : "";
|
|
284
412
|
lines.push(...listItem(entry.component, note));
|
|
285
413
|
}
|
|
414
|
+
if (entries.length && icons.length) lines.push("");
|
|
415
|
+
lines.push(...icons);
|
|
286
416
|
}
|
|
417
|
+
lines.push(...listTail(listed));
|
|
287
418
|
return lines.join("\n");
|
|
288
419
|
}
|
|
289
420
|
/** Several components under one name at a workspace root, and how to pick one. */
|
|
@@ -362,18 +493,26 @@ const REACT_ARIA_NOTABLE = [
|
|
|
362
493
|
"isReadOnly",
|
|
363
494
|
"autoFocus"
|
|
364
495
|
];
|
|
496
|
+
/**
|
|
497
|
+
* A prop's description longer than this is cut to its first sentence: a primitive library's
|
|
498
|
+
* JSDoc (Base UI's, Radix's) runs to paragraphs per prop, and the structured content has it all.
|
|
499
|
+
*/
|
|
500
|
+
const PROP_LINE = 160;
|
|
365
501
|
function renderProp(prop) {
|
|
366
502
|
const optional = prop.required ? "" : "?";
|
|
367
503
|
const fallback = prop.default !== void 0 ? ` = ${prop.default}` : "";
|
|
368
504
|
const deprecated = prop.deprecated ? ` DEPRECATED${prop.deprecated === true ? "" : `: ${prop.deprecated}`}.` : "";
|
|
369
|
-
const
|
|
505
|
+
const text = prop.description?.replace(/\s*\n\s*/g, " ").trim();
|
|
506
|
+
const line = text && text.length > PROP_LINE ? firstSentence(text, PROP_LINE) : text;
|
|
507
|
+
const description = line ? ` — ${line}` : "";
|
|
370
508
|
return `- ${prop.name}${optional}: ${prop.type}${fallback}${description}${deprecated}`;
|
|
371
509
|
}
|
|
372
510
|
/** `origin`: at a workspace root, the package the component comes from and who uses it. */
|
|
373
511
|
function renderComponent(ds, component, origin) {
|
|
374
512
|
const lines = [`# ${component.name}`];
|
|
375
513
|
if (component.deprecated) lines.push(`DEPRECATED${component.deprecated === true ? "" : `: ${component.deprecated}`}`);
|
|
376
|
-
|
|
514
|
+
const description = descriptionOf(component);
|
|
515
|
+
if (description) lines.push(description);
|
|
377
516
|
lines.push("", importStatement(component));
|
|
378
517
|
const facts = [
|
|
379
518
|
component.element ? `Renders <${component.element}>` : void 0,
|
|
@@ -386,6 +525,13 @@ function renderComponent(ds, component, origin) {
|
|
|
386
525
|
if (origin) lines.push(`From ${origin.package}, used by ${listOf$1(origin.projects)}.`);
|
|
387
526
|
const registry = ds.registryOf(component);
|
|
388
527
|
if (registry) lines.push(`Installed from the shadcn registry ${registry.registry} into ${registry.folder}: use it as it is. Its code is the registry's, so change it upstream, not here; \`npx shadcn add --overwrite\` replaces the file.`);
|
|
528
|
+
if (component.subcomponents.length) {
|
|
529
|
+
lines.push("", "## Parts");
|
|
530
|
+
for (const name of component.subcomponents) {
|
|
531
|
+
const part = ds.getComponent(name);
|
|
532
|
+
lines.push(`- ${name}${part?.description ? ` — ${firstSentence(part.description)}` : ""}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
389
535
|
lines.push("", "## Props");
|
|
390
536
|
if (!component.props.length && !component.inherits.length) lines.push("No props.");
|
|
391
537
|
for (const prop of component.props) lines.push(renderProp(prop));
|
|
@@ -411,13 +557,6 @@ function renderComponent(ds, component, origin) {
|
|
|
411
557
|
lines.push(` when ${when}: ${compound.classes}`);
|
|
412
558
|
}
|
|
413
559
|
}
|
|
414
|
-
if (component.subcomponents.length) {
|
|
415
|
-
lines.push("", "## Parts");
|
|
416
|
-
for (const name of component.subcomponents) {
|
|
417
|
-
const part = ds.getComponent(name);
|
|
418
|
-
lines.push(`- ${name}${part?.description ? ` — ${firstSentence(part.description)}` : ""}`);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
560
|
const tokens = ds.relatedTokens(component);
|
|
422
561
|
if (tokens.length) {
|
|
423
562
|
lines.push("", "## Design tokens used");
|
|
@@ -440,31 +579,63 @@ function renderComponent(ds, component, origin) {
|
|
|
440
579
|
}
|
|
441
580
|
return lines.join("\n");
|
|
442
581
|
}
|
|
582
|
+
/** The first line of search results: best first, or, when none clearly matches, that none does. */
|
|
583
|
+
function searchHead(query, hits, where = "") {
|
|
584
|
+
return hits.every((hit) => hit.weak) ? `No component clearly matches "${query}"${where}. The nearest have only a word or two in common with it:` : `Components for "${query}"${where}, best first:`;
|
|
585
|
+
}
|
|
586
|
+
/** The last line of search results. */
|
|
587
|
+
function searchTail(hits) {
|
|
588
|
+
return hits.every((hit) => hit.weak) ? "If none of them fits, the design system may have no component for this: ask, or build it from the components it has (list_components). Call get_component before using one." : "Call get_component for props, variants and examples.";
|
|
589
|
+
}
|
|
590
|
+
/** `1. CardHeader (part of Card) — Top section of a card.` */
|
|
591
|
+
function searchItem(index, component, from = "") {
|
|
592
|
+
const parent = component.parent ? ` (part of ${component.parent})` : "";
|
|
593
|
+
const description = firstSentence(descriptionOf(component));
|
|
594
|
+
return `${index + 1}. ${component.name}${parent}${from}${description ? ` — ${description}` : ""}`;
|
|
595
|
+
}
|
|
443
596
|
function renderSearch(query, hits) {
|
|
444
597
|
if (!hits.length) return `No components match "${query}". Try other words, or list_components to browse.`;
|
|
445
|
-
const lines = [
|
|
598
|
+
const lines = [searchHead(query, hits), ""];
|
|
446
599
|
hits.forEach((hit, i) => {
|
|
447
600
|
const c = hit.component;
|
|
448
|
-
|
|
449
|
-
lines.push(`${i + 1}. ${c.name}${parent} — ${firstSentence(c.description) || "No description."}`);
|
|
601
|
+
lines.push(searchItem(i, c));
|
|
450
602
|
lines.push(` score ${hit.score} · matched: ${hit.matched.join(", ")} · ${importStatement(c)}`);
|
|
451
603
|
});
|
|
452
|
-
lines.push("",
|
|
604
|
+
lines.push("", searchTail(hits));
|
|
453
605
|
return lines.join("\n");
|
|
454
606
|
}
|
|
455
607
|
/** Search hits at a workspace root, each with the package it comes from. */
|
|
456
608
|
function renderCatalogSearch(query, hits) {
|
|
457
609
|
if (!hits.length) return `No components match "${query}" in any project. Try other words, or list_components to browse.`;
|
|
458
|
-
const lines = [
|
|
610
|
+
const lines = [searchHead(query, hits, " across the monorepo's projects"), ""];
|
|
459
611
|
hits.forEach((hit, i) => {
|
|
460
612
|
const c = hit.component;
|
|
461
|
-
|
|
462
|
-
lines.push(`${i + 1}. ${c.name}${parent} from ${hit.entry.package} — ${firstSentence(c.description) || "No description."}`);
|
|
613
|
+
lines.push(searchItem(i, c, ` from ${hit.entry.package}`));
|
|
463
614
|
lines.push(` score ${hit.score} · matched: ${hit.matched.join(", ")} · used by ${listOf$1(hit.entry.projects)} · ${importStatement(c)}`);
|
|
464
615
|
});
|
|
465
|
-
lines.push("",
|
|
616
|
+
lines.push("", `${searchTail(hits)} ${PICK_PROJECT}`);
|
|
466
617
|
return lines.join("\n");
|
|
467
618
|
}
|
|
619
|
+
/**
|
|
620
|
+
* Where a token is listed in get_tokens: under its category, or, for a color of a scoped
|
|
621
|
+
* family (`chart-*`, `sidebar-*`, the syntax colors one file uses), under that family, with
|
|
622
|
+
* what it is for. An agent looking for a green with no `--success` in the theme otherwise finds
|
|
623
|
+
* `chart-2` among the colors, takes its dark value, and check_ui has nothing against it.
|
|
624
|
+
*/
|
|
625
|
+
function tokenGroup(ds, token) {
|
|
626
|
+
const family = ds.tokenIndex.scopedFamilyOf(token);
|
|
627
|
+
return family === void 0 ? token.category : `${SCOPED}${family}`;
|
|
628
|
+
}
|
|
629
|
+
/** Marks a scoped family's group: no category starts with it. */
|
|
630
|
+
const SCOPED = "\0";
|
|
631
|
+
function groupHeading(ds, group, count) {
|
|
632
|
+
if (!group.startsWith(SCOPED)) return `## ${group} (${count})`;
|
|
633
|
+
const family = group.slice(1);
|
|
634
|
+
if (family === "chart") return `## chart (${count}): for charts only, not for status colors`;
|
|
635
|
+
if (family === "sidebar") return `## sidebar (${count}): for the sidebar only, not for status colors`;
|
|
636
|
+
const [file] = ds.tokenIndex.familyFiles.get(family) ?? [];
|
|
637
|
+
return `## ${family} (${count}): for one part of the UI only${file ? ` (${file} uses them)` : ""}, not for status colors`;
|
|
638
|
+
}
|
|
468
639
|
/** Tokens at a workspace root, grouped by category, each with the package that defines it. */
|
|
469
640
|
function renderCatalogTokens(catalog, tokens, filter = {}) {
|
|
470
641
|
if (!catalog.tokens.length) return `No design tokens found in any project of this monorepo (root: ${catalog.root}). Set "tokens" in a project's config: https://onsystem.vercel.app/docs/configuration`;
|
|
@@ -473,10 +644,18 @@ function renderCatalogTokens(catalog, tokens, filter = {}) {
|
|
|
473
644
|
return `No tokens match${what ? ` ${what}` : ""} in any project; call get_tokens without filters to see them.`;
|
|
474
645
|
}
|
|
475
646
|
const groups = /* @__PURE__ */ new Map();
|
|
476
|
-
for (const t of tokens)
|
|
647
|
+
for (const t of tokens) {
|
|
648
|
+
const key = tokenGroup(t.ds, t.token);
|
|
649
|
+
const group = groups.get(key) ?? {
|
|
650
|
+
ds: t.ds,
|
|
651
|
+
list: []
|
|
652
|
+
};
|
|
653
|
+
group.list.push(t);
|
|
654
|
+
groups.set(key, group);
|
|
655
|
+
}
|
|
477
656
|
const lines = [PICK_PROJECT, ""];
|
|
478
|
-
for (const [
|
|
479
|
-
lines.push(
|
|
657
|
+
for (const [key, { ds, list }] of groups) {
|
|
658
|
+
lines.push(groupHeading(ds, key, list.length));
|
|
480
659
|
for (const { token, package: pkg } of list) {
|
|
481
660
|
const usage = token.usage.length ? ` → ${token.usage.join(", ")}` : "";
|
|
482
661
|
lines.push(`- ${token.name}: ${token.value}${usage} (${pkg})`);
|
|
@@ -495,10 +674,13 @@ function renderTokens(ds, tokens, filter = {}) {
|
|
|
495
674
|
return `No tokens match${what ? ` ${what}` : ""}. The design system has ${categories}; call get_tokens without filters to see them.`;
|
|
496
675
|
}
|
|
497
676
|
const groups = /* @__PURE__ */ new Map();
|
|
498
|
-
for (const token of tokens)
|
|
677
|
+
for (const token of tokens) {
|
|
678
|
+
const group = tokenGroup(ds, token);
|
|
679
|
+
groups.set(group, [...groups.get(group) ?? [], token]);
|
|
680
|
+
}
|
|
499
681
|
const lines = [];
|
|
500
|
-
for (const [
|
|
501
|
-
lines.push(
|
|
682
|
+
for (const [group, list] of groups) {
|
|
683
|
+
lines.push(groupHeading(ds, group, list.length));
|
|
502
684
|
for (const token of list) {
|
|
503
685
|
const px = token.category === "spacing" || token.category === "radius" ? ds.tokenIndex.toPx(token.value) : void 0;
|
|
504
686
|
const value = px !== void 0 && !/^-?[\d.]+px$/.test(token.value) ? `${token.value} = ${formatPx(px)}` : token.value;
|
|
@@ -548,7 +730,7 @@ const INSTRUCTIONS = `This server describes the project's design system: its Rea
|
|
|
548
730
|
When writing or changing UI in this project:
|
|
549
731
|
1. Find components with search_components (by intent) or list_components.
|
|
550
732
|
2. Read get_component before using a component: use only the props and variant values it lists.
|
|
551
|
-
3. Style with tokens from get_tokens, never with hex/rgb colors, arbitrary px values or Tailwind's default palette.
|
|
733
|
+
3. Style with tokens from get_tokens, never with hex/rgb colors, arbitrary px values or Tailwind's default palette. chart-* and sidebar-* tokens are for charts and the sidebar only: for a status color the theme has no token for (success, warning, info), add a token or keep the Tailwind color as check_ui says, and never use a chart-* or sidebar-* token for it.
|
|
552
734
|
4. Run check_ui on every snippet or file you write and fix all errors before you finish.
|
|
553
735
|
In a monorepo served from its root, pass \`path\` (the file you are editing) to list_components, get_component, search_components and get_tokens, and \`path\` or \`filename\` to check_ui, so each answer comes from that file's own project.`;
|
|
554
736
|
const READ_ONLY = {
|
|
@@ -575,9 +757,12 @@ const MAX_CODE = 1e6;
|
|
|
575
757
|
/** Diagnostics check_ui returns by default: a 3,000-line file can have thousands, more than a context holds. */
|
|
576
758
|
const DEFAULT_LIMIT = 50;
|
|
577
759
|
const MAX_LIMIT = 1e3;
|
|
578
|
-
/**
|
|
579
|
-
|
|
580
|
-
|
|
760
|
+
/**
|
|
761
|
+
* check_ui's answer for a path or filename that is not JS or TS. `fix`: what to do instead,
|
|
762
|
+
* for a `filename` given with code (the code itself may be fine).
|
|
763
|
+
*/
|
|
764
|
+
function notScript(name, fix = "") {
|
|
765
|
+
return errorResult(`check_ui checks .tsx, .jsx, .ts and .js files; ${name} is not one, so it was not checked.${fix}`);
|
|
581
766
|
}
|
|
582
767
|
/**
|
|
583
768
|
* An integer, without the ±9007199254740991 bounds zod adds to the JSON Schema,
|
|
@@ -675,8 +860,8 @@ const componentSummarySchema = z.object({
|
|
|
675
860
|
description: z.string().optional(),
|
|
676
861
|
importPath: z.string(),
|
|
677
862
|
element: z.string().optional(),
|
|
678
|
-
variants: z.record(z.string(), z.array(z.string())),
|
|
679
|
-
subcomponents: z.array(z.string()),
|
|
863
|
+
variants: z.record(z.string(), z.array(z.string())).optional().describe("Left out when none."),
|
|
864
|
+
subcomponents: z.array(z.string()).optional().describe("Left out when none."),
|
|
680
865
|
...originShape
|
|
681
866
|
});
|
|
682
867
|
const tokenSchema = z.object({
|
|
@@ -733,14 +918,58 @@ function origin(entry) {
|
|
|
733
918
|
projects: entry.projects
|
|
734
919
|
};
|
|
735
920
|
}
|
|
921
|
+
/** A component in list_components, without the fields it has nothing in. */
|
|
736
922
|
function summary(c) {
|
|
923
|
+
const description = descriptionOf(c);
|
|
737
924
|
return {
|
|
738
925
|
name: c.name,
|
|
739
|
-
...
|
|
926
|
+
...description ? { description } : {},
|
|
740
927
|
importPath: c.importPath,
|
|
741
928
|
...c.element ? { element: c.element } : {},
|
|
742
|
-
variants: Object.fromEntries(c.variants.map((v) => [v.name, v.values])),
|
|
743
|
-
subcomponents: c.subcomponents
|
|
929
|
+
...c.variants.length ? { variants: Object.fromEntries(c.variants.map((v) => [v.name, v.values])) } : {},
|
|
930
|
+
...c.subcomponents.length ? { subcomponents: c.subcomponents } : {}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
/** list_components' structured content: what its text lists, as it lists it. */
|
|
934
|
+
function listContent(listed, component, originOf) {
|
|
935
|
+
return {
|
|
936
|
+
components: listed.shown.map((item) => ({
|
|
937
|
+
...summary(component(item)),
|
|
938
|
+
...originOf?.(item)
|
|
939
|
+
})),
|
|
940
|
+
...listed.icons.length ? { icons: listed.icons.map(({ from, items, folder }) => ({
|
|
941
|
+
from,
|
|
942
|
+
names: items.map((item) => component(item).name),
|
|
943
|
+
...folder ? { modules: items.map((item) => component(item).importPath) } : {},
|
|
944
|
+
...originOf && items[0] ? { package: originOf(items[0]).package } : {}
|
|
945
|
+
})) } : {},
|
|
946
|
+
...listed.omitted ? { omitted: listed.omitted } : {}
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Said above a component asked for by a name that is not how its design system writes it:
|
|
951
|
+
* `Card.Header` finds CardHeader, and code written as asked would not compile.
|
|
952
|
+
*/
|
|
953
|
+
function nameNotice(asked, component) {
|
|
954
|
+
if (asked === component.name || component.aliases.includes(asked)) return "";
|
|
955
|
+
if (!asked.includes(".") && !component.name.includes(".")) return "";
|
|
956
|
+
return `There is no \`${asked}\` in this design system: write \`<${component.name}>\`.\n\n`;
|
|
957
|
+
}
|
|
958
|
+
function resourceDescription(component) {
|
|
959
|
+
const description = descriptionOf(component);
|
|
960
|
+
return description ? { description } : {};
|
|
961
|
+
}
|
|
962
|
+
/** A search_components result. */
|
|
963
|
+
function result(hit) {
|
|
964
|
+
const description = descriptionOf(hit.component);
|
|
965
|
+
return {
|
|
966
|
+
name: hit.component.name,
|
|
967
|
+
...hit.component.parent ? { parent: hit.component.parent } : {},
|
|
968
|
+
...description ? { description } : {},
|
|
969
|
+
importPath: hit.component.importPath,
|
|
970
|
+
score: hit.score,
|
|
971
|
+
matched: hit.matched,
|
|
972
|
+
...hit.weak ? { weak: true } : {}
|
|
744
973
|
};
|
|
745
974
|
}
|
|
746
975
|
/** Builds the MCP server: five tools, two resources and one prompt. Transport-agnostic. */
|
|
@@ -798,33 +1027,51 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
798
1027
|
};
|
|
799
1028
|
server.registerTool("list_components", {
|
|
800
1029
|
title: "List design-system components",
|
|
801
|
-
description: "List every component in the project design system with a one-line description, the native element it renders, its variant values, its parts and its import. Call this before writing UI in this project. At a monorepo root, pass `path` for the project of the file you are editing; without it, every project is listed, each component with its package.",
|
|
802
|
-
inputSchema: z.object({
|
|
803
|
-
|
|
1030
|
+
description: "List every component in the project design system with a one-line description, the native element it renders, its variant values, its parts and its import. Icons (components that render an <svg>) are named on one line per import, or per folder for icons with a module each, five or more to a line. Call this before writing UI in this project. `query`, `kind` and `limit` narrow a long list. At a monorepo root, pass `path` for the project of the file you are editing; without it, every project is listed, each component with its package.",
|
|
1031
|
+
inputSchema: z.object({
|
|
1032
|
+
query: z.string().max(MAX_QUERY).optional().describe("Only components with this text in their name, their parts' names or their description."),
|
|
1033
|
+
kind: z.enum(["component", "icon"]).optional().describe("\"icon\": only the components that render an <svg>, one by one. \"component\": all the others."),
|
|
1034
|
+
limit: z.number().int().min(1).max(MAX_LIMIT).optional().describe("Most components to list one by one."),
|
|
1035
|
+
path: projectPath
|
|
1036
|
+
}),
|
|
1037
|
+
outputSchema: z.object({
|
|
1038
|
+
components: z.array(componentSummarySchema).describe("The components listed one by one, after `query`, `kind` and `limit`."),
|
|
1039
|
+
icons: z.array(z.object({
|
|
1040
|
+
from: z.string().describe("The module they are imported from, or, with `modules`, the folder of their modules."),
|
|
1041
|
+
names: z.array(z.string()),
|
|
1042
|
+
modules: z.array(z.string()).optional().describe("For icons with a module each: each one's module, in the order of `names`."),
|
|
1043
|
+
package: originShape.package
|
|
1044
|
+
})).optional().describe("Without `kind`: icons, the components that render an <svg>, by name, five or more per module, or per folder for icons with a module each."),
|
|
1045
|
+
omitted: int().optional().describe("Matching components left out by `limit`.")
|
|
1046
|
+
}),
|
|
804
1047
|
annotations: READ_ONLY
|
|
805
|
-
}, async ({ path: target }) => {
|
|
1048
|
+
}, async ({ query, kind, limit, path: target }) => {
|
|
806
1049
|
const found = await scoped(target);
|
|
807
1050
|
if (isResult(found)) return found;
|
|
1051
|
+
const filter = {
|
|
1052
|
+
query,
|
|
1053
|
+
kind,
|
|
1054
|
+
limit
|
|
1055
|
+
};
|
|
808
1056
|
if ("catalog" in found) {
|
|
809
1057
|
const { catalog } = found;
|
|
1058
|
+
const listed = listComponents(catalog.roots(), (entry) => entry.component, filter, (entry) => entry.package);
|
|
810
1059
|
return {
|
|
811
1060
|
content: [{
|
|
812
1061
|
type: "text",
|
|
813
|
-
text: renderCatalogList(catalog)
|
|
1062
|
+
text: renderCatalogList(catalog, filter)
|
|
814
1063
|
}],
|
|
815
|
-
structuredContent:
|
|
816
|
-
...summary(entry.component),
|
|
817
|
-
...origin(entry)
|
|
818
|
-
})) }
|
|
1064
|
+
structuredContent: listContent(listed, (entry) => entry.component, origin)
|
|
819
1065
|
};
|
|
820
1066
|
}
|
|
821
1067
|
const { ds } = found;
|
|
1068
|
+
const listed = listComponents(ds.roots(), (c) => c, filter);
|
|
822
1069
|
return {
|
|
823
1070
|
content: [{
|
|
824
1071
|
type: "text",
|
|
825
|
-
text: renderComponentList(ds, found.project?.dir)
|
|
1072
|
+
text: renderComponentList(ds, found.project?.dir, filter)
|
|
826
1073
|
}],
|
|
827
|
-
structuredContent:
|
|
1074
|
+
structuredContent: listContent(listed, (c) => c)
|
|
828
1075
|
};
|
|
829
1076
|
});
|
|
830
1077
|
server.registerTool("get_component", {
|
|
@@ -844,18 +1091,21 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
844
1091
|
const matches = catalog.find(name);
|
|
845
1092
|
const [entry] = matches;
|
|
846
1093
|
if (!entry) {
|
|
1094
|
+
if (!catalog.roots().length) return errorResult(noComponentsMessage(catalog.root, void 0, true));
|
|
847
1095
|
const guess = catalog.suggest(name);
|
|
848
1096
|
const names = [...new Set(catalog.roots().map((e) => e.component.name))];
|
|
849
1097
|
return errorResult(`No component named "${name}" in any project.` + (guess ? ` Did you mean "${guess}"?` : "") + ` Available: ${names.join(", ")}.`);
|
|
850
1098
|
}
|
|
851
1099
|
if (matches.length > 1) return errorResult(renderAmbiguous(name, matches));
|
|
1100
|
+
const description = descriptionOf(entry.component);
|
|
852
1101
|
return {
|
|
853
1102
|
content: [{
|
|
854
1103
|
type: "text",
|
|
855
|
-
text: renderComponent(entry.ds, entry.component, entry)
|
|
1104
|
+
text: nameNotice(name, entry.component) + renderComponent(entry.ds, entry.component, entry)
|
|
856
1105
|
}],
|
|
857
1106
|
structuredContent: {
|
|
858
1107
|
...entry.component,
|
|
1108
|
+
...description ? { description } : {},
|
|
859
1109
|
import: importStatement(entry.component),
|
|
860
1110
|
tokens: entry.ds.relatedTokens(entry.component).map((t) => t.name),
|
|
861
1111
|
...origin(entry)
|
|
@@ -865,6 +1115,13 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
865
1115
|
const { ds } = found;
|
|
866
1116
|
const component = ds.getComponent(name);
|
|
867
1117
|
if (!component) {
|
|
1118
|
+
if (!ds.roots().length) return {
|
|
1119
|
+
isError: true,
|
|
1120
|
+
content: [{
|
|
1121
|
+
type: "text",
|
|
1122
|
+
text: noComponentsMessage(ds.root, found.project?.dir)
|
|
1123
|
+
}]
|
|
1124
|
+
};
|
|
868
1125
|
const guess = ds.suggestComponent(name);
|
|
869
1126
|
return {
|
|
870
1127
|
isError: true,
|
|
@@ -875,13 +1132,15 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
875
1132
|
};
|
|
876
1133
|
}
|
|
877
1134
|
const shown = fromRoot(ds, component, found.root);
|
|
1135
|
+
const description = descriptionOf(component);
|
|
878
1136
|
return {
|
|
879
1137
|
content: [{
|
|
880
1138
|
type: "text",
|
|
881
|
-
text: renderComponent(ds, shown)
|
|
1139
|
+
text: nameNotice(name, component) + renderComponent(ds, shown)
|
|
882
1140
|
}],
|
|
883
1141
|
structuredContent: {
|
|
884
1142
|
...shown,
|
|
1143
|
+
...description ? { description } : {},
|
|
885
1144
|
import: importStatement(component),
|
|
886
1145
|
tokens: ds.relatedTokens(component).map((t) => t.name)
|
|
887
1146
|
}
|
|
@@ -889,7 +1148,7 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
889
1148
|
});
|
|
890
1149
|
server.registerTool("search_components", {
|
|
891
1150
|
title: "Search components by intent",
|
|
892
|
-
description: "Find components by what you want to build, e.g. \"confirm a destructive action\", \"status label\", \"text field with validation\". Ranks names, descriptions, docs, props and variant values with BM25 (local, no API key). Use it when you do not know which component fits.",
|
|
1151
|
+
description: "Find components by what you want to build, e.g. \"confirm a destructive action\", \"status label\", \"text field with validation\". Ranks names, descriptions, docs, props and variant values with BM25 (local, no API key). When no component clearly matches, it says so and lists the nearest, marked `weak`. Use it when you do not know which component fits.",
|
|
893
1152
|
inputSchema: z.object({
|
|
894
1153
|
query: z.string().min(1).max(MAX_QUERY).describe("What the UI should do, in plain words."),
|
|
895
1154
|
limit: z.number().int().min(1).max(20).default(5).describe("Maximum results."),
|
|
@@ -902,6 +1161,7 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
902
1161
|
importPath: z.string(),
|
|
903
1162
|
score: z.number(),
|
|
904
1163
|
matched: z.array(z.string()),
|
|
1164
|
+
weak: z.boolean().optional().describe("Set when no component clearly matches the query: each result has only a word or two in common with it."),
|
|
905
1165
|
...originShape
|
|
906
1166
|
})) }),
|
|
907
1167
|
annotations: READ_ONLY
|
|
@@ -916,12 +1176,7 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
916
1176
|
text: renderCatalogSearch(query, hits)
|
|
917
1177
|
}],
|
|
918
1178
|
structuredContent: { results: hits.map((h) => ({
|
|
919
|
-
|
|
920
|
-
...h.component.parent ? { parent: h.component.parent } : {},
|
|
921
|
-
...h.component.description ? { description: h.component.description } : {},
|
|
922
|
-
importPath: h.component.importPath,
|
|
923
|
-
score: h.score,
|
|
924
|
-
matched: h.matched,
|
|
1179
|
+
...result(h),
|
|
925
1180
|
...origin(h.entry)
|
|
926
1181
|
})) }
|
|
927
1182
|
};
|
|
@@ -933,19 +1188,12 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
933
1188
|
type: "text",
|
|
934
1189
|
text: renderSearch(query, hits)
|
|
935
1190
|
}],
|
|
936
|
-
structuredContent: { results: hits.map(
|
|
937
|
-
name: h.component.name,
|
|
938
|
-
...h.component.parent ? { parent: h.component.parent } : {},
|
|
939
|
-
...h.component.description ? { description: h.component.description } : {},
|
|
940
|
-
importPath: h.component.importPath,
|
|
941
|
-
score: h.score,
|
|
942
|
-
matched: h.matched
|
|
943
|
-
})) }
|
|
1191
|
+
structuredContent: { results: hits.map(result) }
|
|
944
1192
|
};
|
|
945
1193
|
});
|
|
946
1194
|
server.registerTool("get_tokens", {
|
|
947
1195
|
title: "Get design tokens",
|
|
948
|
-
description: "List design tokens with resolved values, dark-mode values and how to use each (Tailwind class or CSS variable). Filter by category (color, spacing, radius, typography, shadow, other) or a substring. Use these instead of hex codes, px values or Tailwind default palette classes.",
|
|
1196
|
+
description: "List design tokens with resolved values, dark-mode values and how to use each (Tailwind class or CSS variable). Filter by category (color, spacing, radius, typography, shadow, other) or a substring. Use these instead of hex codes, px values or Tailwind default palette classes. Chart and sidebar colors are listed under their own headings: they are for charts and the sidebar, not for status colors.",
|
|
949
1197
|
inputSchema: z.object({
|
|
950
1198
|
category: z.enum(TOKEN_CATEGORIES).optional().describe("Only tokens of this category."),
|
|
951
1199
|
query: z.string().max(MAX_QUERY).optional().describe("Substring to match in names, usages or descriptions."),
|
|
@@ -1010,10 +1258,10 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
1010
1258
|
});
|
|
1011
1259
|
server.registerTool("check_ui", {
|
|
1012
1260
|
title: "Check UI code against the design system",
|
|
1013
|
-
description: "Lint TSX/JSX against the design system. Reports hardcoded colors, spacing and radius (with the nearest token), native elements that have a design-system component, unknown components, props and variant values, and icon-only buttons without an accessible name. Each diagnostic has line/column, a rule id, a message and a suggested fix. Run it on every snippet or file you write and fix all errors. Pass `code` for unsaved code,
|
|
1261
|
+
description: "Lint TSX/JSX against the design system. Reports hardcoded colors, spacing and radius (with the nearest token), native elements that have a design-system component, unknown components, props and variant values, and icon-only buttons without an accessible name. Each diagnostic has line/column, a rule id, a message and a suggested fix. Run it on every snippet or file you write and fix all errors. Pass `path` for a file in the project, or `code` for unsaved code, with `path` (or `filename`) naming the file it is for or its project folder.",
|
|
1014
1262
|
inputSchema: z.object({
|
|
1015
1263
|
code: z.string().max(MAX_CODE).optional().describe(`TSX/JSX source to check (up to ${MAX_CODE.toLocaleString("en")} characters).`),
|
|
1016
|
-
path: z.string().max(MAX_PATH).optional().describe("Path of a .tsx, .jsx, .ts or .js file
|
|
1264
|
+
path: z.string().max(MAX_PATH).optional().describe("Path of a .tsx, .jsx, .ts or .js file, relative to the project root (the monorepo root, when served from there) or absolute: the file to check, or, with `code`, the file the code is for, as `filename`. With `code`, a project folder only picks the project."),
|
|
1017
1265
|
filename: z.string().max(MAX_PATH).optional().describe("Name to report `code` under; a .jsx extension parses it as JSX. At a monorepo root, the path the code will be saved under, which picks the project whose design system checks it. When the server was started outside your project, pass it as an absolute path: the project is found from it."),
|
|
1018
1266
|
limit: z.number().int().min(1).max(MAX_LIMIT).default(DEFAULT_LIMIT).describe(`Most diagnostics to return, errors first (default ${DEFAULT_LIMIT}). The counts and byRule cover all of them.`)
|
|
1019
1267
|
}),
|
|
@@ -1036,11 +1284,14 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
1036
1284
|
notice: z.string().optional().describe("Set when no components or no color tokens were found, so rules did not run, when components came from modules the model does not include, when the project depends on a component library onsystem does not read yet, or when its dependencies are not installed.")
|
|
1037
1285
|
}),
|
|
1038
1286
|
annotations: READ_ONLY
|
|
1039
|
-
}, async ({ code, path: filePath, filename, limit }) => {
|
|
1287
|
+
}, async ({ code, path: filePath, filename: given, limit }) => {
|
|
1040
1288
|
if (code === void 0 && !filePath) return errorResult("Pass either `code` or `path`.");
|
|
1041
|
-
if (code !== void 0 &&
|
|
1042
|
-
const
|
|
1289
|
+
if (code !== void 0 && given !== void 0 && !SCRIPT_FILE.test(given)) return notScript(given, " Pass as `filename` the .tsx path the code is for, or leave `filename` out.");
|
|
1290
|
+
const folder = code !== void 0 && filePath && !SCRIPT_FILE.test(filePath) ? filePath : void 0;
|
|
1291
|
+
const filename = code === void 0 || folder !== void 0 ? given : given ?? (filePath || void 0);
|
|
1292
|
+
let routeBy = code === void 0 ? filePath : filename ?? folder;
|
|
1043
1293
|
const set = await getProjects?.(routeBy);
|
|
1294
|
+
if (set && folder !== void 0 && routeBy === folder && path.resolve(set.root, folder) === set.root && !set.dirs.includes(".")) routeBy = void 0;
|
|
1044
1295
|
let ds;
|
|
1045
1296
|
if (!set) ds = await getDesignSystem(routeBy);
|
|
1046
1297
|
else if (routeBy) {
|
|
@@ -1128,14 +1379,14 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
1128
1379
|
uri: `ds://components/${c.name}${ambiguous ? `?path=${encodeURIComponent(projects[0] ?? ".")}` : ""}`,
|
|
1129
1380
|
name: c.name,
|
|
1130
1381
|
title: c.name,
|
|
1131
|
-
...c
|
|
1382
|
+
...resourceDescription(c),
|
|
1132
1383
|
mimeType: "text/markdown"
|
|
1133
1384
|
})) };
|
|
1134
1385
|
return { resources: found.ds.components.map((c) => ({
|
|
1135
1386
|
uri: `ds://components/${c.name}`,
|
|
1136
1387
|
name: c.name,
|
|
1137
1388
|
title: c.name,
|
|
1138
|
-
...c
|
|
1389
|
+
...resourceDescription(c),
|
|
1139
1390
|
mimeType: "text/markdown"
|
|
1140
1391
|
})) };
|
|
1141
1392
|
},
|
|
@@ -1216,8 +1467,8 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
1216
1467
|
|
|
1217
1468
|
Work like this:
|
|
1218
1469
|
1. Find components: search_components for each piece of the UI (for example "confirm a destructive action"), or list_components for an overview.
|
|
1219
|
-
2. Read the contract: get_component for every component you use. Use only the props and variant values it lists, and compose parts
|
|
1220
|
-
3. Style with tokens: get_tokens when you need a color, spacing or radius. Never write hex/rgb colors, arbitrary px values or Tailwind default palette classes such as bg-blue-500.
|
|
1470
|
+
2. Read the contract: get_component for every component you use. Use only the props and variant values it lists, and compose its parts as its docs and examples show, when it has them.
|
|
1471
|
+
3. Style with tokens: get_tokens when you need a color, spacing or radius. Never write hex/rgb colors, arbitrary px values or Tailwind default palette classes such as bg-blue-500. chart-* and sidebar-* tokens are for charts and the sidebar only: for a status color the theme has no token for (success, warning, info), add a token or keep the Tailwind color as check_ui says, and never use a chart-* or sidebar-* token for it.
|
|
1221
1472
|
4. Use design-system components instead of native elements (<Button>, not <button>).
|
|
1222
1473
|
5. Verify: run check_ui on the code you wrote, fix every error, and run it again until it reports none.
|
|
1223
1474
|
|