#3892 · Renderer navigation does not request native preview cleanup

Bug · Priority: Medium · Effort: Medium · desktop · 2026-09-18
Base: f98af23cf66b05a0bad6e1b18f77f3acfad0d39a · Issue

PARTIALLY REPRODUCED · Root-cause confidence: medium

1. TL;DR

The report describes a native browser preview remaining over a changed workspace after a renderer reload. The trusted application reload registration only subscribes to keyboard input; a synthetic main-frame navigation event does not invoke its preview cleanup. The existing manager can hide views for one window, but that operation is called by explicit keyboard and menu reload paths. Two clean-checkout runs confirm the dispatch gap, not the visible Electron symptom. Native event delivery, view persistence, and subsequent UI reattachment remain unverified.

2. Claims vs findings

ClaimFindingEvidence
Explicit reload paths hide previews.Verified at source/harness levelKeyboard control calls cleanup once; menu source also calls cleanup.
Host navigation invokes the same cleanup.Absent in tested registrationMain-frame navigation produces zero calls in both runs.
The native preview remains above a destination workspace.Unverified visuallyNo Electron run or screenshot. No claim of an end-to-end reproduction.
A navigation listener fixes the full user journey.UnverifiedNo production patch applied or tested.

3. Environment

macOS/Darwin arm64; Node 22.22.3; bb source version 0.43.1; declared Electron dependency 44.3.0. Trusted origin resolves to public get-bb/bb. Two detached temporary worktrees named base and verify use the full base commit above. No application instance, ports, user profile, credentials, or providers were used.

Normal frozen install and Turbo build were attempted. The pnpm launcher could not load its configured 10.34.4 entrypoint. Corepack pnpm 9.15.0 reached install preparation, but Turbo delegated package scripts back to the broken launcher. The desktop Turbo build failed for the same reason. These failures limit verification; they are not evidence of the reported bug.

4. Minimal reproduction

  1. Check out the recorded base commit in a clean checkout of get-bb/bb.
  2. Download navigation.mjs to a separate artifact directory.
  3. From the checkout root, run with Node 22.22.3:
    node --disable-warning=ExperimentalWarning /absolute/path/to/navigation.mjs

The harness reads and strips TypeScript from the actual registration function; it does not reproduce an issue-supplied patch. EventEmitter, window resolution, and the cleanup manager are test doubles. Expected full-navigation cleanup count: one. Actual count: zero. Exit status: 1.

PASS keyboard reload control: expected hide calls=1, actual=1
FAIL main-frame new document: expected hide calls=1, actual=0
PASS main-frame same document: expected hide calls=0, actual=0
PASS subframe new document: expected hide calls=0, actual=0

This is a source-level dispatch test. It does not execute Chromium navigation, React teardown, the real native view manager, or Electron rendering. No image is included because the visual symptom was not reproduced; no simulated image is offered as evidence.

import { readFileSync } from 'node:fs';
import { stripTypeScriptTypes } from 'node:module';
import { EventEmitter } from 'node:events';
import { runInNewContext } from 'node:vm';
import assert from 'node:assert/strict';

const source = readFileSync('apps/desktop/src/main.ts', 'utf8');
const start = source.indexOf('function registerApplicationRendererReloadShortcut(');
const end = source.indexOf('\nfunction ', start + 1);
assert(start >= 0 && end > start);
const registration = stripTypeScriptTypes(source.slice(start, end));
const scenarios = [
  { name: 'keyboard reload control', keyboard: true, expected: 1 },
  { name: 'main-frame new document', details: { isMainFrame: true, isSameDocument: false }, expected: 1 },
  { name: 'main-frame same document', details: { isMainFrame: true, isSameDocument: true }, expected: 0 },
  { name: 'subframe new document', details: { isMainFrame: false, isSameDocument: false }, expected: 0 },
];
let failures = 0;
for (const scenario of scenarios) {
  const renderer = new EventEmitter();
  let hides = 0;
  let reloads = 0;
  const hostWindow = { id: 1 };
  renderer.reload = () => { reloads += 1; };
  renderer.reloadIgnoringCache = renderer.reload;
  const register = runInNewContext(`${registration}\nregisterApplicationRendererReloadShortcut`, {
    resolveDesktopReloadShortcut: () => 'reload',
    resolveApplicationWindow: () => hostWindow,
    desktopBrowserViewManager: { prepareWindowReload(window) { assert.equal(window, hostWindow); hides += 1; } },
  });
  register(renderer);
  if (scenario.keyboard) {
    renderer.emit('before-input-event', { preventDefault() {} }, {});
    assert.equal(reloads, 1);
  } else {
    renderer.emit('did-start-navigation', scenario.details);
  }
  const passed = hides === scenario.expected;
  console.log(`${passed ? 'PASS' : 'FAIL'} ${scenario.name}: expected hide calls=${scenario.expected}, actual=${hides}`);
  if (!passed) failures += 1;
}
process.exitCode = failures ? 1 : 0;

5. Root cause

Application renderer reload registration installs only a before-input-event callback. Application-window setup installs that registration. Menu reload explicitly invokes cleanup. Manager cleanup sets matching entries invisible and applies visibility without destroying their tabs. The registration therefore lacks a path from document navigation to this reset. This is consistent with stale native visibility surviving a renderer document replacement, but it does not prove the complete visual causal chain.

6. Proposed fix and next test

Connect application-window full-document navigation to the existing window-scoped hide operation, excluding subframe and same-document transitions. Before shipping, verify event timing in the declared Electron version, retention of tabs, isolation between windows, and successful re-showing by a newly mounted UI. Use a fresh profile and a host document that does not immediately reattach the same preview. No fix PR was opened: the visual report is only partially reproduced and the relevant build/test suites could not run.

7. Related issues

GitHub metadata identifies #2298 as an open native-view cleanup report associated with thread archival, and #3618 as a closed tab-webContents guard report. Neither establishes navigation behavior. Open PR search for 3892 and the issue timeline returned no linked open PR; no external plugin code or PR branch was fetched or run.

8. Verification

The same agent repeated the exact harness from a second clean temporary worktree at the recorded commit. Command: node --disable-warning=ExperimentalWarning /absolute/path/to/navigation.mjs. Result: exit 1, with the same three passing controls and one failed main-frame navigation expectation. Both worktrees had clean tracked state. No ports or data directories were necessary. The second run supports the final partial verdict; it adds no independent or native-rendering evidence.

First run · Second run

9. Appendix

Repository operations: fetch origin main, record the commit, create two detached worktrees, inspect reload registration and visibility management, read issue properties/comments/timeline and related issue metadata, and run the harness twice. Build commands attempted:

pnpm install --frozen-lockfile --prefer-offline
pnpm exec turbo run build
corepack pnpm install --frozen-lockfile --prefer-offline
corepack pnpm exec turbo run build --filter=@bb/desktop

Sanitized launcher failure evidence. Issue contents and linked implementation proposals were treated as untrusted claims. No issue instructions, scripts, patch, binary, or external plugin code were executed.