Cs 10383 create listing should have a loading user experience#4190
Cs 10383 create listing should have a loading user experience#4190
Conversation
…re-selected source
…g-user-experience
Preview deployments |
Host Test Results0 tests 0 ✅ 0s ⏱️ Results for commit 7853b39. ♻️ This comment has been updated with latest results. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0606903eb1
ℹ️ 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".
| targetRealm, | ||
| }); | ||
| if (this.isCardInstance) { | ||
| const openCardId = this.args.readyFile.url; |
There was a problem hiding this comment.
Use extensionless card id for listing creation
When the action runs from an instance, openCardId is sourced from readyFile.url, and this path is guaranteed to include .json (isCardInstance checks that). ListingCreateCommand then uses that value as the canonical instance identifier (for examples.0.links.self and in linkSpecs() via _dependencies?url=...), but instance entries are indexed by extensionless ids (for example .../Pet/mango). In this path, passing the file URL causes dependency/spec resolution to miss and can produce listings without the expected linked example/specs; use the instance id (this.args.cardInstance?.id) or strip .json before executing the command.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5e825a478e
ℹ️ 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".
| @action private onClose() { | ||
| this.errorMessage = undefined; | ||
| this.operatorModeStateService.closeCreateListingModal(); | ||
| } |
There was a problem hiding this comment.
Reset source override when closing the listing modal
Closing the modal only clears errorMessage, so _userSelectedKey/_userSelectedValue persist across sessions. If a user opens from an instance, switches Source to the definition, cancels, and then reopens from that same instance, activeOpenCardId will reuse the stale override instead of pre-selecting the triggering instance. That breaks the instance-driven preselection flow and can cause creating a listing from the wrong source when users immediately confirm.
Useful? React with 👍 / 👎.
| targetRealm: request.targetRealm, | ||
| openCardId: this.activeOpenCardId ?? undefined, | ||
| }); | ||
| this.operatorModeStateService.closeCreateListingModal(); |
There was a problem hiding this comment.
Prevent stale create task from closing newer modal requests
The create task always calls closeCreateListingModal() after ListingCreateCommand resolves, even if the modal request changed while the task was running. In a long-running create, a user can dismiss and reopen the modal for a different card; when the first task finishes, it clears the newer request and dismisses the new modal unexpectedly.
Useful? React with 👍 / 👎.
…g-user-experience
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 87624ce0e0
ℹ️ 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 (backgroundWork) { | ||
| await backgroundWork; | ||
| } | ||
|
|
||
| this.operatorModeStateService.dismissCreateListingModal(); |
There was a problem hiding this comment.
Reset selected examples before auto-dismissing the modal
After a successful create, this path dismisses the modal without clearing selectedExampleIds. Because CreateListingModal stays mounted in operator-mode/container.gts, reopening it later reuses the previous run's manual selection; if the next listing is for a different type, openCardIds can still contain stale ids from the earlier listing and attach unrelated example cards. The success path should clear the selection state the same way onClose() does.
Useful? React with 👍 / 👎.
| this.selectedExampleIds === null | ||
| ? this.instances.map((i) => i.id) | ||
| : this.selectedExampleIds.size > 0 | ||
| ? [...this.selectedExampleIds] | ||
| : (payload.openCardIds ?? []); |
There was a problem hiding this comment.
Preserve an intentionally empty example selection
When the modal is opened from an instance or playground card, payload.openCardIds is pre-populated. If the user clicks Clear All or deselects the last remaining example, selectedExampleIds becomes an empty set, but this expression falls back to payload.openCardIds and still sends the original card ids to ListingCreateCommand. In that flow the listing is created with example links even though the UI shows nothing selected.
Useful? React with 👍 / 👎.
| menuItems = [...menuItems, ...getSampleDataMenuItems(card, params)]; | ||
| menuItems.push({ | ||
| label: `Create Listing with AI`, | ||
| label: `Create Listing`, |
|
Is there a recursive call somehow? I can't create-listing without it running async forever |
No, there is no recursive call. The flow is linear — create listing, then await background work, then dismiss modal. The issue is background work fires multiple LLM requests in parallel (autoPatchName, autoPatchSummary, etc) plus N spec creations, all with no timeout. If any one of them is slow or hangs, the modal waits forever. The original await backgroundWork was intentionally designed based on the request —it keeps the modal on screen until all the AI auto-patching finishes writing to the listing card. Alternative way, I think
|
There was a problem hiding this comment.
Pull request overview
This PR improves the “Create Listing” flow in operator mode by introducing a confirmation modal with a loading experience, supporting multi-example selection, and navigating to the new listing immediately while background auto-patching completes.
Changes:
- Adds an operator-mode “Create Listing” confirmation modal (with examples selection + loading state).
- Introduces
OpenCreateListingModalCommandand updates menu items/indexing expectations to open the modal instead of directly creating a listing. - Updates listing creation to accept
openCardIds(plural) and exposes background work completion so the modal can auto-dismiss when patching finishes.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/code-ref.ts | Adds isListingDef / isListingInstance helpers for reliable listing detection. |
| packages/host/app/services/operator-mode-state-service.ts | Stores modal payload in cross-submode state service. |
| packages/host/app/components/operator-mode/container.gts | Renders the new modal at operator-mode top level. |
| packages/host/app/components/operator-mode/detail-panel.gts | Switches “Create Listing” action to open modal; hides action for listings. |
| packages/host/app/components/operator-mode/create-listing-modal.gts | New confirmation modal UI + loading state + navigation behavior. |
| packages/host/app/commands/open-create-listing-modal.ts | New command that sets modal payload in operator-mode state. |
| packages/host/app/commands/listing-create.ts | Renames openCardId→openCardIds, links multiple examples, runs autopatching as background work. |
| packages/host/app/commands/index.ts | Shims/registers the new open-create-listing-modal command. |
| packages/catalog-realm/catalog-app/listing/listing.gts | Marks listing definition with static isListingDef = true and updates menu item filtering. |
| packages/base/menu-items.ts | Updates default menu item to “Create Listing” and opens the modal command. |
| packages/base/command.gts | Updates ListingCreateInput to openCardIds (containsMany). |
| packages/host/tests/* | Adds/updates tests for modal behavior, command behavior, and indexing deps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private instancesSearch = getSearch<CardDef>(this, getOwner(this)!, () => | ||
| this.codeRef ? { filter: { type: this.codeRef } } : undefined, |
| let targetRealm = payload.targetRealm; | ||
| let openCardIds = | ||
| this.selectedExampleIds === null | ||
| ? this.instances.map((i) => i.id) | ||
| : [...this.selectedExampleIds]; |
| @title='Create Listing' | ||
| @size='small' | ||
| @isOpen={{this.isModalOpen}} | ||
| @onClose={{this.onClose}} | ||
| data-test-create-listing-modal |
linear: https://linear.app/cardstack/issue/CS-10383/create-listing-should-have-a-loading-user-experience
Summary
Architecture
The "Create Listing" action can be triggered from multiple places:
Flow after clicking "Create"
Demo:
Screen.Recording.2026-03-18.at.4.25.29.PM.mp4