diff --git a/packages/app/e2e/regression/device-controls.spec.ts b/packages/app/e2e/regression/device-controls.spec.ts index 245d702b9..69c07da00 100644 --- a/packages/app/e2e/regression/device-controls.spec.ts +++ b/packages/app/e2e/regression/device-controls.spec.ts @@ -120,6 +120,15 @@ for (const platforms of [["ios", "android"], ["ios"], ["android"]] as const) { ).toBeEnabled() } + // Only a live JavaScript run names the project Metro serves; the failed iOS build loads nothing. + const metro = page.locator('[data-slot="device-metro"]') + await expect(metro).toHaveCount(expo ? 1 : 0) + if (expo) { + const android = page.getByRole("toolbar", { name: labels.android, exact: true }).locator("xpath=..") + await expect(android.locator('[data-slot="device-metro"]')).toContainText("Metro~/DeviceControls") + await expect(android.getByTitle(directory, { exact: true })).toBeVisible() + } + for (const platform of platforms) { const label = labels[platform] const pane = page.getByRole("toolbar", { name: label, exact: true }) @@ -138,6 +147,7 @@ for (const platforms of [["ios", "android"], ["ios"], ["android"]] as const) { ).toBeEnabled() } if (expo) await expect(controls.getByRole("button", { name: "Stop", exact: true })).toBeEnabled() + await expect(metro).toHaveCount(0) expect(calls).toEqual([ ...(expo ? [{ path: "/api/device-preview/bundler/start", directory }] : []), ...platforms.map((platform) => ({ path: "/api/device-preview/run", directory, platform })), diff --git a/packages/app/src/components/session/session-device-tab.tsx b/packages/app/src/components/session/session-device-tab.tsx index e7b207481..bb1bf955c 100644 --- a/packages/app/src/components/session/session-device-tab.tsx +++ b/packages/app/src/components/session/session-device-tab.tsx @@ -9,8 +9,9 @@ import { createStore } from "solid-js/store" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { useServerSDK } from "@/context/server-sdk" +import { useServerSync } from "@/context/server-sync" import { devicePreviewUrl } from "@/utils/device-preview" -import { createDeviceState, deviceBuildBusy, devicePreviewIcon } from "./device-state" +import { createDeviceState, deviceBuildBusy, deviceFrameworkIcon, devicePreviewIcon } from "./device-state" const ALL_PLATFORMS: DevicePreview.Platform[] = ["ios", "android"] const LOG_TAIL = 40 @@ -127,6 +128,7 @@ function DevicePane(props: { classList?: Record }) { const serverSDK = useServerSDK() + const serverSync = useServerSync() const platform = usePlatform() const language = useLanguage() let frame: HTMLIFrameElement | undefined @@ -154,6 +156,18 @@ function DevicePane(props: { return { text: url() ?? server()?.command ?? "", failed: false } }) + // Both apps load their JavaScript from one Metro, so a successful native build does not say + // which project's bundle is running. Name Metro's project beside every live JS run. + const metro = createMemo(() => { + const current = build() + const bundler = props.device.info()?.bundler + if (!current || !bundler || bundler.status === "exited") return + if (!deviceBuildBusy(current) && current.status !== "running") return + if (!deviceFrameworkIcon(current.framework ?? props.device.info()?.framework)) return + const home = serverSync().data.path.home + return { full: bundler.directory, short: home ? bundler.directory.replace(home, "~") : bundler.directory } + }) + const reload = () => { const next = url() if (!frame || !next) return @@ -250,6 +264,21 @@ function DevicePane(props: { + + {(value) => ( +
+ + {language.t("session.device.bundler.label")} + {/* Clip the start so the project folder stays visible; the LTR isolate keeps `~/` in front. */} + + {value().short} + +
+ )} +