[SUPERSEDED by ymichael/bb#66] feat(app): sidebar Back/Forward route-history controls#1
Closed
brsbl wants to merge 293 commits into
Closed
Conversation
Dragging the Threads/Projects sections in the sidebar looked janky from three separate dnd-kit issues: - Sections scaled/zoomed because `CSS.Transform.toString` emits the scaleX/scaleY dnd-kit computes for differently-sized items. Switch to `CSS.Translate.toString` so only translation is applied. - A dragged section vanished behind the other section's rows: each section's sticky header creates its own stacking context (`isolation: isolate`), so without an elevated z-index the overlapped rows painted on top. Lift the dragged section (and project rows) with position: relative + zIndex while dragging. - Reordering required over-dragging because `closestCenter` keys off the dragged element's center, which sits far below the cursor for the tall Threads section. Use a pointer-first collision strategy so the swap triggers where the cursor actually is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… pnpm store electron-builder's npmRebuild rebuilt better-sqlite3 through the workspace symlink into the shared content-addressed pnpm store, flipping the binary to Electron's ABI and SIGKILLing every plain-node consumer (the @bb/server test suite) until it was rebuilt back. Disable npmRebuild and fetch the Electron prebuild into the packaged copy in the existing afterPack hook instead, so the shared store keeps the node-ABI binary and only the packaged app gets the Electron-ABI one (the server runs via ELECTRON_RUN_AS_NODE, so it needs Electron's ABI). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The runbook only covered the npm package, so releases shipped bb-app to npm while the desktop app (published by the separate build-desktop.yml workflow) stayed on the previous version. Document that a release publishes both outputs from the same commit, add the build-desktop dispatch + verification steps, and cover the desktop-specific failure modes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s + status app + bb app CLI Per-thread Apps system foundation (no frontend yet): apps/<id>/ layout (manifest.json + served assets/ + file-based data/ + logo.*), daemon-owned asset serving + data/ watcher, server routes (entry/assets/icon/data CRUD/ message + WS relay on thread:<id>:app:<id>:data), injected window.bb bridge (capability-gated, advisory for v1), default seeded `status` app, and bb app new|list|open|rm. STATUS remains in place; teardown is Phase 3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ch + app tabs Frontend for the Apps system (additive; STATUS UI left intact for Phase 3): - App tab kind + pinned `status` app tab in the secondary panel - "+" launcher listing the thread's apps with resolved icons - Unified app + file search (Apps + Files sections) - App tab content branches on entry kind: HTML → iframe + injected window.bb, Markdown → static react-markdown - Live data updates via window.bb.data.onChange (with replay/unsubscribe lifecycle guard), shared markdown asset URL transform, centralized STATUS_APP_ID Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the legacy STATUS system end to end (no backwards compat) and makes the Apps system the only path: - Delete STATUS server routes/injection/status-data/status-version, the WS hub status method, the daemon status commands (HOST_DAEMON_PROTOCOL_VERSION→26), the frontend manager-status surface + useThreadStatusVersion, and all STATUS-specific tests. - Rewrite manager/thread system prompts + instructions to the apps model (apps/status, data/state.json, window.bb, bb app CLI). - bb-guide-status-state → bb-guide-app (bb guide app); fold bb guide styling into it; regenerate templates. New managers seed only the `status` app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Guide for migrating an existing manager/thread off the removed STATUS surface (STATUS.html/.md/STATUS folder + STATUS-data) to a `status` app: target layout, manifest, state consolidation into data/state.json, the bbStatusState→window.bb and bbThreadTell→window.bb.message API mapping, agent/maintainer direct-fs writes, and cleanup. Points to `bb guide app` for the full reference. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… on unmatched Replace `/api/v1/threads/:id/apps/:appId/assets/*` with the canonical flat `/api/v1/threads/:id/apps/:appId/*` wildcard → filesystem `apps/<appId>/assets/<*>`. The `assets/` directory is an internal storage detail; URLs no longer expose it. Nested paths preserved. Add an `/api/v1/*` JSON-404 guard so unmatched API URLs return JSON 404 instead of falling through to the bb SPA shell HTML. Improve the fresh-manager missing-manifest path to return `app_not_provisioned` with a rebuild hint (root cause was stale binaries, not a real seeding bug). Update default status template HTML, file-content-urls, migration guide, bb guide app, tests, and regenerated templates to flat URLs only. No back-compat alias kept — apps system isn't shipped yet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seedManagerThreadStorage previously early-returned after copying from
a user-authored manager template, skipping the bundled apps/status
fallback entirely. Fresh managers with any user template on disk would
404 on apps/status because the bundled manifest never got seeded.
Now the bundled set (apps/status/{manifest.json, assets/index.html,
data/state.json}) is always overlaid on top of any user-template copy,
regardless of which template is active. User-authored files still win
because they are copied first and the bundled overlay uses
writeFile(..., flag: "wx") which refuses to overwrite. Missing
non-default templates still warn but no longer skip the seed.
Tests refactored around expectBundledStatusAppSeeded() helper; added
"user-authored files win over the bundled overlay at the same path"
case asserting both behaviors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full-width button below the search input in the secondary-panel New
Tab. Clicking it prefills the thread composer's draft via
usePromptDraftStorage.setDraft (text + clears attachments — full
replacement, not just text), focuses the composer with the [NAME]
placeholder selected, and dismisses the New Tab panel.
Replacing a non-empty draft prompts a browser confirm; cancel
preserves the existing draft (text + attachments), accept replaces
both. Reuses the existing prompt-draft store; no parallel state.
Plumbs a stable textareaId from ThreadDetailPromptArea through
FollowUpPromptBox so the button can focus the composer reliably.
Prefill template references `bb guide app`, the apps/<id>/
{manifest.json, assets/index.html, data/state.json} layout, and the
window.bb.data / window.bb.message browser APIs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Visual: bigger app rows with rounded tile-style icon container, name
+ id caption, uppercase tracking-wider section headers ("APPS",
"FILES"), token-driven hover/active/focus states that work in dark
mode. Create App moves from a full-width outline button below the
search to a dotted "+ Create App" tile at the end of the Apps list,
mirroring app rows with a "Describe an idea, the manager builds it"
hint line. The Apps section now always renders so the tile is
reachable immediately on open.
Prompt: prefilled template drops the [NAME] / [DESCRIBE…] placeholders
in favor of an end-of-text cursor landing. All apps-system context
lives at the top (manifest layout, window.bb usage), with a single
line pointing at `bb app new` for default styling, and a trailing
`What I want:` blank line where the user starts typing. Composer
cursor parks at end of text — no placeholder hunt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The blank template now scaffolds a bb-native HTML surface instead of a barebones one-line demo. New apps get the bb default styling head verbatim from `bb guide styling` (Google Fonts links for Inter + Fira Code, the full `:root` oklch token block, dark-mode overrides, body / .panel defaults), with scaffold-only classes (header chip, title, timestamp slot, section title, task-list rows, pills) layered strictly after the guide block. Visual structure mirrors the status task-list dashboard at a stripped-down plain-HTML scale, with a placeholder line — "Ask your agent to customize the status app how you please." — inviting the user to ask their agent to build on top. Name interpolation goes through a shared escapeHtmlText helper now extracted into @bb/domain and reused from the previous module-local copies in apps/desktop (local-view, log-viewer). XSS test covers `<script>`, `&`, `"`, and `'` in the app name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small visual fixes to the secondary-panel New Tab launcher: 1. Unified the icon containers across Pet / Status / Create App tiles via shared LAUNCHER_TILE_ICON_CLASS constants. All three now share a size-9 raised frame with a size-5 glyph; the only intentional variant is the dashed inner border on Create App. The frame stays constant, only the asset varies. 2. Hide redundant id captions when the app id matches a slugified display name. `pet` / `status` captions disappear; when the id genuinely differs from the name, the caption renders in font-mono so it visibly reads as a developer identifier. 3. Drop the duplicate `title` tooltip on Create App (body hint stays) and the dead `disabled` prop on CreateAppTile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The default status app shipped to fresh managers was hard-coded to a
PR/worker-review workflow (PULL REQUESTS + WORKERS + "Message the
manager" sections, a `pr` landing-mode chip). That assumed one
specific routine; a brand-new bb user has neither. Replace with a
bb-styled generic starting-point dashboard that matches the
`bb app new --template blank` output and invites customization:
"Ask your agent to customize the status app how you please."
Mechanism: extract buildBlankAppIndexHtml({ name }) into a new
apps/server/src/services/threads/blank-app-scaffold.ts. Both
`bb app new --template blank` and `--template status` paths plus the
bundled manager-template overlay now generate index.html through one
generator (only the manifest icon differs). Delete the static
default-template/apps/status/{assets/index.html, data/state.json}
that pinned the old PR-review content.
User-authored manager templates still win over the bundled seed via
the existing flag:"wx" overlay semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A single invalid app manifest in a thread's apps/ directory used to fail the entire GET /api/v1/threads/<id>/apps list response with 422, taking down the whole secondary-panel launcher. The list route now shares a single readAppManifest parse step with the detail and serve routes; zod validation failures on an individual manifest log a structured warning (appId, manifestPath, summarized issue list at WARN level, full issues at debug) and that app is skipped in the list response. Detail and serve routes still surface the failure as a new invalid_manifest 422 error code (added to @bb/server-contract errors) with a one-line message — full zod issues stay in the server log, not on the wire. Tests cover: partial-success list (valid + invalid → 200 with only the valid summary + logger warned); detail returns invalid_manifest 422; serve returns invalid_manifest 422 (not the SPA shell fallthrough). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The thread/manager header sits in an Electron title-bar drag region, so the "..." ThreadActionsMenu trigger was swallowed as a window drag. Apply the existing MACOS_WINDOW_NO_DRAG_CLASS to the header trigger only (sidebar usage unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The native WebContentsView bounds were driven only by an async renderer
pipeline (ResizeObserver/window resize -> rAF -> IPC -> setBounds), so during
a synchronous OS window resize the view trailed the window edge by >=1 frame +
an IPC hop. Electron 41's WebContentsView has no setAutoResize.
Introduce a resize-invariant layout descriptor ({left, top, rightInset,
bottomInset}) the renderer emits only on layout-shape changes. The main process
caches it per view and, on the host BrowserWindow will-resize/resize events,
re-projects bounds from getContentBounds() and calls setBounds() synchronously
in lockstep with the OS resize. Preserves the prior keep-alive (#94),
panel-resize (#96), and containment (#128) behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delete manager templates end to end — server route + storage-template service, host-daemon command handler, the domain/server/host-daemon contract types and fields (templateName / managerTemplateName / ManagerTemplateName / GET /manager-templates / host.list_manager_templates), the bb manager CLI options, the frontend ManagerTemplatePicker + compose-view usage, the bb-guide-manager-templates chapter (regenerated), and all related tests. Managers are still created normally; they now start from default/empty thread storage with no template selection or seeding. Existing on-disk ~/.bb/manager-templates/ user data is left intact and is now unused. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…degen Replace the static single-file app scaffold with a full Vite + React + TypeScript Todo app that showcases the window.bb SDK and live data-binding: - editable source/ + pre-built public/ (served web root, flat refs), copied into each new app by the server (excludes dev-only screenshots/report dirs); - an add-todos skill + README; - SDK types are GENERATED from @bb/sdk (self-contained ambient window.bb d.ts, no imports) via packages/sdk/scripts/generate-app-globals-dts.mjs, with a drift-guard test so the vendored template types can't silently diverge. Polished UI pass (lucide, bb oklch tokens, responsive); Live-pill + bound-data showcase removed; error notices retained. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
electron-builder's default node_modules .d.ts pruning stripped the app-scaffold-template's generated source/src/bb-sdk.d.ts from app.asar, so apps created in the packaged desktop app scaffolded without their window.bb types. Add a dedicated files FileSet for the template tree so it ships verbatim, without relaxing .d.ts pruning elsewhere. Regression test added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a typed realtime subscription API to @bb/sdk: bb.on({event, ...scope,
callback}) returning an idempotent unsubscribe, across thread/project/
environment/host/system (config/apps) / app / app-data (changed+resync) plus a
realtime:connection lifecycle event. One websocket per SDK instance with
ref-counted subscriptions, reconnect + resubscribe, and contract-derived
payloads (no SDK-only mirrors). window.bb.data.onChange now rides the shared
socket with full Phase 1 parity (subscribe-before-replay, buffer, version
dedupe, resync + reconnect replay). Server broadcasts app:changed alongside
system:apps-changed; app-data stays per-application.
Hardened after dual review: reject orphaned socket-ready promise on
close-before-open; connection listeners are observers (no socket ownership);
reset reconnect intent on idle close (no double-replay); reconnect replays
before emitting connected; fail-safe outgoing broadcast validation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Local electron-builder runs previously forced identity=null and CSC_IDENTITY_AUTO_DISCOVERY=false unless the full CI secret set was present, so every locally packaged bb.app shipped the prebuilt Electron's invalidated adhoc linker signature (spctl: "code has no resources but signature indicates they must be present"). macOS provenance-tracks such bundles, which forces syspolicyd to evaluate and journal every exec in the app's process tree — observed pegging syspolicyd at ~380% CPU and stalling process launches system-wide. Signing now resolves to one of three modes: - environment: full CI secrets — sign with the provided cert + notarize (published-release path, unchanged) - keychain: no secrets — sign via keychain auto-discovery, skip notarization (locally built apps never carry the quarantine xattr, so notarization is unnecessary; a valid Developer ID seal is what lets Gatekeeper cache assessments and keeps fresh launches out of the provenance sandbox) - disabled: no secrets and CSC_IDENTITY_AUTO_DISCOVERY=false — explicit unsigned build (CI workflow-artifact-only path, unchanged) Validated: packaged app deep-verifies strict with sealed resources (6105 files), spawn-helper and better_sqlite3.node carry the Team ID, and the hardened-runtime app fully boots (bridge/server/daemon all re-exec the signed binary via ELECTRON_RUN_AS_NODE). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a customModels key to the bb-app managed config so users can register
model ids the provider catalog doesn't offer (e.g. non-public preview
models):
{ "customModels": [{ "providerId": "claude-code", "model": "<id>",
"displayName": "<label>" }] }
The server appends them to the daemon-reported model list in
/system/execution-options — including when the provider catalog fails to
load — with catalog metadata winning on model-id collision (selected-only
entries are promoted rather than shadowed). Reasoning ladders are
per-provider: claude-code gets the full low-max ladder, while codex and pi
cap at xhigh since both reject "max" provider-wide. providerId is
validated by agentProviderIdSchema at the schema boundary so the launcher
and server agree on validity and reload 422s name the offending field.
The launcher preserves customModels across managed-config writes
(pruneManagedConfig previously dropped unknown keys) and surfaces entries
in `bb-app config list`. Reasoning-effort constants move from
@bb/agent-runtime to @bb/domain so the server can build AvailableModel
entries without depending on the runtime package.
No frontend changes: config-changed notifications already invalidate the
execution-options queries, so open pickers refresh on reload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Translate the agent SDK's task event family (task_started/task_updated/
task_progress/task_notification) into a new backgroundTask thread item:
dynamic workflows render as a live timeline row with phase groups and
per-agent progress, folded from workflow_progress delta batches and
throttled adapter-side. Progress/completion events are thread-scoped so
tasks that outlive their spawning turn stay pagination-safe; timeline
windows backfill the latest task state for in-window items.
Lifecycle ownership: the adapter settles open tasks on thread/resume and
provider process exit; the server settles dangling items when a daemon
session re-registers with a new instance id; superseded progress rows are
pruned keep-latest-while-pending.
Adds the "ultracode" reasoning level (ranked between xhigh and max,
reconciling down to xhigh on model switch) for xhigh-capable claude-code
models. The server-owned workflowsEnabled policy flows explicitly through
the daemon contract (protocol v31); the adapter decomposes ultracode into
effort "xhigh" plus flag-tier settings {enableWorkflows, ultracode}.
Also: status:null now completes dangling contextCompaction items, and
session_state_changed/tool_progress/tool_use_summary are classified so
they no longer surface as provider/unhandled debug rows. Fixtures are
captured from a real workflow run driven through the agent SDK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes from a 99-agent adversarial review of e0b03c6d7 (41 raw findings → 28 confirmed after 3-lens verification): Client lifecycle (packages/sdk/src/realtime-client.ts): - connectSocket constructs the socket before creating the socket-ready promise, so a sync throw (bad URL/factory) can no longer orphan a pending promise into an unhandled rejection; the reconnect timer body also contains sync throws instead of crashing the process - onclose always records reconnect intent and emits 'disconnected' even while a stale backoff timer is pending, and a listener-driven connect cancels the pending timer — previously a close during backoff silently skipped the reconnect replay (lost app-data events, Phase 1 parity regression) and the orphaned timer escalated the delay while connected - idle close that cancels a pending reconnect now emits a terminal disconnected event so observers don't wait forever on a promised retry - dispatch iterates a listener snapshot: listeners added inside a callback no longer receive the in-flight event (preserves subscribe-then-replay ordering); replay delivery loops re-check listener.active so unsubscribe stops deliveries immediately - buffered-event flush uses a per-path last-version rule: when the replay snapshot already delivered a path's final buffered state, all buffered events for that path are skipped — the old exact-match dedupe could re-deliver an older value last, leaving consumers permanently stale (versions are content hashes, so ordering cannot disambiguate) - reconnect now notifies app-data:resync subscribers (broadcasts may have been missed) before replaying and emitting reconnected - late realtime:connection observers receive the current state as a microtask snapshot instead of observing nothing until the next transition Version-skew tolerance (domain/server-contract): - changed-message and ThreadChangeMetadata types are now z.infer-derived from their schemas — schema/type drift is a compile error instead of a silent fail-closed realtime blackout at the hub's outgoing gate - new lenient inbound schemas (changedMessageLenientSchema, serverMessageLenientSchema): clients strip unknown fields and filter unknown change kinds instead of dropping whole messages when talking to a newer server; SDK and web app now parse inbound traffic with them (the strict schemas keep guarding the server's outgoing boundary) - changes arrays are readonly in the message types; entity events are delivered as shared objects with mutation blocked at compile time - dropped the dead optional id from AppChangedMessage (never emitted, never consumed; scaffolded apps could only write filters that never match) and documented app:changed as a global app-list signal Node support (packages/sdk/src/node.ts): - the node transport ships a default websocket factory: global WebSocket on Node 22+, the ws package on supported Node 20 — bb.on and bb.data.onChange no longer throw out of the box on Node 20 - BbRealtimeSocket is now a minimal runtime-agnostic shape; default factories adapt the environment socket (browser global, node global, ws) instead of requiring DOM event types API surface cleanup: - *RealtimeOnInput types renamed to *RealtimeOnArgs and on(input) to on(args), matching the SDK-wide Args convention (pre-release rename) - BbSdk and InjectedAppWindowBb derive on() from the shared BbRealtime interface instead of triplicating the signature - CreateCurrentAppDataAreaArgs requires apps/realtime (the only caller always passed both); deleted the unreachable onChange runtime throw - shared cloneJsonValue helper replaces two diverged copies; app-data events are cloned once per delivery instead of twice - realtime URL derivation preserves a path-prefixed baseUrl (mirrors the HTTP transport); same-origin browser derivation still uses /ws - resolveApplicationId reuses requireCurrentApplicationId Frontend: - removed the unreachable app:changed cache registry (the SPA never subscribes to the app entity; system:apps-changed remains the canonical app-list invalidation path) and replaced the bypass test with one pinning the no-op - ws.ts logs dropped inbound messages instead of swallowing them Tests (+54): buffered-flush deliver/stale-path cases, negative id and prefix scope filtering, all previously unexercised dispatch paths, listener exception isolation, unsubscribe-during-dispatch/replay, backoff growth/cap/reset, close-during-pending-backoff, resync-on- reconnect ordering, late connection-observer snapshot, lenient parsing, hub fail-safe validation drop, notifySystem/notifyHost delivery, full change-kind schema-gate sweep per entity, and notifyGlobalAppsChanged end-to-end through real websockets. Known debt (deliberately not addressed here): BbRealtimeClient and the SPA's WebSocketManager remain two parallel realtime implementations; consolidating them is a standalone refactor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ider turn watchdog
The idle watchdog killed healthy workflow turns ("No provider activity
for 906s after item/completed"): item/backgroundTask/progress was added
to the activity list, but those events are thread-scoped (turn_id NULL)
and the candidate query correlated activity strictly on the anchor
row's turn_id, so a streaming workflow was invisible to it.
The anchor is now the newest event that is either scoped to the active
turn (the latest turn/started) or a thread-scoped background task
event. The NULL-turn arm is deliberately restricted to the
backgroundTask family so thread-scoped provider/error noise cannot
defer reaping a wedged turn. item/backgroundTask/completed joins the
activity list — load-bearing, because progress rows are pruned the
moment the completed row lands, which would otherwise false-fire the
watchdog right after a successful workflow.
Guard hardening from adversarial review: active threads with no
turn/started yet are excluded in SQL (a NULL activeTurnId would throw
in row parsing and abort the whole sweep tick); the turn/completed,
pending-interaction, and started-at correlations all key off the
active-turn subquery instead of the anchor's turn_id; OR fragments are
self-parenthesized (drizzle's and() does not wrap raw fragments);
empty-string providerThreadId anchors fall back to the latest real id;
and the persisted lastActivityEventType is a plain string so
activity-list edits never make stored watchdog events unparseable.
The regenerated SDK browser bundle also catches up on the workflow and
reasoning-level domain schemas from a0bd295a7, which shipped without a
bundle regen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s on disk
Rebuilding an app (or editing public/ files directly) never reloaded the
open app surface. Two breaks upstream of the SPA's existing reloadToken
mechanism: the daemon's apps-root watcher silently dropped every path
under <app>/public/ (no classification branch), and even reported storage
hints were gated behind an app-list signature diff that content edits
never alter.
Add a per-app content-changed signal end to end:
- host-watcher: classify <app>/public/** as application-content-changed,
deduped to one event per app per flush batch; source/ stays
unclassified (nothing served changes until a build writes public/).
- host-daemon: dispatch the new observed kind and send
{type: "application-content-changed", applicationId} over the daemon
WS, with per-app offline buffering flushed after reconnect.
- domain/server: APP_CHANGE_KINDS gains "content-changed"; app changed
messages carry an optional id (absent = list-level, present =
app-scoped); hub.notifyAppContentChanged broadcasts it, and notifyApp
is narrowed to the new AppListChangeKind subset so an id-less
content-changed is unrepresentable at the producer.
- SPA: subscribe to the "app" entity and invalidate only the changed
app's detail + markdown-preview queries via REALTIME_APP_CHANGE_REGISTRY,
so the detail refetch bumps dataUpdatedAt and busts the iframe
reloadToken without reloading other open apps.
- sdk: document the new app:changed semantics; regenerate the scaffold
bb-sdk.d.ts and browser runtime bundle.
Also extract the shared mock hub socket test helper that was previously
copy-pasted across four server test files.
Validated end to end against a live dev instance: touching
public/index.html broadcasts {entity:"app",id,changes:["content-changed"]}
within the watcher debounce; touching source/ broadcasts nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ntime already hosts A thread.start/turn.submit whose freshly scanned injected-skill catalog hash differed from the loaded runtime's hash always forced a runtime replacement, which threw whenever the environment had active threads or open terminals. An agent that installed a skill mid-turn (e.g. building an app with a skills/ dir) therefore bricked its own thread: every subsequent message failed with "already has an active runtime with injected skill catalog X; requested Y" and was dropped, and the thread flipped to error status while the daemon-side turn kept running. Thread commands now pass their targetThreadId down to ensureCompatibleEntry; when the entry already hosts that thread and has active runtime work, the stale catalog is reused (with a warn log) and the refresh is deferred to the next launch on an idle environment. Idle entries are still replaced as before. The remaining conflict case (environment busy with other threads or terminals) now throws a typed SkillCatalogConflictError mapped to the new workspace resolution failure code "skill_catalog_conflict" instead of surfacing as "unknown". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ersarial review Follow-up to the stale-catalog deferral fix, addressing review findings: - Keep the about-to-be-active catalog when pruning staging dirs during a runtime replacement. The cleanup keep-list was built only from loaded entries after the replaced entry was deleted, so the freshly staged new catalog was removed before the replacement runtime bound it — the refreshed runtime launched with skill roots pointing at a deleted directory (pre-existing, but the deferral fix made idle replacement the designed refresh path). - Defer the catalog swap for any busy runtime a thread command targets, not just runtimes already hosting the thread. Keying the guard on hosting left the original brick reachable: a terminal-first entry (no catalog, open terminal) or a sibling thread resuming after daemon restart still failed and dropped the message. SkillCatalogConflictError now only guards the no-target invariant. - Warn once per requested catalog per entry instead of on every command while the environment stays busy. - Stop CreateEntryArgs advertising ignored resolution-time fields (injectedSkillSources, targetThreadId). - Pin the full defer-then-refresh-once-idle sequence and staged-catalog survival in tests; the prior reuse test compared an entry's hash with itself. - Regenerate the SDK browser bundle for the skill_catalog_conflict contract code added in the previous commit. Known follow-ups (not in this change): the deferral is invisible to the server (no catalog hash in command results, no idle-transition reconciliation owner), and thread commands remain unlaned so a catalog-bearing command racing an in-flight resume can still replace the runtime mid-resume. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ture The unarchive-resume fixture landed on main without the new required turn.submit option introduced by the workflows commit; align it with the sibling fixtures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
plans/claude-code-workflows.md was swept into the manager-templates removal commit by an over-broad add during conflict resolution. Plans live in thread storage, not the repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t, honest contracts, d.ts codegen Adversarial-review fixes for the apps platform surface: - extract scaffold machinery out of routes/apps.ts into services modules; fs.cp filtered copy applies exclusions at every depth; build-time template copy reuses it so dist ships no dev artifacts; README $-pattern safe - serve the window.bb runtime from a content-hashed immutable endpoint instead of inlining ~865KB into every app HTML response; bundle is regenerated from the real module graph (no regex+Function eval) with a drift-guard test against the committed artifact - delete decorative AppRuntimeBootstrap capabilities/dataUrl/messageUrl; appId/applicationId required on the injected window.bb contract - app d.ts codegen emits BbRealtime (window.bb.on typechecks in new apps), self-containment guards actually fire, drift test typechecks generated output; template source typechecked, source<->public drift guard, useTodos stale-snapshot race fixed, todo helper renamed for accuracy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e entry - delete the ~240-line dead CLI HTTP client; port its tests to the SDK transport that production actually runs - typed BbHttpError (status + server code) replaces 'HTTP 404:' message sniffing; missing app vs missing data path report correctly again - importing @bb/sdk/node no longer eagerly loads CLI config — bb --help/ --version/guide work without BB_SERVER_URL; one canonical package entry (package.json '.' and vitest alias agree) - status area uses domain unions instead of plain strings; dead area exports removed; CLI consumers (guide, pending-todos, context-env) stop shadowing SDK types and building network clients for local work Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- fix stale 'connected' observer state when the last targeted listener unsubscribes while the socket is CLOSING; fix reentrant connect during the 'disconnected' emit corrupting reconnectDelayMs; regression tests for both plus the synchronous-throw path through on()/activateListener - collapse four copy-pasted dispatch methods and eight listener-record interfaces into one generic keyed implementation; named aliases replace inline Extract<> signatures - hub.notifyApp signature matches reality (single change kind); hub tests assert distinct outcomes per kind; integration test reuses the SDK ws adapter; lenient/strict server-message schema drift guard; stale daemon-protocol comment removed Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…watchdog + reasoning-ladder single source - lost background tasks now settle on every realistic path: daemon restart (regardless of previous-session status), lease expiry, and disconnect grace — mirroring pending-interaction reconciliation; settling no longer flips already-completed workflows to interrupted - late thread-scoped backgroundTask events no longer stretch the spawning turn's source range (turn-summary expansion 400 fix) with projection regression tests; error-only workflow rows are expandable; shared settled-state predicate and timeline row types deduped - backgroundTask progress rows fetched by targeted query instead of loading all rows and filtering in JS; timeline window backfill covered by tests and reuses mergeStoredEventRowsById - workflowsEnabled stays required downstream of the server boundary and the default policy is tested with true; watchdog thread-scope list derived from threadOnlyThreadEventTypes; SQL fragments deduped; custom-model reasoning ladders reuse the server policy table; dead taskType contract carry removed Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- delete the dead first-boot PREFERENCES.md host read (and its mode plumbing through thread-send/queued-messages/nudge sweep) that could never find content after seeding removal; drop the dead request field - historical host.list_manager_templates command rows stay in the read-only prune cohort (test on in-memory sqlite) - inline the pass-through ManagerSlot wrapper; drop the stale 'may already exist from user templates' welcome-template sentence and the test pinning it Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…at can fail - browser-view layout projected in a consistent coordinate space so docked DevTools no longer misprojects the native view; will-resize uses the in-flight bounds instead of stale getContentBounds() - attach/setBounds accept the legacy bounds shape under shell/server version skew; one shared collapsed-layout sentinel in the contract; open-tab payload uses the contract type; dead clamp export removed - BrowserTabDeck resize test flushes rAF and fails on revert; ThreadActionsMenu test exercises the actual no-drag fix; no-drag class gated on usesDesktopChrome Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ering - remove unreachable skill_catalog_conflict contract code; thread-brick regression test drives the real command path (targetThreadId from a thread command); staging-survival assertions check the real catalog hash instead of masking null with ''; log-throttle state moved off the exported RuntimeEntry - ServerConnection's three parallel recoverable-message buffers unified behind one keyed pending buffer (existing tests pin behavior) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bb previously had no repo-shipped skills: building-bb-apps existed only as a hand-authored file in one machine's data dir. This adds a built-in skill source so the app-builder skill ships with every install. - Contract: new "builtin" injected-skill source variant (applicationId null); protocol 31 -> 32; dedicated union test. - Content: building-bb-apps SKILL.md checked in under apps/server/src/services/skills/builtin-skills/, copied to dist/builtin-skills by a new build step (mirrors the app scaffold template copy), resolved beside the module in both src and dist layouts. - Discovery: the server scans the builtin root alongside data-dir and app roots. A user data-dir/app skill with the same name overrides the builtin (logged at info); collisions among user sources keep the existing drop-all rule. builtinSkillsRootPath is resolved once at boot into ServerRuntimeConfig and passed explicitly. - Daemon staging is sourceType-agnostic and needs no behavior change; a staging test pins the builtin catalog entry. - Regenerated the SDK app-runtime browser bundle (embeds the contract schema). - TemplateKind cleanup: removed unused "skill_seed", added the actually-generated "system-message", replaced the kind cast with validation at decode. Known limitation (documented in resolveInjectedSkillSources): skill source paths are server-machine paths; daemons on additional hosts skip them with a staging warning, same as data-dir and app skills today. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add browser-style Back/Forward navigation to the left sidebar that moves through the app-shell route history. - New useAppRouteHistoryNavigation hook (app-route-history.ts) tracks the actual React Router entries visited while mounted (including duplicate same-URL pushes with distinct location.keys). PUSH appends and clears the forward stack, REPLACE updates the current slot, POP reconciles to a known entry by key and treats an unrecorded key as the app-owned history boundary. Back/Forward move by a real navigate(delta) that skips equal-URL slots so one click lands on the nearest visibly different route. - New SidebarHistoryNavigationControls renders two accessible icon buttons (Go back / Go forward) using the Button primitive + sidebar tokens, native disabled state, aria-hidden icons, and an onNavigate hook for closing the mobile drawer. - Wire the controls into AppSidebar's primary-actions area above New Thread / New Manager, passing closeOnMobile so the compact drawer closes after an enabled press. Placement keeps the controls out of the macOS titlebar drag region and hidden in the icon-collapsed sidebar. Tests cover initial disabled state, push/back/forward, duplicate same-URL skipping, forward-stack clearing, replace, native/unknown POP boundaries, the onNavigate contract, and sidebar placement with unchanged desktop chrome. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace inline types in function signatures with named aliases per AGENTS: - AppRouteNavigationType for reduceHistory's navigation-kind parameter. - NavigateToOptions for the test navigateTo helper's options argument. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
Author
|
Superseded by ymichael#66, which targets the up-to-date |
…lisions BrowserRouter labels unkeyed document-history entries "default", so a POP can share the mounted entry's key while pointing at a different URL. Two stale-state bugs followed: - The effect deduped on location.key, so a same-key POP was dropped entirely and never reduced. Dedupe on the location object identity instead (React Router hands out a fresh location per navigation). - The POP reducer reconciled by key alone, so it could snap to a recorded slot whose URL no longer matches. Require key AND normalized URL to match; otherwise treat the popped route as the app-owned history boundary. Adds a regression test with MemoryRouter entries that intentionally share the "default" key but have different URLs (verified to fail before this fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
Adds browser-style Back and Forward navigation buttons to the left sidebar that move through the bb app-shell route history. This is app-route history only — it does not touch the in-thread browser tab history in
BrowserTabContent.The controls live in the sidebar primary-actions area, above New Thread / New Manager (keeping them clear of the macOS titlebar/traffic-light drag region and hidden in the icon-collapsed sidebar).
What changed
apps/app/src/lib/app-route-history.ts— newuseAppRouteHistoryNavigationhook. Tracks the actual React Router entries visited while mounted, including duplicate same-URL pushes (distinctlocation.keys):PUSHappends after the current slot and drops the forward stack.REPLACEoverwrites the current slot (no extra Back entry).POPreconciles to a known slot bylocation.key; an unrecorded key is treated as the app-owned history boundary (stack resets to the current route) so the controls never step into unrecorded/off-app history.canGoBack/canGoForwardandgoBack/goForwardskip equal-URL slots and move by a realnavigate(delta), so one click lands on the nearest visibly different route (no fake no-op steps) while preserving router/browser state.apps/app/src/components/sidebar/SidebarHistoryNavigationControls.tsx— two accessible icon buttons (Go back/Go forward) built on theButtonprimitive + sidebar tokens, nativedisabled,aria-hiddenicons,type="button", matchingtitle/aria-label. Exposes anonNavigatecallback so the sidebar can close the mobile drawer after an enabled press. Owns its own row spacing.apps/app/src/components/sidebar/AppSidebar.tsx— renders the controls insideapp-sidebar-primary-actionsaboveProjectListActionButtons, passingcloseOnMobileasonNavigate. Existing top-reserve / macOS chrome behavior is untouched.Tests
SidebarHistoryNavigationControls.test.tsx(10 tests): initial disabled state, accessibility attributes, push enables Back only, Back→Forward URL + disabled-state updates, duplicate same-URL skipping (A→B→B: one Back lands on A, one Forward returns to B), forward-stack clearing (A→B→C, Back, push D), replace updates the slot, native POP reconciles to a known entry, unknown POP boundary, and theonNavigate(drawer-close) contract.AppLayout.test.tsx: controls render above the primary actions, Back before Forward, with exactly one sidebar toggle (desktop chrome unchanged). Existing chrome/resize tests remain valid.Validation
pnpm exec turbo run typecheck --filter=@bb/app— passpnpm exec turbo run lint --filter=@bb/app— passpnpm exec turbo run test --filter=@bb/app --force— 910 passed (139 files), including the new suitesLive browser UI verification was not run: no browser-automation tooling is available in this environment, and the only running server is the installed prod build, which does not include these worktree changes. Behavior is covered by the automated tests above.
🤖 Generated with Claude Code