Conversation
…4268) ## Human comments ## What was wrong When the gate's tunnel Durable Objects restart, hibernated tunnel WebSockets can be orphaned. The bb keeps an open socket that nothing answers, so its heartbeats go unanswered. The object has no tunnel socket, so visitors get `503 offline`. After 60 s the bb redials, and while the object keeps getting requests, every new dial is orphaned the same way. An object heals only after about 45 s with no requests, which busy servers never get. get-bb#4251 reproduced this on staging: any deployment of `bb-connect`, even an unchanged version, triggers it (runs X0–X7 there). This caused the 2026-09-24 01:07 outage, and it's happening again in prod **without any gate deploy**. Prod `bb-connect` has stayed on `6f79c289` since the 01:43 rollback. Since about 07:00 UTC, Durable Object outcomes show recurring bursts at 08:45–10:30, 12:30, 13:30, 14:30 and 16:15, each with `responseStreamDisconnected` up 5–10× and `scriptThrewException` up 10–20×. Each burst leaves more servers stuck: - the 503 share on `*.getbb.app` climbed from 9–10% overnight to 16–30% per hour; - 112 hosts had 100 or more extra 503s from 14:30 to 16:30; - bee's log shows `tunnel heartbeat missed` every minute again. The current gate has no way to recover. ## What changed This is the `TunnelDO` self-restart from get-bb#4251, split out so it can ship ahead of the account stack. It contains no ticket code and no workflow changes. - `apps/connect/src/tunnel-do.ts`: - It records `tunnelOpenedAt` when it accepts a tunnel and `tunnelClosedAt` when the last tunnel socket closes or the owner revokes it. - When a visitor request, a tunnel dial, or the 50 s presence alarm finds no live tunnel socket although the last one never closed (after a 5 s grace), it writes the close, calls `storage.sync()`, then `ctx.abort()`. The restarted object drops the orphaned socket and the bb redials into a working object. - `sync()` is required. Without it, `abort()` discards the write and the object restarts forever (586 aborts in staging run B1 of get-bb#4251). - Objects written by older code only have `serverId`/`machineId`; they restart once the same way, so the first deploy of this code heals existing stuck objects. - The alarm path covers servers nobody is visiting. - `apps/connect/src/worker.test.ts`: a `TunnelDO restarts after its tunnel socket vanishes` suite. It restarts on a visitor request, a dial and the alarm. It doesn't restart after a recorded close, within the grace period, or with a live socket. Legacy objects restart exactly once, and the close record survives the restart. ## How you verified - On `origin/main`'s `tunnel-do.ts` the new suite fails 5 of 9; with this change 9 of 9 pass. - `pnpm exec turbo run typecheck lint test --filter=@bb/connect`: pass (130 tests). - Staging evidence from get-bb#4251, using the same `tunnel-do.ts` logic: - B2: the first deploy of this code over the old gate brought every tunnel back within about 40 s, and DO `internalError` went 205 → 0. - B3 and X10b: later deploys brought each tunnel back within 5–10 s. - Upgrade step B: a pre-stack bb held one tunnel for 30 minutes with 0 heartbeat misses, and visitors got 347/347 200s. ## Rollout **Deploy Connect is enabled, so merging deploys this with `wrangler deploy` at 100%.** That's acceptable: the deploy orphans the tunnels, and this code restarts them. On staging (get-bb#4251 B2), all tunnels were back within about 40 s. The gentler path, recommended in get-bb#4251, is: 1. Disable Deploy Connect. 2. Merge. 3. `pnpm --filter @bb/connect exec wrangler versions upload`. 4. `pnpm --filter @bb/connect exec wrangler rollback <new-version-id> -m "gate: tunnel self-restart"`. A rollback to a never-deployed version closed tunnels cleanly on staging (get-bb#4251 X11). 5. Re-enable Deploy Connect. Either way, go straight to 100%: objects still on old code can't restart. For 30 minutes afterwards, watch: - the 503 share and 503s per host on `*.getbb.app`; - Durable Object `internalError` (one spike as stuck objects restart, then 0) and `clientDisconnected`; - `grep 'plugin:connect\] tunnel' ~/.bb/logs/server-stdio.log` on bee. ## Rollback `pnpm --filter @bb/connect exec wrangler rollback 6f79c289-9e43-4697-b056-78e31bc3e6ad -m "<reason>"`. Use `wrangler rollback`, never `versions deploy <old>@100%`: rollbacks closed tunnels cleanly every time we saw one, and a deployment orphans them again. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong Appearance placed sidebar provider choices ahead of Theme and Palette and mixed interface controls with appearance choices in one card. The order did not reflect how people use the page, and Sidebar footer customization sat among the appearance controls. ## What changed - Put Theme and Palette first in Appearance, followed by Favicon color and Fade inactive splits. - Add an Interface card containing Header, Source code, Diffs, Sidebar, Navigation, and Sidebar footer in that order. The first three selectors appear when a plugin registers them. - Add a focused layout test with all optional selectors registered. No wire protocol, CLI, SDK, or configuration contract changed. ## How you verified - `pnpm exec turbo run test --filter=@bb/app -- src/views/SettingsView.appearance-layout.test.tsx src/views/SettingsView.palette-preview.test.tsx src/components/plugin/PluginSidebarFooterItems.test.tsx` — 17 passed. - `pnpm exec turbo run lint typecheck --filter=@bb/app` — passed; lint reported 0 errors and 200 warnings outside this change. - `pnpm start:worktree --dryrun` — passed, then started the production worktree app at commit `05d063e736018f9f2bac4e2200a72359f84341cc`. - Installed a temporary local fixture in the isolated preview store. In the real app, confirmed Header, Source code, and Diffs appear in Interface with Sidebar, Navigation, and Sidebar footer. - Live review route (owner's BB Connect session required): https://ymichael--21547.getbb.app/settings/appearance Final GitHub checks for `05d063e736018f9f2bac4e2200a72359f84341cc`: 16 passed, 2 skipped, 0 failed. No related issue was found. > AGENT GENERATED
## Human comments ## What was wrong The desktop app's Server switcher (saved servers, Add Server…, Set Server URL…) was only in **Window → Server**. That's an odd place for app-level configuration, and people looking in the bb app menu next to Settings didn't find it. ## What changed - `apps/desktop/src/menu.ts`: the bb app menu gets a **Desktop Settings** submenu (after Settings…) containing the **Server** submenu. **Window → Server** stays as an alias built from the same `createServerMenuItems(args)`. Each copy has its own menu item ID (`bb-desktop-settings-server-menu`, `bb-server-menu`), and `installApplicationMenu` attaches the `menu-will-show` refresh hook to both, so the server list stays current whichever copy is opened. - `apps/desktop/test/menu.test.ts`: a new test checks that Desktop Settings → Server shows the same items as Window → Server, and that selecting a server and Add Server… from the new location call the right handlers. - `apps/desktop/README.md`: the Saved servers section now points to the new location and mentions the Window alias. No wire, CLI, or protocol changes. ## How you verified - `pnpm exec turbo run test typecheck --filter=@bb/desktop`: typecheck passes and 435 tests pass. In that full parallel run `test/preload-build.test.ts` failed once. It passes on its own (`vitest run test/preload-build.test.ts test/menu.test.ts`: 9/9) and doesn't touch the menu code. - Manual check of the packaged desktop app on macOS still to come. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong The Interface card introduced in get-bb#4241 renders its rows in source order. Header and the code renderer controls were placed before Sidebar and Navigation, so the visible order differed from the requested sequence. ## What changed - Render Sidebar and Navigation before Header, Source code, and Diffs; Sidebar footer remains last. - Delete the dedicated Appearance layout test as requested. No wire, SDK, CLI, or configuration contract changed. ## How you verified - `pnpm exec turbo run test --filter=@bb/app -- src/views/SettingsView.palette-preview.test.tsx src/components/plugin/PluginSidebarFooterItems.test.tsx` — 16 passed. - `pnpm exec turbo run lint typecheck --filter=@bb/app` — passed; lint had zero errors and 201 existing warnings. - `pnpm start:worktree --dryrun` — passed, then started the production worktree app at `504ef46d0befd05729a95cb2d6125baba32225ab`. - In the real app with a temporary plugin fixture, verified Sidebar → Navigation → Header → Source code → Diffs → Sidebar footer on Settings → Appearance. - [Live review page](https://ymichael--21547.getbb.app/settings/appearance) requires the owner's BB Connect login. > AGENT GENERATED
…ancelled (get-bb#4187) ## Human comments ## What was wrong Cancelling a thread's environment preparation retired whatever environment the thread held immediately, bypassing the provider's `retireGraceMs` policy. When a new thread starts on a project checkout that already has an environment, the checkout provider returns the existing path and `bindEnvironmentPath` merges the thread's reservation into the existing environment row, so the thread now holds a ready, shared environment. The placement pass then compared that row's stored selection (the original thread's `branch: new` input) with the new request, saw a difference, and cancelled. The cancel retired the shared checkout environment at once even though the project checkout provider declares `retireGraceMs: null`, and the new thread failed with `Environment is destroyed`. Any other cancel of such a thread (archive, stop, deletion, restart during provisioning) followed the same path. ## What changed - `apps/server/src/services/environments/environment-engine.ts`: when the sweep sees a cancelled environment that is already `ready` with a workspace path, it releases the thread's hold on it and applies the provider's normal retirement policy instead of removing it immediately. Unfinished environments (`creating`, `provisioning`, `error`) are still removed immediately, as before. Project checkouts (`retireGraceMs: null`) are therefore never removed by a cancel; a cancelled but finished worktree now waits out its grace period (default 5 minutes), as it would after archiving a thread. - `apps/server/src/services/threads/thread-environment-placement.ts`: the selection comparison that triggers a cancel is skipped once the held environment is `ready` (provider and host changes still trigger it), so a stored selection from the environment's original creator no longer counts as a changed request. - `plugins/environment-project-checkout/server.ts`: implements `experimental_existingPath` for branchless selections that name a path, so such spawns (the reporter's `--environment <path>`) reuse the recorded environment directly instead of creating a reservation and merging it. No wire, CLI, SDK, or schema changes. This deviates from the issue's suggested fixes: the stored selection is kept on the shared environment because it is used to rebuild the environment later. ## How you verified - New server tests in `provider-orchestration.test.ts`: the reporter's regression (adopted checkout with a previous selection), cancel after takeover under both `retireGraceMs: null` and a grace period, cancel while creation finishes, and retirement of a thread's own cancelled ready environment after its grace. Each fails with its source change reverted. - New plugin test for when the checkout provider reuses an existing path. - `pnpm exec turbo run test typecheck lint --filter='...[origin/main]'`: server, plugin, and dependents pass. Two server tests and six integration files timed out under parallel load; all pass when rerun in isolation (22/22 and 15/15). - Live, in isolated `pnpm start:worktree` instances of `origin/main` (4277998) and this branch, with identical scripted fixtures on scratch repos: after a `branch: new` thread is archived, a second thread on the same checkout fails with `Environment is destroyed` and the environment is `destroyed` on main (via both the composer-style no-input spawn and `--environment <path>`), while on this branch the second thread starts and the environment stays `ready`. A second thread sharing a checkout with a live thread starts normally on both. Fixes get-bb#4186 > AGENT GENERATED <!-- slopcop:rule=new-issue-autopilot run=run_8i9t9arxmueclxz1 sha=issue-4186 kind=summary --> --------- Co-authored-by: SawyerHood <sawyerjhood@gmail.com> Co-authored-by: Michael Yong <wrong92@gmail.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ng installed plugins (get-bb#4258) ## Human comments ## What was wrong Before bb's thread list and navigation became bundled plugins, `sidebar.threadListProvider` and `sidebar.navigationProvider` defaulted to `__automatic__`, which showed the first installed plugin for the slot (`resolveReplacement` → `registrations[0]`). Once the bundled `thread-list` and `navigation` plugins registered in the same slots, Automatic became a plugin-id ordering contest (`bb-sidebar` sorts before `thread-list` and wins, `zen-list` would lose). get-bb#4051 (shipped in 0.43.4) and get-bb#4118 (unreleased) resolved that by making the bundled plugins the defaults and reading `__automatic__` as them. Installations that relied on Automatic had no stored choice, so after upgrading they silently lost their community thread list (get-bb#4150, e.g. BB Sidebar), and installing a thread list or navigation plugin no longer had any visible effect. Navigation users (e.g. Compact Nav) would hit the same switch in the next release. ## What changed Automatic is the default again for both keys, redefined as "the first registered provider other than the bundled plugin, falling back to the bundled plugin": - `packages/domain/src/ui-preferences.ts`: both keys default to `__automatic__` and keep it; legacy `__builtin__` still resolves to the bundled plugin, and explicit choices are preserved. Reset returns to Automatic. - `apps/app/src/lib/plugin-replacement-preference.ts`: `resolvePreferredReplacement` takes an optional bundled provider key that Automatic skips unless it is the only registration (`findAutomaticReplacement`). Other exclusive slots (code/diff renderers, file openers) pass no key and behave as before. - `threadListProvider.ts` / `sidebarNavigationProvider.ts`: pass `thread-list/thread-list` and `navigation/navigation`. - Settings → Appearance: the Sidebar and Navigation pickers offer Automatic again and name the provider it currently resolves to. The header picker is unchanged (new slot, never shipped an Automatic default). - `SidebarNavigationRegion.tsx`: the loading skeleton records its height under the selected preference, which is what it already read from, so the Automatic default keeps its remembered height. - `packages/plugin-sdk/src/app-contract.ts`: the `experimental_threadList` and `experimental_sidebarNavigation` doc comments now describe the Automatic default, pinning, and placeholders (they described bundled-plugin defaults, a removed `Original` prop, and a fallback to bb's list). Plugin SDK bumped to 0.5.25 for the published doc change. - Docs: configuration, bb guide template, bb-cli skill reference, plugin-authoring reference, Plugin Guide surface, `docs/plugin-sidebar-thread-list.md` (which still described client-local `localStorage` selection), and the API audit entries. Deviation from the issue's proposed fixes: no migration or notice. Restoring Automatic with the bundled plugin as the fallback covers upgraded installs (an unset preference is Automatic again) and the issue's new-install case (installing a sidebar plugin takes effect), without per-install state. This reverses the opt-in default from get-bb#4051/get-bb#4118; the thread-list plan (`plans/thread-list-plugin.md`) originally intended to keep Automatic-first. No server/daemon wire change. ## How you verified - New tests fail under the old first-registration rule and pass now (checked by swapping in the old resolver): resolver prefers an installed replacement over the bundled one regardless of plugin-id order (`plugin-replacement-preference.test.ts`), thread list setting defaults to Automatic and names the installed plugin, navigation region renders an installed navigation plugin over the bundled one by default. Plugin ids deliberately sort after `thread-list`/`navigation`. - Updated server preference tests: `__automatic__` and explicit values kept, `__builtin__` → bundled, reset → `__automatic__`. - `pnpm exec turbo run typecheck lint --filter='...[origin/main]'` (123 tasks pass). - `pnpm exec turbo run test --filter=@bb/app -- plugin-replacement-preference SidebarThreadListSetting SidebarNavigationSetting SidebarNavigationRegion PluginThreadList SidebarHeader`, `--filter=@bb/server -- public-ui-preferences`, `--filter=@bb/domain`, `--filter=bb-plugin-plugin-api-docs`, `--filter=@bb/cli` (one unrelated `server-export` test failed once in the full run and passed on rerun). - Live QA with a local community fixture plugin (`zen-sidebar`, registering a thread list and a navigation provider; id sorts after `navigation`/`thread-list`), driven through the UI with headless Chromium and the source CLI: - PR HEAD (`pnpm start:worktree`, fresh data): unset preferences read `__automatic__`; after installing the fixture the sidebar renders Zen's list and navigation; Settings → Appearance shows "Automatic — Currently using Zen Sidebar from zen-sidebar" / "Zen Nav". Picking Thread list and Navigation in the pickers stores revision 1 explicit values, and they stay bundled after reload and after disabling/re-enabling the fixture. `bb settings ui reset` returns both keys to `__automatic__`. - Control at the merge base (same fixture, same steps): the fixture is enabled and running but the bundled list and navigation render; the pickers have no Automatic option; `bb settings ui set sidebar.threadListProvider __automatic__` is stored as `thread-list/thread-list`. - Published packages: `bb-app@0.43.3` with the fixture (thread-list-only variant, because 0.43.3's SDK lacks `experimental_useSidebarNavigation`) shows Zen; upgrading the same data dir to `bb-app@0.43.4` shows the bundled list while Zen stays enabled/running and `ui_preferences` has no row. This branch's `bb-app` build on a copy of that upgraded data dir shows Zen again. - Startup flash caveat: with the community bundle artificially delayed 2.5 s, Automatic shows the bundled list/navigation until the community plugin registers (~0.27 s → 2.6 s), then switches; a pinned community provider shows the loading placeholder instead. Without the delay no bundled frame was observed (40 ms sampling). 0.43.3's Automatic had the same pattern (built-in list for ~50 ms before the plugin). Fixes get-bb#4150 > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong The project checkout picker disabled New branch and Checkout when Git state blocked branch changes, but exposed the reason only in a title tooltip. That left the choices unexplained for touch, keyboard, and many pointer users. ## What changed The picker now displays a short, actionable explanation beneath the disabled choices for a dirty checkout, conflicts, an in-progress operation, detached HEAD, an empty repository, and unavailable checkout state. The existing server and host safeguards remain in place. No wire contract or CLI behavior changed. ## How you verified - `pnpm exec turbo run typecheck --filter=bb-plugin-environment-project-checkout` - `pnpm exec turbo run test --filter=bb-plugin-environment-project-checkout` (39 tests passed) - Opened the picker in a running source app with a synthetic dirty checkout and confirmed both options are disabled while the commit or stash guidance is visible. > AGENT GENERATED
## Human comments ## What was wrong The nonexpandable system-row header aligned its leading icon and title by baseline. This raised the icon relative to the title for “Stopped — connection to host was lost” and other short system operation rows. See get-bb#4282. ## What changed Center the icon and title in the shared static row wrapper, consistent with expandable timeline rows. Add an `IconAlignment` Ladle story with the reported title and adjacent compaction, context-clear, and parent-change variants. ## How you verified - Captured original and corrected rendering in the same Chromium viewport through the direct Ladle story. - Opened a disposable thread containing the host-loss event in a `pnpm start:worktree` production build at this PR head; the row renders after reload. - `pnpm exec turbo run test --filter=@bb/app -- --run src/components/thread/timeline/ThreadTimelineRows.system-summary.test.tsx` (8 passed). - `pnpm exec turbo run typecheck --filter=@bb/app` (passed). - `pnpm start:worktree --dryrun` (48 build tasks succeeded); the built server and daemon health endpoints pass. Fixes get-bb#4282 > AGENT GENERATED
…-bb#4276) ## Human comments ## What was wrong Host plugin builds resolved relative imports twice: the private-package validator called `build.resolve` for each source dependency edge, then esbuild resolved the same graph for bundling. These extra resolver round trips dominated preparation time even for a small plugin such as Keep Awake. ## What changed Validate package ownership against esbuild's actual input graph, retaining source checks for direct private BB imports. Relative and absolute imports erased from that graph, including type-only and dead-code imports, receive a separate validation-only pass. Failed validation preserves previously built artifacts and cleans up staging files. This PR contains only commit `5fa41fbffa`; subsequent CSS and scanner optimizations are excluded. ## How you verified - Ran builder build, typecheck, and tests through Turbo: 160 tests passed, one skipped. - Added coverage for erased imports, re-exports, dynamic imports, require calls, JSON, symlinks, aliases, transitive private imports, unresolved type-only imports, and output preservation. - Eight alternating fresh-process Keep Awake builds: median preparation time fell from 330.1 ms to 203.5 ms (38.4%). - Full startup comparisons with empty Turbo caches, warmed toolchains, and unchanged concurrency: 8.62 → 6.77 seconds and 7.89 → 6.79 seconds. The first baseline also had a slower app build; the second pair supports roughly 1.1 seconds saved overall. > AGENT GENERATED
…b#4270) ## Human comments ## What was wrong Claude Code emits one SDK `assistant` message per content block, so when a single API response contains more than one thinking block, each finalized block arrives as its own message with the block at local index 0. The translator keyed reasoning streams by content index: stream deltas used the API index (`thinking-1` for the second block) but the finalizing `item.textClose` used the local index (`thinking-0`). The second block's streamed item never received a close, so it stayed in progress forever, and a duplicate completed reasoning item was created alongside it. This depends on the model, not on the CLI or SDK version. Across local transcripts, `claude-opus-5` produced multiple thinking blocks in ~0% of responses, while `claude-fable-5`/`fable-5-1` did in 2–21% and `claude-opus-5-5` in ~13% (always `thinking > thinking > tool_use`). Almost every never-completed Claude reasoning item in a local `bb.db` since mid-August follows this mis-keyed pattern, with a sharp jump after moving to Opus 5.5 (70 on 2026-09-23). ## What changed - `plugins/provider-claude-code/src/delta-translation.ts`: thinking streams and finalizations share a single `thinking` key, matching how assistant text is already keyed. Blocks are finalized in order before the next one streams, so each block opens and closes its own item. - `plugins/provider-claude-code/src/sdk-extraction.ts`: drop the now-unused `contentIndex` from the stream/thinking extraction helpers; they return plain strings. No wire change: the stream key lives only in the in-memory translator/assembler state, and emitted events keep the same shape. No `HOST_DAEMON_PROTOCOL_VERSION` or Plugin SDK change. Already-orphaned reasoning items in existing threads are not backfilled. ## How you verified - New test `finalizes each thinking block of a response on its own assistant message` streams two thinking blocks (index 0 and 1), each finalized by a single-block assistant message, and asserts start/complete pairs with matching IDs. It fails before the fix and passes after. - `pnpm exec turbo run test typecheck lint --filter=bb-plugin-provider-claude-code`: 366 tests pass, typecheck and lint clean. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
) ## Human comments ## What was wrong On phones, the sidebar collapse caret on a parent thread row is a 20×20 px button, and since get-bb#4077 and get-bb#4098 every other point of the row opens the thread. get-bb#4077 widened the parent row's navigation link to fill the title area around the caret, and get-bb#4098 forwarded taps on empty row space and the status area to that link. So a tap that just misses the caret now opens the thread and closes the compact sidebar. Other row controls grow to 36×36 px on touch screens; the caret never did. get-bb#3195 reported the same 20×20 target for this control, but its fix (get-bb#3199) only enlarged the caret in mobile Recents. ## What changed `plugins/thread-list/app/rows/SidebarChildToggleChevron.tsx` gives the caret an invisible `::after` hit area on coarse-pointer viewports below `md`: 36×36 px, extending 8 px up and down, 6 px left (exactly the gap to the title, so title taps still navigate), and 10 px right. The button, icon, and hover/pressed background keep their 20 px size, so nothing looks different. Desktop and fine-pointer layouts are unchanged. The shared component also serves environment-group rows and nested section rows. On environment rows, the neighbouring action buttons come later in the DOM at the same z-index, so they still win their own taps. No wire, CLI, SDK, or config changes. ## How you verified - `pnpm exec turbo run typecheck lint --filter=bb-plugin-thread-list`: pass (0 errors; 7 existing warnings). - Production `pnpm start:worktree` at this head, with before measured on the same build by hiding only the new `::after`, which is the entire CSS change. - **iOS Safari 26.5, iPhone 17 Pro Max simulator** (safaridriver, W3C touch actions, 3 trials per point): - Parent thread row: taps 4 px left, 6 px right, and 6 px below the caret opened the thread 3/3 before; now they collapse 3/3. Caret center collapses 3/3 both ways. Title center and trailing status still open the thread 3/3. - Environment-group row: caret and near-miss taps collapse 3/3 both before and after. "New thread in environment" and "Environment actions", at their centers and 2 px inside their left edges, still navigate or open their menu 3/3. - **Chrome 390×844 with touch emulation** (CDP touch events, 10 px radius): the same matrix gave the same outcomes. `elementFromPoint` confirms the hit area is 36×36 and starts at the title's right edge. - Screenshots of the sidebar with and without the hit area are pixel-identical. At 1280 px the `::after` is not generated. Related: get-bb#3195, get-bb#4077, get-bb#4098, get-bb#3989. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong The shared Settings provider menu listed registered slots in registration order. Bundled Navigation and Thread list slots looked like third-party entries, so the community choices could appear below them and their descriptions gave no source cue. Automatic already prefers a third-party slot when one is installed (related: get-bb#4258). ## What changed Order menu entries as Automatic, third-party slots, bundled slots, then the owner-built-in option where available. Label bundled slots as built-in BB defaults, identify third-party plugin descriptions, and describe what Automatic chooses. Provider resolution, saved keys, plugin APIs, CLI, and server/daemon wire contracts are unchanged. ## How you verified - `pnpm exec turbo run test --filter=@bb/app -- --run src/components/settings/SidebarNavigationSetting.test.tsx src/components/settings/SidebarThreadListSetting.test.tsx src/components/settings/CodeRendererSettings.test.tsx` (5 passed) - `pnpm exec turbo run typecheck --filter=@bb/app` (passed) - `git diff --check` (passed) > AGENT GENERATED
## Human comments ## What was wrong `matthiasvongrundherr` was absent from the approved contributors list, so the PR gate would close their pull requests. ## What changed Added `matthiasvongrundherr` to `.github/APPROVED_CONTRIBUTORS`. Approval takes effect when this change lands on the base branch. ## How you verified Verified the GitHub login with `gh api`, confirmed the login appears exactly once, reviewed the single-line diff, and ran `git diff --check`. CI is intentionally skipped for this data-only change. > AGENT GENERATED
…eted event (get-bb#4244) ## Human comments ## What was wrong The connect plugin persists shared ports per host, but nothing ever pruned shares whose host was destroyed. Core never told plugins a host was removed, so such shares stayed forever unless someone ran `bb connect unexpose` by hand. This keeps happening with Modal sandboxes, which are created and destroyed often. It was also worse than clutter: `ShareRegistry.declareMachineShares` re-declares ports for every machine host on each tunnel share activation, and the server's `declareSharedPorts` throws `cannot declare shared ports for unknown host <id>` for a destroyed host. That error failed the whole activation, so `TunnelManager.activateShares` rescheduled itself every 5s indefinitely and never reached `shares.list()` / `publish()` for the remaining valid shares. On a real instance with one orphaned Modal-sandbox share, this logged two warnings every 5s (230 pairs in 20 minutes). ## What changed **New plugin event `experimental_host.deleted`** (`packages/plugin-sdk/src/backend-contract.ts`) - Delivers `{ host }` once after a machine is removed. It fires from `DELETE /hosts/:id` for manually added machines (`apps/server/src/routes/hosts.ts`), and after a machine provider finishes removal (`apps/server/src/services/machines/provider-orchestration.ts`). Those are the only two places that set `destroyedAt`. - `host` is the public `Host` DTO at removal time, with status `disconnected`, because `bb.sdk.hosts.get` returns 404 for the host afterwards. - Plumbed through the existing thread-event emitter, like `experimental_terminal.input`: `plugin-thread-events.ts`, `PluginThreadEventEmitter`, the plugin-service implementation, and the handler maps in `plugin-api.ts` and the fake plugin host. - `@bb/db` now exports `HostRow`, replacing a local alias in `entity-lookup.ts`, and `toHostRecord` is exported so the plugin service can build the DTO. **Connect prunes removed hosts' shares** (`plugins/connect/src/shares.ts`, `server.ts`) - New `ShareRegistry.pruneHost(hostId)` removes every share for that host, clears its declaration bookkeeping and snapshot entries, persists the change, and publishes the updated status. If persistence fails, it restores the removed in-memory shares so the next activation can retry pruning. - Pruning happens on `experimental_host.deleted`. - It also happens at activation when declaring a host fails and `hosts.get` confirms the host is gone (404). This covers hosts removed while the plugin wasn't loaded or before this change. Declaration failures on hosts that still exist are still logged and rethrown, so retry behaviour for transient errors is unchanged. Merely disconnected hosts are never affected, since the server only rejects declarations for missing or destroyed hosts. **Docs and version** - Plugin SDK bumped 0.5.24 → 0.5.26; main has since reached 0.5.25, so 0.5.26 keeps the new event on a distinct SDK version. - New `docs/api_to_audit.md` entry with stabilization criteria. - Plugin Guide surface bullet in `plugins/plugin-api-docs/src/surfaces.ts`. - `backend-events.md` authoring reference. - Payload-field map in `plugin-authoring-docs.test.ts`. No daemon wire changes. ## How you verified Each new test fails without its piece of the fix and passes with it: - `public-host-management.test.ts`: `DELETE /hosts/:id` announces the destroyed host row once. - `provider-orchestration.test.ts`: provider machine removal announces it once, even when a second sweep runs. - `connect.test.ts`: `experimental_host.deleted` prunes all of that machine's shares and keeps others, both in storage and in `listShares`. - `connect.test.ts`: activation prunes a removed host's share without logging, and still rejects on a live host's declaration failure. - `connect.test.ts`: a failed KV write leaves the removed host's share retryable; the next activation prunes it when storage recovers. Commands run: - `node .github/workflows/check-plugin-sdk-version.mjs`: passes with the 0.5.26 bump. - `pnpm exec turbo run test typecheck --filter=@get-bb/plugin-sdk --filter=bb-plugin-connect`: 358 SDK and 112 Connect tests pass; both typechecks pass. - `pnpm exec turbo run test --filter=@bb/db`: 593 database tests pass. - GitHub CI on the merged branch: all checks pass. The packages job passed on its third attempt after two attempts hit five-second timeouts in different database migration tests under full CI load. - `pnpm exec turbo run test typecheck --filter=bb-plugin-connect`: 112 tests pass; typecheck passes after the retry fix. - `pnpm exec turbo run typecheck lint --filter='...[origin/main]'`: 124/124 tasks pass. - `pnpm exec turbo run test --filter='...[origin/main]' --filter='!@bb/desktop' --continue`: 82/84 tasks pass. - Three tests failed under full parallel load and all pass when re-run in isolation: `install-machine-script` and `builtin-plugins` hot-reload in server, `CommandPalette` in app. - `@bb/desktop` was excluded because this worktree's Electron install is broken ("Electron failed to install correctly"). That is unrelated to this change. - `node .github/workflows/check-plugin-sdk-version.mjs`: "surface changed … with a version bump". > AGENT GENERATED --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong `idrevnii` was absent from the contributor allowlist, so the PR gate would close their pull requests. ## What changed Added `idrevnii` to `.github/APPROVED_CONTRIBUTORS`. ## How you verified Confirmed the GitHub login, verified it appears exactly once, reviewed the one-line diff, and ran `git diff --check`. CI is skipped for this data-only change. > AGENT GENERATED
## Human comments ## What was wrong `trotterdylan` was absent from the approved contributor list, so the PR gate would close their pull requests. ## What changed Added `trotterdylan` to `.github/APPROVED_CONTRIBUTORS`. ## How you verified Verified the GitHub account through the GitHub API, confirmed the login appears exactly once, reviewed the single-line diff, and ran `git diff --check`. CI is skipped for this data-only change. SlopCop review is skipped. > AGENT GENERATED
## Human comments ## What was wrong The timeline virtualizer treated large automatic or user scroll jumps as fast traversal and unmounted visible row contents until a 300 ms idle timeout. Crossing the virtualization threshold also swapped rendering component trees, remounting existing rows. ## What changed Keep visible virtualized row bodies realized during scrolling, with one stable implementation/wrapper across the threshold. Retain normal rows until the virtualizer has a usable range, capture heights before activation, and retry nested scroll-root discovery. Scroll-anchor lookup recognizes the stable wrapper in both modes. Offscreen rows still unmount. ## How you verified - 127 focused tests passed across 18 files: timeline windowing, threshold DOM/input-state preservation, programmatic scrolling, height, unread divider, row isolation, and scroll anchoring. App typecheck, optimized build, and bundle gate passed. - Persistent optimized `pnpm start:worktree` builds: [control at comparison base 3bcb0ec](https://ymichael--22271.getbb.app/threads/thr_4qfn7ie4py) and [branch at exact head 4932c1d](https://ymichael--24410.getbb.app/threads/thr_fxq22473h7). - Real-product repro: expand the completed 24-sample turn, then scroll up 800 px at 1280×900. Control blanked up to nine visible rows over 18 sampled frames (~300 ms); branch had no placeholder frames during the equivalent scroll. Both histories contain actual Codex commentary and shell results. - Spontaneous streaming blanking was **not** reproduced end to end. Natural streaming observation windows had no placeholder frames on either build. The control used Codex 6-Luna and branch 6-Sol, so cadence/content are not identical. - Supplementary component fixture: simulated bottom-follow during eight-row bursts reproduces blanking before the fix; three fixed bursts had zero blank frames. This is not proof of real-product streaming behavior. - Full screenshots, measurement scripts, provenance, and limitations are in HANDOFF.md in the originating BB thread storage, not committed to this PR. > AGENT GENERATED
## Human comments ## What was wrong When bb stops bundling a built-in plugin, its installed row stays. Startup (`reconcileBundled`) only walks plugins that are currently bundled: it installs new ones and never looks at rows whose built-in is gone. After get-bb#4232 reverted get-bb#4229/get-bb#4230, data dirs that had run those builds kept `bb-ai` and `bb-account` forever, showing as "Missing: Plugin files are missing. Update or reinstall bb." The delete route refuses built-ins, so users couldn't remove them either. get-bb#4240 made the manual uninstall possible; this makes cleaning them up by hand unnecessary. ## What changed `apps/server/src/services/plugins/plugin-service.ts`: - After bundled reconciliation and before `loadAll()`, startup deletes every installed built-in whose name is no longer in this build's registry. It uses the existing orphan check behind `isOrphanedBuiltin`, now a shared `isOrphanedBuiltinRow` helper. - It also deletes the plugin's settings, secrets and schedules through the same helper manual removal uses (`deleteRemovedPluginData`, extracted from `remove()`). That includes `onPluginUnregistered`, which re-dispatches queued messages the plugin was holding. - The row is deleted outright rather than tombstoned, so a plugin that is bundled again later installs fresh. - Startup deletes the row only after settings, schedules, and secret cleanup succeeds. If secret removal fails, the row remains so the next startup retries cleanup. - Rows a user already uninstalled (tombstoned) are left alone, because `listInstalledPlugins` skips them. - Each removal is logged: `plugin <id> removed because bb no longer bundles builtin:<name>; its settings, secrets, and schedules were deleted`. - The check compares against the static registry, not what loaded, so a broken or partial build can't delete bundled plugins. No wire or daemon protocol changes. No CLI or UI changes. ## How you verified - `builtin-plugins.test.ts`: the old "marks a persisted builtin as orphaned" test is replaced by "removes a builtin and its data once bb no longer bundles it". It seeds a setting, a schedule and a secret file, restarts without the built-in, and asserts the row, settings, schedule and secrets directory are gone and the list is empty. It then re-bundles and asserts a fresh, enabled install. It fails with the startup call removed and passes with it. - `builtin-plugins.test.ts` also simulates one secret removal failure, confirms the registration remains, then confirms a second startup removes the secret and row. - `pnpm exec turbo run test --filter=@bb/server -- --run test/services/plugins/builtin-plugins.test.ts`: 35/35. - `pnpm exec turbo run typecheck --filter=@bb/server`: clean. - `pnpm exec turbo run typecheck lint --filter=@bb/server`: clean. - `turbo run test --filter=@bb/server -- test/services/plugins`: 521/521. - Live, in a disposable `pnpm start:worktree` instance whose data dir really went through get-bb#4230 then the revert, so it had `bb-ai` and `bb-account` rows with their folders missing, plus one seeded `bb-ai` setting. After restarting on this branch, both rows and the setting were gone, the API listed no orphans, `bb-guide` was unchanged, and the server log had both removal lines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) > AGENT GENERATED --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Saved servers take an optional name in the existing Add Server and Set Server URL dialogs, shown in Window → Server instead of the host. A Show This Mac checkbox hides the built-in entry while another server is selected. Set Server URL edits a server in place, keeping its position and name. The quick palette lists the other servers through optional BbDesktopApi members that expose names and opaque ids, not URLs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Names and the Show This Mac choice move to server-menu.json, so server-target.json keeps the exact format released builds read and write. Parsing now ignores unknown fields instead of discarding the whole file, which a later save then overwrote. A missing or corrupt server-menu.json falls back to defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Set Server URL with an unchanged URL now only renames the server instead of selecting it and reloading every window. Clearing the current server while This Mac is hidden falls back to the next saved server. Editing a URL onto another saved server keeps that server's name. Saves are serialized so overlapping writes cannot leave an older state on disk, one malformed name no longer drops the others, and loaded names use the dialog's whitespace rules. The menu refreshes as soon as the dialog saves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
idrevnii
force-pushed
the
bb/desktop-server-manager
branch
from
September 24, 2026 20:58
42f5e38 to
67952f4
Compare
Owner
Author
|
Superseded by get-bb#4296. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human comments
What was wrong
Window → Server names saved servers only by host (
10.0.0.3:38886) and always lists This Mac. The rendered app cannot reach that list at all:BbDesktopApiexposes no server targets (get-bb#3133). That works for one extra server. It breaks down for people who deliberately run several independent bb instances, each with its own users, projects, and plugins, and switch between them in one desktop app; for example a personal server, one shared with a friend, and a homelab "ops" server. Those instances are separate on purpose, so moving them onto machines of a single server (the reason get-bb#628 gave for removing the earlier multi-server UI from get-bb#605) is not a substitute. Separately,server-target.jsonis parsed with a strict schema: a file containing any field the running build does not know loads as "no saved servers", and the next save (for example Add Server…) overwrites it. Moving between desktop builds, in either direction, can therefore silently erase the saved list.What changed
This keeps server targeting in the native menu, the direction set by get-bb#628 and get-bb#3919, and adds no renderer management UI.
Desktop shell (
apps/desktop):server-url-dialog*.ts: the existing Add Server… and Set Server URL… dialogs get an optional Name field, using the same markup and CSS as the URL field.server-target.ts:server-menu.json.server-target.jsonkeeps exactly the format released builds write, so older and newer builds read each other's files.server-menu.jsononly resets names and the This Mac choice.menu.ts,server-list.ts:preload.ts,packages/desktop-contract/src/servers.ts: optionalgetServerTargets,onServerTargetsChange, andselectServerTargetmembers onBbDesktopApi, as Expose the Desktop server selector in the sidebar header get-bb/bb#3133 proposes.setActiveServerTargetpath as the menu.App (
apps/app):palette-server-actions.ts,useDesktopServerTargets.ts).Other surfaces:
apps/desktop/README.md(Saved servers) and the verify-bb desktop recipe.HOST_DAEMON_PROTOCOL_VERSIONbump. No plugin API change.dataDirectory.openin Add a quick palette command to open the data directory get-bb/bb#4256), and the CLI already targets any server throughBB_SERVER_URL.Deviation from get-bb#3133: it asks for a sidebar selector. This PR adds the bridge it describes but switches from the quick palette only, keeping app chrome unchanged; a sidebar control could build on the same members. Related: get-bb#1451 (multiple desktop servers). The This Mac checkbox also covers the "hide This Mac from the Server menu" part of get-bb#3168, though not its request to stop starting the bundled runtime.
Palette entries need both a desktop build and a served app that include this change; older desktops simply omit the optional bridge members.
How you verified
pnpm exec turbo run typecheck lint --filter=@bb/app --filter=@bb/desktop --filter=@bb/desktop-contractpnpm exec turbo run test --filter=@bb/desktop --filter=@bb/desktop-contract: 444 + 22 passed, 1 skipped (existing).pnpm exec turbo run test --filter=@bb/app: 5076 passed, 5 skipped (existing).main'sserver-target.tswith a real file:[]/builtin;server-target.test.ts:server-target.jsonpasses a copy of the released strict schema;server-menu.jsonleaves the server list intact.server-list.test.ts: names and hosts, This Mac visibility rules, opaque ids.menu.test.ts: the Show This Mac checkbox.palette-server-actions.test.tsandCommandPalette.test.tsx: switching from the palette.pnpm start:worktree, with two saved servers pointing at it through127.0.0.1andlocalhost, driven through CDP.Refs get-bb#3133, get-bb#1451, get-bb#3168
🤖 Generated with Claude Code