# DOX — packages/blackhole-plugin/src/client

Files in this directory. One row per source file. See change: add-blackhole-plugin.

| File | Purpose |
|------|---------|
| `BlackholeSettings.tsx` | `settings-section` component (general tab). Three self-produced states: not-installed (registry says `pi-blackhole` missing), parse-error (NO form, explicit DISABLED save control, recovery actions), form (scalar accordions + chain editors). Saves through the host Save Bar via `useSettingsDraftSource({ id: "plugin:blackhole" })`. Exports `buildPayload(draft)` — scalars + chains split back to `<worker>Model`/`<worker>FallbackModels`, `null` unsets, `NaN`/`undefined` says nothing. Copy never demands a restart; apply is attributed to the extension. |
| `ChainEditor.tsx` | One worker's ordered chain. Ranked list, expandable per-model fields (`provider`, `id`, `thinking`, `cooldownHours`, `contextWindow`). Move-up/down/remove are real `<button>`s with accessible names naming the model; boundary controls DISABLED not absent; no remove on a single-entry chain. Renders the shared `base model → session model` tail as non-entry text, marked `data-excluded` when `sessionFallback` is off. |
| `blackhole-api.ts` | REST client. `getConfig` (a 409 parse-error is a RESULT, not a throw), `putConfig`, and `isExtensionInstalled` — reads `GET /api/plugins` `status.missingRequirements`, i.e. pi's package registry, NEVER the filesystem. An unreported probe resolves to installed so an unknown answer cannot fabricate a not-installed state. |
| `field-groups.ts` | Display copy + grouping only (control kind derives from `FIELD_DESCRIPTORS`). `FIELD_GROUPS` = compaction behaviour, observational memory, trigger thresholds, token budgets, runtime & diagnostics. `WORKER_META` = observer/reflector/dropper name, role, and config key pair. |
| `index.tsx` | Client entry barrel. Exports `BlackholeSettings` (name MUST match the manifest claim's `component`) and the i18n `catalog`. |
| `__tests__/BlackholeSettings.test.tsx` | L1. The three states at component level: not-installed from the registry (E23, F6) and that it is SELF-produced; installed-but-never-run rendering defaults not the missing state (E22), incl. an unreported probe never fabricating not-installed; parse-error rendering no form + a disabled save control (X1, X3); no-restart / attributed-apply copy (F7); chain render order (E18). Plus the payload rule: `buildPayload(toDraft(...))` emits ONLY file-set or user-edited keys, never materialising untouched defaults, and validates against the server validator. |
| `__tests__/ChainEditor.test.tsx` | L1. Ordering + promotion (E19), boundary controls disabled-not-absent (F2), no remove on a single-entry chain (E21), accessible names naming the model on every control (F1, F3), real focusable `<button>`s, the implicit tail shown as a non-entry and `data-excluded` per `sessionFallback` (F4, F5), and per-model field typing — numeric edits emit NUMBERS and `(inherit)` omits `thinking`, round-tripped through the real server validator. |
