From 89cb4309b52c327ede09c5ddbeb4e7eea236ea77 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Mon, 13 Apr 2026 10:08:45 -0400 Subject: [PATCH 1/6] Add Dev Console shortcut for non-embedded apps in app dev For non-embedded apps, adds a (c) keyboard shortcut and URL link in the DevSessionUI footer that opens the store admin with ?dev-console=show, giving developers quick access to extension previews. Also fixes AppManagementClient.appFromIdentifiers() to extract the embedded field from the app home module config, and makes the embedded state reactive via DevSessionStatusManager so it updates live when the toml changes during dev. Co-Authored-By: Claude Opus 4.6 --- .../dev-session/dev-session-status-manager.ts | 1 + .../dev/processes/dev-session/dev-session.ts | 2 +- .../dev/processes/setup-dev-processes.ts | 3 +- .../dev/ui/components/DevSessionUI.test.tsx | 59 +++++++++++++++++-- .../dev/ui/components/DevSessionUI.tsx | 26 ++++++-- packages/app/src/cli/utilities/app/app-url.ts | 4 ++ .../app-management-client.ts | 1 + 7 files changed, 86 insertions(+), 10 deletions(-) diff --git a/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts b/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts index 027caff0bcf..8b2669c2404 100644 --- a/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts +++ b/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts @@ -17,6 +17,7 @@ export interface DevSessionStatus { isReady: boolean previewURL?: string graphiqlURL?: string + appEmbedded?: boolean statusMessage?: {message: string; type: DevSessionStatusMessageType} } diff --git a/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts b/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts index 1e9602ce520..3a94d858f46 100644 --- a/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts +++ b/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts @@ -265,7 +265,7 @@ export class DevSession { const hasPreview = event.app.allExtensions.filter((ext) => ext.isPreviewable).length > 0 const useDevConsole = firstPartyDev() && hasPreview const newPreviewURL = useDevConsole ? this.options.appLocalProxyURL : this.options.appPreviewURL - this.statusManager.updateStatus({previewURL: newPreviewURL}) + this.statusManager.updateStatus({previewURL: newPreviewURL, appEmbedded: event.app.configuration.embedded}) } /** diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts index 9a2a3723740..3d6d9f4dd20 100644 --- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts +++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts @@ -125,7 +125,8 @@ export async function setupDevProcesses({ ? `http://localhost:${graphiqlPort}/graphiql?key=${encodeURIComponent(resolvedGraphiqlKey)}` : undefined - const devSessionStatusManager = new DevSessionStatusManager({isReady: false, previewURL, graphiqlURL}) + const appEmbedded = reloadedApp.configuration.embedded + const devSessionStatusManager = new DevSessionStatusManager({isReady: false, previewURL, graphiqlURL, appEmbedded}) const processes = [ ...(await setupWebProcesses({ diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx index ac93a0479fc..ca586bb5055 100644 --- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx +++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx @@ -121,10 +121,12 @@ describe('DevSessionUI', () => { expect(output).toContain('(q) Quit') // Shortcuts and URLs should be visible - expect(output).toContain('(g) Open GraphiQL') - expect(output).toContain('(p) Preview in your browser') + expect(output).toContain('(g) Open GraphiQL (Admin API)') + expect(output).toContain('(p) Open app preview') + expect(output).toContain('(c) Open Dev Console for extension previews') expect(output).toContain('Preview URL: https://shopify.com') expect(output).toContain('GraphiQL URL: https://graphiql.shopify.com') + expect(output).toContain('Dev Console URL: https://mystore.myshopify.com/admin?dev-console=show') renderInstance.unmount() }) @@ -171,6 +173,55 @@ describe('DevSessionUI', () => { renderInstance.unmount() }) + test('opens the dev console URL when c is pressed for non-embedded apps', async () => { + // Given + devSessionStatusManager.updateStatus({appEmbedded: false}) + + // When + const renderInstance = render( + , + ) + + await waitForInputsToBeReady() + await sendInputAndWait(renderInstance, 10, 'c') + + // Then + expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://mystore.myshopify.com/admin?dev-console=show') + + renderInstance.unmount() + }) + + test('does not show dev console shortcut when app is embedded', async () => { + // Given + devSessionStatusManager.updateStatus({appEmbedded: true}) + + // When + const renderInstance = render( + , + ) + + await waitForInputsToBeReady() + + // Then + const output = unstyled(renderInstance.lastFrame()!) + expect(output).not.toContain('(c) Open Dev Console') + expect(output).not.toContain('Dev Console URL') + + renderInstance.unmount() + }) + test('quits when q is pressed', async () => { // Given const abortController = new AbortController() @@ -356,7 +407,7 @@ describe('DevSessionUI', () => { await waitForInputsToBeReady() // Initial state - expect(unstyled(renderInstance.lastFrame()!)).not.toContain('preview in your browser') + expect(unstyled(renderInstance.lastFrame()!)).not.toContain('Open app preview') // When status updates devSessionStatusManager.updateStatus({ @@ -365,7 +416,7 @@ describe('DevSessionUI', () => { graphiqlURL: 'https://new-graphiql.shopify.com', }) - await waitForContent(renderInstance, 'Preview in your browser') + await waitForContent(renderInstance, 'Open app preview') // Then expect(unstyled(renderInstance.lastFrame()!)).toContain('Preview URL: https://new-preview-url.shopify.com') diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx index 35f3cf44187..3af7492d2f4 100644 --- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx +++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx @@ -7,6 +7,7 @@ import { DevSessionStatusMessageType, } from '../../processes/dev-session/dev-session-status-manager.js' import {MAX_EXTENSION_HANDLE_LENGTH} from '../../../../models/extensions/schemas.js' +import {buildDevConsoleURL} from '../../../../utilities/app/app-url.js' import {OutputProcess} from '@shopify/cli-kit/node/output' import {Alert, ConcurrentOutput, Link, TabularData} from '@shopify/cli-kit/node/ui/components' import {useAbortSignal} from '@shopify/cli-kit/node/ui/hooks' @@ -147,6 +148,13 @@ const DevSessionUI: FunctionComponent = ({ } }, }, + { + key: 'c', + condition: () => Boolean(!status.appEmbedded && status.isReady), + action: async () => { + await openURL(buildDevConsoleURL(shopFqdn)) + }, + }, ], content: ( <> @@ -157,14 +165,19 @@ const DevSessionUI: FunctionComponent = ({ )} {canUseShortcuts && ( - {status.graphiqlURL && status.isReady ? ( + {status.isReady ? ( - {figures.pointerSmall} (g) Open GraphiQL (Admin API) in your browser + {figures.pointerSmall} (p) Open app preview ) : null} - {status.isReady ? ( + {!status.appEmbedded && status.isReady ? ( - {figures.pointerSmall} (p) Preview in your browser + {figures.pointerSmall} (c) Open Dev Console for extension previews + + ) : null} + {status.graphiqlURL && status.isReady ? ( + + {figures.pointerSmall} (g) Open GraphiQL (Admin API) ) : null} @@ -181,6 +194,11 @@ const DevSessionUI: FunctionComponent = ({ Preview URL: ) : null} + {!status.appEmbedded ? ( + + Dev Console URL: + + ) : null} {status.graphiqlURL ? ( GraphiQL URL: diff --git a/packages/app/src/cli/utilities/app/app-url.ts b/packages/app/src/cli/utilities/app/app-url.ts index 79afa5cd860..f201f1be874 100644 --- a/packages/app/src/cli/utilities/app/app-url.ts +++ b/packages/app/src/cli/utilities/app/app-url.ts @@ -13,6 +13,10 @@ export function buildAppURLForAdmin(storeFqdn: string, apiKey: string, adminDoma return `https://${adminDomain}/store/${storeName}/apps/${apiKey}?dev-console=show` } +export function buildDevConsoleURL(storeFqdn: string) { + return `https://${storeFqdn}/admin?dev-console=show` +} + export function buildAppURLForMobile(storeFqdn: string, apiKey: string) { const normalizedFQDN = normalizeStoreFqdn(storeFqdn) const adminUrl = storeAdminUrl(normalizedFQDN) diff --git a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts index 918d5775ccc..e9f8a6c98f3 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts @@ -366,6 +366,7 @@ export class AppManagementClient implements DeveloperPlatformClient { organizationId: String(numberFromGid(app.organizationId)), grantedScopes: app.activeRoot.grantedShopifyApprovalScopes, applicationUrl: appHomeModule?.config?.app_url as string | undefined, + embedded: appHomeModule?.config?.embedded as boolean | undefined, flags: [], developerPlatformClient: this, } From ba9cc432c8f13fe20b06e358a9ca2cbe6149914d Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:07:40 -0400 Subject: [PATCH 2/6] Add changeset for dev console shortcut Co-Authored-By: Claude Opus 4.6 --- .changeset/dev-console-shortcut.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dev-console-shortcut.md diff --git a/.changeset/dev-console-shortcut.md b/.changeset/dev-console-shortcut.md new file mode 100644 index 00000000000..54d87c39b5b --- /dev/null +++ b/.changeset/dev-console-shortcut.md @@ -0,0 +1,5 @@ +--- +'@shopify/app': minor +--- + +Add (c) Dev Console shortcut for non-embedded apps in `app dev` From d1717fb1010f57fe28ee9493f4585ee133395438 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:08:54 -0400 Subject: [PATCH 3/6] Remove duplicate changeset Co-Authored-By: Claude Opus 4.6 --- .changeset/dev-console-shortcut.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/dev-console-shortcut.md diff --git a/.changeset/dev-console-shortcut.md b/.changeset/dev-console-shortcut.md deleted file mode 100644 index 54d87c39b5b..00000000000 --- a/.changeset/dev-console-shortcut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@shopify/app': minor ---- - -Add (c) Dev Console shortcut for non-embedded apps in `app dev` From d55260b57e215f8f09129767e80156aa0620cc6b Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:17:54 -0400 Subject: [PATCH 4/6] Fix lint error and add changeset Co-Authored-By: Claude Opus 4.6 --- .changeset/blue-taxes-cry.md | 5 +++++ .../app/src/cli/services/dev/ui/components/DevSessionUI.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/blue-taxes-cry.md diff --git a/.changeset/blue-taxes-cry.md b/.changeset/blue-taxes-cry.md new file mode 100644 index 00000000000..1da22306571 --- /dev/null +++ b/.changeset/blue-taxes-cry.md @@ -0,0 +1,5 @@ +--- +'@shopify/app': minor +--- + +Added a separate Dev Console link to the `app dev` output for non-embedded apps diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx index 3af7492d2f4..8e68af5d5cd 100644 --- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx +++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx @@ -194,7 +194,7 @@ const DevSessionUI: FunctionComponent = ({ Preview URL: ) : null} - {!status.appEmbedded ? ( + {status.appEmbedded === false ? ( Dev Console URL: From e33b56519fb020f992b6596b25ecf9f274710dad Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:27:30 -0400 Subject: [PATCH 5/6] Add analytics metadata to dev console shortcut action Co-Authored-By: Claude Opus 4.6 --- .../app/src/cli/services/dev/ui/components/DevSessionUI.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx index 8e68af5d5cd..af31a0d79fc 100644 --- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx +++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx @@ -152,6 +152,9 @@ const DevSessionUI: FunctionComponent = ({ key: 'c', condition: () => Boolean(!status.appEmbedded && status.isReady), action: async () => { + await metadata.addPublicMetadata(() => ({ + cmd_dev_preview_url_opened: true, + })) await openURL(buildDevConsoleURL(shopFqdn)) }, }, From b2880a17c496ccbf54bbe2ff69e74963bf467bd5 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:29:39 -0400 Subject: [PATCH 6/6] Set appEmbedded: false in test initial status for dev console assertions Co-Authored-By: Claude Opus 4.6 --- .../app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx index ca586bb5055..896547607ea 100644 --- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx +++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx @@ -40,6 +40,7 @@ const initialStatus: DevSessionStatus = { isReady: true, previewURL: 'https://shopify.com', graphiqlURL: 'https://graphiql.shopify.com', + appEmbedded: false, } const onAbort = vi.fn()