From 085fb08e1c0cab917dad7119c977cfb45ad723de Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 17 Mar 2026 12:39:40 +0800 Subject: [PATCH 1/3] Default listing creation to CardListing except for FieldListing --- packages/host/app/commands/listing-create.ts | 72 +------------------- 1 file changed, 2 insertions(+), 70 deletions(-) diff --git a/packages/host/app/commands/listing-create.ts b/packages/host/app/commands/listing-create.ts index ce94c134ece..905f07a90a3 100644 --- a/packages/host/app/commands/listing-create.ts +++ b/packages/host/app/commands/listing-create.ts @@ -33,12 +33,9 @@ import type RealmService from '../services/realm'; import type RealmServerService from '../services/realm-server'; import type StoreService from '../services/store'; -type ListingType = 'card' | 'app' | 'skill' | 'theme' | 'field'; +type ListingType = 'card' | 'field'; const listingSubClass: Record = { card: 'CardListing', - app: 'AppListing', - skill: 'SkillListing', - theme: 'ThemeListing', field: 'FieldListing', }; @@ -177,66 +174,12 @@ export default class ListingCreateCommand extends HostBaseCommand< private async guessListingType( codeRef: ResolvedCodeRef, ): Promise { - if (this.isTheme(codeRef)) { - return 'theme'; - } if (await this.isFieldCodeRef(codeRef)) { return 'field'; } - try { - const oneShot = new OneShotLlmRequestCommand(this.commandContext); - const systemPrompt = - 'Respond ONLY with one token: card, app, skill, or theme. No JSON, no punctuation.'; - const userPrompt = 'What is the listingType?'; - const result = await oneShot.execute({ - codeRef, - systemPrompt, - userPrompt, - llmModel: 'openai/gpt-4.1-nano', - }); - const maybeType = parseResponseToSingleWord(result.output, true); - if ( - maybeType === 'app' || - maybeType === 'skill' || - maybeType === 'theme' - ) { - return maybeType; - } - return 'card'; - } catch { - return 'card'; - } + return 'card'; } - private isTheme(codeRef: ResolvedCodeRef): boolean { - const codeRefModule = codeRef?.module?.toLowerCase(); - const codeRefName = codeRef?.name?.toLowerCase(); - const knownBaseModules = [ - 'https://cardstack.com/base/structured-theme', - 'https://cardstack.com/base/style-reference', - 'https://cardstack.com/base/brand-guide', - ]; - if ( - codeRefModule && - knownBaseModules.some((base) => codeRefModule.includes(base)) - ) { - return true; - } - if (codeRefName) { - const normalizedName = codeRefName - .split('') - .filter((char) => char !== '-' && char !== '_' && char !== ' ') - .join(''); - if ( - ['theme', 'structuredtheme', 'stylereference', 'brandguide'].includes( - normalizedName, - ) - ) { - return true; - } - } - return false; - } private async isFieldCodeRef(codeRef: ResolvedCodeRef): Promise { try { @@ -568,14 +511,3 @@ function parseResponseToString( return firstLine.slice(0, maxLength); } -function parseResponseToSingleWord( - response?: string, - lowerCase: boolean = false, -): string | undefined { - const str = parseResponseToString(response, 50)?.trim(); - if (!str) return undefined; - let token = str.split(/\s+/)[0].replace(/[^A-Za-z0-9_-]/g, ''); - if (!token) return undefined; - if (lowerCase) token = token.toLowerCase(); - return token; -} From 00b9e282a5649d32cdec18c662771cc41b8f754f Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 17 Mar 2026 12:57:34 +0800 Subject: [PATCH 2/3] fix lint --- packages/host/app/commands/listing-create.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/host/app/commands/listing-create.ts b/packages/host/app/commands/listing-create.ts index 905f07a90a3..bd07a53fe4b 100644 --- a/packages/host/app/commands/listing-create.ts +++ b/packages/host/app/commands/listing-create.ts @@ -180,7 +180,6 @@ export default class ListingCreateCommand extends HostBaseCommand< return 'card'; } - private async isFieldCodeRef(codeRef: ResolvedCodeRef): Promise { try { const cardDef = await loadCardDef(codeRef, { @@ -510,4 +509,3 @@ function parseResponseToString( if (!firstLine) return undefined; return firstLine.slice(0, maxLength); } - From 68d794e90c7044ffee29e8d28d37a4e2d035914a Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 17 Mar 2026 15:57:55 +0800 Subject: [PATCH 3/3] remove applisting type checking --- packages/host/app/commands/listing-create.ts | 72 +++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/packages/host/app/commands/listing-create.ts b/packages/host/app/commands/listing-create.ts index bd07a53fe4b..f05e3855fbb 100644 --- a/packages/host/app/commands/listing-create.ts +++ b/packages/host/app/commands/listing-create.ts @@ -33,9 +33,11 @@ import type RealmService from '../services/realm'; import type RealmServerService from '../services/realm-server'; import type StoreService from '../services/store'; -type ListingType = 'card' | 'field'; +type ListingType = 'card' | 'skill' | 'theme' | 'field'; const listingSubClass: Record = { card: 'CardListing', + skill: 'SkillListing', + theme: 'ThemeListing', field: 'FieldListing', }; @@ -174,10 +176,64 @@ export default class ListingCreateCommand extends HostBaseCommand< private async guessListingType( codeRef: ResolvedCodeRef, ): Promise { + if (this.isTheme(codeRef)) { + return 'theme'; + } if (await this.isFieldCodeRef(codeRef)) { return 'field'; } - return 'card'; + try { + const oneShot = new OneShotLlmRequestCommand(this.commandContext); + const systemPrompt = + 'Respond ONLY with one token: card, skill, or theme. No JSON, no punctuation.'; + const userPrompt = 'What is the listingType?'; + const result = await oneShot.execute({ + codeRef, + systemPrompt, + userPrompt, + llmModel: 'openai/gpt-4.1-nano', + }); + const maybeType = parseResponseToSingleWord(result.output, true); + if ( + maybeType === 'skill' || + maybeType === 'theme' + ) { + return maybeType; + } + return 'card'; + } catch { + return 'card'; + } + } + + private isTheme(codeRef: ResolvedCodeRef): boolean { + const codeRefModule = codeRef?.module?.toLowerCase(); + const codeRefName = codeRef?.name?.toLowerCase(); + const knownBaseModules = [ + 'https://cardstack.com/base/structured-theme', + 'https://cardstack.com/base/style-reference', + 'https://cardstack.com/base/brand-guide', + ]; + if ( + codeRefModule && + knownBaseModules.some((base) => codeRefModule.includes(base)) + ) { + return true; + } + if (codeRefName) { + const normalizedName = codeRefName + .split('') + .filter((char) => char !== '-' && char !== '_' && char !== ' ') + .join(''); + if ( + ['theme', 'structuredtheme', 'stylereference', 'brandguide'].includes( + normalizedName, + ) + ) { + return true; + } + } + return false; } private async isFieldCodeRef(codeRef: ResolvedCodeRef): Promise { @@ -509,3 +565,15 @@ function parseResponseToString( if (!firstLine) return undefined; return firstLine.slice(0, maxLength); } + +function parseResponseToSingleWord( + response?: string, + lowerCase: boolean = false, +): string | undefined { + const str = parseResponseToString(response, 50)?.trim(); + if (!str) return undefined; + let token = str.split(/\s+/)[0].replace(/[^A-Za-z0-9_-]/g, ''); + if (!token) return undefined; + if (lowerCase) token = token.toLowerCase(); + return token; +}