diff --git a/packages/base/links-to-editor.gts b/packages/base/links-to-editor.gts index dba3f3f783..a982e3ad03 100644 --- a/packages/base/links-to-editor.gts +++ b/packages/base/links-to-editor.gts @@ -87,7 +87,10 @@ export class LinksToEditor extends GlimmerComponent { /> {{/if}} diff --git a/packages/host/app/components/host-mode/breadcrumb-item.gts b/packages/host/app/components/host-mode/breadcrumb-item.gts index 95b31ba3db..3c6fba1d5d 100644 --- a/packages/host/app/components/host-mode/breadcrumb-item.gts +++ b/packages/host/app/components/host-mode/breadcrumb-item.gts @@ -6,6 +6,7 @@ import { cached } from '@glimmer/tracking'; import { cardTypeIcon, isCardInstance } from '@cardstack/runtime-common'; +import { inferStoreReadType } from '@cardstack/host/lib/read-type'; import { getCard } from '@cardstack/host/resources/card-resource'; import type { ComponentLike } from '@glint/template'; @@ -26,7 +27,13 @@ export default class HostModeBreadcrumbItem extends Component { return undefined; } - return getCard(this, () => this.args.cardId); + return getCard(this, () => this.args.cardId, { + type: this.readType, + }); + } + + private get readType() { + return inferStoreReadType(this.args.cardId); } @cached diff --git a/packages/host/app/components/host-mode/card.gts b/packages/host/app/components/host-mode/card.gts index 57af68290f..1868bb6e3b 100644 --- a/packages/host/app/components/host-mode/card.gts +++ b/packages/host/app/components/host-mode/card.gts @@ -6,6 +6,7 @@ import { BoxelButton, CardContainer } from '@cardstack/boxel-ui/components'; import CardRenderer from '@cardstack/host/components/card-renderer'; import CardError from '@cardstack/host/components/operator-mode/card-error'; +import { inferStoreReadType } from '@cardstack/host/lib/read-type'; import { getCard } from '@cardstack/host/resources/card-resource'; interface Signature { @@ -25,7 +26,13 @@ export default class HostModeCard extends Component { return undefined; } - return getCard(this, () => this.args.cardId!); + return getCard(this, () => this.args.cardId!, { + type: this.readType, + }); + } + + private get readType() { + return inferStoreReadType(this.args.cardId); } get card() { diff --git a/packages/host/app/components/host-mode/content.gts b/packages/host/app/components/host-mode/content.gts index 56e8b987fe..fa2f040462 100644 --- a/packages/host/app/components/host-mode/content.gts +++ b/packages/host/app/components/host-mode/content.gts @@ -12,6 +12,7 @@ import { } from '@cardstack/runtime-common'; import { meta } from '@cardstack/runtime-common/constants'; +import { inferStoreReadType } from '@cardstack/host/lib/read-type'; import { getCard } from '@cardstack/host/resources/card-resource'; import type { @@ -45,7 +46,13 @@ export default class HostModeContent extends Component { return undefined; } - return getCard(this, () => this.args.primaryCardId!); + return getCard(this, () => this.args.primaryCardId!, { + type: this.primaryReadType, + }); + } + + private get primaryReadType() { + return inferStoreReadType(this.args.primaryCardId); } get cardIds() { diff --git a/packages/host/app/components/host-mode/stack-item.gts b/packages/host/app/components/host-mode/stack-item.gts index 663bee6f55..8850add513 100644 --- a/packages/host/app/components/host-mode/stack-item.gts +++ b/packages/host/app/components/host-mode/stack-item.gts @@ -11,6 +11,7 @@ import { cached, tracked } from '@glimmer/tracking'; import { ContextButton } from '@cardstack/boxel-ui/components'; import { and, bool } from '@cardstack/boxel-ui/helpers'; +import { inferStoreReadType } from '@cardstack/host/lib/read-type'; import { getCard } from '@cardstack/host/resources/card-resource'; import HostModeCard from './card'; @@ -37,7 +38,13 @@ export default class HostModeStackItem extends Component { if (!this.args.cardId) { return undefined; } - return getCard(this, () => this.args.cardId); + return getCard(this, () => this.args.cardId, { + type: this.readType, + }); + } + + private get readType() { + return inferStoreReadType(this.args.cardId); } @cached diff --git a/packages/host/app/components/operator-mode/interact-submode.gts b/packages/host/app/components/operator-mode/interact-submode.gts index 536ab66e57..78dc0c1b2f 100644 --- a/packages/host/app/components/operator-mode/interact-submode.gts +++ b/packages/host/app/components/operator-mode/interact-submode.gts @@ -36,6 +36,7 @@ import { codeRefWithAbsoluteURL, identifyCard, isCardInstance, + isFileDefInstance, isResolvedCodeRef, CardError, loadCardDef, @@ -53,7 +54,7 @@ import { import CopyCardToStackCommand from '@cardstack/host/commands/copy-card-to-stack'; -import { StackItem } from '@cardstack/host/lib/stack-item'; +import { StackItem, type StackItemType } from '@cardstack/host/lib/stack-item'; import { stackBackgroundsResource } from '@cardstack/host/resources/stack-backgrounds'; @@ -186,6 +187,7 @@ export default class InteractSubmode extends Component { request: new Deferred(), closeAfterSaving: opts?.closeAfterCreating, stackIndex, + type: 'card', }); this.addToStack(newItem); return localId; @@ -212,6 +214,9 @@ export default class InteractSubmode extends Component { : cardOrURL instanceof URL ? cardOrURL.href : cardOrURL.id; + if (!cardId) { + return; + } if (opts?.openCardInRightMostStack) { stackIndex = this.stacks.length; } else if (typeof opts?.stackIndex === 'number') { @@ -229,10 +234,12 @@ export default class InteractSubmode extends Component { } stackIndex = opts.stackIndex; } + let stackItemType = this.getStackItemType(cardOrURL, cardId); let newItem = new StackItem({ id: cardId, format, stackIndex, + type: stackItemType, relationshipContext: opts?.fieldName ? { fieldName: opts.fieldName, @@ -251,6 +258,23 @@ export default class InteractSubmode extends Component { this.operatorModeStateService.editCardOnStack(stackIndex, card); }; + private getStackItemType( + cardOrURL: CardDef | URL | string, + cardId: string, + ): StackItemType { + if ( + cardOrURL && + typeof cardOrURL === 'object' && + !(cardOrURL instanceof URL) + ) { + return isFileDefInstance(cardOrURL as CardDef) ? 'file-meta' : 'card'; + } + let fileMetaInstanceOrError = + this.store.peek(cardId, { type: 'file-meta' }) ?? + this.store.peekError(cardId, { type: 'file-meta' }); + return fileMetaInstanceOrError ? 'file-meta' : 'card'; + } + private saveCard = (id: string): void => { this.store.save(id); }; @@ -500,6 +524,7 @@ export default class InteractSubmode extends Component { id: url.href, format: 'isolated', stackIndex: 0, + type: this.getStackItemType(url, url.href), }); // it's important that we await the stack item readiness _before_ // we mutate the stack, otherwise there are very odd visual artifacts @@ -542,6 +567,7 @@ export default class InteractSubmode extends Component { id: url.href, format: 'isolated', stackIndex, + type: this.getStackItemType(url, url.href), }); // await stackItem.ready(); this.operatorModeStateService.clearStackAndAdd( diff --git a/packages/host/app/components/operator-mode/operator-mode-overlays.gts b/packages/host/app/components/operator-mode/operator-mode-overlays.gts index fd0b9d60a9..c9cff84704 100644 --- a/packages/host/app/components/operator-mode/operator-mode-overlays.gts +++ b/packages/host/app/components/operator-mode/operator-mode-overlays.gts @@ -32,12 +32,16 @@ import { } from '@cardstack/boxel-ui/icons'; import type { CommandContext } from '@cardstack/runtime-common'; +import { isFileDefInstance } from '@cardstack/runtime-common'; + import { CardCrudFunctionsContextName, CommandContextName, getMenuItems, } from '@cardstack/runtime-common'; +import { isFileMetaId } from '@cardstack/host/lib/read-type'; + import type { CardCrudFunctions, CardDef, @@ -345,6 +349,9 @@ export default class OperatorModeOverlays extends Overlays { case 'select': return !this.isField(renderedCard) && !!this.args.toggleSelect; case 'edit': + if (this.isFileMetaTarget(renderedCard)) { + return false; + } return this.realm.canWrite(this.getCardId(renderedCard.cardDefOrId)); case 'more-options': return true; @@ -353,6 +360,16 @@ export default class OperatorModeOverlays extends Overlays { } } + private isFileMetaTarget( + renderedCard: StackItemRenderedCardForOverlayActions, + ): boolean { + let cardDefOrId = renderedCard.cardDefOrId; + if (typeof cardDefOrId === 'string') { + return isFileMetaId(cardDefOrId); + } + return isFileDefInstance(cardDefOrId); + } + @action private registerDropdownAPI( renderedCard: StackItemRenderedCardForOverlayActions, @@ -398,6 +415,9 @@ export default class OperatorModeOverlays extends Overlays { protected override getFormatForCard( renderedCard: StackItemRenderedCardForOverlayActions, ): Format { + if (this.isFileMetaTarget(renderedCard)) { + return 'isolated'; + } return renderedCard.stackItem.format as Format; } diff --git a/packages/host/app/components/operator-mode/overlays.gts b/packages/host/app/components/operator-mode/overlays.gts index b0871721e1..19290fbcc7 100644 --- a/packages/host/app/components/operator-mode/overlays.gts +++ b/packages/host/app/components/operator-mode/overlays.gts @@ -230,7 +230,9 @@ export default class Overlays extends Component { let canWrite = this.realm.canWrite(cardId); format = canWrite ? format : 'isolated'; if (this.args.viewCard) { - await this.args.viewCard(new URL(cardId), format, { + let target = + typeof cardDefOrId === 'string' ? new URL(cardId) : cardDefOrId; + await this.args.viewCard(target, format, { fieldType, fieldName, }); diff --git a/packages/host/app/components/operator-mode/preview-panel/index.gts b/packages/host/app/components/operator-mode/preview-panel/index.gts index 2f2ad9a7d6..1ea80d29dd 100644 --- a/packages/host/app/components/operator-mode/preview-panel/index.gts +++ b/packages/host/app/components/operator-mode/preview-panel/index.gts @@ -24,6 +24,7 @@ import { getMenuItems, identifyCard, isCardInstance, + isFileDefInstance, isResolvedCodeRef, } from '@cardstack/runtime-common'; @@ -94,7 +95,11 @@ export default class PreviewPanel extends Component { private openInInteractMode = () => { if (this.cardId) { - this.operatorModeStateService.openCardInInteractMode(this.cardId); + this.operatorModeStateService.openCardInInteractMode( + this.cardId, + 'isolated', + isFileDefInstance(this.args.card) ? 'file-meta' : 'card', + ); } }; diff --git a/packages/host/app/components/operator-mode/stack-item.gts b/packages/host/app/components/operator-mode/stack-item.gts index e6b783c786..faa4f1970d 100644 --- a/packages/host/app/components/operator-mode/stack-item.gts +++ b/packages/host/app/components/operator-mode/stack-item.gts @@ -43,6 +43,7 @@ import { type getCard, type getCards, type getCardCollection, + isFileDefInstance, cardTypeDisplayName, PermissionsContextName, RealmURLContextName, @@ -178,7 +179,9 @@ export default class OperatorModeStackItem extends Component { } private makeCardResource = () => { - this.cardResource = this.getCard(this, () => this.args.item.id); + this.cardResource = this.getCard(this, () => this.args.item.id, { + type: this.args.item.type, + }); }; private get url() { @@ -654,12 +657,26 @@ export default class OperatorModeStackItem extends Component { this.card[realmURL] && !this.isBuried && !this.isEditing && + !this.isFileCard && this.realm.canWrite(this.card[realmURL].href) ); } private get isEditing() { - return !this.isBuried && this.args.item.format === 'edit'; + return ( + !this.isBuried && !this.isFileCard && this.args.item.format === 'edit' + ); + } + + private get isFileCard() { + return ( + this.args.item.type === 'file-meta' || + (this.card ? isFileDefInstance(this.card) : false) + ); + } + + private get cardFormat() { + return this.isFileCard ? 'isolated' : this.args.item.format; } private get showError() { @@ -831,7 +848,7 @@ export default class OperatorModeStackItem extends Component { ; stackIndex: number; id: string; + type?: StackItemType; closeAfterSaving?: boolean; relationshipContext?: { fieldName?: string; @@ -14,11 +15,21 @@ interface Args { }; } +export type StackItemType = 'card' | 'file-meta'; + +function inferStackItemType(type?: StackItemType): StackItemType { + if (type) { + return type; + } + return 'card'; +} + export class StackItem { format: Format; request?: Deferred; stackIndex: number; closeAfterSaving?: boolean; + type: StackItemType; #id: string; relationshipContext?: | { @@ -33,6 +44,7 @@ export class StackItem { request, stackIndex, id, + type, closeAfterSaving, relationshipContext, } = args; @@ -41,6 +53,7 @@ export class StackItem { this.format = format; this.request = request; this.stackIndex = stackIndex; + this.type = inferStackItemType(type); this.closeAfterSaving = closeAfterSaving; this.relationshipContext = relationshipContext; } @@ -57,12 +70,14 @@ export class StackItem { closeAfterSaving, stackIndex, relationshipContext, + type, } = this; return new StackItem({ format, request, closeAfterSaving, id, + type, stackIndex, relationshipContext, ...args, diff --git a/packages/host/app/resources/card-resource.ts b/packages/host/app/resources/card-resource.ts index 814673c404..29827de09d 100644 --- a/packages/host/app/resources/card-resource.ts +++ b/packages/host/app/resources/card-resource.ts @@ -9,6 +9,8 @@ import { Resource } from 'ember-modify-based-class-resource'; import { isCardInstance, isFileDefInstance } from '@cardstack/runtime-common'; +import type { StoreReadType } from '@cardstack/runtime-common'; + import type { BaseDef } from 'https://cardstack.com/base/card-api'; import type StoreService from '../services/store'; @@ -16,22 +18,25 @@ import type StoreService from '../services/store'; interface Args { named: { id: string | undefined; + type?: StoreReadType; }; } export class CardResource extends Resource { #id: string | undefined; + #type: StoreReadType | undefined; #hasRegisteredDestructor = false; #hasReference = false; @service declare private store: StoreService; modify(_positional: never[], named: Args['named']) { - let { id } = named; - if (id !== this.#id) { + let { id, type } = named; + if (id !== this.#id || type !== this.#type) { this.dropReferenceIfHeld(); this.#id = id; + this.#type = type; if (this.#id) { - this.store.addReference(this.#id); + this.store.addReference(this.#id, { type: this.#type }); this.#hasReference = true; } } @@ -50,6 +55,32 @@ export class CardResource extends Resource { } } + private get readType(): StoreReadType { + return this.#type ?? 'card'; + } + + private get fallbackReadType(): StoreReadType { + return this.readType === 'file-meta' ? 'card' : 'file-meta'; + } + + private peekForType(type: StoreReadType): unknown { + if (!this.#id) { + return undefined; + } + return type === 'file-meta' + ? (this.store.peek(this.#id, { type: 'file-meta' }) as unknown) + : (this.store.peek(this.#id) as unknown); + } + + private peekErrorForType(type: StoreReadType) { + if (!this.#id) { + return undefined; + } + return type === 'file-meta' + ? this.store.peekError(this.#id, { type: 'file-meta' }) + : this.store.peekError(this.#id); + } + // Note that this will return a stale instance when the server state for this // id becomes an error. use this.cardError to see the live server state for // this instance. @@ -57,7 +88,9 @@ export class CardResource extends Resource { if (!this.#id) { return undefined; } - let maybeCard = this.store.peek(this.#id) as unknown; + let maybeCard = + this.peekForType(this.readType) ?? + this.peekForType(this.fallbackReadType); return isCardInstance(maybeCard) || isFileDefInstance(maybeCard) ? (maybeCard as BaseDef) : undefined; @@ -67,7 +100,12 @@ export class CardResource extends Resource { if (!this.#id) { return undefined; } - let maybeError = this.store.peekError(this.#id); + if (this.readType === 'file-meta' && this.peekForType('file-meta')) { + return undefined; + } + let maybeError = + this.peekErrorForType(this.readType) ?? + this.peekErrorForType(this.fallbackReadType); return maybeError && !isCardInstance(maybeError) ? maybeError : undefined; } @@ -79,7 +117,9 @@ export class CardResource extends Resource { if (!this.#id) { return false; } - return Boolean(this.store.peek(this.#id)); + return this.readType === 'file-meta' + ? Boolean(this.store.peek(this.#id, { type: 'file-meta' })) + : Boolean(this.store.peek(this.#id)); } get autoSaveState() { @@ -100,10 +140,15 @@ export class CardResource extends Resource { // ``` // If you need to use `getCard()` in something that is not a Component, then // let's talk. -export function getCard(parent: object, id: () => string | undefined) { +export function getCard( + parent: object, + id: () => string | undefined, + opts?: { type?: StoreReadType }, +) { return CardResource.from(parent, () => ({ named: { id: id(), + type: opts?.type, }, })); } diff --git a/packages/host/app/services/operator-mode-state-service.ts b/packages/host/app/services/operator-mode-state-service.ts index d92c8a2c82..25f26fbddd 100644 --- a/packages/host/app/services/operator-mode-state-service.ts +++ b/packages/host/app/services/operator-mode-state-service.ts @@ -27,7 +27,7 @@ import { import type { Submode } from '@cardstack/host/components/submode-switcher'; import { Submodes } from '@cardstack/host/components/submode-switcher'; -import { StackItem } from '@cardstack/host/lib/stack-item'; +import { StackItem, type StackItemType } from '@cardstack/host/lib/stack-item'; import { file, @@ -92,6 +92,7 @@ export interface OperatorModeState { interface CardItem { id: string; format: 'isolated' | 'edit' | 'head'; + type?: StackItemType; } export type FileView = 'inspector' | 'browser'; @@ -413,6 +414,9 @@ export default class OperatorModeStateService extends Service { editCardOnStack(stackIndex: number, card: CardDef): void { let item = this.findCardInStack(card, stackIndex); + if (item.type === 'file-meta') { + return; + } this.replaceItemInStack( item, item.clone({ @@ -875,11 +879,14 @@ export default class OperatorModeStateService extends Service { throw new Error(`Unknown format for card on stack ${item.format}`); } if (item.id) { - let instance = this.store.peek(item.id); + let instance = + this.store.peek(item.id) ?? + this.store.peek(item.id, { type: 'file-meta' }); if (!isLocalId(item.id) || instance?.id) { serializedStack.push({ id: instance?.id ?? item.id, format: item.format, + type: item.type === 'card' ? undefined : item.type, }); } } @@ -903,12 +910,14 @@ export default class OperatorModeStateService extends Service { fieldName?: string; fieldType?: 'linksTo' | 'linksToMany'; }, + type?: StackItemType, ) { let stackItem = new StackItem({ id, stackIndex, format, relationshipContext, + type, }); return stackItem; } @@ -961,6 +970,7 @@ export default class OperatorModeStateService extends Service { id: item.id, format, stackIndex, + type: item.type, }), ); } @@ -1129,7 +1139,11 @@ export default class OperatorModeStateService extends Service { })); }); - openCardInInteractMode(id: string, format: Format = 'isolated') { + openCardInInteractMode( + id: string, + format: Format = 'isolated', + type: StackItemType = 'card', + ) { this.clearStacks(); // Determine realm URL. If id is a localId, look up the instance in the store to read its realm. let realmHref: string | undefined; @@ -1152,11 +1166,13 @@ export default class OperatorModeStateService extends Service { id: `${realmHref}index`, stackIndex: 0, format: 'isolated', + type: 'card', }); let newItem = new StackItem({ id, // keep provided id (may be localId) so later replacement on save works stackIndex: 0, format, + type, }); this.addItemToStack(indexItem); this.addItemToStack(newItem); @@ -1183,6 +1199,7 @@ export default class OperatorModeStateService extends Service { id, format: 'isolated', stackIndex: 0, + type: 'card', }); this.clearStacks(); this.addItemToStack(stackItem); diff --git a/packages/host/app/services/store.ts b/packages/host/app/services/store.ts index 161bae71bd..7f03b0594c 100644 --- a/packages/host/app/services/store.ts +++ b/packages/host/app/services/store.ts @@ -232,10 +232,11 @@ export default class StoreService extends Service implements StoreInterface { } } - addReference(id: string | undefined) { + addReference(id: string | undefined, opts?: { type?: StoreReadType }) { if (!id) { return; } + let readType: StoreReadType = opts?.type ?? 'card'; // synchronously update the reference count so we don't run into race // conditions requiring a mutex let currentReferenceCount = this.referenceCount.get(id) ?? 0; @@ -259,7 +260,7 @@ export default class StoreService extends Service implements StoreInterface { this.subscribeToRealm(new URL(id)); // intentionally not awaiting this. we keep track of the promise in // this.newReferencePromises - this.wireUpNewReference(id); + this.wireUpNewReference(id, readType); } } @@ -859,12 +860,26 @@ export default class StoreService extends Service implements StoreInterface { deferred.fulfill(); } - private async wireUpNewReference(url: string) { + private async wireUpNewReference( + url: string, + readType: StoreReadType = 'card', + ) { let deferred = new Deferred(); await this.withTestWaiters(async () => { this.newReferencePromises.push(deferred.promise); try { await this.ready; + if (readType === 'file-meta') { + let instanceOrError = await this.getFileMetaInstance({ + idOrDoc: url, + }); + this.setIdentityContext( + instanceOrError as FileDef | CardErrorJSONAPI, + 'file-meta', + ); + deferred.fulfill(); + return; + } // Check file-meta map as well as card map — file-meta instances // are loaded into their own map by store.get(id, { type: 'file-meta' }) let fileMetaInstance = diff --git a/packages/host/tests/acceptance/file-chooser-test.gts b/packages/host/tests/acceptance/file-chooser-test.gts index b4422e0e5b..2d4a28c3ed 100644 --- a/packages/host/tests/acceptance/file-chooser-test.gts +++ b/packages/host/tests/acceptance/file-chooser-test.gts @@ -47,6 +47,7 @@ module('Acceptance | file chooser tests', function (hooks) { { id: fileId, format: 'isolated', + type: 'file-meta', }, ], ], diff --git a/packages/host/tests/integration/components/operator-mode-ui-test.gts b/packages/host/tests/integration/components/operator-mode-ui-test.gts index 732f4abe4f..d67dfb7bf4 100644 --- a/packages/host/tests/integration/components/operator-mode-ui-test.gts +++ b/packages/host/tests/integration/components/operator-mode-ui-test.gts @@ -86,6 +86,91 @@ module('Integration | operator-mode | ui', function (hooks) { .includesText('Author'); }); + test(`click on "links to" the embedded file will open it on the stack`, async function (assert) { + let linkedFileId = `${testRealmURL}FileLinkCard/notes.txt`; + + await ctx.testRealm.write( + 'file-link-card.gts', + ` + import { CardDef, Component, field, contains, linksTo, StringField } from 'https://cardstack.com/base/card-api'; + import { FileDef } from 'https://cardstack.com/base/file-api'; + + export class FileLinkCard extends CardDef { + static displayName = 'File Link Card'; + @field title = contains(StringField); + @field attachment = linksTo(FileDef); + + static isolated = class Isolated extends Component { + + }; + } + `, + ); + + await ctx.testRealm.write( + 'FileLinkCard/notes.txt', + 'Hello from a file link', + ); + await ctx.testRealm.write( + 'FileLinkCard/with-file.json', + JSON.stringify({ + data: { + type: 'card', + attributes: { + title: 'Linked file example', + }, + relationships: { + attachment: { + links: { + self: './notes.txt', + }, + data: { + type: 'file-meta', + id: './notes.txt', + }, + }, + }, + meta: { + adoptsFrom: { + module: '../file-link-card', + name: 'FileLinkCard', + }, + }, + }, + }), + ); + + ctx.setCardInOperatorModeState(`${testRealmURL}FileLinkCard/with-file`); + await renderComponent( + class TestDriver extends GlimmerComponent { + + }, + ); + + await waitFor('[data-test-file-link-attachment] [data-test-card]'); + await click('[data-test-file-link-attachment] [data-test-card]'); + await waitFor('[data-test-stack-card-index="1"]'); + assert.dom('[data-test-stack-card-index]').exists({ count: 2 }); + assert + .dom(`[data-test-stack-card="${linkedFileId}"]`) + .exists('linked file opens as a second stack card'); + assert.strictEqual( + ctx.operatorModeStateService.state?.stacks?.[0]?.[1]?.id, + linkedFileId, + 'operator mode state targets the linked file', + ); + assert.strictEqual( + ctx.operatorModeStateService.state?.stacks?.[0]?.[1]?.type, + 'file-meta', + 'stack item type is file-meta', + ); + }); + test(`toggles mode switcher`, async function (assert) { ctx.setCardInOperatorModeState(`${testRealmURL}BlogPost/1`); await renderComponent( diff --git a/packages/runtime-common/index.ts b/packages/runtime-common/index.ts index ccf9faa6ee..da3485a6a3 100644 --- a/packages/runtime-common/index.ts +++ b/packages/runtime-common/index.ts @@ -440,6 +440,7 @@ export type AutoSaveState = { export type getCard = ( parent: object, id: () => string | undefined, + opts?: { type?: StoreReadType }, ) => // This is a duck type of the CardResource { id: string | undefined;