Conversation
Preview deployments |
…e-should-successfully
…e-should-successfully
There was a problem hiding this comment.
Pull request overview
This PR adds the ability to click on a linked FileDef in operator mode, which opens it as a card in the stack. The implementation introduces a type system to distinguish between regular cards and file-meta instances throughout the stack handling logic.
Changes:
- Introduced
StackItemTypeto track whether stack items are 'card' or 'file-meta' instances - Added optional
typeparameter togetCardfunction andCardResourceto support loading file-meta instances - Implemented read-only behavior for file-meta cards (no edit mode, always isolated format)
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/index.ts | Added optional type parameter to getCard function signature |
| packages/host/app/lib/stack-item.ts | Added StackItemType field and type inference logic to stack items |
| packages/host/app/lib/read-type.ts | New utility function to infer store read type from URL patterns |
| packages/host/app/resources/card-resource.ts | Extended CardResource to support both card and file-meta types with fallback logic |
| packages/host/app/services/store.ts | Updated reference management to handle file-meta type wiring |
| packages/host/app/services/operator-mode-state-service.ts | Added type tracking in serialization/deserialization and prevented editing of file-meta items |
| packages/host/app/components/operator-mode/stack-item.gts | Added file card detection and forced isolated format for file-meta instances |
| packages/host/app/components/operator-mode/overlays.gts | Passed CardDef instances (not just URLs) to viewCard handler for type detection |
| packages/host/app/components/operator-mode/operator-mode-overlays.gts | Disabled edit button for file-meta targets |
| packages/host/app/components/operator-mode/preview-panel/index.gts | Added type parameter when opening cards in interact mode |
| packages/host/app/components/operator-mode/interact-submode.gts | Added type inference logic for stack items based on instance or store state |
| packages/host/app/components/host-mode/*.gts | Added type inference using URL patterns for host mode components |
| packages/base/links-to-editor.gts | Formatting change only (split hash onto multiple lines) |
| packages/host/tests/integration/components/operator-mode-ui-test.gts | Added test verifying linked files open in stack with correct type |
| packages/host/tests/acceptance/file-chooser-test.gts | Updated test expectation to include type field in serialized state |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return this.readType === 'file-meta' | ||
| ? Boolean(this.store.peek(this.#id, { type: 'file-meta' })) | ||
| : Boolean(this.store.peek(this.#id)); |
There was a problem hiding this comment.
The isLoaded getter doesn't use the fallback logic that the card getter uses (lines 91-93). This means if the type hint is incorrect, isLoaded could return false even when the card/file-meta instance is actually loaded in the store under the other type. Consider updating this getter to also check the fallback type, similar to how the card getter works.
| return this.readType === 'file-meta' | |
| ? Boolean(this.store.peek(this.#id, { type: 'file-meta' })) | |
| : Boolean(this.store.peek(this.#id)); | |
| let maybeCard = | |
| this.peekForType(this.readType) ?? | |
| this.peekForType(this.fallbackReadType); | |
| return isCardInstance(maybeCard) || isFileDefInstance(maybeCard); |
| 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'; | ||
| } |
There was a problem hiding this comment.
When cardOrURL is a URL string that points to a file (has extension, not .json), but the file-meta instance is not yet loaded in the store, this method will return 'card' instead of 'file-meta'. This could cause the wrong type to be used when opening a file via direct navigation. Consider using inferStoreReadType as a fallback when no instance is found in the store to correctly detect file URLs based on their extension pattern.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb32ae9e82
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (this.readType === 'file-meta' && this.peekForType('file-meta')) { | ||
| return undefined; |
There was a problem hiding this comment.
Do not suppress file-meta errors in card resource
When readType is 'file-meta', this early return hides real load failures because store.peek(id, { type: 'file-meta' }) can return a CardErrorJSONAPI, not just a successful FileDef. In that case cardError becomes undefined, so consumers that gate on !card && !cardError will show a loading state instead of surfacing the fetch error (e.g., missing/forbidden linked files opened in the stack).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
i think this comment is trying to convey the subtleties of peek returning local state when the server state is in error (as i was mentioning in another comment). consider peekLive() if you need to know the current server state of the instance.
| // intentionally not awaiting this. we keep track of the promise in | ||
| // this.newReferencePromises | ||
| this.wireUpNewReference(id); | ||
| this.wireUpNewReference(id, readType); |
There was a problem hiding this comment.
could we add a test for file def reference counting? there are a bunch of reference count balancing tests in the store-test.gts, could we update this to include file def's in the mix too?
| ? (this.store.peek(this.#id, { type: 'file-meta' }) as unknown) | ||
| : (this.store.peek(this.#id) as unknown); |
There was a problem hiding this comment.
be aware there is a very subtle nuance around peeking. if the server state of the instance is in error and the local state is not, then the peek will give u the local state. unsure if this effects what you are trying to do. there is a peekLive() that will give you the server state regardless of whether or not it is in error.
| export function isFileMetaId(id: string | null | undefined): boolean { | ||
| if (!id) { | ||
| return false; | ||
| } | ||
| return hasExtension(id) && !id.endsWith('.json'); | ||
| } | ||
|
|
||
| export function inferStoreReadType( | ||
| id: string | null | undefined, | ||
| ): StoreReadType { | ||
| return isFileMetaId(id) ? 'file-meta' : 'card'; | ||
| } |
There was a problem hiding this comment.
This is an AI anti-pattern that I have had to fight . We should be using more explicit type specification, not inferring stuff from URLs
There was a problem hiding this comment.
Note from our call:
- consider adding a subprotocol or url annotation/prefix in urls in operator mode state that differentiate between card or file
- default should be card
This PR adds the ability to click on a FileDef, which opens as a card in the stack.
Here is an example with a
@field featuredImage = linksTo(ImageDef);:Screen.Recording.2026-02-20.at.10.32.48.mov