Repository navigation
Merge upstream desktop-v0.47.1 into fork - #296
Merged
Merged
Conversation
## Human comments ## What was wrong Thread rename used viewport width to choose between the dialog and inline editor, so wide touch devices such as landscape iPads received the inline editor. Its deferred focus could select the title without opening the software keyboard. Header double-click always started inline editing, including on coarse-pointer devices. ## What changed Header menu and shortcut rename now use the dialog when the viewport is compact or the primary pointer is coarse. Title double-click uses the dialog for coarse pointers and stays inline for fine pointers at either width. The shared sidebar rename hook follows the same pointer policy, including quick actions. | Pointer | Viewport | Menu/shortcut | Title double-click | | --- | --- | --- | --- | | Fine | Compact | Dialog | Inline | | Fine | Wide | Inline | Inline | | Coarse | Either | Dialog | Dialog | The plugin registry entry is regenerated from the shared component. The dialog retains its existing tap-to-focus behavior on touch devices. No server/daemon wire, CLI, or SDK changes. ## How you verified - New presentation regression cases failed before the fix and pass afterward. - 207 relevant tests passed through Turbo: header/menu (73), sidebar rename/thread row (103), project row (31). - `pnpm exec turbo run test typecheck --filter=@bb/plugin-registry` passed. - `pnpm exec turbo run lint typecheck --filter=@bb/app --filter=@bb/shared-ui --filter=bb-plugin-thread-list` passed with existing lint warnings. - iPad mini simulator, iOS 26.5 Safari, landscape: menu rename and title double-tap open the dialog; tapping its input opens the keyboard; saving persists the new title, confirmed through the server API. - Chromium at 600px with a fine/non-coarse pointer: mouse double-click opens the inline header editor. - `pnpm start:worktree --dryrun` built successfully; the production-style worktree server renders the verification thread. - Native TestFlight shell and an Android device were not tested. Fixes get-bb#6110 > AGENT GENERATED
## Human comments
## What was wrong
Moving bb's composer into a bundled plugin (the stacked PR) needs a few
core changes that have nothing to do with plugins. The thread-link
parser was buried in the paste code. The composer handle carried four
loose voice methods. Split panes and the phone home screen reached into
the composer's DOM to focus it and size it. And the command menu still
had "load more" code that could never run, because
`useCommandSuggestions` always returned `hasMore: false`. Landing these
first keeps the composer PR down to the plugin move.
## What changed
Five commits, no intended behaviour change:
1. **Remove the composer's unused header option.** No caller sets it.
Also formats four files main had left unformatted.
2. **Remove the composer's command list paging that never pages.** Drops
`hasMore`/`isLoadingMore`/`loadMore` from the hook and the typeahead
config, along with the prefetch effect, the ArrowDown hold at the last
command, the menu's scroll handler and `mention-menu-scroll`.
3. **Parse thread links in `lib/thread-links`.** `parseThreadLink` sits
next to the batched resolver, and the paste code takes a parser function
instead of an origin. A link without a project names the personal
project instead of `null`, which matches the same threads as before.
4. **Group the prompt box handle's voice methods** into
`voice.{textBeforeCursor, deliverTranscript, playCompletionTransition}`.
5. **Stop pane focus and the compact home from reaching into composer
DOM.** The composer registers its root for pane focus,
`focusPaneComposer` reports whether a pane's composer took focus, and
the compact home passes the available height through
`ComposerMaxHeightContext` for the composer to cap its own editor. The
formula is the same as before.
## How you verified
- `pnpm exec turbo run typecheck lint --filter='...[origin/main]'` and
the affected `@bb/app` tests at each commit; `@bb/app` and
`@bb/client-core` suites on the head.
- Live, on a `start:worktree` server running the stack:
- a pasted thread link becomes a pill;
- the slash menu wraps from the last command (73rd ArrowDown) back to
the first;
- "Focus previous/next chat pane" puts the caret in that pane's
composer;
- on a 390×380 phone viewport the composer's available height (208px)
caps the editor and the composer sits at the chrome row (top 56px).
> AGENT GENERATED
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Human comments ## What was wrong The automations plugin already stored why a run failed (`automation_runs.error`) and why an automation stopped (`automations.last_error`), but the app rendered neither for agent automations: `RunRow` showed `run.error` only for script runs, and only `bb automation show` read `lastError`. A run refused before it reached a thread, for example because its target thread had been archived, therefore showed a red ✕ and `0.0s` with no thread to open, while bb silently paused the automation. The stored text was also vague (`Target thread … is unavailable: missing, deleted, archived, or not runnable`) although the plugin had already read which one applied, and other automations on the same thread were paused with `target thread deleted` even when it had been archived. ## What changed - **Run rows** (`detail-view.tsx`): a failed run shows its `error` in the existing detail block for every run mode, not only scripts. Agent runs that reached a thread stay clickable; that thread is the full log. - **Automation header and list row** (`detail-view.tsx`, `overview-view.tsx`, `metadata.tsx`): an automation with a `lastError` shows it under the detail header and at the end of the list row's metadata. The list's red ✕ follows the same rule, so it never appears without its reason: it clears on Resume, which already cleared `lastError`, and hides while a run is in flight. - **Precise target-thread reasons** (`src/run.ts`, `src/data.ts`): a refused target thread records `Target thread <id> was archived`, `was deleted`, `no longer exists`, or `is <status>`, and every automation paused on that thread gets the same message. The `thread.deleted` handler records `Target thread <id> was deleted`. The `WHERE last_error = 'target thread deleted'` marker query became `UPDATE … RETURNING`. - **Stories**: a paused-by-failure list row and a `Failure States` detail story. - Error text stays free text rendered verbatim; nothing parses it. No wire, schema, or CLI flag changes. Errors stored before this change keep their old wording. ## How you verified - `pnpm exec turbo run typecheck lint test --filter=bb-plugin-automations`: 141 tests pass; lint reports 0 errors (4 pre-existing `set-state-in-effect` warnings in untouched code). - The new and updated tests fail against `main`'s implementation (6 failed, 135 passed) and pass on this branch: archived and missing targets name their cause on the run and on both automations, the list ✕ appears only with its reason (including during a retry and after Resume), and the detail header and a failed agent run show the error. - The screens below render the automations Ladle stories with `main`'s components (before) and this branch's (after). Dark theme and 390px also checked. Not exercised in a full dev app. - Found on a real install: two scheduled automations pointed at threads archived the evening before; each next run failed in under 40 ms, bb paused both, and the app showed only a red ✕. Before: `main` at `b143a2286` · After: `1a1d53ad8` · Ladle stories, light theme, 2× | Screen / state | Viewport | Before | After | | --- | --- | --- | --- | | Automation detail · paused after its target thread was archived | Desktop · 1100 wide |  |  | | Installed list · one failing, one paused by a failure | Desktop · 1100 wide |  |  | | Installed list · same rows | Compact · 390 wide |  |  | Refs get-bb#4166: this covers its visible reason and the vague wording; recovering a target thread left in `error` stays open there. > AGENT GENERATED
…t-bb#6064) ## Human comments ## What was wrong The connect-next gate-restart end-to-end test required platform-dependent socket error wording. [CI failed](https://github.com/get-bb/bb/actions/runs/39644649159/job/119377531583) after the tunnel correctly reached `offline` because frpc reported `connection reset by peer`, which the assertion did not accept. ## What changed Require a nonempty error string when the gate disappears instead of matching a list of operating-system error messages. The test still verifies the offline transition, reconnection with the same frpc process, and successful requests after recovery. Only the test changes. ## How you verified - Confirmed the exact assertion failure in the linked CI logs. - The original assertion passed five local runs; the intermittent socket reset was not reproduced locally. - `pnpm exec turbo run test typecheck lint --filter=bb-plugin-bb--connect-next` passed, including all 28 plugin tests. - `pnpm exec oxfmt --check plugins/connect-next/src/frp-e2e.test.ts` and `git diff --check` passed. > AGENT GENERATED --------- Co-authored-by: Michael Yong <wrong92@gmail.com>
## Human comments ## What was wrong The thread Info tab was a hard-coded list of core rows and sections in `ThreadMetadataContent`, so plugins had no way to add to it. Automations that target a thread (get-bb#5554) could only be seen in the global Automations view. An earlier attempt (local commit 11ec452c81) gave plugins a data hook that only core rendered from, and core did not use it itself. ## What changed **Two slots, used by core and plugins alike** - `app.slots.experimental_threadInfoRow` adds a row to the details block, and `app.slots.experimental_threadInfoSection` adds a section below it. - A registration is `{ id, group, order?, component }`. The component receives `{ threadId, projectId }` and mounts once per visible thread. - `ThreadMetadataContent` now renders only through `ThreadInfoRows` / `ThreadInfoSections` (`apps/app/src/components/secondary-panel/info/ThreadInfoSlots.tsx`). These merge `CORE_THREAD_INFO_ROWS` and `CORE_THREAD_INFO_SECTIONS` with plugin registrations. - Placement works like thread actions: sort by `group`, then `order` within it (unset last), then registration order, with core first on a tie. The comparator thread actions used moved to `apps/app/src/lib/grouped-placement.ts`, and both use it. - bb's groups are exported as `experimental_THREAD_INFO_ROW_GROUPS` (thread, environment, git, pullRequest, lifecycle) and `experimental_THREAD_INFO_SECTION_GROUPS` (related, changes, storage). Core uses orders 10, 20, 30 within each group and keeps today's order. - Plugin entries mount in a per-thread crash boundary. A crash removes only that row or section. **Shared building blocks** - `experimental_InfoSection` (heading, count pill, collapse), `experimental_DetailRow`, `experimental_InfoList` (the "N more" list) and `experimental_InfoListRow` are the components core draws with. - `DetailRow` (`{ label, icon?, align?, children }`) is now bb's only label/value row: every core Info row and the commit dialog draw with it, and it is `experimental_DetailRow`. The old variant with class overrides is deleted. Branch and Archived truncate through an inner span, and the commit dialog's vertical changed-files row draws its own markup. - Section collapse now comes from the slot. Core ids are unchanged (`commits`, `forks`, …), so saved collapse state carries over. Plugin sections are stored as `<pluginId>/<id>` in `infoPanel.collapsedSections`. - Section separators moved from `divide-y` on direct children to each `[data-info-section]`, so plugin wrappers do not break them. **Automations (first plugin caller, get-bb#5554)** - A section in the `related` group, ahead of Child threads, lists automations whose `execution.targetThreadId` is the open thread. It shows 3 rows with "N more", status labels from the overview, and refreshes on any automations realtime signal. Selecting a row opens the automation. - `automations_list` and `bb automation list --target-thread <id>` take the target-thread filter. This is the server, CLI and test part of 11ec452c81. The skill documents it. **Docs and API process** - `docs/api_to_audit.md` entry with core callers and audit items. - New Plugin Guide card "Thread Info rows and sections", with an Info tab fixture in the side panel wireframe. - Authoring references and API indexes updated. - Plugin SDK 0.6.69 → 0.6.70. **Not moved through the slots** - The empty/loading decision (`hasAnyThreadMetadata` plus the forks query in `ThreadDetailSecondaryContent`) still decides from core state whether the tab shows details, a skeleton, or "No thread details available." Plugin entries do not count toward it. This is audit item 3. No server/daemon wire changes. ## How you verified **Tests** - `ThreadInfoSlots.test.tsx` covers: - plugin rows and sections placed among core's by order, with unordered ones last; - a crashing plugin row or section dropped while core stays; - per-registration collapse, where a plugin section that reuses a core id does not collapse the core section. - `thread-info-section.test.tsx` (automations) covers the target-thread RPC, realtime refresh from another project, status labels, and navigation. - Server harness test for `automations_list` and the CLI `--target-thread` filter. - Guide fixture, order, and mobile tests. - `turbo run test typecheck lint` for `@get-bb/plugin-sdk`, `@bb/app`, `bb-plugin-plugin-api-docs`, `bb-plugin-automations` and `@bb/domain`: 20/20 tasks. - `turbo run typecheck lint --filter='...[origin/main]'`: 142/142 tasks. - `@bb/server` `test/services/plugins`: 551 tests passed. - `check-plugin-sdk-version.mjs` passes. **`DetailRow` consolidation:** Info panels at 589 and 339 px wide (long branch names truncating, an archived thread, a PR) and the commit dialog were pixel-compared against main. The dialog and five of eight panels are identical; the rest differ only by an animated glyph, a ticking relative time, and one anti-aliased pixel. **Parity with main in the real app** - Built `apps/app` at main (cc292b9) and at this branch, and served each through a proxy in front of the live bb, so both builds showed the same real threads. - Captured the Info panel at 1500×2200 for five threads covering parent/child, PR, commits, uncommitted files, a detached checkout and thread storage, then pixel-diffed the panels. - Two threads are identical, and so is a third once its slow workspace status (87 ahead / 198 behind) had loaded. - The other two differ only in the animated status glyphs of running child threads. - Collapsing "Child threads" on the branch build showed it collapsed on the main build, so the preference key is unchanged. It was restored afterwards. **Automations in a dev instance (`pnpm start:worktree`)** - Created five automations that target a thread. The Info tab showed "Automations 5" below the details with three rows and "2 more", in the same style as core sections. - Expand, collapse and re-expand work. - Pausing and deleting from the CLI updated the section without a reload. - Selecting a row opened `/plugins/automations/automations/<project>/<id>`. - The Guide card renders with badge 13 over the Info tab. Fixes get-bb#5554 > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ping (get-bb#5951) ## Human comments ## What was wrong The timeline's scroll position had about ten independent owners, and each one corrected scrollTop on its own: - `AutoHeightContainer`, which applied its height a frame late; - TanStack virtual's own corrections; - CSS `overflow-anchor`, which Chrome applies and Safari does not; - the bottom-anchored body's intent timers, settle tail, restore attempt loop, width-change restore and shrink re-attach; - the content hold timer; - the detail scroller's stick and smooth-follow logic; - older-page loading. When two of them disagreed in a frame, the reader saw a jump. Examples: - Android Chrome snapped to the bottom on scroll-up when the browser clamped to a stale height. - The view jumped when the first older page switched windowing on. - Restores landed off target after a thread switch or rotation. - Blank frames appeared after Home, End or a scrollbar jump. - The bottom moved when a row was expanded while following the bottom. Attach and detach were inferred from scrollTop, so a layout change could flip them. ## What changed - **`ScrollPositionKeeper` is the one owner of the reading position.** It stores the position as explicit state: attached to the bottom, or a row key plus its offset from the top of the viewport. - Every layout change is reconciled to that position before paint. The keeper hears about them through ResizeObservers on the root, the content and each mounted row, and through each windowed list's commit. - Targets are computed from the stored position, not from relative deltas. - A scroll counts as the reader's only during an input session: wheel, touch and momentum, scroll keys, or a pointer held on the scroller. Any other scroll, such as a clamp, is reconciled back. - Attach and detach follow the reader's scroll direction. Layout changes never flip them. - **Windowed lists render rows in normal flow between measured spacers.** The window is keyed to a row instead of scrollTop, and the reading row is always mounted. Coverage uses the heights the spacers were rendered with, and an estimate never moves the view. - **Thread switches and resizes restore through the same position.** The controller keeps the loaded pages of the 8 most recent threads, so the saved row is still there on return. - **Message overflow ("Show more") is measured before first paint,** so offscreen rows keep a stable size. - **iOS WebKit keeps windowing off, as on main.** A WKWebView race remains, described under Known residuals, and windowing stays off there until it is fixed. - **Sending on touch devices keeps the reader attached.** The send button submits on pointerup and is then replaced, so the tap's touchend never reaches the scroller. A touch now also ends on the last touch pointer's pointerup (window, capture phase), and an app-initiated scroll to the end ends the reader's input session. - **User-visible differences from main:** - **Timeline height.** The timeline's own height no longer eases when content grows. Previously this only happened on desktop Chrome and Firefox, in idle threads. Row expand and collapse animations are unchanged. - **Detail panes.** Nested tool-output and terminal panes jump to their bottom while following streamed output instead of smooth-scrolling. - **No re-attach on shrink.** A detached reader is no longer re-attached when content shrinks onto the bottom; they re-attach by scrolling down to the end. - **Deleted:** - `AutoHeightContainer` and its snap plumbing; - TanStack virtual in the timeline; - CSS bottom scroll anchoring; - the shell's intent timers; - the bottom-restore settle tail; - the restore attempt loop; - the width-change restore path; - shrink re-attach; - the content hold timer; - the detail scroller's own stick heuristics and smooth follow. - `docs/timeline-pagination.md` describes the model. ## How you verified **Sweep on production data.** A read-only snapshot of a production bb database was served through the built app and driven by an automated harness. A per-frame detector measured each visible row's on-screen position, and the harness compared origin/main against this branch. The breadth pass took every thread longer than one page and ran wheel-up, a medium fling-up, and scroll to top and back, at 1280 px desktop and at phone width. | Cells that loaded on both builds | origin/main with a scroll incident | this branch with a scroll incident | |---|---|---| | 959 | 416 | 2 | Curated threads ran the full scenario matrix: - drags and flings; - wheel streams; - PageUp, PageDown, Home and End; - scrollbar drag; - jump-to-bottom; - resize and rotation; - keyboard; - expand and collapse of every row kind, detached and attached; - thread switch and return; - live streaming, attached and detached. They ran at 1280 and 1600 desktop and at phone width with touch. These threads are clean apart from the residuals below. The detector flagged a few more items, and screencasts of the painted frames show they are not visible movement. A cell counted only if its input reached the page, scrollTop travelled at least 100 px, and it rendered at 20 fps or more. **Perf, origin/main → this branch.** Chrome trace of a long scroll, median of 3 runs. Desktop: 80 wheel steps at 1280 px. Phone: 8 flings at 4× CPU throttle. | thread / platform | scripting ms | forced layouts | long frames (count / total ms) | |---|---|---|---| | 4,000-event thread, desktop | 752 → 603 | 438 → 95 | 1 / 58 → 0 | | 4,000-event thread, phone | 1339 → 667 | 357 → 37 | 2 / 199 → 0 | | bundle-size thread, desktop | 164 → 214 | 30 → 8 | 0 → 0 | | bundle-size thread, phone | 400 → 505 | 50 → 11 | 2 / 180 → 1 / 70 | - On the bundle-size thread, scripting is higher because this branch keeps loading older pages through the gesture, where main stopped short: 447 scroll events against 115 on phone. Per scroll event, the cost is about the same. - The scroll handler reads layout on every event by design, so main's "no layout reads in scroll handlers" test is not ported. A test pins a logarithmic number of row measurements per scroll event instead. **iOS (windowing off, as on main), cells with issues:** - iOS Safari: 1 of 28 (a 10 px drift), against 11 on main (up to 8,065 px). - WKWebView: 0 of 16, against 7 on main. **Send, attached and detached:** all attempts are counted. | run | main | before the send fix | this branch | |---|---|---|---| | Phone-width Chrome, detached send, 3 threads | 12/12 | 11 of 12 failed | 12/12 | | Desktop 1280, Enter to send | 12/12 | — | 12/12 | | Android emulator, detached send, 6 threads | all pass | 1 lost attachment | all pass | A streamed reply after the send isn't covered, because the snapshot server has no host. **Commands:** `pnpm exec turbo run typecheck lint test --filter=@bb/app` passes (646 files, 6,258 tests). Regression tests cover the windowed list's startup (scroll root pending, no usable geometry, start at the end), unmount saves, nested lists, the anchor loss when windowing activates, the clamp snap, thread-switch restore and spacer coverage, and each fails without its fix. **Known residuals:** - **WKWebView race.** A JS scrollTop write can race a new gesture: WebKit applies the write late, and the keeper folds both jumps into the reading position. This is why iOS keeps windowing off. A fix is designed but not in this PR. - **Android coverage is thin.** Valid Android emulator runs cover sends and a small set of threads. The breadth comparison ran in desktop Chrome with phone-width touch emulation. - **Blank frames on fast scrollbar drags.** On a long thread, very fast drags still paint 3–5 blank frames per drag (main: 10–11). The rows are in the DOM, and the blanks are raster lag. - **Stutter on a throttled phone.** At 2× CPU throttle, a fling across an older-page load can stall one 66 ms frame and then catch up. It lands in the right position. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ry host has no source (get-bb#6054) ## Human comments ## What was wrong A thread created with no environment (`project-default`) and no live parent always resolved to the primary host. Examples are a Tasks preset, an automation run, or `bb thread spawn` without an environment flag. [`resolveProjectDefaultThreadEnvironment`](https://github.com/get-bb/bb/blob/b143a22860944a96bf183605dfb0ffe58b1de283/apps/server/src/services/threads/thread-default-policy.ts#L278-L282) took `requireConnectedPrimaryHostId` and then looked up the project's checkout on that host. It never checked that the project had a checkout there. A project whose only checkout lives on another machine, such as a laptop, therefore failed with 404 [`Project has no local-path source for host`](https://github.com/get-bb/bb/blob/b143a22860944a96bf183605dfb0ffe58b1de283/apps/server/src/services/projects/project-workspace.ts#L89) (get-bb#4446). ## What changed - [`thread-default-policy.ts`](https://github.com/alexandre-vl/bb/blob/fa610160fc79f2e00be379265c92f7fcfdd39068/apps/server/src/services/threads/thread-default-policy.ts#L267-L283): the new `resolveProjectDefaultHostId` keeps the primary host whenever it has a checkout of the project, so today's placement is unchanged for those projects. Otherwise it takes a persistent machine that holds a checkout. It skips destroyed and removing machines, and prefers the project's default source, then the oldest one. That machine is readied the way an explicitly chosen one is (`ensureHostSessionReadyForWork`): - a suspended machine is woken; - an offline machine answers 502, so the thread never silently moves to another machine. - [`project-sources.ts`](https://github.com/alexandre-vl/bb/blob/fa610160fc79f2e00be379265c92f7fcfdd39068/packages/db/src/data/project-sources.ts#L223-L249): `getProjectSourceOnPersistentHost`, one join query. - [`bb guide threads`](https://github.com/alexandre-vl/bb/blob/fa610160fc79f2e00be379265c92f7fcfdd39068/packages/templates/src/templates/bb-guide-threads.md#L113) says where a thread with no environment flag starts. - No server/daemon wire change, so no `HOST_DAEMON_PROTOCOL_VERSION` bump. ## How you verified - A new [`project-default machine`](https://github.com/alexandre-vl/bb/blob/fa610160fc79f2e00be379265c92f7fcfdd39068/apps/server/test/public/public-threads.project-default.test.ts#L236) block runs against the real server and a migrated SQLite database. Each test creates a project-default thread through the public API. The "`main`" column comes from running the same file against `main`'s server code (`b143a2286`): 4 tests failed and 2 passed. | Project checkouts | `main` | This PR | | --- | --- | --- | | Only on a laptop (not the primary) | 404, `expected 404 to be 201` | 201, worktree on the laptop | | On the primary and a laptop, default source on the laptop | 201 on the primary | 201 on the primary | | Default source on a temporary machine, another on a laptop | 404, `expected 404 to be 201` | 201 on the laptop | | On an offline laptop | 404, `expected 404 to be 502` | 502, no fallback to the primary | | On a suspended laptop | 404, `expected 404 to be 201` | 201, laptop woken | | Only on a temporary machine | 404 | 404 | If the fix only requires a connected machine instead of waking it, the suspended-machine test fails with `expected 502 to be 201`. - `pnpm exec turbo run typecheck lint --filter=@bb/server` passed. - The six server suites that cover project-default placement passed (177 tests): `thread-default-policy`, `environment-providers`, `public-threads.project-default`, `thread-runtime-config`, `provider-management` and `plugin-sdk`. - Before the wake was added, `pnpm exec turbo run typecheck test --filter=@bb/server --filter=@bb/db --filter=@bb/templates` passed `@bb/server` 3631, `@bb/db` 632 and `@bb/templates` 46. - No UI change, so there are no screenshots; the status codes above are the before and after. I did not reproduce it in a live instance, which needs a second enrolled machine. Fixes get-bb#4446 > AGENT GENERATED
…#6021) ## Human comments ## What was wrong The sidebar scroller did not reserve scrollbar space. On platforms with classic scrollbars, expanding enough threads to overflow narrowed the rows, changing title truncation and moving trailing icons left. Overlay scrollbars were unaffected. ## What changed `SidebarContent` uses `scrollbar-gutter: stable` and measures the reserved width with a `ResizeObserver`. The thread list subtracts that width from its right padding, preventing an unnecessarily large gap. Existing scrollbar visibility behavior is preserved, including the drawer-specific rules. No server/daemon or public API contracts change. ## How you verified - Reproduced on the baseline in headless Chrome on macOS with overlay scrollbars disabled: expanding a synthetic parent with 25 children moved its trailing icon 11px left. - With the fix, the icon stayed at x=308px in both collapsed and expanded states. Confirmed the scrollbar remains visible at rest after restoring the original visibility rules. - Overlay-scrollbar configuration measured a zero-width gutter. - `pnpm exec turbo run test --filter=@bb/app -- sidebar.test.tsx theme.test.ts --passWithNoTests`: 132 tests passed after the visibility adjustment. - App lint and typecheck passed during review. `pnpm start:worktree` rebuilt successfully after the adjustment; server and daemon health checks passed. - The added component test verifies initial scrollbar-width publication and updates after resize observation; real-browser measurements verify layout stability. - Safari/iOS and Electron were not tested. > AGENT GENERATED --------- Co-authored-by: Michael Yong <wrong92@gmail.com>
## Human comments ## What was wrong The delete confirmation counted sidebar children (`parentThreadId`), but deletion traversed only lifecycle ownership (`lifecycleOwnerThreadId`). Confirming deletion therefore left sidebar-only children alive despite promising to delete them. ## What changed Deletion now recursively follows both sidebar parenting and lifecycle ownership, including hidden and archived descendants. The API's cleanup effects, durable deletion marks, and project deletion use the same traversal. Archival behavior remains unchanged. Updated the CLI guide and lifecycle documentation; no server/daemon wire changes. ## How you verified - Extended the existing lifecycle regression: sidebar descendants failed before the fix and pass afterward. Covers mixed relationship trees, hidden/archived descendants, interruption recovery, and independent-thread survival. - Turbo server tests: 44 tests passed across lifecycle ownership, parenting, and plugin thread events; final expanded lifecycle rerun passed all 10 tests. - Turbo database tests: 50 tests passed across lifecycle ownership and thread data. - Turbo server/database typechecks and `git diff --check` passed. - Built and launched `pnpm start:worktree` with an isolated development store. Using this branch's CLI, unconfirmed deletion returned 409; confirmed deletion removed the parent, sidebar child, owned grandchild, and hidden archived leaf (all subsequently returned 404). An independent thread survived, and rename/archive/unarchive passed. - In the browser, used the native Thread actions → Delete dialog: the parent and both sidebar descendants disappeared, and reopening the child failed. CLI independently confirmed all three returned 404. Retained separate manual-QA fixtures. Fixes get-bb#5973 > AGENT GENERATED
🚨 **SLOP COP** 🚨 · `new-issue-autopilot` ## Human comments ## What was wrong Child creation loaded and validated the parent but resolved provider, model, and reasoning from caller input and project defaults without consulting parent execution history. Only permission policy used that history. This contradicted the documented execution-default precedence. [Reproduction and root-cause report](https://get-bb.github.io/reports/issues/6118.html). ## What changed - Pass the validated parent into the existing server creation-default resolver. - Resolve a parent's recorded provider before project defaults when no caller-owned provider is supplied; inherit model/reasoning only when the selected provider matches the parent. - Preserve explicit overrides, source-aware picker ownership, and the existing project/product fallback when the parent has no execution history. - Keep permission policy, service-tier policy, public CLI/SDK contracts, daemon protocol, and stored schema unchanged. Existing guide precedence now matches this creation path. - Add one table-driven creation-boundary regression covering seven inheritance/override/fallback cases. Four existing resolver fixtures explicitly supply no parent. ## How you verified - Before the fix: the original two-case regression failed on unchanged trusted main, then failed identically in a second clean checkout at `cc292b975fc35e16d7480cd9b8d55eff0ed88d8f` (same agent). Assertions inspect the real outbound `thread.start` command using migrated SQLite and repository provider registrations. - After the fix: the original focused regression passes. The expanded regression and five relevant existing suites pass: **6 files, 124 tests**. - `pnpm exec turbo run test --filter=@bb/server -- test/threads/thread-create-parent-execution.test.ts test/services/threads/thread-execution-plan.test.ts test/threads/thread-runtime-config.test.ts test/threads/thread-default-policy.test.ts test/threads/thread-create-seed-without-run.test.ts test/threads/thread-create-session-options.test.ts` - `pnpm exec turbo run typecheck --filter=@bb/server` passes; changed files are formatted with the repository formatter; `git diff --check` passes. - Frozen install and normal trusted-base builds succeeded in both checkouts after building the existing SDK runtime first. - Change size from `git diff --numstat origin/main`: **205 additions + 4 deletions = 209 text lines across 4 files**, no binary changes, dependencies, generated files, or migrations. - No account-backed provider turn or browser smoke test was run. The verification-skill inventory preflight reports the existing unmapped `diagnostics` CLI family; no unrelated inventory baseline was changed. Fixes get-bb#6118 > AGENT GENERATED <!-- slopcop:rule=new-issue-autopilot run=run_pvqncotgmvjtsaaf sha=issue-6118 kind=summary --> --------- Co-authored-by: SawyerHood <sawyerjhood@gmail.com> Co-authored-by: Michael Yong <wrong92@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…s reads (get-bb#6018) ## Human comments ## What was wrong The environment's stored branch name, which sidebar labels and thread lists use, was refreshed as a side effect of every git status read (`workspace-status.ts`, `recordEnvironmentCurrentBranch`). That ties core's branch labels to core's git status route. The git/GitHub plugin plan moves git status into a plugin, so the branch has to reach the server some other way first. This is PR 3 of that plan, decision D6: the daemon reports the branch. ## What changed - **Daemon (`watch-manager.ts`):** while an environment's workspace is watched, the daemon reports branch metadata (current branch and default branch) through the existing `environment-metadata-change` message: - when the watch becomes ready; - after git or shared-ref changes, including coalesced git and content batches; - on detached HEAD, where the branch is `null`. Unchanged values are not re-sent. A removed watch can't publish a stale observation. - **Server:** the branch write is removed from git status reads, including commit preflight, together with its now-unused argument and normalization helper. Status responses still return the observed status, but they no longer overwrite the stored branch. The separate refresh when an environment is attached or reused (`thread-environment-placement.ts`) stays, because it runs before any watch exists. - **Wire:** `HOST_DAEMON_PROTOCOL_VERSION` goes from 249 to 250. Older daemons are rejected at session open and update through the existing path, so branch metadata always comes from a branch-reporting daemon. Main's connect-next, desktop broker proof, and cleanup inspection capability exports are retained. - **Performance:** shared-ref reads now return a typed snapshot containing the fingerprint and default branch. Shared-only events reuse that snapshot and the cached per-worktree current branch, eliminating four redundant Git processes per workspace. Local Git events still reread the current branch. This is an internal host-workspace contract change; no additional wire or database changes. - **Startup:** local-state snapshots now include the current branch and HEAD-derived fingerprint from one porcelain-v2 status read. Initial watcher processing reuses the local and shared snapshots for metadata, eliminating dedicated branch/default-branch queries and a separate HEAD query. Reads run in the serialized watcher event path, normally at readiness. No additional wire/database changes. - No SDK, CLI or UI changes. ## How you verified - Updated against `origin/main` at `cf2018d777b4b7637cdb03492d389b6d36778fc7`. - `pnpm exec turbo run typecheck lint test --filter='...[origin/main]' --concurrency=2`: **265/265 tasks passed** on startup follow-up head `b4b8789d63`. - Focused daemon/watch and real Git workspace snapshot tests passed. Restoring redundant current-branch reads makes the new shared-ref regression test fail. - Formatting and `git diff --check` passed. - GitHub CI on `b4b8789d63`: **all 40 applicable checks passed**, with 7 skipped by workflow conditions. - **New tests:** - daemon branch reporting at watch ready, after git changes, on detached HEAD, on a default-branch-only change, deduplication, and stale-watch suppression; - server: thread-list branch fields update from daemon messages with no status requests; - server: status GET and commit preflight leave the stored branch alone. - **Negative checks:** putting the old status-read writes back makes their regression tests fail. Deduplicating on the branch alone fails the default-branch case. - **Live, in a source dev app, with every browser status fetch blocked:** - the initial watch stored the branch; - a real `git switch` changed the sidebar label from `qa/d6-initial` to `feature/d6-without-status`; - detached HEAD stored `null`, and the label fell back to "Worktree"; - the daemon received zero `workspace.status` calls. ## Performance measurement Measured main `cf2018d777`, pre-fix PR `655739f7db`, and fixed PR `f0f55ddcd0` on this Mac using an isolated bb clone with 1, 5, and 20 full linked worktrees, each on a separate branch. Five trials per event. The harness runs the real daemon WatchManager, native HostWatcher, and Git implementation; it counts direct Git child processes in memory. Metadata timing ends at the daemon metadata callback, before websocket transport/server persistence. No status requests are made. A real empty commit in another, unwatched worktree changes shared refs. Each cell is **Git process count / median completion ms** (time from starting the trigger until the last daemon Git child exits; the triggering Git process is excluded from counts). | Watched workspaces | Main | Pre-fix PR | Fixed PR | |---:|---:|---:|---:| | 1 | 3 / 289 | 7 / 201 | 3 / 409 | | 5 | 15 / 338 | 35 / 285 | 15 / 373 | | 20 | 60 / 1166 | 140 / 2994 | 60 / 870 | These commits emit no metadata message: branch labels have not changed, so PR versions deduplicate them. Main does not report branch metadata from watches. To measure actual message latency, a second shared-ref workload changes `refs/remotes/origin/HEAD` between two default branches. Process counts are identical to the table above. Times are medians in ms to the last workspace's metadata callback; both PR versions emit exactly one message per workspace. | Watched workspaces | Main completion (no metadata) | Pre-fix PR metadata | Fixed PR metadata | |---:|---:|---:|---:| | 1 | 154 | 146 | 218 | | 5 | 150 | 194 | 287 | | 20 | 1066 | 3062 | 808 | Appending to tracked README.md in every watched worktree spawns **zero Git processes and zero metadata messages** on all versions/counts. Median last content-status notifications in ms: | Watched workspaces | Main | Pre-fix PR | Fixed PR | |---:|---:|---:|---:| | 1 | 82 | 89 | 84 | | 5 | 134 | 142 | 146 | | 20 | 138 | 143 | 146 | At 20 workspaces the fix removes 80 extra Git processes per shared-ref event, matching main's 60, and lowers median metadata latency from 3062 to 808 ms (~74%). Low-count timing differences reflect system/trigger variability; this does not claim a latency improvement at every count. The preexisting three fingerprint processes per workspace remain. Those shared-ref measurements preceded the startup follow-up below; startup is now 8 processes/workspace. Harness, per-process traces, and the detailed report are outside the checkout at `/Users/michael/.bb/thread-storage/thr_r6a3k624u7/branch-perf/` (`measure.mjs`, `report.md`, and `{main,branch,fixed}-{1,5,20}.json`). Source snapshots for the measured revisions are retained there. ## Repeated startup measurement Measured main `cf2018d777`, previous PR head `f0f55ddcd0`, and startup fix `b4b8789d63`. Five completed fresh-process trials at each count, alternating version order, using the same real bb worktrees/native watcher/Git harness described above. Timing starts before watch-set replacement and includes provisioning, ignore discovery, and initial reads. Observation/quiet windows are excluded. **Git process counts** were identical across all five completed trials in each cell: | Watched workspaces | Main | Previous PR | Startup fix | |---:|---:|---:|---:| | 1 | 10 | 14 | 8 | | 5 | 50 | 70 | 40 | | 20 | 200 | 280 | 160 | The fix removes 120 processes versus the previous PR at 20 workspaces, and uses 40 fewer than main. Each workspace now uses two provisioning checks, two local-state calls, three shared-ref calls, and one ignore-discovery call. **Startup completion, median (min–max), ms:** | Watched workspaces | Main | Previous PR | Startup fix | |---:|---:|---:|---:| | 1 | 238 (134–457) | 272 (152–345) | 375 (202–479) | | 5 | 1534 (914–3970) | 1110 (993–1745) | 1081 (1051–3030) | | 20 | 4899 (4658–6040) | 5376 (4661–22630) | 6901 (4573–11398) | Completion is the last daemon Git child exit. Both PR versions emit one metadata message per workspace; their last-metadata medians round to the same millisecond as completion. Main emits no branch metadata. **Timing tradeoff:** the process-count target is met, but this is not a demonstrated wall-time improvement. At 20 workspaces the new median is 6.90 s versus main's 4.90 s and the previous PR's 5.38 s. Initial snapshots now run after readiness instead of overlapping setup, and the host timings have wide ranges. All-root-watch-ready medians for 1/5/20 workspaces were main 234/1534/4900 ms, previous PR 239/1087/5356 ms, and startup fix 198/642/6367 ms. Content invalidation still happens immediately at readiness; metadata computation follows asynchronously. One additional previous-PR 20-workspace attempt failed during the existing root ignore-discovery Git command; its log was retained and the sample rerun. Native Git timeout settings were unchanged. The measurement harness's settling deadline was raised to 60 seconds while completing the series. The detailed report, raw traces, and source snapshots remain outside the checkout in `/Users/michael/.bb/thread-storage/thr_r6a3k624u7/branch-perf/`: `startup-report.md`, `startup.mjs`, `run-startup-samples.py`, and `startup-{main,fixed,startup}-{1,5,20}-{0..4}.json`. > AGENT GENERATED --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Human comments ## What was wrong App rebuilds discarded reusable React Compiler transforms between CI runners. When dependency inputs changed, frozen-install validation also blocked planning from releasing every matrix job. ## What changed - Restore and save the existing content-addressed React Compiler cache in app-building jobs, with bounded, optional restoration. - Run frozen dependency validation alongside the matrix as a separate check, preserving the dependency-change detector and frozen install. - Record controlled measurements and rejected experiments in `docs/ci-performance.md`. Shard counts and selected coverage are unchanged. The resolver-filter and declaration-bundler experiments are excluded. ## How you verified - [Compiler-cache benchmark](https://github.com/get-bb/bb/actions/runs/39842305719): three alternating cold/warm pairs per platform. Median paired savings, charging restore time and the measured save cost, were **27s on Linux and 29s on Windows** per reusable app rebuild. All output files matched within each platform. This does not measure total CI savings or first-build performance. - [Validation benchmark](https://github.com/get-bb/bb/actions/runs/39844281314): three copies of each arrangement. Median planning duration fell from **39s to 13s**; all frozen installs passed. Durations exclude runner queue time, and runs already skipping validation do not gain this time. - [Full CI with both fixes](https://github.com/get-bb/bb/actions/runs/39844633490): **45 jobs passed in 6m00s**. The subsequent commit only updates documentation. **The three-minute target remains unmet.** - Compiler-cache correctness tests passed through Turbo, including invalidation and corrupt-cache recovery. - Seven workspace-tool tests and 30 CI selection/performance tests passed through Turbo. `git diff --check` passed. > AGENT GENERATED
## Human comments ## What was wrong Two RPC query tests each waited through about seven seconds of real retry backoff before checking error and recovery behavior. The test file took about 15 seconds even though its assertions do not depend on wall-clock waiting. ## What changed Use Vitest fake timers around the two retry paths and advance the clock inside React `act`. Restore real timers before the remaining assertions and in cleanup. All six tests and their assertions remain intact. Production code, check selection, and shard counts are unchanged. ## How you verified - All 396 SDK tests and SDK typecheck passed: `pnpm exec turbo run test typecheck --filter=@get-bb/plugin-sdk`. - [CI benchmark](https://github.com/get-bb/bb/actions/runs/39853020854): three before/after pairs on four-vCPU runners, reversing order in the second pair. Prerequisites prepared once; every measured test execution forced through Turbo with `--only --force`, so test-result caching cannot explain the gain. - Linux test-file durations: **15.60 → 1.46s**, **15.24 → 1.37s**, **15.21 → 1.68s**. All six tests passed in every sample. - Windows test-file durations: **16.64 → 2.70s**, **16.69 → 2.70s**, **15.98 → 2.15s**. All six tests passed in every sample. Both benchmark jobs completed successfully. - Local test-file duration: **14.98 → 0.70s**. Formatting and `git diff --check` passed. These are test-file measurements, not total CI savings. Concurrent work can hide some or all of this gain in workflow completion time; the three-minute CI target is still unmet. > AGENT GENERATED
## Human comments ## What was wrong bb's composer was core code, so the only way to change how the prompt box looks or edits was to change bb itself. The thread list is already a bundled plugin that a fork can replace. The composer had no such slot: plugins could add actions, popups and decorations to it, but couldn't draw it. ## What changed Builds on get-bb#6121. - **The composer is now the bundled Composer plugin** (`plugins/composer`, `bb--composer`). It registers through a new `app.slots.experimental_composer` slot. - Core renders every composer surface through `ComposerBox`: follow-up, new thread, queued and sent message editors, side chat, embedded chat. - `ComposerBox` resolves the slot and hands the plugin its input, view and host services. - With no composer plugin enabled, no composer is shown, the same rule as the thread list. - Appearance gets a Composer picker; the preference is `composer.provider`. - **How the plugin talks to bb:** - it reads and writes the draft through `useComposer()`; - it connects through `experimental_ComposerRoot` and `experimental_useComposerContributions()`; - it searches mentions and commands through `experimental_useComposerMentionSearch` / `CommandSearch`, which take the query from the composer. - **The renderer contract is shaped for any composer, not bb's own box:** - `submission.enter` / `.modifier` are actions the host works out from the surface and the "Enter steers" setting (kind send/queue/steer, `requiresInput`, shortcut, `run()`). Programmatic `submit()` uses the same choice, only where Enter sends. - `compact` is `{ when: "always" | "narrow", placeholder }`. - The host calls `editor.focusEnd()` itself and measures the menu the composer hands to `ComposerRoot`. - Services are grouped: `threadLinks.{parse, cached, resolve}`, `clipboard.{readMessage, readMessageImage}`, `attachmentSrc`. bb releases local previews when attachments leave the draft. - The composer is a remembered page-drawing surface, so it loads with the first plugins. Composer CSS moves from `app.css` into `plugins/composer/src/composer.css`. - Plugin Guide card, `docs/api_to_audit.md` entries and bb-guide references are added. `@bb/app` lists the composer plugin as a workspace dependency, as it does thread-list, so CI's filtered install includes it. Plugin SDK 0.6.71. - While the composer plugin loads, the page skeleton and `ComposerBox` draw the same loading block at the composer's real size, so it doesn't jump between two skeletons. On a phone thread the page skeleton now draws the one-line composer, fixing a jump main also has. - `dropdown-menu`'s compact-sheet context and `menu-item-hover`'s last-hovered context are shared across bundled copies (`Symbol.for` on `globalThis`, like the persistent drawer stack). bb's send and `+` menu items for other plugins now render inside the composer plugin's phone sheet instead of crashing, and hovering between the plugin's items and bb's leaves one row highlighted. Known deviation: a plugin's `submit()` on a sent-message editor rejects, matching get-bb#6104. ## How you verified - `pnpm exec turbo run typecheck lint --filter='...[origin/main]'` (160/160). - Tests pass for `@bb/app`, `@bb/server`, the composer plugin, `@get-bb/plugin-sdk`, plugin-api-docs, bb-guide, `@bb/web`, `@bb/client-core` and `@bb/scripts`. - `composer-contract.test.tsx` runs bb's composer and a minimal textarea composer against every surface. - Live on a `start:worktree` server: - the composer renders from the plugin, and @mention, pasted-thread-link pills, the slash menu and attachments work; - Enter sends while idle; while a turn runs, Enter steers and Cmd+Enter queues; - the send menu shows Queue / Save draft / Send later; - at phone width a blurred editor collapses to the one-line form, and a short desktop pane uses the narrow CSS form. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## What was wrong Workspace watch startup spawned eight Git processes per workspace. Provisioning, local state, and shared refs independently checked the same repository; provisioning also read the Git directory separately, and shared refs queried origin/HEAD separately from its ref listing. ## What changed Startup now uses four Git processes for a workspace with a resolvable origin/HEAD: 1. One `rev-parse --is-inside-work-tree --is-bare-repository --git-dir --git-common-dir` supplies both provisioning checks. Local/shared reads reuse the known repository result. 2. The existing ignored-directory status scan runs before watcher setup. 3. The existing fresh local-state status scan runs after root watch readiness. 4. One `for-each-ref` listing supplies branch refs and origin/HEAD's target using `%(symref)`. When the listing omits `refs/remotes/origin/HEAD`, the existing `git symbolic-ref refs/remotes/origin/HEAD` call remains as a fallback. Those workspaces use **five** processes, including repositories with no origin/HEAD or a dangling/unresolvable target. The same rule handles files and reftable storage, without reading ref-storage internals. Both status scans remain because their ordering closes the setup window. WatchManager and host-watcher match main: no setup snapshot is cached. Snapshot fingerprints keep their existing representation, including the symbolic default-branch target, so retargeting origin/HEAD to another branch at the same commit still invalidates metadata. There are no daemon wire, CLI, or configuration changes. ## How you verified `pnpm exec turbo run typecheck lint test --filter=@bb/host-daemon --filter=@bb/host-workspace --filter=@bb/host-watcher --concurrency=2` passed 13/13 tasks (`four-checks-final.log`). - Real-Git default-branch coverage runs against files and reftable storage: missing origin/HEAD, a resolvable target, a changed target at the same commit, dangling targets/chains, and local-branch fallback. Reftable cases skip only if Git cannot perform the migration. Both formats pass locally. Negative controls disabling either the unlisted-HEAD fallback or the listed symbolic target fail the assertions. - Provisioning coverage includes ordinary checkouts, linked worktrees, bare repositories, plain directories, and repository paths containing spaces/newlines where supported. - The setup-window regression still passes: after ignore discovery, it edits README and changes main→feature before root subscription activation, with no watch event delivered. It verifies content invalidation, dirty content status, and feature metadata. The same test failed on the former setup-cache implementation `1bfe2c22a0`, reporting main. Before/after evidence remains in `setup-window-before.log` and `setup-window-after.log`. ### Common-case startup measurements Five fresh-process samples per version at **1, 5, and 20 workspaces**, alternating main/branch and branch/main order. Baseline: **origin/main `c0013e2aba`**. Branch: **`a7c6c2c3f7`**. The source snapshots were captured at `b859834f4e`; the final head changes only test fixture initialization, and all measured production source files match it byte for byte (`four-revisions.json`). No older five/six-process samples are pooled into these results. Used the existing external `startup.mjs` harness and `startup-report.md` method, with the same isolated clone and native-watched linked worktrees. The clock starts before replaceWatchSet, including provisioning, ignore discovery, and watcher setup. Watch-ready ends at the last workspace-root readiness callback; completion ends at the last Git child exit. The 1.6-second observation floor and 650-ms idle wait are excluded. Our package tests and fixture setup had finished before sampling; no browser, server, or status-request traffic was generated. | Workspaces | origin/main Git processes | Branch Git processes | Reduction | |---:|---:|---:|---:| | 1 | 8 | 4 | 50% | | 5 | 40 | 20 | 50% | | 20 | 160 | 80 | 50% | Counts were identical across all five samples in each cell. All 30 samples produced exactly one initial content invalidation and one metadata message per workspace, identical branch/default-branch/path/worktree values, and no raw watch events. All workspace-root watches ready, median (min–max), ms: | Workspaces | origin/main | Branch | |---:|---:|---:| | 1 | 96 (91–132) | 88 (84–96) | | 5 | 227 (222–307) | 215 (203–247) | | 20 | 1630 (1508–7808) | 2028 (1342–6408) | Startup completion, median (min–max), ms: | Workspaces | origin/main | Branch | |---:|---:|---:| | 1 | 175 (171–227) | 156 (152–181) | | 5 | 398 (393–477) | 383 (375–425) | | 20 | 1821 (1650–8460) | 2104 (1438–6908) | The process reduction is consistent; timing is mixed. Readiness medians improved slightly at 1/5 workspaces and regressed at 20. Both versions have wide 20-workspace ranges. These samples do not establish a general latency improvement. The original `measure.mjs` harness with zero event repetitions independently confirms 8→4, 40→20, and 160→80 processes. Separate one-workspace missing-HEAD and dangling-HEAD runs each confirm **8→5**, with identical metadata between main and the branch (`four-fallback-summary.json`); these count checks are excluded from the timing tables. Scripts, source snapshots, JSON traces, fixtures, logs, and the report remain outside the checkout under `/Users/michael/.bb/thread-storage/thr_ci4wag9p6h/branch-perf/dedup/`. The harness adaptations change import paths to preserved snapshots; instrumentation and settle rules are unchanged. `four-run-samples.py` reproduces the primary series, `four-summarize.py` validates counts/report parity and computes the tables, and `four-fallback.py` checks fallback counts. Primary traces are `startup-four-*-<count>-<0..4>.json`. CI on final head `a7c6c2c3f7` is green: **18 successful checks, 8 skipped**. The PR remains open and unmerged. > AGENT GENERATED
## Human comments ## What was wrong Package CI spent time serializing independent API recovery waits and making one Miniflare database round trip for every test migration statement. The API recovery file took about 18 seconds, and D1-backed fixtures repeated 76 migration calls for each fresh database. ## What changed - Run the independent Actions API recovery cases concurrently, scheduling the ten-second timeout first. Each case retains its own HTTP server, child process, assertions, and context-bound cleanup; the real timeout and retry paths still execute. - Batch D1 test migration statements between PRAGMA boundaries. PRAGMAs still execute separately, and fixtures still use the real migrations and fresh D1 databases. - Advance fake timers in the stubbed account API retry test. The first CI run exposed its real 600ms deadline expiring under load before the expected HTTP 503 assertion. Retry counts, error checks, and elapsed-time checks remain covered. Production API behavior, selected tests, and shard counts are unchanged. ## How you verified All 635 affected tests and four package typechecks passed locally using Turbo. The tests cover real HTTP retry/timeout behavior and real Miniflare/D1 gateway behavior. A separate real-D1 comparison applied the sequential and batched migration paths and verified identical definitions for all 63 schema objects and identical foreign-key settings. [API-only benchmark](https://github.com/get-bb/bb/actions/runs/39854976341) and [D1/combined benchmark](https://github.com/get-bb/bb/actions/runs/39855766366) each run three before/after pairs on four-vCPU Linux and Windows runners, reversing order for the middle pair. Prerequisites are prepared once; measured tests use Turbo `--only --force` so result-cache hits cannot explain the gain. Both use two Vitest workers; the combined benchmark uses CI's four concurrent Linux tasks and two Windows tasks. The table reports median paired reductions in elapsed Turbo execution time, including all tests in the named packages, not just the edited file. | Change and measured scope | Linux | Windows | | --- | ---: | ---: | | API recovery: entire scripts suite | 1.89s | 5.59s | | D1 batching: database, connect, and AI gateway suites | 2.85s | 8.03s | | Both: all four suites together | 1.57s | 19.17s | The API test file itself consistently drops from roughly 18 seconds to 11 seconds. Gains do not add: other tests run concurrently, and complete-suite timing varies. These are affected-suite benchmarks, not proof of an equal end-to-end CI reduction or of meeting the three-minute target. All benchmark jobs passed. Combined runs passed all 635 tests on Linux; Windows passed 620 with the existing 15 platform skips. Combined elapsed times (before → after): Linux 22.13 → 22.04s, 25.17 → 23.60s, 21.99 → 15.68s; Windows 51.57 → 30.99s, 63.64 → 49.69s, 56.41 → 37.24s. The follow-up account API timer fix passed all 90 connect-next tests and typechecking. The benchmark numbers above measure the first two changes only. `git diff --check` and formatting passed. > AGENT GENERATED
## Human comments ## What was wrong Server tests paid real notification batching and plugin shutdown delays, polled fixture daemons at one-second intervals, and reran database migrations in native-plugin subprocesses. These waits and fixture costs added up across 65 existing test cases. ## What changed - Use the existing notification-clock helper for six system-message taxonomy cases, then inspect the real persisted events after flushing notifications. - Give the install-hook fixture the existing 100ms shutdown budget used by other plugin fixtures. Its 200ms install-handler deadline and hanging-handler assertions are preserved. - Accelerate one-second sleeps to 100ms inside the machine installer's fake executable directory. Other sleep durations, real shell execution, process lifecycle checks, and all 53 cases remain. - Use `createMigratedConnection()` in all three native-plugin subprocess fixtures, inheriting the migration template prepared by global setup. ## How you verified All 3,698 server tests and typechecking passed locally through Turbo. All 19 regular PR checks passed, with eight expected skips. The focused Linux runs passed all 65 cases; Windows passed 12 with the existing 53 POSIX-only installer skips. Each complete Linux shard run passed the same 1,748 tests with one existing test skip. Controlled benchmarks used four-vCPU runners, two Vitest workers, three before/after pairs, and reversed order for the middle pair. Prerequisites were prepared once, then tests ran through Turbo with `--only --force` so task result caching could not explain the difference. Baseline is merged main commit `93d1fc0c86`; candidate is `cf80b4ab6f`. [Complete Linux server shard](https://github.com/get-bb/bb/actions/runs/39859265143/job/120105432052): | Pair | Before | After | Reduction | | --- | ---: | ---: | ---: | | 1 | 175.97s | 123.31s | 52.66s | | 2, reverse order | 158.54s | 155.16s | 3.38s | | 3 | 121.31s | 87.33s | 33.98s | Median paired reduction: **33.98s**. All three pairs improved, but the wide range is material: this is evidence of a shard improvement, not a promise of 34 seconds off every CI run or of meeting the three-minute total-CI target. Median paired file-duration reductions from the [focused Linux job](https://github.com/get-bb/bb/actions/runs/39859265143/job/120105431975) and [corrected Windows benchmark](https://github.com/get-bb/bb/actions/runs/39860093044): | File / change | Linux | Windows | | --- | ---: | ---: | | System-message notification clocks | 10.01s | 10.01s | | Install-hook cleanup budget | 4.46s | 4.46s | | Installer fixture polling | 1.79s | Existing platform skip | | Native-plugin database template | 0.44s, noisy | 1.25s | These are file timings while all changed files run together, not independent estimates that can be summed. The focused four-file invocation **regressed by a median 2.23s on Linux** (36.49→37.37s, 29.47→35.62s, 28.64→30.87s). Moving the notification file into isolated workers changes scheduling, so individual file savings do not translate directly to elapsed time. On Windows the focused invocation improved by a median **3.71s** (29.50→26.73s, 27.03→23.32s, 30.15→25.36s). The full-shard measurements above are the relevant Linux CI result. The initial Windows benchmark omitted normal CI's `TEMP`/`TMP` settings and failed the native reload fixture. The corrected benchmark matches those settings and passed all six measured executions; regular PR CI also passed. Formatting and `git diff --check` passed. > AGENT GENERATED
…6125) ## Human comments ## What was wrong GitHub pull request status was core. The daemon ran `gh`, the server assembled and watched the status, and the app, SDK, CLI and Tasks and GitHub plugins each reached it through core routes and commands. Core's PR UI couldn't leave core because there was nothing outside core to read from. ## What changed **New bundled, default-enabled plugin: `plugins/github-pr-status` (`bb--github-pr-status`)** - `host.ts` / `host/git-host.ts`: the in-worktree lookup moved from `packages/host-workspace/src/git-host.ts`. It covers the current branch, upstream and fork remotes (`getPullRequestTarget`), the merge-queue GraphQL query, and `gh pr ready|ready --undo|merge`. It runs on the environment's own machine with the daemon's shell `PATH`. - `server.ts`: - resolves the environment; - serves a keyed `pullRequests` read, one row per environment id (up to 200), so a list of rows loads in one request; - serves `markReady`, `markDraft` and `merge`, each of which re-checks state before acting; - registers `bb pull-request show|ready|draft|merge <environment-id>`. - `store.ts` is the single place reads are cached (10 s), re-checked while watched, and published when they change. - PR snapshots are kept by PR URL, so environments sharing a PR share one snapshot. Persistence, cross-row reuse and webhooks can feed this one place later. - While a row is watched (`bb.rpc.experimental_whileWatched`), it re-checks every 30 s when checks are pending, auto-merge is on, the PR is queued or mergeability is unknown. That is the same rule as core's `pull-request-watch.ts`. - Other triggers match main's: - `status-changed` and environment create/delete fetch fresh; - a completed turn re-checks through the 10-second cache; - `git-refs-changed` only marks the cached row stale; - `metadata-changed` re-checks only when the environment's branch actually changed (one environment read, no `gh`); - in the app, a cached row is re-fetched on mount, and again when the page is shown if it's older than 30 s (1 h once merged or closed). Main got this from TanStack's `refetchOnMount: "always"` and focus refetch. Here it uses only the public read hook's `refetch`, in `pull-request-queries.ts`. - It invalidates the row's tag only when the row changed, and never for a result a viewer is already receiving. - `contract.ts` is the public contract. The read returns the fully assembled shape the UI renders: state, attention, checks, review, mergeability, auto-merge and merge queue. **Watched reads** - `bb.rpc.experimental_whileWatched` comes from `bb/5939-recut`, mechanism only: the `plugin-read` subscription target, the server registry and producer lifecycle, app query hooks holding the subscription, per-target subscribe validation, and fake-host support. - Left out: the SDK `experimental_watchRpc` / `experimental_watchDiscovery`, the CLI `--watch` flags, and plugin-signal scoping. **Core reads and acts only through the plugin's public contract** - The composer banner segment, Info row and thread view use `experimental_useRpcKeyedQuery` / `experimental_useRpcMutation` with `pluginId: "bb--github-pr-status"` (`apps/app/src/hooks/queries/pull-request-queries.ts`). These are the same calls another plugin makes, so moving the UI later is a cut and paste. - The app's RPC hook host now allows those hooks outside a plugin slot when `pluginId` is explicit. - `bb thread show`, Tasks (`listTaskPullRequests`, now one batched read) and the GitHub plugin's `pullForThread` call the same RPC. **Deleted from core** - The server PR watcher, route, cache, `environment-pull-request` tag and subscription target, and the PR action branches. - The SDK `environments.pullRequest` / `markPullRequestReady` / `markPullRequestDraft` / `mergePullRequest`, and `bb environment pull-request …`. - The `ThreadPullRequest` / `GitHostPullRequest` domain schemas. - The daemon's `workspace.pull_request` and `workspace.pull_request_action`. **`HOST_DAEMON_PROTOCOL_VERSION` 250 → 251**, compatible `[250, 251]`. 250 stays compatible because the server no longer sends either command. 246–249 stay removed, as get-bb#6018 intended. **`experimental_useSidebarThreadPullRequest`** - Removed from `@get-bb/plugin-sdk/app`, the Plugin Guide and the docs. - Published bundles keep working: the app installs `retiredPluginSdkAppMembers` on the plugin runtime, backed by the plugin's read. It returns `{ isLoading: false, pullRequest: null }` when the plugin is disabled. - **Builds never fail on it.** bb builds git-sourced marketplace plugins at install and update, and rebuilds path and builtin plugins when the SDK changes. So the `bb-plugin-build` runtime shim keeps the name in its `@get-bb/plugin-sdk/app` export list. Importing it is a build warning with the replacement call, which `bb plugin build`/`dev` print and the server writes to the plugin's log. - Migration guide: `bb-plugin-authoring/references/frontend-pull-requests.md`. The runtime shim is recorded in `docs/api_to_audit.md`. - Plugin SDK 0.6.72. **No wake.** Plugin host calls never wake a suspended host. A read on an asleep or offline machine returns `unavailable`, which the UI renders as no PR. Ready, Draft and Merge fail with "<machine> isn't available …". **Behaviour change:** reads no longer wake a suspended host. On main, `workspace.pull_request` used the `work` wake policy, so opening a thread whose machine was suspended woke it. ## How you verified **Request measurement in the real app.** Main and this branch ran as two `pnpm start:worktree` instances side by side. Each had a thread on a project checkout of PR get-bb#6117's branch while its checks were pending (`checks_pending`, 0 failed). In each, a headless page opened the thread and every `/api` request was recorded for 3 minutes, both runs at the same time: | | PR requests | when (s) | all `/api` requests | |---|---|---|---| | main | 6 × `GET /environments/:id/pull-request` | 1, 32, 64, 95, 127, 158 | 84 | | branch | 6 × `POST /plugins/bb--github-pr-status/rpc/pullRequests` | 1, 33, 64, 96, 127, 159 | 88 | The other `/api` differences changed from run to run (hosts, server-move and execution-options in this run; system config, providers and timeline in another). They come from background activity in the freshly restarted branch instance and its extra sidebar threads, not PR traffic. An earlier run found two bugs that are fixed here with tests: - a re-check right after open published a result the opening read was already fetching, costing one extra request; - a watch that went idle when checks finished never resumed when a new CI run started. **Busy thread.** Main and this branch ran side by side again, each with a thread on its own scratch PR in a private test repo (`ymichael/test-project` #303 and #304, both closed and their branches deleted afterwards). Each thread was open in a page. Both received the same agent turn at the same moment: three commits, a `git fetch`, a third commit between them, and a `git push`, which also started the repo's CI. Over 5 minutes, a process monitor counted every `gh` started by each instance, attributed by process ancestry and confirmed by working directory. The page counted PR read requests. Two runs gave identical counts: | | `gh` calls | PR read requests | |---|---|---| | main | 7 (4 `pr view`, 3 merge-queue `api graphql`) | 4 | | branch | 5 (3 `pr view`, 2 merge-queue `api graphql`) | 2 | Before this fix the branch also re-checked with `gh` on every `git-refs-changed` and `metadata-changed`, so a thread that commits and pushes would have exceeded main. **Focus refresh.** Main (`f909788625`) and this branch ran side by side with the same open PR. A page on each hid and showed itself 5 times over 4 minutes, with gaps from 20 s to 60 s, so some shows were under the 30 s stale time and some over. A process monitor counted `gh` per instance: | | `gh` calls | PR read requests | |---|---|---| | main | 5 | 5 | | branch | 5 | 6 | The extra read is the realtime connection resuming at the same moment the page showed. Both reads hit the plugin's 10 s cache, so there's no extra `gh`. **Published plugins that use the retired hook.** On the review app, I installed four git-sourced Community marketplace plugins that call `experimental_useSidebarThreadPullRequest`. Each went through `bb plugin install <plugin>@bb-community`, so bb cloned and built it at its published commit. Each was then used as the sidebar thread list: | Plugin | Commit | Builds | Warning in `plugin.log` | Renders PR badges | |---|---|---|---|---| | t3sidebar | `8b535729dd` | ✅ | ✅ `src/ThreadCard.tsx:6` | ✅ | | dockside | `e8736f3858` | ✅ | ✅ `components/inbox/thread-card.tsx:7` | ✅ | | radar-sidebar | `9a20bfab7e` | ✅ | ✅ `components/radar/RadarThreadRow.tsx:23` | ✅ | | thread-inbox | `c0981fb6fe` | ✅ | ✅ `src/ThreadCard.tsx:18` | ✅ | gtd-sidebar fails to install on main too ("Could not resolve @get-bb/plugin-sdk"), so it's unrelated. This test caught a real bug: T3 Sidebar crashed with a React hook-order error. React Compiler only treats `use…` names as hooks, so it cached the `experimental_useRpcKeyedQuery` call inside `useEnvironmentPullRequest` and skipped it on re-renders with unchanged arguments. Core's own callers pass a fresh options object on every render, which hid it. The hooks are now imported under `use`-prefixed local names, and all four plugins render. **UI parity.** Screenshots of the same thread on main and on the branch show the same composer banner segment (PR get-bb#6117 with the Merge split button) and the same Info row (get-bb#6117, Open, Checks running). `bb thread show` printed the PR through the plugin. On this PR, `bb pull-request draft <env>` converted it to draft. The banner then showed "PR get-bb#6125 · Draft" with Mark ready, and clicking Mark ready showed the loading toast, made the PR ready on GitHub, and returned the banner to the open state with Merge. **Tests** - New plugin tests (75): - host-entry fork, upstream and detached cases using a fake `gh`, moved from host-workspace; - the parse, action-argument and merge-queue table; - the assembly table, moved from the server; - store polling, publish-on-change and the viewer race; - server reads, refusals, the no-wake refusal and the CLI. - Also added: the `bb-plugin-build` retired-import warning test, the focus-refresh staleness table and refetch tests, Tasks batched reads and the disabled-plugin case, CLI `thread show` through RPC, and a hub test for the `plugin-read` target. - After merging get-bb#6135: `pnpm exec turbo run typecheck lint --filter='...[origin/main]'` passes 162/162, and `turbo run test --filter='...[origin/main]'` passes 115/115. That includes get-bb#6135's host-workspace default-branch tests and the host-daemon `watch-manager-startup` setup-window test. The host-workspace merge kept get-bb#6135's code. This branch only removes the PR lookup from it. **Not covered** - `gh` ran only on a local host. Remote hosts use the same plugin host-artifact path as the other host plugins but weren't exercised. - Merge wasn't run against a real PR. It uses the same `gh pr merge --<method>` arguments, covered by the host tests. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Human comments ## What was wrong Six database test files reran migrations for ordinary query/schema cases, and five mocked CLI machine/update cases waited through real polling delays. CI also exposed a Windows race when concurrent tests downloaded and published the same FRP archive cache entry. ## What changed - Use a fresh real migrated-template copy in six database test files. Migration and on-disk persistence tests keep their existing setup. - Advance fake timers in five mocked machine/update polling cases, restoring timers after each test. Existing output, call-count, failure, and confirmation assertions remain. - Use unique FRP cache staging filenames, accept a failed publication only when another writer left a checksum-valid archive, and clean up staging files. ## How you verified All 633 database tests and 879 CLI tests passed, with one existing CLI skip, plus both package typechecks. All 40 FRP tests and its typecheck passed. The two cache-race regression cases failed against the old helper (valid competing archive rejected; staging file leaked) and pass with the fix. Formatting and `git diff --check` passed. [Controlled benchmark of the final batch](https://github.com/get-bb/bb/actions/runs/39874256386) runs three before/after pairs on Linux and Windows for the complete database suite, complete CLI suite, and both together. It reverses the middle pair, uses two Vitest workers on four-vCPU runners, and forces all Turbo tasks including prerequisites. Dependency installation is outside the measurements. The FRP race repair is not counted as a speed improvement. All 25 selected CI checks passed (eight expected skips). Median paired wall-time reductions, including prerequisites: | Complete tasks | Linux | Windows | | --- | ---: | ---: | | DB | 0.90s faster | 1.43s faster | | CLI | 5.45s faster | 2.32s faster | | DB + CLI together | 8.10s faster | 2.29s faster across six pairs | The first Windows combined run regressed 1.60s median; a [fresh-runner repeat](https://github.com/get-bb/bb/actions/runs/39875142800) improved 10.44s median. All six pairs are included in the 2.29s pooled median above. Three improved and three regressed; savings ranged from -8.78s to +14.23s, so the Windows aggregate is variable and does not establish a consistent wall-time improvement. Complete combined pairs (before→after): - Linux: 48.284→45.535s, 44.224→35.285s, 45.117→37.019s. - Windows first runner: 38.708→38.798s, 39.836→41.439s, 42.101→50.883s. - Windows fresh runner: 40.659→35.999s, 47.178→32.945s, 61.470→51.035s. These are affected-task measurements, not end-to-end CI savings. Per-file median savings (files overlap in execution, so these are not additive wall-time savings): | File | Linux | Windows | | --- | ---: | ---: | | CLI machine polling | 2.52s | 2.51s | | CLI update polling | 3.02s | 3.01s | | DB schema | 1.68s | 1.27s | | DB prepared queries | 0.65s | 0.71s | | DB event ingestion | 0.63s | 0.46s | | DB experiments | 0.44s | 0.34s | | DB provider catalogs | 0.39s | 0.35s | | DB prompt history | 0.29s | 0.23s | > AGENT GENERATED
## Human comments
## What was wrong
Thread action tabs, navigation-page fixed tabs, and core Info/Diff used
different registration and opening paths. That made tab context and
lifecycle inconsistent and prevented plugins from owning core pinned
tabs. Saved layouts also needed to survive the transition, plugin
loading, visibility changes and reloads.
## What changed
- Add one registration: `app.slots.experimental_tab({ id, title, icon,
tooltip?, layout?, context, availability, useVisible?, component, ...
})`. Context is `{ kind: "thread" }` with `{ threadId }` props, or `{
kind: "nav-panel", panelId }` with `{ subPath }` props. Fixed tabs have
one non-closable identity; action tabs launch closable instances with
persisted/deduplicated params. Only actions accept `run` and params.
Navigation-page actions also fit this model.
- Use one registry and destination dispatcher.
`ExperimentalAppPanel.experimental_openTab` is the new opener; existing
fixed-tab, action and thread-panel open calls remain wrappers over the
same implementation. A visibility hook hides a fixed tab or launcher
while retaining saved fixed order and existing action instances. Hooks
execute inside the plugin crash boundary; plugin generations reset
crashed content across every context.
- Register core Info and Diff as thread/fixed tabs. Preserve Diff's
Meta+D toggle, changed-file/commit entry points, Git setting, visibility
and pane-local review controllers. Preserve get-bb#6117's ordered Info row and
section registrations. There is no core-only Info/Diff content dispatch.
- Migrate first-party Docs, GitHub, Side Chat, Tasks, Workflows and the
ThreadChat demo. Remove the unshipped `experimental_threadFixedTab` API.
Remove `threadPanelAction`, `navPanel.fixedTabs`, and their old
registration/prop types from public SDK declarations and normal
Guide/authoring documentation. Keep lenient runtime adapters so
already-published plugins keep loading. Git install/update and
path/builtin rebuilds emit migration **warnings**, also recorded in
plugin logs; they never fail builds for these retired names. Runtime SDK
exports remain available. Record the 92/15 known legacy marketplace
callers and removal criteria in `docs/api_to_audit.md`.
- Preserve existing `plugin-page-fixed` and `plugin-panel` IDs/params.
Convert old Info/Diff entries, selected IDs and split placement to core
`plugin-fixed` entries without reordering, including revision-checked
server rewrites. SDK/CLI thread tab inspection/update uses the same
persisted model. No SQL schema or daemon wire change.
- Update Plugin Guide cards/surfaces and authoring migration examples.
Rebase onto main `2884950f17` (get-bb#6125); bump SDK with
`bump-plugin-sdk.mjs --patch` to **0.6.73**, above main's 0.6.72.
New-thread-screen actions retain their separate project context and
share the action-opening implementation.
- Resolve get-bb#6125 conflicts by retaining the retired PR-hook runtime
export and both tab adapters. Keep named-import and
property-registration warnings together, all non-fatal. Use main's
returned `migrationWarnings` and `logPluginBuildWarnings` pipeline for
all three names so warnings are logged once; preserve both audit entries
and migration docs. First-party callers of the new tab API require SDK
>=0.6.73.
## How you verified
- After rebasing, freshly clone/build/install **Graph** at
`3fd202e9af47` and **T3 Sidebar** at `8b535729dd22` through bb's Git
install flow. Both artifact manifests report SDK 0.6.73. Graph logs the
two tab warnings; T3 logs the retired PR-hook warning at
`src/ThreadCard.tsx:6`. Both have zero plugin-log errors. Graph's
thread/fixed tabs render, and T3's actual sidebar renders PR get-bb#6013
alongside the matching core Info row, with no browser errors. Restore
the sidebar preference and disable T3 after QA. Evidence:
`/Users/michael/.bb/thread-storage/thr_fkjwifewbd/pr6013-rebase-6125/verification.md`.
- `pnpm exec turbo run typecheck lint test --filter='...[origin/main]'
--concurrency=1` passes: **243/243 tasks**, including 6,323 app tests
and 3,675 server tests. SDK version guard, formatting and `git diff
--check` pass. The unchanged source-layout builtin watcher test failed
in the initial broad run, then passed in its complete isolated suite
(28/28) and the full rerun. No timeout was weakened.
- One six-case contract table covers new thread fixed/action, new
navigation-page fixed/action and both legacy adapters: stable saved
identity, persisted params, deduplication, close behavior, visibility,
remount and crash/reload. Existing migration and split-readiness tests
remain. Independent mutations prove crash recovery failures in both the
table and the actual navigation-page host; repaired focused tests pass.
A full-suite failure also verified the legacy fixed-target callback
contract before its repair.
- Real same-store upgrade from main `c0013e2aba`: save
first-party/marketplace tabs and a Side Chat instance, upgrade to the
branch, and compare persisted data. Every action entry, parameter and
order remains unchanged; Info/Diff convert in place. Verify Info, Diff,
changed-file navigation, Meta+D, Docs/Tasks navigation, GitHub,
Workflows and restored Side Chat. Prior branch evidence covers
reordered/split Diff restoration and the Git setting.
- Actual fresh Git-source install flow at immutable public commits:
**Agent Graph** `3fd202e9af47`, **SQL** `69593e887aaf`, **Host Monitor**
`1d84e329e69d` (`plugins/host-monitor`) and **JB Flow** `66d4700cb1c3`.
They build, load and log the migration warnings. Graph/SQL/JB Flow
action tabs open, close, reopen and restore once; Graph/SQL/Host Monitor
fixed tabs reopen and persist without close controls. No errors in the
fresh app console or their plugin logs. JB Flow's published commit has
actions but no fixed tabs.
- Install original published **Lazygit 0.2.10** and **Global Workflows
0.1.3** npm bundles; app.js hashes match the scanned published
artifacts. Both legacy action tabs open, close and restore. The Lazygit
terminal renders; Global Workflows shows an empty configured catalog. No
workflows, database queries, voice sessions or external GitHub writes
run.
- **Handsfree 0.1.6** has the same sidebar voice-bar crash on main and
this branch; its fixed Views tab renders the same companion state at
phone width. This is recorded as a pre-existing baseline, not counted
toward the three working fixed-tab plugins.
- Review app runs from the new head with `pnpm start:worktree`:
[prepared
thread](https://ymichael--26550.getbb.app/projects/proj_nyijgxmg6f/threads/thr_whvufeb6m5).
Detailed local evidence:
`/Users/michael/.bb/thread-storage/thr_fkjwifewbd/pr6013-unified/verification.md`.
- Size: **80 files, +3,093 / -999**; production/tooling +1,805/-673,
tests/support +1,095/-195, docs +193/-131. The previous cleanup removed
188 net lines, including the JSX glyph assertion and redundant fixed-tab
setup. Visibility observers remain so plugin hooks run within crash
isolation.
> AGENT GENERATED
## Human comments ## What was wrong The desktop app pinned Electron 44.3.0, several patch releases behind the latest 44.x line (44.9.3), missing upstream security and stability fixes. ## What changed - `apps/desktop/package.json`: Electron `44.3.0` → `44.9.3` (stays on major 44). - `pnpm-lock.yaml`: only the Electron entry (version + integrity). - `apps/desktop/README.md` and `apps/desktop/test/electron-builder-config.test.ts`: version string updated to match. No wire changes. ## How you verified - `pnpm install --frozen-lockfile` accepts the lockfile. - `npx electron --version` → v44.9.3. - `pnpm exec turbo run test typecheck --filter=@bb/desktop`: 600 tests passed (3 skipped), typecheck clean. > AGENT GENERATED Co-authored-by: Sawyer <kirbyhood@gmail.com>
## Human comments ## What was wrong Follow-up to get-bb#6125. PR snapshots were shared by URL, but loads and timers were keyed by environment, so five environments watching one PR repeated the same GitHub work. Each open-PR load used two requests (`gh pr view` plus a merge-queue query), ref changes expired the cache without comparing upstream commits, unrelated completed turns could run gh, and a restart lost every snapshot. ## What changed - **Shared, persisted refresh.** In-flight loads and pending-check timers are shared per PR URL. Snapshots and each environment's current link (a PR URL or known absence) persist in the plugin's own SQLite database through `bb.storage.database()` / `migrate()`. There is no core migration and no history; snapshots no link references are deleted, and identical content or links write nothing. Persisted rows are validated on restore and invalid ones are dropped and rediscovered. `PERSISTENCE_ENABLED` in `persistence.ts` turns saving off in one line; bumping `PERSISTENCE_VERSION` discards rows saved by older code (kept in SQLite `user_version`, no schema change). - **Restart.** The app's first read is served saved data immediately and refreshed in the background. CLI and plugin reads wait for overdue data, as on main. - **Smarter triggers.** Ref events compare the host's actual branch and upstream OID locally and refetch only when they change; a detected push gets one bounded follow-up, because GitHub can briefly return the old head. Completed turns rediscover on a branch with no PR and recheck a known PR only if the turn ran `gh`. Viewers are invalidated only when their row changes. - **Watched-row timing.** One timer drives every watched check: pending checks, auto-merge, merge queue and unknown mergeability every ~30 s; other open PRs every 5 minutes; merged and closed PRs never. When it fires, every check at least halfway to its due time joins, so rounds line up and share GraphQL batches. This is a deliberate addition over main, which never rechecks a quiet PR in the background, so a visible sidebar missed approvals, web merges and new conflicts until a focus or turn. App reads reuse a "no PR" result for 5 minutes instead of rerunning discovery for every PR-less row on each focus; CLI and plugin reads keep the 10 s cache, so an agent running `gh pr create` then `bb pull-request show` in one turn still sees the new PR. - **Batched GraphQL reads.** Reads on each workspace host go through `gh api graphql`, grouped by GitHub hostname and credential into documents of at most 25 PRs with a 10 ms collection window. Rate limits pause only the affected credential. A GitHub Enterprise server without `isInMergeQueue` is retried once without it and remembered. - **Discovery matches `gh pr view`.** The base repo is the `gh`-resolved remote (`gh repo set-default`), else `upstream`, `github`, `origin`, then any other remote, and `GH_REPO` overrides it. A branch pushed to another owner matches only that owner's fork PRs; otherwise only same-repository PRs match, and the default branch ignores merged and closed PRs. bb's existing rules for branches tracking a differently named fork branch are unchanged. - **Cheaper reads.** Environment and machine lookups happen only when data must be fetched, not on cache hits. Suspended machines return unavailable without running gh; actions name the machine and how to start it. - **Lifecycle.** Disabling the plugin hides cached PR status in core and the retired sidebar shim. Deleting an environment clears its watched row and orphaned storage. The keyed `pullRequests` read keeps main's contract (one `pullRequest` per environment), so existing plugin callers are unaffected. No SDK member, daemon wire field or plugin RPC shape changes. The host-entry contract gains `view` inputs (`url`, `refresh`) and an `upstream` method, which ship together with the plugin. ETags were measured and excluded: check-runs and commit statuses each returned 304 but still cost one request each, versus one combined GraphQL read. ## How you verified Baseline: origin/main `2884950f17` (get-bb#6125). Fresh isolated source dev apps, real browser/plugin/realtime stack, real agent turns and disposable PRs in ymichael/test-project, with a temporary workspace-host gh wrapper counting child processes and GitHub HTTP requests (not shipped). | Matched real-app case | Window | Main gh / HTTP / plugin reads | Branch gh / HTTP / plugin reads | | --- | --- | --- | --- | | Quiet thread, open PR, including opening read | 73.036 s | 2 / 2 / 1 | 1 / 1 / 1 | | Busy agent turn committing/pushing, pending checks | 53.461 s | 4 / 4 / 2 | 2 / 2 / 1 | | Five watched threads/environments sharing one pending PR | 35 s | 10 / 10 / 0 | 1 / 1 / 0 | A completed turn without tools caused two gh/HTTP calls on main and zero on the branch. The same runs exercised Draft/Ready/Merge from the UI, pushes showing the new head with pending checks, an agent `gh pr create` appearing without reload, suspended-machine refusals, hot plugin disable, and CLI show/ready/draft/merge. These real-app measurements predate the last commit (`c48b1c6a1b`), which went back to the one-PR read shape, aligned discovery with gh, and fixed restart reads. That commit removes requests (no linked historical PRs are refetched) rather than adding any. It is covered by new regression tests (the first and fourth were reproduced failing against the previous branch head before the fix): - a stranger's fork PR and a merged same-repo PR on `main` are not matched; - a fork clone with an `upstream` remote is looked up in the upstream repository, and a `gh-resolved` remote is honoured; - a GHES error for `isInMergeQueue` is retried without the field and remembered; - CLI/plugin reads after a restart return fresh data instead of the saved row; - cached reads make no environment or machine lookups. The 5-minute timing and persistence switch commits (`28725412ff`, `ce87a99bc8`) add tests that fail on the previous store: staggered quiet PRs join one round and merged PRs are never rechecked; "no PR" is reused by viewers for 5 minutes but not by CLI reads or forced discovery; rows from another persistence version are discarded. With `PERSISTENCE_ENABLED = false`, typecheck passes and only the 5 save/restore tests fail. A host bench reading 20 real open get-bb/bb PRs by URL, with the per-check `upstream` git step, made 3 `gh api graphql` calls per lined-up round in 3.7–4.4 s (three runs); without the git step the same round is 1 call, so per-check git work spreads reads across the 10 ms window. Unaligned 5-minute timers would make about 20 calls per round. Turbo `typecheck test lint --filter='...[origin/main]'` at `ce87a99bc8`: 29/29 tasks passed. Earlier, 39/39 tasks passed (app, CLI, PR Status, GitHub, Tasks, bb-guide, plugin-api-docs, host-daemon, demo-server, integration tests). > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…PR Status plugin (get-bb#6023) ## Human comments ## What was wrong Git and GitHub pull request status were still core UI. The composer's context banner hard-wired its parent-thread, changed-files and pull request rows. The Info tab drew the Pull request row from core, using PR data owned by the bundled GitHub PR Status plugin (get-bb#6125). There was no slot through which core and plugins could add context beside a thread's composer. ## What changed - **`app.slots.experimental_threadContextSegment`** (SDK 0.6.75): segments share the context row above an existing thread's composer. - A registration takes separate `summary`, `action?` and `details?` components, like composer customizations take separate lists. The host mounts each in its own crash boundary: `summary` in the row, `action` at the row's end, `details` below the row. - `group` then `order` place segments, as for Info rows and thread actions. `experimental_THREAD_CONTEXT_SEGMENT_GROUPS` names only core's groups, `thread` (`1_thread`) and `git` (`3_git`); the PR plugin uses its own `2_pullRequest`. - Props give `threadId`, `projectId`, `environment`, the visible `segments` as `{ key, pluginId }` (`key` is `<pluginId>/<id>` with core as `bb--core`, the shape thread action keys use), and a shared one-open-at-a-time disclosure. - `useVisible` runs in plugin scope per thread. - The banner is drawn above `ComposerBox`, so it shows under every composer plugin. - **Core uses the same path.** The parent thread (including read-only history) and changed files (with the merge-base action) register through `definePluginApp` and the collector, and render through the same host as plugins. Core segments render without a plugin crash boundary, as core Info rows do, so their DOM is unchanged. - **Active child threads are their own core row** (`ThreadChildThreadsBanner`) above the context row, like the todo and workflow cards, with their own open state. They are not a segment, so the slot has no card placement that would overlap composer `banners`. - **GitHub PR Status plugin gets an app entry** (`plugins/github-pr-status/app.tsx`, `ui/`): - the pull request segment: status pill, label rules, Mark ready and the merge split button with Convert to draft, the remembered merge method under the same localStorage key, and the same toast copy; - the Info tab's Pull request row, through get-bb#6117's `experimental_threadInfoRow`; - the display helpers, status pill, next-step label, favicon and the keyed read hook, moved from core with their tests. Core has no pull request UI or PR imports left. The one exception is the retired `experimental_useSidebarThreadPullRequest` runtime member for old marketplace plugins. - **`environment` on slot props.** It is `{ id } | null` on the new slot and on `ExperimentalThreadInfoProps`. - On both, it is null when the thread has no environment, its host is not active, or the environment is not a usable checkout. - On the segment slot only, it is also null when "Show Git changes and Commit button" is off. That keeps main's behaviour, where the PR hides with changed files and the Info row does not. - **No extra PR reads after the move.** - The plugin hook refetches only when an environment starts showing, not for each extra view, so opening Info adds no read. - `WebSocketManager` sends its final unsubscribe a microtask later and cancels it when the same target resubscribes in that task. Without this, a thread switch on the same environment dropped the plugin read subscription and restarted the server's watch, which can run `gh`. - `BannerActionSlot` and the banner action buttons moved to `@bb/shared-ui/prompt-banner-actions`, so the plugin draws the same controls. - **One toast path for core and plugins.** The `toast` plugins import from `sonner` is now a facade over core's `appToast` (`apps/app/src/lib/notifications/plugin-toast.ts`), replacing the recording wrapper. - `toast()`, `message`, `info`, `success`, `warning`, `error`, `loading`, `dismiss` and `promise` render and record exactly like core's toasts. - `custom`, `getHistory` and `getToasts` pass through. - A toast whose `action` or `cancel` is a plain React node (sonner allows it; no bundled plugin uses it) falls back to sonner. - Every plugin's toasts change appearance to match core's, which is intended. - **The retired `experimental_useSidebarThreadPullRequest` member keeps main's refresh.** It uses main's hook, kept in core as `useRetiredSidebarEnvironmentPullRequest` with main's tests: a cached row refetches on mount, and a stale row on page show. - Core's copy and the plugin's copy of that hook announce every refetch they start with a `window` event (`PULL_REQUESTS_REFETCH_EVENT` in the plugin contract) and mark those rows loaded. - A stale-row refetch rechecks staleness right before it runs, so a row that already has a fetch in flight, or was refreshed within its staleness window, is not read twice. SDK refetch behaviour is unchanged. - **Docs:** - a `docs/api_to_audit.md` entry; the Info entry and the live-reads and retired-member text updated; - the Banners Guide card's symbols, bullets and fixture, with anatomy anchors; - `frontend-registration.md` and `frontend-api-index.md`; - the PR stories moved to the plugin and added to Ladle. No database, daemon wire or CLI changes. Rebased onto get-bb#6143. Its "hide cached PR status when the plugin is disabled" check in core's PR hook moved with that hook's last core caller, the retired sidebar shim (`retired-sidebar-pull-request.ts`), with get-bb#6143's test; the plugin's own hook only renders while the plugin is enabled. The keyed read stays one PR per environment, so the segment and Info row need no change. **Deviations from main** - While core's commit or push runs, the PR buttons are no longer disabled; the plugin cannot see core's git actions. The replacement Michael asked for is to pass the displayed head to `gh pr merge --match-head-commit`. That needs `headRefOid` on the PR, which get-bb#6143 adds, so it lands on top of get-bb#6143. - A crash in the PR segment removes only that segment, or its part. - The active child threads card keeps its own open state, as Michael asked. On main it shared one open section with the context row. Now it can be open while changed files or the parent details are open, and it opens by itself when a child newly needs input even while another section is open. **Known follow-ups:** - The segment slot's `environment` folds in the "Show Git changes and Commit button" setting. That setting should become the git plugin's own when changed files move out of core, and the `git` segment group moves with it. - `experimental_THREAD_INFO_ROW_GROUPS.pullRequest` (from get-bb#6117) still names a plugin's group in core. Removing it is a public SDK removal, so it is left for a separate change. ## How you verified - Parity checklist: every banner row and segment, the Info PR row, refresh sources, phone and desktop layout, and each composer. Item IDs: A composers, B inline row, C child card, D read-only, E disclosure, F data, G layout, H Info row. <details><summary>Checklist results</summary> | Checklist | Result | |---|---| | A1, A2, A3, A8 | Same. A4–A7 unchanged code paths (banner only in the follow-up stack); not driven separately. | | B1–B5, B11, B13–B15 | Same text and pixels at 1400 and 390 px for: PR + changed files, PR alone, parent + PR + changed files, parent + PR. | | B6–B8, B10 | Same: menu items, Convert to draft, Mark ready, Squash merge on disposable PRs; toasts identical. | | B7 persistence | Same key `bb.pullRequest.mergeMethod`; Squash merge remembered. | | B9 | Deviation: core commit/push no longer disables the PR buttons. | | C1–C6 | Same content, auto-open on a new waiting question and 8 px spacing; the card is now its own row with its own open state (deviation above). | | D1–D3 | Same pixels for archived child with parent. | | E1–E4 | Same: parent disclosure body, focus to Collapse, focus back to toggle. | | F1–F6 | Same PR reads (4 per scripted session); fewer WS frames on the branch. | | G1–G4 | Same compact rules (390 px and narrowed by the right panel). | | H1–H4 | Same markup and row order; Show Git changes off keeps the row on both; plugin disabled hides it on both. | </details> Each item was driven in two source dev instances side by side, one on this branch and one on `origin/main` 2884950, with identical fixtures and disposable PRs in `ymichael/test-project`. - Text was identical at 1400 and 390 px for PR + changed files, PR alone, parent + PR + changed files, and parent + PR. - Screenshots were pixel-identical apart from the caret and the scheduled-time text. - The same held for archived history, Show Git changes off, the plugin disabled, the child-thread card with a waiting question, the pending-question stack, and the parent disclosure with its focus handoff. - Toasts: the PR plugin's Mark ready toasts match main's pixel for pixel; the loading toast differs only in the spinner frame. The Drafts plugin's "Draft saved" and the probe's `success`, `error`, `info` and `promise` toasts render as core toasts, with the close button, the outlined icon and an entry in the notification center. - Actions on disposable PRs: Convert to draft, Mark ready and Squash merge went through with the same toasts and the segment refreshed. The merge method was remembered. A CLI `bb pull-request draft` updated the segment live in the production build (`pnpm start:worktree`). - Requests, from browser CDP counts of `bb--github-pr-status` HTTP reads and WebSocket frames. The scripted session: load a thread with a PR and wait 40 s, open Info, switch to a thread on the same environment and back, switch to another environment and back, then fire a page show after 35 s. | | PR reads | WS frames | |---|---|---| | main (dev) | 4 | 12 | | branch (dev) | 4 | 8 | | branch (production build) | 4 | 8 | Before the WebSocket fix the branch sent 16 frames, because of resubscribes on same-environment switches. With a probe plugin that renders `experimental_useSidebarThreadPullRequest` in the thread header (an old marketplace sidebar's call), the same session gave: | | load | 4 thread switches | page show | |---|---|---|---| | main | 1 | 4 | 1 | | branch | 1 | 4 | 1 | Before the shared announcement, the branch's page show read twice. - Tests: - new host contract tests (`ThreadPromptContextBanner.segments.test.tsx`): group and order placement, a plugin's own group, `segments` keys, visibility, one-open disclosure, per-component and `useVisible` crash isolation, read-only, and the row held while git loads; - child-threads row tests: auto-open for a newly waiting child while changed files are open, and the question flow through `ThreadChildThreadsBanner`; - plugin tests for label rules, the attention matrix, actions and visibility; - query tests for refetch on first show and same-commit remounts, which fail without the deferral; - a WebSocketManager test for same-task resubscribe; - a plugin `toast` test through the real toaster (error and promise), which fails on the old shim; - a test with the plugin's hook and the retired member's hook on one environment: a stale page show makes one read, and two without the announcement. - `pnpm exec turbo run test typecheck lint --filter='...[origin/main]' --continue`: 239/244 passed on the first run. - Fixed: the Automations Info fixture (`environment`), the server's slot-prop docs table, and the 500-line cap on `frontend-registration.md`. - Passed when rerun alone: `CommandPalette`, `PluginSettings`, `builtin-plugins` hot-reload and the MCP CLI timeout (the first run had heavy parallel load). - Affected packages rerun green: `bb-plugin-plugin-api-docs`, `bb-plugin-automations`, `@bb/server`. - `bb plugin build` passes for `plugins/plugin-api-docs` and `plugins/github-pr-status`. The Plugin Guide's Banners card and fixture were checked in the app at desktop and phone layouts. - After the groups and component split: `pnpm exec turbo run test typecheck lint --filter='...[origin/main]' --continue` gave 242/244. A test-harness mock of the banner and the old auto-open test were updated and `@bb/app` reran green; `bb-plugin-bb--storage-retention` (untouched) passed when rerun alone. - In the production build (`pnpm start:worktree`) on a checkout of this branch: parent, PR and changed files in that order with Merge at the end; the child-threads row above the context row, open together with changed files; only the child row beside a waiting question. - CI: see the checks on this PR. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
) ## Human comments ## What was wrong The root dev scripts (`dev`, `start`, `start:worktree`, `desktop`, `desktop:worktree`, `dev:host-daemon`, `start:host-daemon`) run through `dotenv -c <env> --`. dotenv-cli 11 calls `dotenvExpand({ parsed: process.env })` (`node_modules/dotenv-cli/cli.js:92`), which expands `$VAR` references in **every inherited environment variable**, not only values loaded from `.env` files. On a non-primary machine, bb gives agent threads GitHub HTTPS auth via `GH_TOKEN` plus a credential helper passed as `GIT_CONFIG_VALUE_1` (`apps/server/src/services/machines/git-credentials.ts`). That helper is a shell function that reads `$line`, `$protocol` and `$host`. When an agent started a dev instance from inside a bb thread, dotenv-cli replaced those with empty strings. The dev server, host daemon and plugin workers all inherited the broken helper, which never returns a password. Because `credential.helper=` also disables osxkeychain, worktree setup for any private HTTPS repo failed: ``` git fetch --quiet origin +refs/heads/main:refs/remotes/origin/main failed: fatal: could not read Username for 'https://github.com': terminal prompts disabled ``` I traced the environment through each process in the chain. The helper was intact in the `dotenv-cli` process and already broken in its child `run-desktop.ts`, and in every process started after it. ## What changed - `package.json`: add `--no-expand` to all 7 `dotenv` invocations so inherited environment variables pass through verbatim. No `.env*` files exist in the repo or in the main local checkout, so nothing depends on expansion. ## How you verified - Same environment, through the old and new wrappers: `dotenv -c development -- git ls-remote origin HEAD` against a private HTTPS repo fails with the error above. `dotenv --no-expand -c development -- git ls-remote origin HEAD` returns `HEAD`. - End to end: ran `pnpm start:worktree` from inside a bb agent thread (so it inherited the credential helper) and confirmed the helper reached the host daemon intact. Using `pnpm bb:dev`, I added the private repo as a project and spawned a `--new-environment worktree` thread. Provisioning got through "Fetching origin/main", "Fetched origin/main" and "Created worktree", and the thread finished idle. Before this change the same flow failed at the fetch. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bump bb-app and @bb/desktop to 0.47.0, and add the 0.47.0 changelog section and release metadata.
## Human comments ## What was wrong omp and opencode namespace each model ID by the service that runs it, so one model can appear several times under the same name. For example, omp lists `openai/gpt-5.6-terra` (OpenAI API key) and `openai-codex/gpt-5.6-terra` (ChatGPT subscription) as "GPT-5.6 Terra" and "GPT-5.6-Terra". The picker already shows a grey source label from `AvailableModel.routeProviderId`, but nothing in the ACP path set that field, so these rows were indistinguishable. Picking the wrong one fails (for example, an API key with no credits left returns `credit_balance_exhausted`). get-bb#2062 reported this and was closed by get-bb#5266, but [the follow-up comment](get-bb#2062) confirms that the duplicate rows remained. ## What changed - `AcpDialect` gains an optional `presentModel(model)` hook. The bridge applies it to every model in `model/list` results (`packages/provider-bridge-acp/src/bridge/bridge.ts`). - The omp and opencode dialects set `routeProviderId` to the ID's first segment (`openrouter/anthropic/claude-sonnet-5.5` → `openrouter`) and strip that prefix from the name when the agent repeats it (opencode's `openai/GPT-4.1` → `GPT-4.1`). IDs without a `/` are unchanged. Other ACP agents are untouched. - Model IDs are unchanged, so selection, routing and search are unaffected. The picker renders the label the way it already does for Pi, with no app changes. - `routeProviderId` is an existing optional field, so there is no wire change and no `HOST_DAEMON_PROTOCOL_VERSION` bump. `AcpDialect` is part of the plugin SDK (`experimental_registerAcpDialect`), so third-party dialects can use the same hook. The SDK version on main (0.6.75) is already ahead of the published 0.6.73, so no bump is needed. This takes over the problem from get-bb#6115 by @lemonleks, who reported it and proposed labelling rows by ID namespace. That PR inferred the label inside the picker. This change has the omp and opencode adapters supply it instead, which avoids doubling opencode's name prefix and lets plugin dialects opt in. ## How you verified - New `bridge.test.ts` case: the omp and opencode dialects label `anthropic/…`, `openrouter/anthropic/…` and `openai/…` models and strip opencode's duplicated prefix. The generic `acp` dialect is left unchanged. - `pnpm exec turbo run typecheck lint test --filter=@bb/provider-bridge-acp`: all checks pass (538 tests). - `pnpm exec turbo run build:types typecheck --filter=@get-bb/plugin-sdk`: passes, and the bundled `AcpDialect` type includes `presentModel`. - Ran the omp dialect over a real omp 17.4.1 catalog (118 models). `openai/gpt-5.6-terra` gets the label `openai`, and `openai-codex/gpt-5.6-terra` gets `openai-codex`. Fixes get-bb#2062 > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…bb#6146) ## Human comments ## What was wrong Docs had two document stacks and a hand-rolled tree store, all driven by broad `vault-changed` signals. The Docs panel (`NotePane` → `useSavedDocument`) never listened for changes: saves from another window, `bb docs write`, agent turns and external editors never showed, and typing on that stale view ended in "Changed on disk / Reload (whole page) / Overwrite". Every save also ran `renameToTitle`, which renamed the file after its heading. That broke every thread card, Document tab and link pointing at the old path ("HTTP 404: Path does not exist" plus an endless skeleton). The pane was keyed by path, so the rename also destroyed and rebuilt the editor mid-typing: keystrokes were dropped and the caret jumped. Thread cards did refresh, but they compared SHAs and threw a conflict whose Retry re-saved against the old SHA forever. They applied remote text with `addToHistory: true`, so Cmd+Z reverted an agent's edit and saved it back, and they rebuilt the editor whenever a new preview link was minted. Switching notes blanked the pane and flashed a skeleton, the first click from `/docs` re-listed the vault and blanked the tree, and clicking the empty space under a note didn't focus it. The native watcher shared one debounce across vaults, so a change in one vault could swallow another's signal, and remote vaults were polled every 10 s even when nobody was looking. Findings with exact steps are in `docs-findings.md` in the investigation thread's storage. ## What changed Everything is in `plugins/docs`. There is no other package change, no SDK or plugin API change, no bb.db schema change and no daemon protocol change. - **One document session for every surface.** The Docs panel, thread cards, the Document tab and the Markdown file opener use one session per document (`document-session.ts`). Reads come from `experimental_useRpcQuery` and writes go through `experimental_useRpcMutation`. Views of the same document share one read. The local draft is authoritative: - a change from elsewhere that lands while there is unsaved typing keeps the typing and offers **Keep mine / Use theirs** in place, with no page reload and no silent overwrite; - a document moved or deleted by another writer stops saving and offers **Save a copy**, so saving never recreates it; a view that opens on a missing document shows "This document was moved or deleted." instead of a raw error over an endless skeleton; - a removed vault makes the editor read-only. - **Stable file names.** A note's title is its heading (or frontmatter `title`), and editing it never renames the file, so cards, tabs and links keep working. A new `Untitled` note takes its first heading as its file name the first time it's saved from the Docs panel, inside the save request. Card labels and the Document tab's header follow the note's opening heading. - **Reads and writes.** - `readDocument` returns the file, its proposal and the preview link as one snapshot, serialized with the vault's writes. - `saveDocument` saves, names an `Untitled` note when asked, and refuses a stale SHA with a `conflict` refusal. - `renameToTitle` is gone (only the app called it). - `contract.ts` holds the browser-safe contract and its tags. - **Scoped invalidation from every write path.** RPC, CLI, HTTP, sync and the watcher all invalidate exactly the tags they changed: `doc:<vault>:<path>`, `folder:<vault>:<path>`, `tree:<vault>`, `vault:<vault>` and `vaults`. - The tree is refreshed only when entries or titles change; opening search refetches it so previews are current. - The realtime `vault-changed` / `proposal-changed` signals are removed (only the Docs app listened to them). - **Watching only while watched** (`vault-watches.ts`, via `bb.rpc.experimental_whileWatched` on `listNotes` and `readDocument`). - Local vaults are watched natively, with a per-vault debounce and path-scoped invalidation. - Events from atomic-write scratch files and echoes of the server's own writes are dropped. - Remote vaults are polled every 10 s only while one of their reads is on screen. - Preview links are shared per vault root and renewed while a document is on screen. - **Editor, limited to what the data layer and typing needed** (the shared-editor work replaces the rest). - The TipTap editor stays mounted across saves and preview-link renewals; only image and embed nodes are refreshed. - Outside text is applied without entering undo history, and only blocks whose Markdown changed are replaced. - Switching notes keeps the current note on screen while the next loads hidden, then swaps in one frame with the cursor at the end. A note slower than 1 s shows its skeleton as before. - Clicking below a note, in the panel or the Document tab, puts the cursor at its end; clicking beside the text puts it on the nearest line. - The grey focus ring around card editors is removed. - **Faster paths.** - The document loads in parallel with the tree instead of waiting for it. - The first open from `/docs` no longer re-lists the vault, so the tree doesn't blank. - Revisited documents show at once from the query cache. - A debounced save that finds new typing waits for the next pause instead of saving back-to-back. - **Docs:** README and PLUGIN_OVERVIEW describe live documents, the conflict choices, stable file names and the new tags. ### Intended changes users will see - Open documents update live; the Docs panel used to stay stale until reopened. - "Changed on disk / Reload / Overwrite" in the panel and "copy them before reloading / Retry" in cards are replaced by Keep mine / Use theirs. - Editing a note's heading no longer renames its file. Only a new `Untitled` note is named after its heading, once. The app has no other way to rename a file; `bb docs move` and the tree's drag-to-folder still move files. - Card labels and the Document tab's header follow the note's heading. A tab's title in the tab strip is still the one it opened with. - A document moved or deleted elsewhere shows a notice, with Save a copy when there is a draft to keep. - Cmd+Z no longer undoes other writers' changes. - Switching notes no longer flashes a blank pane or a skeleton. - Clicking the empty space below a note focuses it. As a result, a drag selection can't start in that margin. - The focus ring around card editors is gone. - Opening tree search refetches the tree once. Body edits that only change a note's preview no longer refresh the tree. - With no Docs view open, nothing watches the vaults, so the `@` mention cache relies on its 10 s expiry for edits made outside bb. ## How you verified ### Side by side on real browsers `pnpm start:worktree` ran for main at 1e80ca7 and for this branch. Each had identical fixtures: vault Alpha with 41 notes plus folders, an image and HTML; vault Beta with 2 notes; and a thread with three Docs cards in two vaults. Headless Chrome drove each flow with real keyboard input, with `fetch` and `WebSocket` instrumented in the page. "150 ms" adds that much network latency, roughly a remote bb connect URL. Remote writes were made by another client through the Docs RPC, timed exactly 0 ms or 400 ms after the last keystroke. The CLI, external-editor and agent-turn flows used the real `bb docs` CLI, a shell write, and a Claude Code Haiku turn. "Requests" counts Docs RPC calls in the window after the action. | Flow | main requests | branch requests | main result | branch result | |---|---:|---:|---|---| | Open Docs panel (`/docs`) | 1 | 1 | tree 948 ms | tree 718 ms | | First note from `/docs` | 3 | 1 | 84 ms | 39 ms | | Reload on a document, 0 ms | 3 | 2 | median 1900 ms to editor | median 525 ms to editor | | Reload on a document, 150 ms | 3 | 2 | median 1089 ms to editor | median 938 ms to editor | | Switch 7 documents (5 new, 2 revisits), 0 ms | 14 | 7 | new 30 ms, revisit 32 ms, 7 blank panes | new 32 ms, revisit 20 ms, 0 blank panes | | Switch 7 documents (5 new, 2 revisits), 150 ms | 14 | 7 | new 177 ms, revisit 179 ms, 7 blank panes | new 191 ms, revisit 26 ms, 0 blank panes | | Body save in the panel | 5 | 2 | saved | saved | | Change a note's H1, then keep typing | 7 | 3 | file renamed to `bulk-8-renamed.md`, editor replaced; `Bulk 8 Renamed` | file stays `bulk-8.md`, same editor, focus kept; `Bulk 8 Renamed more` | | New note, type title + line + list at human speed, 0 ms | 23 | 14 | text correct, editor replaced | text correct, same editor, named `meeting-notes-a.md` | | New note, type title + line + list at human speed, 150 ms | 16 | 15 | **text wrong**: `action item one`, editor replaced | text correct, same editor, named `meeting-notes-b.md` | | Type in an existing note's H1, pause 690 ms, keep typing, 0 ms | 17 | 6 | **lost/misplaced**: `Bulk 9 Xy ⏎ Body 9.g to type through the rename`, editor replaced | text correct, file stays `bulk-9.md` | | Type in an existing note's H1, pause 690 ms, keep typing, 150 ms | 20 | 6 | **lost/misplaced**: `Bulk 9 Xyz cont ⏎ Body 9.ype through the rename`, editor replaced | text correct, file stays `bulk-9.md` | | Second window: A types, B shows it, B types, A shows it | 10 | 6 | neither window updates; B's edit never reaches disk | B shows A after 738 ms, A shows B after 627 ms (quiet rerun); disk has both | | `bb docs write` to the open panel document | 2 | 1 | never shown | shown 499 ms after the write | | External editor writes the open panel document | 1 | 1 | never shown | shown 73 ms after the write | | Agent turn edits the open panel document (`bb docs pull/push`) | 2 | 1 | never shown | shown 0 ms after the write | | External edits to two vaults 50 ms apart, cards open | 10 | 2 | Alpha card updates | both cards within 225 ms | | Write in Beta while viewing Alpha | 2 | 0 | Alpha re-listed | nothing refetched | | Open thread with 3 cards in 2 vaults | 11 | 3 | median 1794 ms over 3 reruns | median 1274 ms over 3 reruns | | Save in a card | 7 | 2 | saved | saved | | Save with the same document in a card and the Document tab | 7 | 2 | both views show it | both views show it | | Agent proposal arrives at a card | 2 | 1 | shown 60 ms | shown 56 ms | | Reject the proposal | 4 | 2 | restored | restored | | Remote change 0 ms after a keystroke, panel, other paragraph | 3 | 1 | **remote never shown**; save fails into Reload (page) / Overwrite | typing kept; Keep mine / Use theirs offered | | Remote change 400 ms after a keystroke, panel, other paragraph | 3 | 1 | **remote never shown**; save fails into Reload (page) / Overwrite | typing kept; Keep mine / Use theirs offered | | Remote change 0 ms after a keystroke, panel, same paragraph | 2 | 1 | **remote never shown**; save fails into Reload (page) / Overwrite | typing kept; Keep mine / Use theirs offered | | Remote change 400 ms after a keystroke, panel, same paragraph | 2 | 1 | **remote never shown**; save fails into Reload (page) / Overwrite | typing kept; Keep mine / Use theirs offered | | Remote change 0 ms after a keystroke, card, other paragraph | 7 | 1 | **stuck**: "copy them before reloading" + Retry that conflicts again; remote never shown | typing kept; Keep mine / Use theirs offered | | Remote change 400 ms after a keystroke, card, other paragraph | 7 | 1 | **stuck**: "copy them before reloading" + Retry that conflicts again; remote never shown | typing kept; Keep mine / Use theirs offered | | Remote change 0 ms after a keystroke, card, same paragraph | 7 | 3 | **stuck**: "copy them before reloading" + Retry that conflicts again; remote never shown | typing kept; Keep mine / Use theirs offered | | Remote change 400 ms after a keystroke, card, same paragraph | 17 | 2 | **typing lost**: replaced by the other writer's text, no notice | typing kept; Keep mine / Use theirs offered | | Remote change 400 ms after a keystroke, panel, other paragraph, 150 ms | 3 | 1 | **remote never shown**; Reload / Overwrite | typing kept; Keep mine / Use theirs offered | | Remote change 0 ms after a keystroke, card, other paragraph, 150 ms | 7 | 1 | **stuck** on Retry | typing kept; Keep mine / Use theirs offered | | Agent rewrites a card, then Cmd+Z in the card | - | - | **agent edit reverted and saved back** | agent edit kept | | Another writer moves the open document, then typing | - | - | no notice; typing ends in a conflict whose Overwrite recreates the old file | notice + Save a copy; old path not recreated | | Preview link renews while typing in a card | - | - | editor rebuilt, focus lost, later keystrokes lost | same editor, focus kept, image uses the new link | | Remove the vault of the open document | 3 | 1 | editor stays editable against a missing vault | editor read-only, error shown, tree has Retry | | HTML page, new folder, drag into folder, context-menu delete, search | 16 | 10 | all work | all work | Every flow uses no more requests than main. Rows marked "-" check behavior, not request counts. Each flow is one run, with medians for timing. Single-run milliseconds vary with machine load; the card-thread and two-window rows are quiet reruns. Switching notes, frame by frame at 300 ms latency: main goes blank for about 200 ms, shows a skeleton for about 100 ms, then the note. The branch keeps the previous note until the new one replaces it at about 330 ms, with no blank or skeleton frame. A revisit takes 56 ms on the branch and 327 ms on main. The `bb docs` CLI suite ran against both servers (`vaults`, `list`, `read`, `write`, `pull`/`status`/`push`, stale-push refusal, `propose`/`reject`/`undo`/`accept`/`undo`, `mkdir`/`move`/`remove`, `vault-add`/`vault-remove`): 20/20 checks pass on both, with identical results. Before and after screenshots of every user-facing change are in the verification report in the thread's storage. ### Tests - New tests that fail on main or on the previous commit, and pass here: - a card being edited offers Keep mine / Use theirs instead of getting stuck on Retry; - Cmd+Z doesn't undo another writer's change; - the Docs page shows another writer's save; - a moved document isn't recreated; - the editor survives a preview-link renewal; - switching notes keeps the current note on screen until the next one loads, then focuses it; - clicking below a note focuses its end. - Also new: - an `Untitled` note is named after its heading, and a named note keeps its path when its heading changes; - session tests for conflicts, own-save echoes, stale reads, rejected saves, renames and proposals; - server tests for watched-only vault watchers, echo suppression, and two vaults changing within one debounce. - `pnpm exec turbo run build typecheck lint test --filter='...[1e80ca7]' --continue` (the Docs plugin and `@bb/bundled-plugins`): 60 successful / 60 total. Docs: 125 tests. - `generate:bb-official-marketplace` with `--force` passes; the overview is 1,918 characters. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…et-bb#6190) ## Human comments ## What was wrong Storage & retention only knew about worktrees that BB environments created. Worktrees that agents created themselves with `git worktree add` (outside BB) never showed up in the report or in cleanup. They piled up unnoticed: on one machine, 72 untracked worktrees were holding about 250 GiB. Separately, the BB development storage section could only remove one entry at a time, and it would let you delete the dev instance the running BB was using. ## What changed - **Finding outside worktrees.** A new host RPC, `listGitWorktrees` (`plugins/storage-retention/src/git-worktrees.ts`), runs `git worktree list --porcelain -z` for each project repo. For each linked worktree it reports uncommitted changes, commits that are on no branch, tag or remote, the lock state, and the last git activity. Last git activity is the newest mtime of the worktree's `HEAD`, `index` and `logs/HEAD`. The scan sets `GIT_OPTIONAL_LOCKS=0` so it never rewrites any index. - **Report.** The server (`storage.ts`) filters out paths BB owns (live environments and project sources), measures the rest, and adds them to the report as `externalWorktrees`. It also adds `externalCount`/`externalBytes` to each project's worktree row. - **Removing.** A new `removeGitWorktrees` RPC and a `removeExternalWorktrees` plugin RPC remove worktrees with `git worktree remove`. Without force, they skip anything that is locked, has uncommitted changes, or has unsaved detached commits, and report the reasons. With force, they run `--force` (twice for a locked worktree). Bulk cleanup (`startCleanup` kind `externalWorktrees`) is always the safe version. Force works only on explicitly named paths. - **UI.** - "Worktrees created outside BB" is grouped by repo, with warnings on each row and a link to the thread that used the worktree, if any. - A row that safe removal skips offers "Remove anyway". - The "Worktrees by project" table gains an "Other worktrees" column and better column alignment. - **BB development storage.** - Entries can be selected per row or per group, filtered to "Archived threads only", and removed together. - The instance this BB is running from is marked, can't be selected, and the server refuses to remove it. - **CLI and docs.** - New command `bb storage-retention remove-external-worktrees` with `--machine`, `--path`, `--force` and `--yes`. - `cleanup --kind externalWorktrees`. - README and skill updated. - **Plugin SDK bump.** Bumped `@get-bb/plugin-sdk` to 0.6.76. `main` already fails the npm version guard after get-bb#6186 changed the ACP provider bridge, and this PR can't go green without the bump. - **Protocol.** No change to the server/daemon protocol. The new host RPCs belong to the plugin's own host contract. ## How you verified - New `storage.test.ts` case against real git repos: - Finds worktrees created outside BB and leaves BB-owned ones out. - Bulk removal deletes only the safe ones. - Locked, dirty and detached-with-commits worktrees are skipped with the right reasons. - Force removes them. - The dev-instance test now checks the current-instance flag and that removing it is refused. - Windows CI showed that git reports worktree paths with forward slashes. The host now normalizes them to native paths. - `pnpm exec turbo run typecheck lint test --filter='...[origin/main]'`: 9/9 passed. - Manual QA on a `start:worktree` instance with fixture repos: - Grouping, warnings, "Remove anyway", per-row and bulk removal. - Multi-select removal of dev storage, and the current instance being protected. > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Human comments ## What was wrong The "Checks" job on main fails at the plugin SDK npm version guard. `@get-bb/plugin-sdk@0.6.75` was published from c8d5860 at 21:45 UTC. At 22:16, get-bb#6186 merged changes to `packages/provider-bridge-acp`, which is bundled into the SDK's `dist/provider-bridge-acp*.js` and `bundled-types/`. The SDK version was not bumped. get-bb#6186's own CI passed because 0.6.75 was not on npm when it ran, and the guard only compares against published versions. Main now builds different content under a published version, so every later main and PR run fails the guard. ## What changed `node scripts/bump-plugin-sdk.mjs --patch`: `packages/plugin-sdk/package.json` and `packages/domain/src/plugin-sdk-version.ts` move from 0.6.75 to 0.6.76. ## How you verified - `node packages/plugin-sdk/scripts/check-npm-version-guard.mjs` after `turbo run build --filter=@get-bb/plugin-sdk`: PASS (0.6.76 is not on npm yet). - `node .github/workflows/check-plugin-sdk-version.mjs` against origin/main: "Plugin SDK surface unchanged." > AGENT GENERATED 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Human comments
## What was wrong
About two thirds of recent fork pull requests (47 of 72 since Oct 19)
referenced no issue, including feature and UI changes that
CONTRIBUTING.md says need sign-off on an issue first. Nothing enforced
this: the PR gate checked only contributor approval and "Allow edits by
maintainers". Its close comment for authors who weren't approved was
also hard to follow ("now closes on its own", "can reopen"). The
`needs-approval` label it tried to add does not exist in the repo, so
that `gh pr edit` failed silently every time.
## What changed
- `.github/workflows/pr-gate.yml`: on `opened` and `reopened`, PRs from
authors who are not OWNER/MEMBER and not bots must close an open issue
in this repo (GraphQL `closingIssuesReferences`). Failing PRs are closed
and labelled `needs-issue`. `synchronize` skips the issue check, so
maintainers can push to contributor branches without closing them. A new
`edited` trigger reopens a closed `needs-issue` PR once an approved
author links an issue (retrying the lookup a few times for link lag).
The close comment now lists each requirement the PR misses (approval,
linked issue, maintainer edits), with the next step for each. The gate
creates the `needs-approval`/`needs-issue` labels on first use.
- `CONTRIBUTING.md`: lists the three requirements up front, adds a
Linked Issue section that explains why the issue is required, and tells
bug fixers to file and link an issue first.
- `.github/PULL_REQUEST_TEMPLATE.md`: marks `Fixes #` as required for
outside contributors.
- `packages/scripts/test/pr-approval-workflows.test.ts`: follows the
renamed gate step and new comment text, and adds a case table for the
issue check (unlinked/linked, members, owners, `synchronize`, and the
`edited` reopen path).
Open PRs that already lack an issue are only affected if someone reopens
them. A PR the workflow reopens with `GITHUB_TOKEN` does not start new
CI runs, but its existing head checks remain.
## How you verified
`pnpm exec turbo run test lint typecheck --filter=@bb/scripts` passes
(257 tests). Extracted the step script from the workflow with js-yaml
and ran it with a stubbed `gh` through 12 scenarios: new author with and
without an issue, approved author with and without an issue,
`synchronize` (no issue check), member, bot, edits off, `edited` on an
open PR (no-op), and `edited` on a closed `needs-issue` PR when the
issue is now linked (reopens), still missing (four lookups, no action),
and when the author is not approved (no action). Checked that the
labels, the close/reopen calls, and the comment text matched in each
case. The `edited` event on closed PRs has not been exercised on GitHub
yet.
> AGENT GENERATED
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
added 25 commits
October 25, 2026 06:57
test-change-reason: assert the runtime member against the source hook so removing the retired member cannot silently update both actual and expected values.
test-change-reason: formatting-only follow-up to keep the strengthened runtime-member assertion in the repository formatter style.
test-change-reason: formatting-only follow-up for the already-reviewed batch tests and scroll implementation; no assertions or behavior changed.
test-change-reason: assert the keyed-query plugin id as the public literal so changing the source constant cannot change the test expectation with it.
test-change-reason: isolate inheritedParentReasoningLevel from child override, last execution, and project defaults
test-change-reason: the prior scenario measured movement before touchend and did not exercise momentum after release.
test-change-reason: malformed known targets may follow the connect-time device-handoff token; the assertion allows that token and rejects every extra protocol message.
test-change-reason: restores the no-growth correction case that 9c473e0 rewrote; both sequences are now covered.
test-change-reason: adapt the restored fork case to the scroll event dispatched by the windowed-list correction after remount.
test-change-reason: advance past the ordinary scroll settle interval so the case specifically covers iOS momentum after touchend.
test-change-reason: advance beyond the ordinary settle interval and inject a scroll while a post-touchend layout correction is pending.
This was referenced Oct 26, 2026
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.
Summary
Integrate upstream desktop-v0.47.1 into the albrand/bb fork while preserving fork behavior across the moved composer, timeline, CLI, protocol and PR-status paths.
Changes and value
The merge keeps upstream commit
5e1acd765039e874643f34c4d35409e58484e408and merge commita433b5b4. Follow-up commits restore fork timeline easing and reading-position behavior, composer settings and history search, CLI coverage, protocol handling, and PR-status refresh behavior. The host daemon protocol is253at this sync head; PR #278 owns254at its rebase.Decisions
gh-only recheck gates remain. A rejected thread event lookup still refreshes and invalidates.--usage, token/child summaries, partial-history metrics, summary-route fallback, and status alias. Keep the exact-once timeline request assertion. The corrected fixture values and per-turn totals are covered in the test.historycontract. Its first-party SDK field isinternal_DO_NOT_USE_promptHistorySearch;plugins/composeris its only caller and is listed indocs/api_to_audit.md.typescript-7is not restored: upstream Typecheck with tsrs instead of TypeScript 7 tsc get-bb/bb#5861 retired the inherited alias, and fork commit24daa71da2added only the Ajv dependencies, which remain.Fork-loss disposition
All 32 exact-head fork-loss audit files are classified: 18 kept (including moved or reworded behavior) and 14 restored or ported. No fork behavior is classified as upstream-superseded. The row-by-row disposition is
296-part-c-fork-loss-classification-65e00d2344.md; line candidates and destination matches are296-fork-loss-audit-65e00d2344.txtand296-fork-loss-destinations-65e00d2344.txt. The deleted scroll suite is mapped case by case in296-scroll-preservation-coverage-map-65e00d2344.md. The fork feature inventory passes at this head:296-feature-check-65e00d2344.log.No-regression checks
bb thread list296-final-no-regression-65e00d2344.log296-final-no-regression-65e00d2344.log296-final-no-regression-65e00d2344.logbb device copy/bb device open296-final-no-regression-65e00d2344.log296-final-app-65e00d2344.log296-final-thread-list-65e00d2344.log296-final-server-regressions-65e00d2344.log296-final-prstatus-65e00d2344.log296-final-composer-65e00d2344.log;296-final-app-65e00d2344.log296-final-cli-thread-show-65e00d2344.log296-final-provider-parity-65e00d2344.log296-final-install-65e00d2344.log296-final-typecheck-65e00d2344.log;296-final-app-lint-65e00d2344.log;296-final-format-65e00d2344.log296-real-chromium-65e00d2344.log; QA gate296-real-chromium-qa-gate-65e00d2344.log296-mutant-*-65e00d2344.log296-final-shipped-skills-quality-65e00d2344.log;296-final-native-activity-questions-65e00d2344.logRound-4 per-item checklist:
296-checklist-65e00d2344.md. The exact-head evidence bundle includes the fork feature inventory/checker, remerge audit, scroll-test base-to-head diff, all raw test logs, and every RED mutant log.CI
CI run 40163993596 passed on
65e00d23445116e5cf7be1e42c0280665232bf6a; all non-skipped jobs passed. Provider Model Listing and Node Compatibility Smoke were skipped by workflow conditions. PR Gate run 40163991985 passed.Ticket
No issue: fork sync card SYNC-0471; issues are disabled on albrand/bb