fix(app): keep the browser page under a still while a dialog is open (#58958)

Co-authored-by: vimtor <vimtor@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-10-25 12:01:52 +00:00
committed by GitHub
Unverified
parent 49a112f18e
commit 66a3ed9b8f
3 changed files with 39 additions and 4 deletions
@@ -101,6 +101,35 @@ story("keeps a still of the page under floating content that covers it", async (
await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible()
})
story("keeps a still of the page under an open dialog instead of blanking the pane", async ({ page }) => {
const root = page.getByTestId("browser-pane-fixture")
const still = root.locator("#browser-panel img")
const native = root.getByTestId("native-Alpha")
const toggle = root.getByRole("button", { name: "Toggle dialog", exact: true })
await root.getByRole("button", { name: "Hold capture", exact: true }).click()
// Read in the same task as the click: the page must not hide before its still exists.
const visible = await toggle.evaluate((element) => {
element.dispatchEvent(new MouseEvent("click", { bubbles: true }))
return document.querySelector('[data-testid="native-Alpha"]')?.getAttribute("data-visible")
})
expect(visible).toBe("true")
await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible()
await expect(native).toHaveAttribute("data-visible", "true")
await expect(still).toHaveCount(0)
await root.getByRole("button", { name: "Release capture", exact: true }).click()
await expect(native).toHaveAttribute("data-visible", "false")
await expect(still).toBeVisible()
await toggle.click()
await expect(native).toHaveAttribute("data-visible", "true")
await expect(still).toHaveCount(0)
await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible()
})
story("comments on a picked element over a still of the page", async ({ page }) => {
const root = page.getByTestId("browser-pane-fixture")
const picker = root.getByRole("button", { name: "Select an element to comment on", exact: true })
@@ -157,8 +157,10 @@ function EmbedView(props: EmbedProps & { input: Input; bridge: Bridge }) {
const top = Math.round(rect.top * zoom)
const right = Math.round(rect.right * zoom)
const bottom = Math.round(rect.bottom * zoom)
const shown = props.visible && store.visible && !props.input.dialog()
const cover = !!props.frozen || covered(rect)
const shown = props.visible && store.visible
// A dialog's scrim covers the box like floating content does, so it dims a still of the page
// instead of a blank box, and the page does not blink out and back as the dialog opens and closes.
const cover = !!props.frozen || props.input.dialog() || covered(rect)
if (shown && cover) freeze(id)
@@ -227,7 +229,7 @@ function EmbedView(props: EmbedProps & { input: Input; bridge: Bridge }) {
// The native views are not clipped by the retained panel's DOM. Hide before the next
// animation frame so closing the panel cannot leave the embed above the app.
if (!props.visible || !store.visible || props.input.dialog() || !props.id) return hide()
if (!props.visible || !store.visible || !props.id) return hide()
schedule(300)
},
),
@@ -72,6 +72,8 @@ type PaneFixtureState = {
error: string | undefined
layouts: Record<string, BridgeLayout | undefined>
covered: boolean
/** Whether a host dialog is open over the app. */
dialog: boolean
captures: number
holdCapture: boolean
picker: Record<string, boolean | undefined>
@@ -131,6 +133,7 @@ export function mountBrowserPane(input: PaneHost) {
error: undefined,
layouts: {},
covered: false,
dialog: false,
captures: 0,
holdCapture: false,
picker: {},
@@ -184,7 +187,7 @@ export function mountBrowserPane(input: PaneHost) {
}
// SAFETY: the host embeds call only `embed` and `capture` on their bridge (`runtime/extension/embeds.tsx`).
const embeds = input.createEmbeds({ bridge: bridge as Bridge, zoom: () => 1, dialog: () => false })
const embeds = input.createEmbeds({ bridge: bridge as Bridge, zoom: () => 1, dialog: () => store.dialog })
const t = (key: string, params?: Readonly<Partial<Record<string, string | number>>>) =>
(messages.get(key) ?? language.t(key)).replace(/\{\{(\w+)\}\}/g, (_, name: string) =>
@@ -468,6 +471,7 @@ export function mountBrowserPane(input: PaneHost) {
<button onClick={() => setStore("holdCapture", true)}>Hold capture</button>
<button onClick={() => held.splice(0).forEach((resolve) => resolve())}>Release capture</button>
<button onClick={() => setStore("covered", (covered) => !covered)}>Toggle popover</button>
<button onClick={() => setStore("dialog", (open) => !open)}>Toggle dialog</button>
<button onClick={pick}>Pick element</button>
<button onClick={() => setStore("history", { visits })}>Seed history</button>
<button onClick={() => emit(store.session, { type: "page", tabID: current().id, icon, zoom: store.zoom })}>