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
identities, forks, provider configuration/connections and same-version Form
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
2.0.7 from recommendation 2.0.11 and keep 2.0.10 usable with optional update.
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.
- Server builds clean generated `dist` before compilation; desktop acceptance
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
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 { Dialog } from "@kobalte/core/dialog"
import { Portal } from "solid-js/web"
import { useI18n } from "../lib/i18n"
import { useConfig } from "../stores/preferences"
import { sseManager } from "../lib/sse-manager"
@ -35,9 +36,9 @@ export default function OpenCodeSetup() {
})
return <>
<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>
</div>
</div></Portal>
</Show>
<Dialog open={openCodeSetupOpen()} onOpenChange={setOpenCodeSetupOpen}>
<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() }
})
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()
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 } }))
try {
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 90_000 })
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") })
await page.getByRole("button", { name: "Close", exact: true }).click()
await page.getByRole("button", { name: "OpenCode setup required" }).click()