fix(setup): keep recovery above pending folder loading

Rebuilt desktop acceptance exposed a blocked recovery action after dismissing setup during an initial folder open. Mount the persistent recovery entry through a body portal so the root-contained folder loading overlay cannot intercept its pointer events. Opening setup still removes the entry and preserves the dialog's modal behavior.

Exercise the real loading-overlay class with the full app stylesheet and normal Playwright clicks; all seven setup scenarios and UI typecheck pass. Independent gatekeeper review found no remaining issue. Record successful Windows and native Linux beta/2.0.3 migrations to the corrected 2.0.7 minimum.
This commit is contained in:
Pascal André 2026-09-20 23:55:54 +02:00
parent e76e4484ab
commit 781c3a42b7
No known key found for this signature in database
3 changed files with 18 additions and 3 deletions

View file

@ -252,6 +252,10 @@ synthetic seed. Keep the compatibility audit in
- Native 2.0.3→2.0.7 migration passed with 215-message histories, distinct old - Native 2.0.3→2.0.7 migration passed with 215-message histories, distinct old
identities, forks, provider configuration/connections and same-version Form identities, forks, provider configuration/connections and same-version Form
durability controls. Evidence: `codenomad-history-migration-IoFhtY`. durability controls. Evidence: `codenomad-history-migration-IoFhtY`.
- Native beta-19271→2.0.7 migration also passed on Windows; both beta-19271 and
2.0.3→2.0.7 passed under native Linux/WSL. Exact CLI archives were verified,
original seed hashes stayed unchanged, and isolated processes were cleaned up.
Evidence: `pr696-native-207-210/MIGRATION-MINIMUM207.md`.
- Seven browser setup scenarios passed. Rendered captures distinguish minimum - Seven browser setup scenarios passed. Rendered captures distinguish minimum
2.0.7 from recommendation 2.0.11 and keep 2.0.10 usable with optional update. 2.0.7 from recommendation 2.0.11 and keep 2.0.10 usable with optional update.
Evidence: approved temporary `opencode/696-rigorous-browser/`. Evidence: approved temporary `opencode/696-rigorous-browser/`.
@ -264,5 +268,10 @@ synthetic seed. Keep the compatibility audit in
also approved the compatible-daemon fixture and generated-output clean step. also approved the compatible-daemon fixture and generated-output clean step.
- Server builds clean generated `dist` before compilation; desktop acceptance - Server builds clean generated `dist` before compilation; desktop acceptance
checks that deleted adapters/installers do not survive incremental packaging. checks that deleted adapters/installers do not survive incremental packaging.
- Rebuilt Tauri acceptance found a recovery-layer regression when opening a
folder: the loading overlay intercepted the persistent setup button after
dismissal. The recovery entry now mounts through a body portal. The real-style
browser regression uses normal pointer clicks with the folder overlay present;
all seven setup scenarios and UI typecheck passed after correction.
- Rebuilt-host acceptance and final remote CI are recorded in the PR after - Rebuilt-host acceptance and final remote CI are recorded in the PR after
completion; earlier green tests are not substituted for those results. completion; earlier green tests are not substituted for those results.

View file

@ -1,5 +1,6 @@
import { createEffect, onCleanup, onMount, Show, untrack } from "solid-js" import { createEffect, onCleanup, onMount, Show, untrack } from "solid-js"
import { Dialog } from "@kobalte/core/dialog" import { Dialog } from "@kobalte/core/dialog"
import { Portal } from "solid-js/web"
import { useI18n } from "../lib/i18n" import { useI18n } from "../lib/i18n"
import { useConfig } from "../stores/preferences" import { useConfig } from "../stores/preferences"
import { sseManager } from "../lib/sse-manager" import { sseManager } from "../lib/sse-manager"
@ -35,9 +36,9 @@ export default function OpenCodeSetup() {
}) })
return <> return <>
<Show when={needsOpenCodeSetup() && !openCodeSetupOpen()}> <Show when={needsOpenCodeSetup() && !openCodeSetupOpen()}>
<div class="fixed bottom-4 right-4 z-50 border border-base bg-surface-secondary p-3" role="status"> <Portal><div class="fixed bottom-4 right-4 z-50 border border-base bg-surface-secondary p-3" role="status">
<button class="selector-button" onClick={() => openOpenCodeSetup()}>{t("settings.opencode.setup.required")}</button> <button class="selector-button" onClick={() => openOpenCodeSetup()}>{t("settings.opencode.setup.required")}</button>
</div> </div></Portal>
</Show> </Show>
<Dialog open={openCodeSetupOpen()} onOpenChange={setOpenCodeSetupOpen}> <Dialog open={openCodeSetupOpen()} onOpenChange={setOpenCodeSetupOpen}>
<Dialog.Portal><Dialog.Overlay class="modal-overlay" /> <Dialog.Portal><Dialog.Overlay class="modal-overlay" />

View file

@ -62,13 +62,18 @@ test("missing installation and incompatible daemon expose different actions; res
finally { await page.close() } finally { await page.close() }
}) })
test("closing required-update screen leaves a persistent recovery entry", async () => { test("closing required-update screen leaves recovery clickable above a pending folder overlay", async () => {
const page = await browser.newPage() const page = await browser.newPage()
await page.route("**/api/**", route => route.fulfill({ json: { state: "update_required", currentVersion: "2.0.6", await page.route("**/api/**", route => route.fulfill({ json: { state: "update_required", currentVersion: "2.0.6",
latestVersion: "2.0.11", minimumVersion: "2.0.7", recommendedVersion: "2.0.11", binaryPath: "opencode2", target: "host", canUpgrade: true, canRestart: false } })) latestVersion: "2.0.11", minimumVersion: "2.0.7", recommendedVersion: "2.0.11", binaryPath: "opencode2", target: "host", canUpgrade: true, canRestart: false } }))
try { try {
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 90_000 }) await page.goto(url, { waitUntil: "domcontentloaded", timeout: 90_000 })
await page.getByRole("dialog").waitFor() await page.getByRole("dialog").waitFor()
await page.evaluate(() => {
const overlay = document.createElement("div")
overlay.className = "folder-loading-overlay"
document.getElementById("root")!.append(overlay)
})
if (process.env.CODENOMAD_SETUP_CAPTURE) await page.screenshot({ path: path.join(process.env.CODENOMAD_SETUP_CAPTURE, "opencode-setup-required.png") }) if (process.env.CODENOMAD_SETUP_CAPTURE) await page.screenshot({ path: path.join(process.env.CODENOMAD_SETUP_CAPTURE, "opencode-setup-required.png") })
await page.getByRole("button", { name: "Close", exact: true }).click() await page.getByRole("button", { name: "Close", exact: true }).click()
await page.getByRole("button", { name: "OpenCode setup required" }).click() await page.getByRole("button", { name: "OpenCode setup required" }).click()