Skip to content

bb plugin screenshot: plan and capture marketplace listing screenshots from the running app - #2250

Draft
brsbl wants to merge 4 commits into
mainfrom
bb/plugin-screenshot
Draft

bb plugin screenshot: plan and capture marketplace listing screenshots from the running app#2250
brsbl wants to merge 4 commits into
mainfrom
bb/plugin-screenshot

Conversation

@brsbl

@brsbl brsbl commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Adds bb plugin screenshot, which produces the screenshots a marketplace listing needs, plus the skill that says which ones are worth taking.

Draft. The capture works end to end; the fixture-thread slice and a packaged-bb Electron path are not in it yet.

What it does

$ bb plugin screenshot plugins/tasks --capture ./shots
01-panel.png  /plugins/tasks/tasks  (navPanel)
skipped messageDirective — needs the capture fixture; pass --fixture-thread <id>
wrote ./shots/01-panel.png  (navPanel)

A headless window — the desktop package's own Electron, no new dependency — loads the running bb, asks the renderer which surfaces the plugin registered, navigates to each, and writes a PNG. Enumeration comes from the live app rather than parsing source: the author submitting a plugin has it installed, and only the renderer knows the real registrations, including a nav panel's path, a directive's id, and a file opener's extensions.

A plugin with no visual surface prints "no listing screenshots to take" and exits clean. That is the common case for agent-tool and provider plugins, and their listings should not wait on an image that cannot exist.

Testing it on real plugins is what shaped this

Eleven bundled plugins on a dev instance built from this branch. Eight captures — and six were empty states:

Before seeding After bb tasks seed-demo --yes
"No projects yet" Nine tasks across three projects

That gap is the point of the skill. The command finds the right surfaces and photographs them faithfully; whether the result sells the plugin depends on data existing first.

Two capture bugs came out of the same run, both fixed here:

  • Registration is not readiness. The harness read the slot snapshot before any plugin frontend had mounted and reported "no surfaces" for everything. It now waits for the target plugin, and on timeout names the plugins that did register.
  • Seeding restarts a plugin's service, so a plugin is briefly unregistered while the window navigates, and its route renders blank. Registration is now re-checked at the destination, then the harness waits for the DOM to stop changing instead of a fixed delay.

Other captures, unseeded, showing surfaces that need no data:

connect settings connect sidebar footer github — an empty state the skill would reject

The skill

plugin-listing-screenshots covers what the command cannot decide: which screens to lead with for each kind of plugin, what to seed first, and what makes a shot unusable — an empty panel, a skeleton row, a single lonely item, a real secret or private repo.

Its seeding advice names a path that already exists rather than inventing one: bb.cli is a plugin surface, eleven of bb's bundled plugins register one, and bb tasks seed-demo is the convention worth copying. The skill asks plugin authors with data to ship a seed-demo subcommand — a few lines that make their own listing images one command, and give reviewers a way to see the plugin working.

submit-a-plugin and the plugins guide both point at it.

Shape

Where What
packages/domain Surface catalog and planner — which surfaces exist, how each is reached
apps/app/src/lib/plugin-slots.ts One hook, window.__bbPluginSlotSnapshot, over the existing snapshot
apps/desktop/scripts/plugin-capture.cjs The harness: navigate, wait, capturePage
apps/cli bb plugin screenshot [path] [--capture <dir>] [--app-url <url>] [--fixture-thread <id>] [--json]
builtin skills plugin-listing-screenshots

The harness sits beside desktop rather than inside main.ts because capture needs a window and a URL, not the owned-runtime lifecycle. It requires Electron lazily so its planning half runs under plain node, where the CLI suite tests it against the same catalog the CLI plans from.

Verification

461 CLI tests, 154 domain tests, app/CLI/domain typecheck. The guide durability test enforces that the new subcommand and both flags are documented.

Not in this PR

  • Fixture seeding. Surfaces that only exist inside a thread need --fixture-thread; the shared fixture that creates that thread, with a message carrying the plugin's directive id and a file of its claimed extensions, is the next slice.
  • Packaged bb. Electron resolves from the desktop package in a checkout; BB_ELECTRON covers other cases.
  • A panel that never finishes loading is still photographed mid-load. The skill tells authors to open every image.

Found while testing, not fixed here

