From 8737f7bbea64e8cf74544c3b972183c3cac6a394 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 14 Mar 2026 23:24:29 +0800 Subject: [PATCH 01/20] remove navigate the listing open in interact mode --- packages/host/app/commands/listing-create.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/host/app/commands/listing-create.ts b/packages/host/app/commands/listing-create.ts index ce94c134ece..54e6d940cf7 100644 --- a/packages/host/app/commands/listing-create.ts +++ b/packages/host/app/commands/listing-create.ts @@ -149,7 +149,6 @@ export default class ListingCreateCommand extends HostBaseCommand< if (!listingId) { throw new Error('Failed to create listing card (no localId)'); } - await this.operatorModeStateService.openCardInInteractMode(listingId); const commandModule = await this.loadCommandModule(); const listingCard = listing as CardAPI.CardDef; // ensure correct type From d700f68a667cd5787bf800f32e760626a1df54cc Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 15 Mar 2026 10:26:26 +0800 Subject: [PATCH 02/20] implement openCreateListingModal as a host command --- packages/host/app/commands/index.ts | 6 + .../app/commands/open-create-listing-modal.ts | 33 ++ .../components/operator-mode/container.gts | 2 + .../operator-mode/create-listing-modal.gts | 348 ++++++++++++++++++ .../services/operator-mode-state-service.ts | 16 + 5 files changed, 405 insertions(+) create mode 100644 packages/host/app/commands/open-create-listing-modal.ts create mode 100644 packages/host/app/components/operator-mode/create-listing-modal.gts diff --git a/packages/host/app/commands/index.ts b/packages/host/app/commands/index.ts index 4fd424e63da..d125fdf950d 100644 --- a/packages/host/app/commands/index.ts +++ b/packages/host/app/commands/index.ts @@ -37,6 +37,7 @@ import * as ListingUpdateSpecsCommandModule from './listing-update-specs'; import * as ListingUseCommandModule from './listing-use'; import * as OneShotLlmRequestCommandModule from './one-shot-llm-request'; import * as OpenAiAssistantRoomCommandModule from './open-ai-assistant-room'; +import * as OpenCreateListingModalCommandModule from './open-create-listing-modal'; import * as OpenInInteractModeModule from './open-in-interact-mode'; import * as OpenWorkspaceCommandModule from './open-workspace'; import * as PatchCardInstanceCommandModule from './patch-card-instance'; @@ -256,6 +257,10 @@ export function shimHostCommands(virtualNetwork: VirtualNetwork) { '@cardstack/boxel-host/commands/open-ai-assistant-room', OpenAiAssistantRoomCommandModule, ); + virtualNetwork.shimModule( + '@cardstack/boxel-host/commands/open-create-listing-modal', + OpenCreateListingModalCommandModule, + ); virtualNetwork.shimModule( '@cardstack/boxel-host/commands/open-workspace', OpenWorkspaceCommandModule, @@ -404,6 +409,7 @@ export const HostCommandClasses: (typeof HostBaseCommand)[] = [ ListingUseCommandModule.default, OneShotLlmRequestCommandModule.default, OpenAiAssistantRoomCommandModule.default, + OpenCreateListingModalCommandModule.default, OpenInInteractModeModule.default, OpenWorkspaceCommandModule.default, GenerateThemeExampleCommandModule.default, diff --git a/packages/host/app/commands/open-create-listing-modal.ts b/packages/host/app/commands/open-create-listing-modal.ts new file mode 100644 index 00000000000..c7e51d1a285 --- /dev/null +++ b/packages/host/app/commands/open-create-listing-modal.ts @@ -0,0 +1,33 @@ +import { service } from '@ember/service'; + +import type * as BaseCommandModule from 'https://cardstack.com/base/command'; + +import HostBaseCommand from '../lib/host-base-command'; + +import type OperatorModeStateService from '../services/operator-mode-state-service'; + +export default class OpenCreateListingModalCommand extends HostBaseCommand< + typeof BaseCommandModule.ListingCreateInput +> { + @service declare private operatorModeStateService: OperatorModeStateService; + + description = 'Open create listing confirmation modal'; + + async getInputType() { + let commandModule = await this.loadCommandModule(); + const { ListingCreateInput } = commandModule; + return ListingCreateInput; + } + + requireInputFields = ['codeRef', 'targetRealm']; + + protected async run( + input: BaseCommandModule.ListingCreateInput, + ): Promise { + this.operatorModeStateService.openCreateListingModal({ + codeRef: input.codeRef, + targetRealm: input.targetRealm, + openCardId: input.openCardId, + }); + } +} diff --git a/packages/host/app/components/operator-mode/container.gts b/packages/host/app/components/operator-mode/container.gts index 45653d4104c..b514090ff56 100644 --- a/packages/host/app/components/operator-mode/container.gts +++ b/packages/host/app/components/operator-mode/container.gts @@ -41,6 +41,7 @@ import PrerenderedCardSearch from '../prerendered-card-search'; import { Submodes } from '../submode-switcher'; import ChooseFileModal from './choose-file-modal'; +import CreateListingModal from './create-listing-modal'; import type CardService from '../../services/card-service'; import type CommandService from '../../services/command-service'; @@ -143,6 +144,7 @@ export default class OperatorModeContainer extends Component { diff --git a/packages/host/app/components/operator-mode/submode-layout.gts b/packages/host/app/components/operator-mode/submode-layout.gts index 5817ddeecc3..f801d63b65c 100644 --- a/packages/host/app/components/operator-mode/submode-layout.gts +++ b/packages/host/app/components/operator-mode/submode-layout.gts @@ -31,7 +31,6 @@ import AiAssistantPanel from '@cardstack/host/components/ai-assistant/panel'; import AiAssistantToast from '@cardstack/host/components/ai-assistant/toast'; import ProfileSettingsModal from '@cardstack/host/components/operator-mode/profile/profile-settings-modal'; import ProfileInfoPopover from '@cardstack/host/components/operator-mode/profile-info-popover'; -import Toast from '@cardstack/host/components/toast'; import config from '@cardstack/host/config/environment'; @@ -455,18 +454,6 @@ export default class SubmodeLayout extends Component { @onInputInsertion={{this.storeSearchElement}} /> {{/if}} - {{#if this.operatorModeStateService.toast}} - - {{this.operatorModeStateService.toast.message}} - - {{/if}} void; + openCardIds?: string[]; } // Below types form a raw POJO representation of operator mode state. @@ -155,8 +148,7 @@ export default class OperatorModeStateService extends Service { private moduleInspectorHistory: Record; @tracked profileSettingsOpen = false; - @tracked createListingModalRequest?: CreateListingModalRequest; - @tracked toast?: OperatorModeToast; + @tracked createListingModalPayload?: CreateListingModalPayload; @service declare private cardService: CardService; @service declare private codeSemanticsService: CodeSemanticsService; @@ -225,20 +217,12 @@ export default class OperatorModeStateService extends Service { this.schedulePersist(); }; - showCreateListingModal = (request: CreateListingModalRequest) => { - this.createListingModalRequest = request; + showCreateListingModal = (payload: CreateListingModalPayload) => { + this.createListingModalPayload = payload; }; dismissCreateListingModal = () => { - this.createListingModalRequest = undefined; - }; - - showToast = (toast: OperatorModeToast) => { - this.toast = toast; - }; - - dismissToast = () => { - this.toast = undefined; + this.createListingModalPayload = undefined; }; setNewFileDropdownOpen = () => { @@ -271,8 +255,7 @@ export default class OperatorModeStateService extends Service { this.cardTitles = new TrackedMap(); this.moduleInspectorHistory = {}; this.profileSettingsOpen = false; - this.createListingModalRequest = undefined; - this.toast = undefined; + this.createListingModalPayload = undefined; window.localStorage.removeItem(ModuleInspectorSelections); this.schedulePersist(); } From 74d2a99a09304ec3ffa9f53db65338221be7438b Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 15:33:21 +0800 Subject: [PATCH 11/20] add isListingDef checking --- .../catalog-app/listing/listing.gts | 1 + .../operator-mode/create-listing-modal.gts | 4 +-- .../components/operator-mode/detail-panel.gts | 29 ++++++++++++++----- packages/runtime-common/code-ref.ts | 8 +++++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/packages/catalog-realm/catalog-app/listing/listing.gts b/packages/catalog-realm/catalog-app/listing/listing.gts index 90d0b733264..05d7369cea3 100644 --- a/packages/catalog-realm/catalog-app/listing/listing.gts +++ b/packages/catalog-realm/catalog-app/listing/listing.gts @@ -564,6 +564,7 @@ class EmbeddedTemplate extends Component { export class Listing extends CardDef { static displayName = 'Listing'; static headerColor = '#6638ff'; + static isListingDef = true; @field name = contains(StringField); @field summary = contains(MarkdownField); diff --git a/packages/host/app/components/operator-mode/create-listing-modal.gts b/packages/host/app/components/operator-mode/create-listing-modal.gts index 32dc0896efe..bf818544881 100644 --- a/packages/host/app/components/operator-mode/create-listing-modal.gts +++ b/packages/host/app/components/operator-mode/create-listing-modal.gts @@ -22,11 +22,11 @@ import { type ResolvedCodeRef, } from '@cardstack/runtime-common'; -import ItemButton from '@cardstack/host/components/card-search/item-button'; -import { Submodes } from '@cardstack/host/components/submode-switcher'; import ListingCreateCommand from '@cardstack/host/commands/listing-create'; +import ItemButton from '@cardstack/host/components/card-search/item-button'; import ModalContainer from '@cardstack/host/components/modal-container'; import { SelectedTypePill } from '@cardstack/host/components/operator-mode/create-file-modal'; +import { Submodes } from '@cardstack/host/components/submode-switcher'; import { getSearch } from '@cardstack/host/resources/search'; import type CommandService from '@cardstack/host/services/command-service'; diff --git a/packages/host/app/components/operator-mode/detail-panel.gts b/packages/host/app/components/operator-mode/detail-panel.gts index d6c1421472f..3155c214974 100644 --- a/packages/host/app/components/operator-mode/detail-panel.gts +++ b/packages/host/app/components/operator-mode/detail-panel.gts @@ -34,6 +34,8 @@ import { isFileDef, isBaseDef, isCardErrorJSONAPI, + isListingDef, + isListingInstance, internalKeyFor, type ResolvedCodeRef, type CardErrorJSONAPI, @@ -255,12 +257,13 @@ export default class DetailPanel extends Component { ] : []), ...(this.realm.canWrite(this.args.readyFile.url) && - this.args.selectedDeclaration?.exportName + this.args.selectedDeclaration?.exportName && + !isListingDef(this.args.selectedDeclaration?.cardOrField) ? [ { label: 'Create Listing', icon: Package, - handler: this.createListingWithAI, + handler: this.createListing, }, ] : []), @@ -298,13 +301,18 @@ export default class DetailPanel extends Component { icon: Copy, handler: this.duplicateInstance, }, - ...(this.realm.canWrite(this.args.readyFile.url) + ...(this.realm.canWrite(this.args.readyFile.url) && + !isListingInstance(this.args.cardInstance) ? [ { label: 'Create Listing', icon: Package, - handler: this.createListingWithAI, + handler: this.createListing, }, + ] + : []), + ...(this.realm.canWrite(this.args.readyFile.url) + ? [ { label: 'Delete', icon: IconTrash, @@ -420,7 +428,7 @@ export default class DetailPanel extends Component { this.args.openSearch(`carddef:${refURL}`); } - @action private async createListingWithAI() { + @action private async createListing() { const command = new OpenCreateListingModalCommand( this.commandService.commandContext, ); @@ -429,19 +437,24 @@ export default class DetailPanel extends Component { throw new Error('targetRealm is required to create listing'); } if (this.isCardInstance) { - const openCardId = this.args.cardInstance?.id; + const openCardIds = this.args.cardInstance?.id + ? [this.args.cardInstance.id] + : []; const codeRef = this.cardInstanceType?.type ? getResolvedCodeRefFromType(this.cardInstanceType.type) : undefined; if (!codeRef) { throw new Error('Cannot create listing: card type not yet loaded'); } - await command.execute({ openCardId, codeRef, targetRealm }); + await command.execute({ openCardIds, codeRef, targetRealm }); } else { const codeRef: ResolvedCodeRef = this.selectedDeclarationAsCodeRef; + const openCardIds = this.args.cardInstance?.id + ? [this.args.cardInstance.id] + : []; await command.execute({ codeRef, - openCardId: this.args.cardInstance?.id, + openCardIds, targetRealm, }); } diff --git a/packages/runtime-common/code-ref.ts b/packages/runtime-common/code-ref.ts index 483172bfb0a..72dcc015156 100644 --- a/packages/runtime-common/code-ref.ts +++ b/packages/runtime-common/code-ref.ts @@ -129,6 +129,14 @@ export function isFileDef(def: any): def is typeof FileDef { return isBaseDef(def) && 'isFileDef' in def; } +export function isListingDef(def: any): boolean { + return isCardDef(def) && 'isListingDef' in def; +} + +export function isListingInstance(card: any): boolean { + return isListingDef(card?.constructor); +} + export function isFieldInstance( fieldInstance: any, ): fieldInstance is T { From beab058e7bc0636cca50a9111a5e1eada15322c2 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 15:34:02 +0800 Subject: [PATCH 12/20] update test --- .../open-create-listing-modal-test.gts | 20 ++++---- .../components/create-listing-modal-test.gts | 46 +++---------------- 2 files changed, 17 insertions(+), 49 deletions(-) diff --git a/packages/host/tests/integration/commands/open-create-listing-modal-test.gts b/packages/host/tests/integration/commands/open-create-listing-modal-test.gts index 8ddcc276150..b8fec97ba13 100644 --- a/packages/host/tests/integration/commands/open-create-listing-modal-test.gts +++ b/packages/host/tests/integration/commands/open-create-listing-modal-test.gts @@ -50,20 +50,20 @@ module('Integration | commands | open-create-listing-modal', function (hooks) { name: 'Pet', }, targetRealm: testRealmURL, - openCardId: `${testRealmURL}Pet/mango`, + openCardIds: [`${testRealmURL}Pet/mango`], } as never); - assert.deepEqual(operatorModeStateService.createListingModalRequest, { + assert.deepEqual(operatorModeStateService.createListingModalPayload, { codeRef: { module: `${testRealmURL}pet`, name: 'Pet', }, targetRealm: testRealmURL, - openCardId: `${testRealmURL}Pet/mango`, + openCardIds: [`${testRealmURL}Pet/mango`], }); }); - test('stores modal request without openCardId', async function (assert) { + test('stores modal request without openCardIds', async function (assert) { let commandService = getService('command-service'); let operatorModeStateService = getService('operator-mode-state-service'); @@ -79,16 +79,16 @@ module('Integration | commands | open-create-listing-modal', function (hooks) { targetRealm: testRealmURL, } as never); - let request = operatorModeStateService.createListingModalRequest; + let request = operatorModeStateService.createListingModalPayload; assert.deepEqual(request?.codeRef, { module: `${testRealmURL}pet`, name: 'Pet', }); assert.strictEqual(request?.targetRealm, testRealmURL); assert.strictEqual( - request?.openCardId, + request?.openCardIds, undefined, - 'openCardId is absent from state', + 'openCardIds is absent from state', ); }); @@ -106,18 +106,18 @@ module('Integration | commands | open-create-listing-modal', function (hooks) { name: 'Pet', }, targetRealm: testRealmURL, - openCardId: `${testRealmURL}Pet/mango`, + openCardIds: [`${testRealmURL}Pet/mango`], } as never); assert.ok( - operatorModeStateService.createListingModalRequest, + operatorModeStateService.createListingModalPayload, 'request is set after execute', ); operatorModeStateService.dismissCreateListingModal(); assert.strictEqual( - operatorModeStateService.createListingModalRequest, + operatorModeStateService.createListingModalPayload, undefined, 'request is cleared after dismissCreateListingModal', ); diff --git a/packages/host/tests/integration/components/create-listing-modal-test.gts b/packages/host/tests/integration/components/create-listing-modal-test.gts index ac5cbd1e01f..d4818f8cfa3 100644 --- a/packages/host/tests/integration/components/create-listing-modal-test.gts +++ b/packages/host/tests/integration/components/create-listing-modal-test.gts @@ -64,7 +64,7 @@ module('Integration | components | create-listing-modal', function (hooks) { .includesText(ctx.realmName); }); - test('source select defaults to definition when no openCardId', async function (assert) { + test('shows codeRef in modal', async function (assert) { await renderComponent( class TestDriver extends GlimmerComponent { @@ -78,35 +78,7 @@ module('Integration | components | create-listing-modal', function (hooks) { await waitFor('[data-test-create-listing-modal]'); - assert - .dom( - '.ember-power-select-trigger [data-test-create-listing-definition-option]', - ) - .exists('definition option is selected in the trigger'); - }); - - test('source select pre-selects instance when openCardId is provided', async function (assert) { - let openCardId = `${testRealmURL}Pet/mango`; - - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - ctx.operatorModeStateService.showCreateListingModal({ - codeRef: { module: `${testRealmURL}pet`, name: 'Pet' }, - targetRealm: testRealmURL, - openCardId, - }); - - await waitFor('[data-test-create-listing-modal]'); - - assert - .dom( - `.ember-power-select-trigger [data-test-create-listing-instance-option="${openCardId}"]`, - ) - .exists('instance option matching openCardId is selected in the trigger'); + assert.dom('[data-test-create-listing-coderef]').includesText('Pet'); }); test('cancel button closes modal', async function (assert) { @@ -127,27 +99,23 @@ module('Integration | components | create-listing-modal', function (hooks) { assert.dom('[data-test-create-listing-modal]').doesNotExist(); }); - test('shows error when listing creation fails', async function (assert) { + test('shows create button', async function (assert) { await renderComponent( class TestDriver extends GlimmerComponent { }, ); - // Provide an incomplete codeRef (missing 'module') so the create task - // fails synchronously with a descriptive error message. ctx.operatorModeStateService.showCreateListingModal({ - codeRef: { name: 'Pet' } as any, + codeRef: { module: `${testRealmURL}pet`, name: 'Pet' }, targetRealm: testRealmURL, }); await waitFor('[data-test-create-listing-modal]'); - await click('[data-test-create-listing-confirm-button]'); - - await waitFor('[data-test-create-listing-error]'); assert - .dom('[data-test-create-listing-error]') - .includesText('Cannot create listing without a resolved code ref'); + .dom('[data-test-create-listing-confirm-button]') + .includesText('Create'); + assert.dom('[data-test-create-listing-confirm-button]').isNotDisabled(); }); }); From 13a49f9ce3e00ae82da95ebb4a8dd290192f431b Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 15:40:49 +0800 Subject: [PATCH 13/20] remove toast --- packages/host/app/components/toast.gts | 162 ------------------------- 1 file changed, 162 deletions(-) delete mode 100644 packages/host/app/components/toast.gts diff --git a/packages/host/app/components/toast.gts b/packages/host/app/components/toast.gts deleted file mode 100644 index 8bb98a16284..00000000000 --- a/packages/host/app/components/toast.gts +++ /dev/null @@ -1,162 +0,0 @@ -import { on } from '@ember/modifier'; - -import Component from '@glimmer/component'; - -import { - BoxelButton, - IconButton, - LoadingIndicator, -} from '@cardstack/boxel-ui/components'; -import { eq } from '@cardstack/boxel-ui/helpers'; -import { IconX } from '@cardstack/boxel-ui/icons'; - -import AlertCircle from '@cardstack/boxel-icons/alert-circle'; -import CircleCheck from '@cardstack/boxel-icons/circle-check'; - -interface Signature { - Element: HTMLDivElement; - Args: { - isVisible: boolean; - onDismiss: () => void; - status?: 'loading' | 'success' | 'error'; - ctaLabel?: string; - onCtaClick?: () => void; - }; - Blocks: { - header: []; - default: []; - }; -} - -export default class Toast extends Component { - -} From 0a12266f32fd32f5fda76128d3cee0d8de80d0b1 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 15:42:49 +0800 Subject: [PATCH 14/20] menu item should pass openCardIds --- packages/base/menu-items.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/base/menu-items.ts b/packages/base/menu-items.ts index fdfb1b3544e..efe10ce45d9 100644 --- a/packages/base/menu-items.ts +++ b/packages/base/menu-items.ts @@ -167,7 +167,7 @@ export function getDefaultCardMenuItems( } await new OpenCreateListingModalCommand(params.commandContext).execute({ codeRef, - openCardId: cardId, + openCardIds: [cardId], targetRealm, }); }, From 368cfd9b998e95522296d9bce7e489bc7e8f73ca Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 15:53:13 +0800 Subject: [PATCH 15/20] fix test --- .../open-create-listing-modal-test.gts | 24 +++++----- .../components/create-listing-modal-test.gts | 44 +++++-------------- 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/packages/host/tests/integration/commands/open-create-listing-modal-test.gts b/packages/host/tests/integration/commands/open-create-listing-modal-test.gts index b8fec97ba13..c786bb36b13 100644 --- a/packages/host/tests/integration/commands/open-create-listing-modal-test.gts +++ b/packages/host/tests/integration/commands/open-create-listing-modal-test.gts @@ -36,7 +36,7 @@ module('Integration | commands | open-create-listing-modal', function (hooks) { ); }); - test('stores modal request in operator mode state', async function (assert) { + test('stores modal payload in operator mode state', async function (assert) { let commandService = getService('command-service'); let operatorModeStateService = getService('operator-mode-state-service'); @@ -63,7 +63,7 @@ module('Integration | commands | open-create-listing-modal', function (hooks) { }); }); - test('stores modal request without openCardIds', async function (assert) { + test('stores modal payload without openCardIds', async function (assert) { let commandService = getService('command-service'); let operatorModeStateService = getService('operator-mode-state-service'); @@ -79,20 +79,20 @@ module('Integration | commands | open-create-listing-modal', function (hooks) { targetRealm: testRealmURL, } as never); - let request = operatorModeStateService.createListingModalPayload; - assert.deepEqual(request?.codeRef, { + let payload = operatorModeStateService.createListingModalPayload; + assert.deepEqual(payload?.codeRef, { module: `${testRealmURL}pet`, name: 'Pet', }); - assert.strictEqual(request?.targetRealm, testRealmURL); - assert.strictEqual( - request?.openCardIds, - undefined, - 'openCardIds is absent from state', + assert.strictEqual(payload?.targetRealm, testRealmURL); + assert.deepEqual( + payload?.openCardIds, + [], + 'openCardIds is empty when not provided', ); }); - test('dismissCreateListingModal clears the request', async function (assert) { + test('dismissCreateListingModal clears the payload', async function (assert) { let commandService = getService('command-service'); let operatorModeStateService = getService('operator-mode-state-service'); @@ -111,7 +111,7 @@ module('Integration | commands | open-create-listing-modal', function (hooks) { assert.ok( operatorModeStateService.createListingModalPayload, - 'request is set after execute', + 'payload is set after execute', ); operatorModeStateService.dismissCreateListingModal(); @@ -119,7 +119,7 @@ module('Integration | commands | open-create-listing-modal', function (hooks) { assert.strictEqual( operatorModeStateService.createListingModalPayload, undefined, - 'request is cleared after dismissCreateListingModal', + 'payload is cleared after dismissCreateListingModal', ); }); }); diff --git a/packages/host/tests/integration/components/create-listing-modal-test.gts b/packages/host/tests/integration/components/create-listing-modal-test.gts index d4818f8cfa3..250eaa1fc23 100644 --- a/packages/host/tests/integration/components/create-listing-modal-test.gts +++ b/packages/host/tests/integration/components/create-listing-modal-test.gts @@ -1,4 +1,4 @@ -import { click, waitFor } from '@ember/test-helpers'; +import { waitFor } from '@ember/test-helpers'; import GlimmerComponent from '@glimmer/component'; import { module, test } from 'qunit'; @@ -15,17 +15,7 @@ module('Integration | components | create-listing-modal', function (hooks) { let noop = () => {}; - test('modal is hidden by default', async function (assert) { - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - assert.dom('[data-test-create-listing-modal]').doesNotExist(); - }); - - test('modal renders when request is set', async function (assert) { + test('modal renders when payload is set', async function (assert) { await renderComponent( class TestDriver extends GlimmerComponent { @@ -81,25 +71,7 @@ module('Integration | components | create-listing-modal', function (hooks) { assert.dom('[data-test-create-listing-coderef]').includesText('Pet'); }); - test('cancel button closes modal', async function (assert) { - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - ctx.operatorModeStateService.showCreateListingModal({ - codeRef: { module: `${testRealmURL}pet`, name: 'Pet' }, - targetRealm: testRealmURL, - }); - - await waitFor('[data-test-create-listing-modal]'); - await click('[data-test-create-listing-cancel-button]'); - - assert.dom('[data-test-create-listing-modal]').doesNotExist(); - }); - - test('shows create button', async function (assert) { + test('shows example instances for a card type', async function (assert) { await renderComponent( class TestDriver extends GlimmerComponent { @@ -112,10 +84,14 @@ module('Integration | components | create-listing-modal', function (hooks) { }); await waitFor('[data-test-create-listing-modal]'); + await waitFor('[data-test-examples-container]'); + assert.dom('[data-test-examples-container]').exists(); + assert + .dom(`[data-test-create-listing-example="${testRealmURL}Pet/mango"]`) + .exists('shows the Pet instance as an example'); assert - .dom('[data-test-create-listing-confirm-button]') - .includesText('Create'); - assert.dom('[data-test-create-listing-confirm-button]').isNotDisabled(); + .dom(`[data-test-card-catalog-item-selected="true"]`) + .exists('example is auto-selected'); }); }); From 5336b8ca509058a8796f3856dbcca2e1ac276540 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 16:01:20 +0800 Subject: [PATCH 16/20] fix test --- .../host/tests/acceptance/code-submode/inspector-test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/host/tests/acceptance/code-submode/inspector-test.ts b/packages/host/tests/acceptance/code-submode/inspector-test.ts index 2b8615ef2e9..1e711759cc8 100644 --- a/packages/host/tests/acceptance/code-submode/inspector-test.ts +++ b/packages/host/tests/acceptance/code-submode/inspector-test.ts @@ -2729,11 +2729,16 @@ export class ExportedCard extends ExportedCardParent { assert .dom('[data-test-create-listing-modal]') .exists('confirmation modal appears after clicking Create Listing'); + + await waitFor('[data-test-create-listing-examples]'); + assert + .dom('[data-test-select-all]') + .isChecked('select all checkbox is checked'); assert .dom( - `.ember-power-select-trigger [data-test-create-listing-instance-option]`, + '[data-test-create-listing-examples] [data-test-card-catalog-item-selected="true"]', ) - .exists('instance option is pre-selected in the source dropdown'); + .exists({ count: 2 }, 'all instances are pre-selected'); }); test('cancel button in Create Listing modal closes the modal', async function (assert) { From 065d64d861a8f50dcbb0801f7b5e9850af9914a1 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 16:23:11 +0800 Subject: [PATCH 17/20] remove accidentally added submodule reference Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/catalog-new/contents | 1 - 1 file changed, 1 deletion(-) delete mode 160000 packages/catalog-new/contents diff --git a/packages/catalog-new/contents b/packages/catalog-new/contents deleted file mode 160000 index c3845a0cb6e..00000000000 --- a/packages/catalog-new/contents +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c3845a0cb6e2a59d9995c3857904d518979fae85 From 87624ce0e05a5999fce3ac248e9c810fdbb1d3d8 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 16:25:17 +0800 Subject: [PATCH 18/20] fix lint --- packages/host/app/commands/listing-create.ts | 4 +--- .../app/components/operator-mode/create-listing-modal.gts | 8 ++++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/host/app/commands/listing-create.ts b/packages/host/app/commands/listing-create.ts index 7efde970b80..4b3eb2d4daa 100644 --- a/packages/host/app/commands/listing-create.ts +++ b/packages/host/app/commands/listing-create.ts @@ -112,7 +112,6 @@ export default class ListingCreateCommand extends HostBaseCommand< protected async run( input: BaseCommandModule.ListingCreateInput, ): Promise { - const cardAPI = await this.loadCardAPI(); let { openCardIds, codeRef, targetRealm } = input; if (!codeRef) { @@ -402,8 +401,7 @@ export default class ListingCreateCommand extends HostBaseCommand< await Promise.all( openCardIds.map(async (openCardId) => { try { - const instance = - await this.store.get(openCardId); + const instance = await this.store.get(openCardId); if (isCardInstance(instance)) { addCard(instance as CardAPI.CardDef); } else { diff --git a/packages/host/app/components/operator-mode/create-listing-modal.gts b/packages/host/app/components/operator-mode/create-listing-modal.gts index bf818544881..3100c371b59 100644 --- a/packages/host/app/components/operator-mode/create-listing-modal.gts +++ b/packages/host/app/components/operator-mode/create-listing-modal.gts @@ -24,6 +24,7 @@ import { import ListingCreateCommand from '@cardstack/host/commands/listing-create'; import ItemButton from '@cardstack/host/components/card-search/item-button'; +import type { NewCardArgs } from '@cardstack/host/components/card-search/utils'; import ModalContainer from '@cardstack/host/components/modal-container'; import { SelectedTypePill } from '@cardstack/host/components/operator-mode/create-file-modal'; import { Submodes } from '@cardstack/host/components/submode-switcher'; @@ -131,7 +132,7 @@ export default class CreateListingModal extends Component { } private get instances(): CardDef[] { - return this.instancesSearch.instances; + return this.instancesSearch.instances as CardDef[]; } private get hasInstances(): boolean { @@ -155,7 +156,10 @@ export default class CreateListingModal extends Component { ); } - @action private onSelectExample(selection: string) { + @action private onSelectExample(selection: string | NewCardArgs) { + if (typeof selection !== 'string') { + return; + } // First manual toggle: start from "all selected" state let current = this.selectedExampleIds ?? new Set(this.instances.map((i) => i.id)); From 83b9a0365778977a86a547731b5f7de6c7a9a27b Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 18:07:57 +0800 Subject: [PATCH 19/20] fix test --- packages/catalog-realm/catalog-app/listing/listing.gts | 2 +- packages/host/tests/unit/card-def-menu-items-test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/catalog-realm/catalog-app/listing/listing.gts b/packages/catalog-realm/catalog-app/listing/listing.gts index 05d7369cea3..58f0e0f43cb 100644 --- a/packages/catalog-realm/catalog-app/listing/listing.gts +++ b/packages/catalog-realm/catalog-app/listing/listing.gts @@ -642,7 +642,7 @@ export class Listing extends CardDef { [getMenuItems](params: GetMenuItemParams): MenuItemOptions[] { let menuItems = super [getMenuItems](params) - .filter((item) => item.label?.toLowerCase() !== 'create listing with ai'); + .filter((item) => item.label?.toLowerCase() !== 'create listing'); if (params.menuContext === 'interact') { const extra = this.getGenerateExampleMenuItem(params); if (extra) { diff --git a/packages/host/tests/unit/card-def-menu-items-test.ts b/packages/host/tests/unit/card-def-menu-items-test.ts index 922796178f0..f35014355fb 100644 --- a/packages/host/tests/unit/card-def-menu-items-test.ts +++ b/packages/host/tests/unit/card-def-menu-items-test.ts @@ -121,13 +121,13 @@ module('Unit | CardDef menu items', function (hooks) { }); let hasCreateListing = items.some((i: MenuItemOptions) => - i.label.includes('Create Listing with AI'), + i.label.includes('Create Listing'), ); let hasSampleDataTagged = items.some((i: MenuItemOptions) => (i.tags || []).includes('playground-sample-data'), ); - assert.ok(hasCreateListing, 'contains Create Listing with AI'); + assert.ok(hasCreateListing, 'contains Create Listing'); assert.ok( hasSampleDataTagged, 'contains items tagged playground-sample-data', From 5e3dd97bf43a7792cc215b16e123d122ae8dd97c Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 18 Mar 2026 21:24:07 +0800 Subject: [PATCH 20/20] address codex feedback --- .../app/components/operator-mode/create-listing-modal.gts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/host/app/components/operator-mode/create-listing-modal.gts b/packages/host/app/components/operator-mode/create-listing-modal.gts index 3100c371b59..5cd803c1fbd 100644 --- a/packages/host/app/components/operator-mode/create-listing-modal.gts +++ b/packages/host/app/components/operator-mode/create-listing-modal.gts @@ -63,13 +63,10 @@ export default class CreateListingModal extends Component { } let targetRealm = payload.targetRealm; - // null means all selected → use all instance IDs let openCardIds = this.selectedExampleIds === null ? this.instances.map((i) => i.id) - : this.selectedExampleIds.size > 0 - ? [...this.selectedExampleIds] - : (payload.openCardIds ?? []); + : [...this.selectedExampleIds]; let result = await new ListingCreateCommand( this.commandService.commandContext, @@ -99,6 +96,7 @@ export default class CreateListingModal extends Component { await backgroundWork; } + this.selectedExampleIds = null; this.operatorModeStateService.dismissCreateListingModal(); });