Skip to content

fix: "+" button to open project does nothing in serve mode after redeployment #22087

@OpenCodeEngineer

Description

@OpenCodeEngineer

Bug

Clicking the "+" button in the sidebar to open a new project shows nothing — no dialog, no error message.

Root Cause

Three compounding issues:

1. Stale client bundle after redeployment

When the server is redeployed with a new build, all Vite chunk hashes change (e.g. dialog-select-directory-utHQcNsA.jsdialog-select-directory-CRC4imRH.js). Browser tabs that were open before the redeployment still have the old main JS bundle loaded, which tries to dynamically import chunks with old hashes that no longer exist on the server.

2. Silent failure via void import(...) pattern

The dynamic imports in layout.tsx use the void import(...) pattern which discards the promise. When the import fails (404 on stale chunk), the rejection becomes an unhandled promise rejection — no error dialog, no console message visible to the user, no recovery mechanism.

// layout.tsx:1507 — the failing code
void import("@/components/dialog-select-directory").then((x) => {
  if (dialogDead || dialogRun !== run) return
  dialog.show(/* ... */)
})

3. SPA fallback masks the 404

In instance.ts (embedded UI path), the catch-all route falls back to index.html for any missing path, including .js chunk files:

embeddedWebUI[path.replace(/^\//, "")] ?? embeddedWebUI["index.html"] ?? null

This means a missing .js chunk actually returns HTML content instead of a proper 404, causing a confusing MIME type error rather than a clean import failure.

4. No cache-busting headers on HTML

Neither instance.ts nor server.ts set Cache-Control headers on HTML responses, so browsers may cache stale index.html that references old chunk hashes.

Why tests didn't catch this

  • E2E tests always start fresh with the current build — they never test "old client + new server" scenarios
  • The void import(...) anti-pattern silently swallows errors as unhandled rejections — no test asserts on this
  • The SPA fallback bug in instance.ts actually masks the 404 — making it even harder to detect during development
  • No integration test verifies behavior when a lazy chunk returns 404

Fix

  1. Fix SPA fallback in instance.ts: Only fall back to index.html for extensionless routes (matching server.ts behavior), so stale .js requests properly 404
  2. Add Cache-Control: no-cache to HTML responses: Both instance.ts and server.ts now set cache headers on HTML, ensuring browsers revalidate on each navigation
  3. Global stale-module detection in entry.tsx: Listen for unhandledrejection events matching dynamic import failures and show a persistent "new version available" overlay with a Reload button — covers all 15+ dynamic import sites across the app

Metadata

Metadata

Assignees

Labels

webRelates to opencode on web / desktop

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions