Skip to content

Ship: global apps (storage/slug/public web root/skills/standalone route) + desktop browser fixes#59

Merged
SawyerHood merged 12 commits into
mainfrom
sawyer/ship-2026-06-03
Jun 3, 2026
Merged

Ship: global apps (storage/slug/public web root/skills/standalone route) + desktop browser fixes#59
SawyerHood merged 12 commits into
mainfrom
sawyer/ship-2026-06-03

Conversation

@SawyerHood
Copy link
Copy Markdown
Collaborator

Ships the sawyer-next batch (10 commits, rebased on current main, all validated green incl. real bundle builds).

Global apps

  • Global app storage — apps are global on the local host; <dataDir>/apps/<applicationId>/ is the source of truth (no install DB). Removed the old /threads/:id/apps/:slug routes and the built-in status app.
  • Human slug app idsapplicationId is a validated slug (^[a-z0-9][a-z0-9-]*$) = folder = manifest.id; optional name for display. bb app new derives a slug; duplicate ids 409.
  • public/ web root — apps serve their public/ folder as the app root (/api/v1/apps/<slug>/<path>, folder name not in URL); manifest.json + data/ private. Standard relative builds work with no <base>/rewriting.
  • Top-level Apps sidebar section + standalone /apps/:applicationId route (shared AppViewer) so apps open thread-independently.
  • Live app-list refresh — create/delete + watcher changes push a realtime event; no restart needed.
  • Global app skills injection — skills in apps/<slug>/skills/ and ~/.bb/skills/ inject into every agent runtime (codex skills/extraRoots/set, claude-code local plugin).

Desktop browser surface

  • Keep browser WebContentsView alive across thread switches (no flicker/reload).
  • Fix containment on resize — bounds resync on sidebar position shifts + clamped to the viewport.

Other

  • Per-thread secondary-panel open/closed memory.
  • Build fix: drop deleted default-template copy from the server build.

🤖 Generated with Claude Code

SawyerHood added 12 commits June 3, 2026 09:14
Apps are now global on the local host (filesystem source-of-truth at
<dataDir>/apps/<applicationId>/, manifest.id == applicationId, no install
DB). Adds global /api/v1/apps/* routes, BB_APPS_ROOT/BB_APP_* runtime roots,
app-session message target context, atomic create/tombstone-delete, bb app
current CLI, daemon app-data tracking + apps-root watcher, and a top-level
sidebar Apps section (drops per-manager app nesting). Removes the old
thread-scoped /threads/:id/apps routes and the built-in manager status app.
Global apps now open on their own thread-independent route via a shared
AppViewer (factored out of AppTabContent). Sidebar Apps rows navigate to
the standalone route and are no longer disabled without a selected thread;
adds loading / app_missing / invalid_manifest states and deep-linking.
Aggregates skills from every global app's apps/<applicationId>/skills/ and
the data-dir-level <dataDir>/skills/ into one validated catalog (agentskills.io
contract; fail-closed name collisions) and injects them into every new/resumed
runtime via the existing AgentRuntimeSkillRoot plumbing (codex
skills/extraRoots/set, claude-code generated local plugin). Adds an
injectedSkillSources field to the host-daemon runtime context (protocol 30),
server-side catalog resolver, daemon staging/packaging, and host-watcher
coverage of both skill roots. Also: per-host thread storage no longer inherits
ambient BB_THREAD_STORAGE; daemon heartbeat lease extension is monotonic.
The built-in status app removal deleted src/services/threads/default-template,
but the @bb/server build still tried to --copy-dir it, breaking the prod bundle
build (ENOENT). Remove the stale copy step.
The app id is now a validated slug (^[a-z0-9][a-z0-9-]*$, max 64) that is the
folder name and manifest.id, replacing the opaque app_<hex> scheme. bb app new
derives a slug from --name or takes an explicit --id; duplicate ids are rejected
(409 app_exists). manifest.name stays optional and is used for display when set,
falling back to the slug otherwise.
Apps now serve their public/ folder as the web root: /api/v1/apps/<slug>/
maps to public/index.html and /<path> to public/<path>, with the public/
folder name absent from the URL. Renames the old assets/ concept to public/
(resolveApplicationPublicPath / PUBLIC_DIRECTORY_NAME) and removes the special
/assets/* route. manifest.json and data/ stay private siblings outside public/
(traversal-validated; data/ behind authenticated routes). Standard relative
builds resolve without <base> injection or URL rewriting. bb app new scaffolds
public/index.html; guidance: build into public/ with a relative base.
Global apps are filesystem-backed with no DB, so the app list had no realtime
invalidation. Adds an 'apps-changed' system change kind: create/delete routes
emit it, and the host-watcher's app folder/manifest changes broadcast it (the
server diffs the visible app-list signature before broadcasting). The frontend
realtime cache registry invalidates the app list/detail queries, so the sidebar
Apps section updates without a bb restart.
Re-adds per-thread memory of whether the secondary panel is open: adds a
threadId-keyed, localStorage-persisted atom (bb.thread.secondaryPanel.open)
mirroring the existing per-thread conversation-collapse atom, wired through the
panel toggle + ThreadDetailView so each thread restores its own open/closed
state on switch (default closed). Regressed when conversation-collapse moved to
a per-thread atom while panel-open stayed transient.
The in-app browser's native WebContentsView was detached/destroyed when a
thread's panel deck unmounted, so switching threads flashed (and recreated +
reloaded the view on return). Browser views are now retained per tab in a
shared registry with thread/environment ownership: unmount only releases
visibility (hide), not the view; views are destroyed only on tab close or
thread/environment teardown. Switching threads hides-before-show + bounds-
before-show, eliminating the flicker.
The browser WebContentsView synced bounds only on ResizeObserver + window
resize, so a left-sidebar width drag shifted the panel's left/top without a
size change and the native view overflowed over the sidebar. Now a sidebar
width/open-state change emits a bounds-sync event (position-only resyncs), and
measured bounds are clamped to the viewport on both the renderer and before the
native setBounds, so the view stays anchored inside its container. Preserves the
rAF resize coalescing and keep-alive behavior.
Updates server-contract + host-daemon-contract invariant test fixtures for
changes that landed in this batch but whose contract tests weren't in the
per-package validation filters (CI caught them):
- appManifestSchema.name is now optional (defaults to slug) — allowlist entry.
- app-data broadcast/resync + app-data-change-request use applicationId (not
  appId/threadId).
- SYSTEM_CHANGE_KINDS includes apps-changed.
- thread runtime context requires injectedSkillSources; session-open response
  requires trackedApplicationDataTargets.
@SawyerHood SawyerHood merged commit cbffde1 into main Jun 3, 2026
6 checks passed
@SawyerHood SawyerHood deleted the sawyer/ship-2026-06-03 branch June 3, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant