Cross-plugin claims
A plugin can claim a surface that another plugin declares. The declaring plugin owns the surface contract and its place in the graph.
The claimant supplies one implementation. The kernel connects both plugins through the contract ID.
This page builds the cross-plugin claims from bb.vcs-ui. It also composes the replaceable bb.files-ui.diff surface.
Use this when
- Add content to another plugin's UI. Claim its typed
listsurface. - Keep domain data separate from presentation. Put data in a headless plugin and claims in a UI plugin.
- Follow the active surface graph. Let a claim become inactive when the declaring winner omits its child surface.
- Compose a replaceable surface. Render the current global winner instead of importing one implementation.
- Handle an owner change. Keep independent claims active when only one graph path changes.
What you build
You will build the app artifact for bb.vcs-ui. The plugin adds two list items:
- A version-control changes panel in
bb.thread-ui.sidePanels. - A head item in
bb.layout.statusbar.
The changes panel renders file patches through bb.files-ui.diff. It follows the user's global diff choice.
The data stays in the headless bb.vcs service. The UI plugin requires that service.
Understand the owners
The kernel declares only bb.app.root. Active surface winners declare all other surfaces.
The default thread view declares bb.thread-ui.sidePanels. The active layout declares bb.layout.statusbar on a different graph path.
- The thread view declares
bb.thread-ui.sidePanels.bb.vcs-uiaddsvcs-changes, which composesbb.files-ui.diff. - The root mounts the layout. The layout declares
bb.layout.statusbar, andbb.vcs-uiaddsvcs-head.
bb.vcs-ui does not own these two list surfaces. It must use the types and rules from their owners.
Contract IDs use dots. Examples include bb.thread-ui.sidePanels, bb.layout.statusbar, and bb.files-ui.diff.
The owner declares one contract kind: single, list, keyed, or chain. A claimant must follow that kind.
1. Declare the claims
Every claim is static. Add each supplied surface to claims in bb.plugin.jsonc.
// bb.plugin.jsonc
{
"id": "bb.vcs-ui",
"version": "2.0.0",
"claims": [
{ "surface": "bb.thread-ui.sidePanels" },
{ "surface": "bb.layout.statusbar" }
],
"requires": [
{ "service": "bb.vcs", "range": "^1" }
],
"artifacts": {
"app": "./dist/app.js"
}
}Do not add a claim for bb.files-ui.diff. bb.vcs-ui uses that surface but does not provide it.
The build generates contract.json. It validates each claim against the surface owner's contract.
The host can inspect the claims before it loads the app artifact. It can also show their state in the global picker.
2. Add the changes panel
Import the surface types from the owner plugin. The panel contract supplies the current thread context.
// src/app.tsx
import type {
ThreadSidePanelContribution,
ThreadSidePanelProps,
} from "@bb/thread-ui/contracts";
function VcsChangesPanel(_props: ThreadSidePanelProps) {
return (
<section aria-label="Version-control changes">
<h2>Changes</h2>
<p>Select a file to see its patch.</p>
</section>
);
}
const vcsChangesPanel: ThreadSidePanelContribution = {
id: "vcs-changes",
title: "Changes",
icon: "ListTree",
order: 30,
placement: "thread",
layout: "flush",
component: VcsChangesPanel,
};The id identifies this item inside the list surface. It is not a contract ID.
This small panel confirms that the foreign claim works. A complete panel reads changes and history from the required bb.vcs service.
The app artifact does not start Git itself.
3. Add the head status item
The layout plugin owns the status bar contract. Import its types before you create the list item.
import type {
LayoutStatusItemProps,
LayoutStatusItemRegistration,
} from "@bb/layout/contracts";
function VcsHeadIndicator(_props: LayoutStatusItemProps) {
return <span title="Current version-control head">Current head</span>;
}
const vcsHeadStatus: LayoutStatusItemRegistration = {
id: "vcs-head",
title: "Current head",
icon: "GitBranch",
order: 20,
component: VcsHeadIndicator,
};This small item confirms that the status bar claim works. The complete item reads the repository for the current route.
It shows the current head and its ahead or behind counts. It returns null when the route has no repository.
It does not add version-control data to the layout contract.
Both claims use list surfaces. All valid list items can stay active, and no item joins the winner picker as a replacement.
The user can hide or reorder each list item through the controls that the surface owner provides.
4. Compose the active diff winner
Use Surface when your component must follow a replaceable surface. Pass the props that its owner contract defines.
import { Surface } from "@get-bb/plugin/app";
import type { VcsFileDiff } from "@bb/vcs/contracts";
function VcsPatch({ file }: { file: VcsFileDiff }) {
return (
<Surface
id="bb.files-ui.diff"
props={{
patch: file.patch,
path: file.path,
view: "unified",
overflow: "scroll",
showLineNumbers: true,
fullFileContents: file.fullFileContents,
}}
/>
);
}This component does not import DefaultDiffRenderer. It asks the kernel to mount the current bb.files-ui.diff winner.
bb.files-ui.diff is a replaceable single surface. Its selected provider receives the typed Original component.
VcsPatch does not receive Original, because it consumes the surface. Only the active single surface provider receives it.
5. Supply both claims
The app artifact uses one factory from @get-bb/plugin/app. The typed api object supplies both list items.
import { definePlugin } from "@get-bb/plugin/app";
export default definePlugin((api) => {
api.surfaces.add("bb.thread-ui.sidePanels", vcsChangesPanel);
api.surfaces.add("bb.layout.statusbar", vcsHeadStatus);
});The loader stages both calls as one plugin generation. A factory error leaves no partial set of claims.
The kernel removes both registrations when the plugin unloads. You do not need a cleanup function for these calls.
What happens when the declaring winner changes
A child surface exists only while the active parent winner declares it. The claimant stays installed when that child disappears.
For bb.vcs-ui, each owner path changes independently:
| Change | Changes panel | Head item | Plugin state |
|---|---|---|---|
A new thread view keeps bb.thread-ui.sidePanels |
Remounts in the new view | Stays active | Active |
A new thread view omits bb.thread-ui.sidePanels |
Becomes inactive | Stays active | Active |
A new layout keeps bb.layout.statusbar |
Stays active | Remounts in the new layout | Active |
A new layout omits bb.layout.statusbar |
Stays active | Becomes inactive | Active |
| The old declaring winner returns | Its child claims become active again | Follows its own path | Active |
The host does not treat an absent child surface as a plugin failure. It shows the static claim as inactive.
A replacement can keep an ecosystem compatible by declaring the same child contract ID. It can omit the child to make an explicit break.
The kernel rebuilds the affected surface subtree after a winner change. Components in that subtree mount again, so their local state can reset.
The bb.files-ui.diff winner has a separate lifecycle. A diff winner change remounts every composed diff with the new global winner.
That change does not restart bb.vcs-ui. A bb.vcs service winner change does restart it because bb.vcs is a required service edge.
Pitfalls
- Do not declare a foreign surface in your
surfacesblock. Claim the contract that its owner already declares. - Do not add a claim for a surface that you only render through
Surface. - Do not import an exact component when you must follow the user's winner.
- Do not assume that a child surface always exists. Its active parent winner controls its lifetime.
- Do not use
requiresfor a UI owner. Service edges apply to services, not surface presence. - Do not use one missing graph path to disable unrelated claims.
- Do not expect
Originalon alistclaim. Only asinglesurface winner receives it.
See also
- Guide 7, Surfaces and kinds — claim each of the four contract kinds.
- Guide 8, Services — use
requires,optional, andwatchedservice edges. - Guide 16, Typed contracts — publish and consume surface contract types.
design/08-bb-vcs.md— read thebb.vcs,bb.vcs-ui, andbb.gitcontracts.- Design section 4, The surface graph — understand child surface ownership.