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
16 changes: 16 additions & 0 deletions packages/host/app/commands/open-create-listing-modal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { service } from '@ember/service';

import { isFieldDef } from '@cardstack/runtime-common';
import { loadCardDef } from '@cardstack/runtime-common/code-ref';

import type * as BaseCommandModule from 'https://cardstack.com/base/command';

import HostBaseCommand from '../lib/host-base-command';
Expand All @@ -24,10 +27,23 @@ export default class OpenCreateListingModalCommand extends HostBaseCommand<
protected async run(
input: BaseCommandModule.ListingCreateInput,
): Promise<undefined> {
let declarationKind: 'card' | 'field' = 'card';
if (input.codeRef) {
try {
let cardOrField = await loadCardDef(input.codeRef, {
loader: this.loaderService.loader,
});
declarationKind = isFieldDef(cardOrField) ? 'field' : 'card';
} catch {
declarationKind = 'card';
}
}

this.operatorModeStateService.showCreateListingModal({
codeRef: input.codeRef,
targetRealm: input.targetRealm,
openCardIds: input.openCardIds,
declarationKind,
});
}
}
20 changes: 18 additions & 2 deletions packages/host/app/components/card-catalog/modal.gts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type State = {
availableRealmUrls: string[];
hasPreselectedCard?: boolean;
consumingRealm?: URL;
preselectConsumingRealm?: boolean;
};