The Tasks panel renders placeholder rows that never resolve in a fresh window, though the sidebar shows the seeded counts and the DOM is quiet with no animate-pulse nodes. Worth a look independent of this PR.

BB-Thread-ID: thr_yrs6swfgtc

🤖 Generated with Claude Code

brsbl added 3 commits August 21, 2026 02:05
A marketplace listing has to show the plugin's own UI, but nothing today
tells an author which screenshots to take, and the entry has nowhere to
put them. This adds the half that can be worked out without running
anything: what to photograph, and where each shot lives.

`bb plugin screenshot [path]` reads the plugin's frontend source, finds the
surfaces it registers, and plans one shot per surface with the route to
capture and the file to write. Source rather than dist/, because a
submission screenshot has to be plannable before the plugin is built or
installed anywhere.

Surfaces split by how they are reached. A nav panel owns a URL, and
homepage/settings/thread-list/sidebar surfaces render on a route or in
chrome every route paints, so those are navigable. The rest only exist
once a thread, composer, or open file is in a particular state; they are
planned against one shared capture fixture, so every plugin's listing is
shot against the same workspace and no author has to build one. Without a
fixture thread they are reported rather than planned, so a listing never
photographs the empty app.

A plugin that registers no visual surface plans nothing and exits clean:
an agent-tool or provider plugin has no UI to show, and its listing must
not wait on a screenshot that cannot exist.

The detector reads source text rather than parsing: it only has to answer
whether the plugin paints somewhere, a false positive costs one skipped
shot, and every plugin vendors SDK declarations that name every slot —
which the walk skips along with dist/ and node_modules.
`bb plugin screenshot --capture <outDir>` now takes the pictures. A
headless Electron window (the desktop package's own Electron — no new
dependency) loads the author's running bb, asks the renderer which
surfaces the plugin actually registered, navigates to each one, and
writes one PNG per surface.

Enumeration moved from source scanning to the running app. The author
submitting a plugin has it installed, and the renderer is the one place
that knows the real registrations — including the facts the scanner
could not see: each nav panel's path, a directive's id, a file opener's
extensions. The scanner remains only for the no-server plan printout.

The app exposes one hook for this: `window.__bbPluginSlotSnapshot` in
plugin-slots.ts, returning the existing snapshot. The harness reads
ids and paths from it, never components.

The harness (apps/desktop/scripts/plugin-capture.cjs) is deliberately
not part of desktop main: capture needs a window and a URL, not the
owned-runtime lifecycle. It requires Electron lazily so its planning
half runs under plain node, where the CLI suite tests it against the
same catalog the CLI plans from — one mapping test guarantees every
capturable surface has a snapshot key.

Verified end to end: the built CLI spawned the harness against a page
exposing the hook, enumeration found three surfaces, and three PNGs of
the right routes were written at 1440x900@2x.
Plugin frontends mount after the shell, so the harness was reading the
slot snapshot before any plugin had registered and reporting "no
surfaces" for every plugin. It now waits until the target plugin shows
up in the snapshot, and on timeout says which plugins did register so
a miss is debuggable.

--app-url covers source dev instances, where Vite serves the app shell
on a different port than the server; it defaults to the server URL,
which is what a packaged bb serves.
@brsbl
brsbl marked this pull request as draft August 21, 2026 19:58
Capturing eight real plugins on a dev instance showed the gap this
command actually has: six of the eight shots were empty states. The
command finds the right surfaces and photographs them faithfully, and
the result is still unusable for a listing, because nothing seeded the
plugin's data first.

The skill (plugin-listing-screenshots) is the missing judgement: which
screens to lead with for each kind of plugin, what to seed before
shooting, and what makes a shot unusable — an empty panel, a skeleton
row, a lonely single item, a real secret. It names the seeding path that
already exists: `bb.cli` is a plugin surface, eleven bundled plugins
register one, and `bb tasks seed-demo` shows the convention worth
copying. submit-a-plugin and the plugins guide both point at it.

Two capture fixes came out of the same run. Re-check registration after
navigating, not only before: seeding restarts a plugin's service, and a
plugin that is briefly unregistered renders a blank route. Then wait for
the DOM to stop changing rather than a fixed delay, so a panel that
fetches after mounting is photographed with its content.

Neither fix makes capture infallible — a panel that never finishes
loading is still photographed mid-load — so the skill tells authors to
open every image, and the command's own docs say the same.
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