bb.settings

bb.settings presents settings pages, schema forms, and safe secret controls through kernel ports.

Purpose

The plugin owns the Settings application and the bb.settings.pages list surface. It declares no domain service.

The kernel owns each settings schema and the fixed bb.preferences and bb.secrets ports. bb.settings consumes these ports.

This plugin is presentation only. Other consumers can use preferences without the Settings application.

Surfaces

ID kind replaceable props contract sketch notes
bb.settings.pages list no SettingsPageProps with pageId, ownerPluginId, route, compact, and navigate() Each claim adds one page. The user can hide or reorder pages.

bb.settings.pages

The surface accepts ordered page registrations. The kernel keeps the claim and order records with the other surface claims.

import type { ComponentType } from "react";

export interface SettingsRoute {
  pageId: string;
  subpath: readonly string[];
}

export interface SettingsPageProps {
  pageId: string;
  ownerPluginId: string;
  route: SettingsRoute;
  compact: boolean;
  navigate(next: SettingsRoute): void;
}

export interface SettingsPageRegistration {
  /** Unique inside the claimant. The runtime forms `<pluginId>/<id>`. */
  id: string;
  label: string;
  description?: string;
  icon?: string;
  order?: number;
  component: ComponentType<SettingsPageProps>;
}

The runtime sorts visible pages by the user order. It then uses order, the claimant ID, and the registration ID.

The default bb.settings implementation adds the General and Experiments pages. Other first-party plugins add domain pages through the same surface.

For each manifest settings schema, bb.settings also makes one generated plugin page. A custom page claim does not replace that generated page.

An author can omit the generated page in the manifest. The kernel manifest contract controls that choice.

Schema form boundary

The manifest declares the schema. See Kernel: plugin manifest.

The bb.settings plugin reads the validated schema from bb.plugins. It does not accept a second runtime schema registration.

The generated form uses the kernel bb.preferences port for non-secret fields. It uses the kernel bb.secrets port for secret status.

The field sends a secret value directly to the kernel's human-only secret flow. The plugin never receives the value.

The form shows only configured, missing, saving, or error after a secret write. Logs and preference records never contain the value.

Services

ID kind replaceable default provider purpose
This presentation plugin declares no service.

Kernel port use

bb.settings consumes the fixed bb.preferences port. The port reads, writes, clears, lists, reloads, and watches persistent preference cells.

The kernel owns preference storage, validation, defaults, revisions, and actor limits. The UI only presents these operations.

Dependency edges

The first-party bb.settings plugin declares these edges.

edge contract reason
required bb.preferences Reads and writes validated non-secret settings.
required bb.plugins Reads validated manifest schemas and plugin status.
required bb.secrets Shows secret status and follows set or unset changes.

The plugin owns only presentation. It does not wrap or replace any of these ports.

Exports

Module imports select exact code. They do not follow the active surface or service winner.

module export purpose
@bb/settings/components SettingsPage Provides the standard page header and width rules.
@bb/settings/components SettingsSection Groups related controls.
@bb/settings/components SettingsRow Aligns a label, help text, control, and error text.
@bb/settings/components SchemaSettingsForm Renders one validated kernel manifest schema.
@bb/settings/components SecretSettingField Uses the kernel human-only secret flow and exposes status only.
@bb/settings/hooks usePreference Adapts one profile or thread cell from bb.preferences.
@bb/settings/hooks usePluginSettings Adapts all non-secret manifest settings from bb.preferences.
import type {
  JsonValue,
  PreferenceAddress,
  PreferenceWriteOptions,
} from "@get-bb/plugin/contracts";

export interface UsePreferenceState<T> {
  value: T;
  source: "stored" | "default";
  revision: string | null;
  isLoading: boolean;
  error: Error | null;
  set(next: T, options?: PreferenceWriteOptions): Promise<void>;
  clear(options?: PreferenceWriteOptions): Promise<void>;
}

export function usePreference<T extends JsonValue>(
  address: PreferenceAddress,
): UsePreferenceState<T>;

export interface UsePluginSettingsState {
  values: Readonly<Record<string, JsonValue>> | undefined;
  isLoading: boolean;
  error: Error | null;
}

export function usePluginSettings(
  pluginId: string,
): UsePluginSettingsState;

usePluginSettings() omits all secret keys. A component uses SecretSettingField when it needs a secret control.

Host roles

bb.settings declares no host role. Its presentation code uses typed adapters for kernel ports.

Example

This plugin adds a settings page and reads a kernel preference. Its manifest declares the preference schema by the kernel rules.

{
  "id": "acme.github",
  "version": "2.0.0",
  "claims": [{ "surface": "bb.settings.pages" }],
  "requires": [
    { "service": "bb.preferences", "range": "^1" }
  ],
  "artifacts": {
    "app": "./dist/app.js"
  }
}

The omitted manifest settings block declares acme.github/reviewMode. See Kernel: plugin manifest.

// src/app.tsx
import { definePlugin } from "@get-bb/plugin/app";
import {
  SettingsPage,
  SettingsSection,
  usePreference,
} from "@bb/settings";

const reviewMode = {
  key: "acme.github/reviewMode",
  scope: "profile",
} as const;

function GitHubSettings() {
  const mode = usePreference<string>(reviewMode);

  return (
    <SettingsPage title="GitHub">
      <SettingsSection title="Reviews">
        <select
          value={mode.value}
          disabled={mode.isLoading}
          onChange={(event) => void mode.set(event.currentTarget.value)}
        >
          <option value="fast">Fast</option>
          <option value="balanced">Balanced</option>
          <option value="strict">Strict</option>
        </select>
      </SettingsSection>
    </SettingsPage>
  );
}

export default definePlugin((api) => {
  api.surfaces.provide("bb.settings.pages", {
    id: "github",
    label: "GitHub",
    description: "Configure GitHub review behavior.",
    order: 300,
    component: GitHubSettings,
  });
});

usePreference() uses the fixed kernel port. bb.settings supplies the hook and form presentation only.

Covers

old item ID new contract/verb note
app.hooks.useSettings @bb/settings/hooks: usePluginSettings() The hook adapts bb.preferences, omits secrets, and reports errors.
app.slots.settingsSection bb.settings.pages surface Each old section becomes an ordered settings page claim.
app.contracts.PluginSettingsSectionProps SettingsPageProps Pages receive routing and compact-layout data.
app.contracts.PluginSettingsSectionRegistration SettingsPageRegistration A list-surface registration replaces the fixed slot registration.
app.contracts.PluginSettingsState UsePluginSettingsState The UI state adapts bb.preferences, reports errors, and keeps secret values out.
app.contracts.PluginSettingsSectionRegistration.id SettingsPageRegistration.id The runtime qualifies the ID with the claimant plugin ID.
app.contracts.PluginSettingsSectionRegistration.title SettingsPageRegistration.label The page label appears in navigation and the page header.
app.contracts.PluginSettingsSectionRegistration.description SettingsPageRegistration.description The page can show the description below its heading.
app.contracts.PluginSettingsSectionRegistration.component SettingsPageRegistration.component The component now receives SettingsPageProps.