function isNewCardArgs(item: string | NewCardArgs): item is NewCardArgs {
Expand Down Expand Up @@ -111,6 +112,8 @@ export default class CardCatalogModal extends Component<Signature> {
@searchKey={{state.searchKey}}
@baseFilter={{state.baseFilter}}
@availableRealmUrls={{state.availableRealmUrls}}
@consumingRealm={{state.consumingRealm}}
@preselectConsumingRealm={{state.preselectConsumingRealm}}
as |Bar Content|
>
<ModalContainer
Expand Down Expand Up @@ -259,6 +262,8 @@ export default class CardCatalogModal extends Component<Signature> {
createNewCard?: CreateNewCard;
preselectedCardTypeQuery?: Query;
consumingRealm?: URL;
preselectConsumingRealm?: boolean;
preselectedCardUrls?: string[];
},
): Promise<undefined | string | string[]> {
let result = await this._chooseCard.perform(
Expand Down Expand Up @@ -297,6 +302,8 @@ export default class CardCatalogModal extends Component<Signature> {
multiSelect?: boolean;
preselectedCardTypeQuery?: Query;
consumingRealm?: URL;
preselectConsumingRealm?: boolean;
preselectedCardUrls?: string[];
} = {},
) => {
await this.realmServer.ready;
Expand Down Expand Up @@ -342,6 +349,14 @@ export default class CardCatalogModal extends Component<Signature> {
preselectedCardUrl = `${instances[0].id}.json`;
}
}
let preselectedCardUrls = (
opts?.preselectedCardUrls?.length
? opts.preselectedCardUrls
: preselectedCardUrl
? [preselectedCardUrl]
: []
).map((url) => (url.endsWith('.json') ? url : `${url}.json`));

let cardCatalogState = new TrackedObject<State>({
id: this.stateId,
request,
Expand All @@ -350,10 +365,11 @@ export default class CardCatalogModal extends Component<Signature> {
dismissModal: false,
baseFilter: query.filter,
availableRealmUrls: this.realmServer.availableRealmURLs,
selectedCards: preselectedCardUrl ? [preselectedCardUrl] : [],
selectedCards: preselectedCardUrls,
multiSelect: opts?.multiSelect ?? false,
hasPreselectedCard: Boolean(preselectedCardUrl),
hasPreselectedCard: preselectedCardUrls.length > 0,
consumingRealm: opts.consumingRealm,
preselectConsumingRealm: opts.preselectConsumingRealm,
});
this.stateStack.push(cardCatalogState);
return await request.deferred.promise;
Expand Down
33 changes: 32 additions & 1 deletion packages/host/app/components/card-search/panel.gts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '@cardstack/runtime-common/helpers/card-type-display-name';

import { getPrerenderedSearch } from '@cardstack/host/resources/prerendered-search';
import type RealmService from '@cardstack/host/services/realm';
import type RealmServerService from '@cardstack/host/services/realm-server';
import type RecentCards from '@cardstack/host/services/recent-cards-service';

Expand All @@ -51,6 +52,8 @@ interface Signature {
searchKey: string;
baseFilter?: Filter;
availableRealmUrls?: string[];
consumingRealm?: URL;
preselectConsumingRealm?: boolean;
};
Blocks: {
default: [
Expand All @@ -72,13 +75,15 @@ interface Signature {
| 'activeSort'
| 'onSortChange'
| 'filteredRecentCards'
| 'initialFocusedSection'
>,
string,
];
};
}

export default class SearchPanel extends Component<Signature> {
@service declare private realm: RealmService;
@service declare private realmServer: RealmServerService;
@service declare private recentCardsService: RecentCards;
@consume(CardContextName) declare private cardContext:
Expand All @@ -87,7 +92,32 @@ export default class SearchPanel extends Component<Signature> {
@consume(GetCardCollectionContextName)
declare private getCardCollection: getCardCollection;

@tracked private selectedRealms: PickerOption[] = [];
@tracked private selectedRealms: PickerOption[] = this.initialSelectedRealms;

private get shouldPreselectConsumingRealm(): boolean {
return Boolean(this.args.preselectConsumingRealm);
}

private get initialSelectedRealms(): PickerOption[] {
let consumingRealm = this.args.consumingRealm;
if (!this.shouldPreselectConsumingRealm || !consumingRealm) {
return [];
}
let realmURL = consumingRealm.href;
let info = this.realm.info(realmURL);
let label = info?.name ?? realmURL;
let icon = info?.iconURL ?? undefined;
return [{ id: realmURL, icon, label, type: 'option' }];
Comment on lines +102 to +110
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectedRealms is initialized with a newly constructed PickerOption when preselectConsumingRealm is enabled. The Boxel Picker (used by RealmPicker) determines selection via object identity (includes(selected, option)), so this preselected option will not match the options instances created inside RealmPicker. This can cause the consuming realm to not appear selected in the dropdown and can lead to toggle/deselect issues (duplicates) when interacting with the picker. Consider preselecting by realm URL and mapping to the actual option objects used by the picker (or providing an id-based matcher) so selected references come from the current @options array.

Suggested change
let consumingRealm = this.args.consumingRealm;
if (!this.shouldPreselectConsumingRealm || !consumingRealm) {
return [];
}
let realmURL = consumingRealm.href;
let info = this.realm.info(realmURL);
let label = info?.name ?? realmURL;
let icon = info?.iconURL ?? undefined;
return [{ id: realmURL, icon, label, type: 'option' }];
// Do not construct a new PickerOption instance here, since the Boxel Picker
// determines selection by object identity. The Picker should own the
// selection instances based on its own @options array.
return [];

Copilot uses AI. Check for mistakes.
}

private get initialFocusedSectionId(): string | null {
let consumingRealm = this.args.consumingRealm;
if (!this.shouldPreselectConsumingRealm || !consumingRealm) {
return null;
}
return `realm:${consumingRealm.href}`;
}

@tracked private activeSort: SortOption = SORT_OPTIONS[0];

@cached
Expand Down Expand Up @@ -293,6 +323,7 @@ export default class SearchPanel extends Component<Signature> {
activeSort=this.activeSort
onSortChange=this.onSortChange
filteredRecentCards=this.baseFilteredRecentCards
initialFocusedSection=this.initialFocusedSectionId
)
this.joinedSelectedRealmURLs
}}
Expand Down
4 changes: 3 additions & 1 deletion packages/host/app/components/card-search/search-content.gts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ interface Signature {
searchResource: PrerenderedSearchResource;
activeSort: SortOption;
onSortChange: (sort: SortOption) => void;
initialFocusedSection?: string | null;
};
Blocks: {};
}
Expand All @@ -168,7 +169,8 @@ export default class SearchContent extends Component<Signature> {

@tracked activeViewId = 'grid';
/** Section id when focused: 'realm:<url>' or 'recents'. Null = no focus */
@tracked focusedSection: string | null = null;
@tracked focusedSection: string | null =
this.args.initialFocusedSection ?? null;
@tracked displayedCountBySection: Record<string, number> = {};

@consume(GetCardContextName) declare private getCard: getCard;
Expand Down
Loading
Loading