Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-cats-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'onsystem': patch
---

Explain when a project has no design-system components available.
10 changes: 10 additions & 0 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
capDiagnostics,
importStatement,
listComponents,
noComponentsMessage,
type Listing,
renderAmbiguous,
renderCatalogList,
Expand Down Expand Up @@ -484,6 +485,9 @@ export function createServer({ getDesignSystem, getProjects }: CreateServerOptio
const matches = catalog.find(name);
const [entry] = matches;
if (!entry) {
if (!catalog.roots().length) {
return errorResult(noComponentsMessage(catalog.root, undefined, true));
}
const guess = catalog.suggest(name);
const names = [...new Set(catalog.roots().map((e) => e.component.name))];
return errorResult(
Expand Down Expand Up @@ -515,6 +519,12 @@ export function createServer({ getDesignSystem, getProjects }: CreateServerOptio
const { ds } = found;
const component = ds.getComponent(name);
if (!component) {
if (!ds.roots().length) {
return {
isError: true,
content: [{ type: 'text', text: noComponentsMessage(ds.root, found.project?.dir) }],
};
}
const guess = ds.suggestComponent(name);
return {
isError: true,
Expand Down
13 changes: 11 additions & 2 deletions src/server/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ function listTail(listed: Listing<unknown>): string[] {
return lines;
}

/** The shared explanation when a project has no design-system components to show. */
export function noComponentsMessage(root: string, project?: string, monorepo = false): string {
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.`;
}
const where = project ? ` in ${project}` : '';
return `No design-system components found${where}. Check the "components" globs in the config (root: ${root}).`;
}

/** `project`: the folder of a workspace root's project the list is for. */
export function renderComponentList(
ds: DesignSystem,
Expand All @@ -181,7 +190,7 @@ export function renderComponentList(
const roots = ds.roots();
const where = project ? ` in ${project}` : '';
if (!roots.length) {
return `No components found${where}. Check the "components" globs in the config (root: ${ds.root}).`;
return noComponentsMessage(ds.root, project);
}
const listed = listComponents(roots, (c) => c, filter);
if (!listed.matched) {
Expand Down Expand Up @@ -240,7 +249,7 @@ const PICK_PROJECT =
export function renderCatalogList(catalog: Catalog, filter: ListFilter = {}): string {
const roots = catalog.roots();
if (!roots.length) {
return `No components found in any project of this monorepo (root: ${catalog.root}). Run \`onsystem inspect\` there to see what was looked at.`;
return noComponentsMessage(catalog.root, undefined, true);
}
const listed = listComponents(
roots,
Expand Down
4 changes: 2 additions & 2 deletions test/design-system-packages-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,12 @@ describe('what apps get from a design-system package (docs/design-system-package
);
expect(whatAppsGet).toContain(`\`check\` says \`${notice}\`\n`);
// Agents find no component.
expect(await tool(app, 'list_components', {})).toMatch(/^No components found/);
expect(await tool(app, 'list_components', {})).toMatch(/^No design-system components found/);
expect(await tool(app, 'search_components', { query: 'button' })).toMatch(
/^No components match/,
);
expect(await tool(app, 'get_component', { name: 'Button' })).toMatch(
/^No component named "Button"/,
/^No design-system components found/,
);
// check_ui reports from the same rules.
const checked = await tool(app, 'check_ui', { path: 'app/page.tsx' });
Expand Down
19 changes: 19 additions & 0 deletions test/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,25 @@ describe('MCP server over the in-memory transport', () => {
expect(text(result)).toContain('Did you mean "Button"?');
});

it('get_component says when the project has no components at all', async () => {
const ds = await load(fixture({ 'package.json': '{ "name": "plain" }' }));
const server = createServer({ getDesignSystem: () => Promise.resolve(ds) });
const other = new Client({ name: 'empty', version: '1.0.0' });
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([server.connect(serverTransport), other.connect(clientTransport)]);

const result = await other.callTool({ name: 'get_component', arguments: { name: 'Button' } });
const listing = await other.callTool({ name: 'list_components', arguments: {} });

expect(result.isError).toBe(true);
expect(text(result)).toContain('No design-system components found');
expect(text(result)).toContain('Check the "components" globs in the config');
expect(text(result)).toContain(`(root: ${ds.root})`);
expect(text(result)).not.toContain('Available: .');
expect(text(result)).toBe(text(listing));
await other.close();
});

it('search_components', async () => {
const result = await client.callTool({
name: 'search_components',
Expand Down
Loading