AGENTS.md@packages/blackhole-plugin/src/client · git:20261013.8b61564 · 2026-10-13 · sha256 457f42c0a0f79390
AGENTS.md@packages/blackhole-plugin/src/client git:20261013.8b61564A
Immutable. This exact content never changes and is served at /api/v1/blob/457f42c0a0f79390.
# 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 + base-model picker + recommended-defaults action + chain editors). Four-state registry gating (`pending\|ok\|empty\|unavailable`). 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. See change: add-blackhole-plugin, blackhole-model-picker-chains. Change: `toDraft` marks bound slots from `roleBindings`; payload via `toWire`; base picker enables `allowRoles`; `reattach`. See change: add-role-aware-model-refs. |
| `ChainEditor.tsx` | One worker's ordered chain. Ranked list with UI primitive model selector (group-wrapped; free-text inputs removed), thinking override checkbox + ThinkingLevelSelector, add-model flow with adding row, empty state line naming base/session tail. 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. See change: add-blackhole-plugin, blackhole-model-picker-chains. Change: Entry/add pickers enable `allowRoles`; bound entry shows role, status chip, Reattach; thinking control shows `from @role`. See change: add-role-aware-model-refs. Role-bound slot summary via `RoleRef` (token keeps `…-entry-<i>-role`); binding-status warning pills; detached reason + "Reattach to @role" in a warning callout; level-drop notice = warning callout, one sentence, no emoji. BlackholeSettings drops `var(--severity-*,#hex)` fallbacks. See change: retro-ux-model-selector. |
| `blackhole-api.ts` | REST client. `getConfig` (a 409 parse-error is a RESULT, not a throw), `putConfig`, `isExtensionInstalled` (repointed at `/status`), and `getModels` (`GET /api/models` registry client, parses slash ids, ignores `thinkingLevelMap`, resolves `ok\|unavailable`, never throws). Write direction resolves by exact match. See change: add-blackhole-plugin, add-blackhole-session-pipeline, blackhole-model-picker-chains. Change: `rolesAvailable`/`roleBindings` on ConfigOk; `reattachBinding(field)`. See change: add-role-aware-model-refs. |
| `detail-navigation.ts` | Module-scoped navigation state for the `content-view` drill-in (F8). `openPipelineDetail(id)`/`closePipelineDetail()` flip `activeForSessionId` + `bumpSlotClaimsVersion()`; `isPipelineDetailActive(session)` — the manifest predicate string — returns false until explicit navigation and only for THAT session. DORMANT: its only caller is MemorySubcard's Details button, which no longer mounts — flag stays permanently false. See change: add-blackhole-session-pipeline, park-blackhole-session-card. |
| `installed-gate.ts` | Client boot gate (design D4). `shouldRenderMemorySubcard()` — synchronous, fails closed until resolve (F1); manifest `shouldRender` name. `resolveInstalled({fetchImpl,backoffMs,slowIntervalMs})` — fetches `/status`; network error/non-200/malformed body are transient: capped backoff ×3 then ~60s fixed interval UNTIL first success, never gives up (X1/X2); success finalizes page-lifetime value, `bumpSlotClaimsVersion()` once, no re-poll. `isTestEnvironment()` — `import.meta.vitest` / typeof-guarded `process.env.VITEST` (NOT `MODE`); `typeof process` guard is load-bearing for the browser bundle. PARKED: `shouldRenderMemorySubcard()` hard-returns `false` (subcard not informative enough); `return installed === true;` retained as a comment above it — restore by uncommenting. Resolve machinery below is untouched and still runs. See change: add-blackhole-session-pipeline, park-blackhole-session-card. |
| `MemorySubcard.tsx` | `session-card-memory` claim component. Mount-time fetch of ONE `/session/:id` response (idle sessions are snapshots — accepted trade-off); derives states via `pipeline-state.ts`: healthy row / no-activity-yet / workers-off (no meter) / cooldown advisory (model + remaining min) / pending-batch advisory (count + /blackhole flush). Worker indicators: textual id + state-describing `aria-label`, never colour-alone (F7). Exact lag alongside approximate proximity; detail affordance in EVERY state (F10). DORMANT: never mounts while `shouldRenderMemorySubcard` is parked; component + tests retained intact. No CSS for its `bh-mem__*` classes has ever shipped. See change: add-blackhole-session-pipeline, park-blackhole-session-card. |
| `OmEntryCard.tsx` | `custom-entry-renderer` claim component for `om.observations.recorded`, `om.reflections.recorded`, `om.observations.dropped`. Presentation only (container owns claim lookup/expand/fetch): collapsed-first single line (distinct per-type icon marked `aria-hidden`, ADDITIVE to a text label, plus a record count ONLY when the body parses completely — a truncated/non-JSON body shows no count); expanded = discrete records from `payload`, rendered as PLAIN TEXT (`recordText`; no markdown/linkify/`dangerouslySetInnerHTML`); `payloadError` degrades to the stored body `<pre>`; no expand affordance without `entryId`. Includes `input` records. See change: add-custom-entry-renderer-slot. |
| `PipelineDetailView.tsx` | `content-view` claim component (one-active). Provenance labels: cursors → `<id>-pending.json`, resolved model + reason → `pi-blackhole-cooldown.json`, proximity → dashboard accounting with non-convertibility caveat. NO in-memory-only values (`consolidationInFlight`, counts, last errors); transcript pointer present. `onClose` + `closePipelineDetail()` = the return path. Session-scoped — the settings page renders none of this. DORMANT: unreachable while the subcard is parked (no other entry point sets `isPipelineDetailActive`); retained intact. See change: add-blackhole-session-pipeline, park-blackhole-session-card. |
| `pipeline-api.ts` | Typed client for `/session/:id` (`getSessionPipeline`) + the `SessionPipelineResponse` shape mirrored from the server reader; `WORKER_IDS`. See change: add-blackhole-session-pipeline. |
| `pipeline-state.ts` | Pure derivation of the subcard view model (no DOM): `deriveSubcardState`, `deriveLag` (E11 — tip − cursor exact; cursor==tip → 0; cursor AHEAD → `{kind:"stale"}`, no numeric), `deriveProximity` (F5/F6 — fraction for an UNSCALED fill; null when `contextTokens` or `compactAfterTokens` missing; never a number/percentage/threshold/alert in render). See change: add-blackhole-session-pipeline. |
| `routes.ts` | Shared client route paths: `STATUS_ROUTE`, `SESSION_ROUTE_BASE`, `sessionRoute(id)`. See change: add-blackhole-session-pipeline. |
| `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 the components the manifest claims reference (`BlackholeSettings`, `MemorySubcard`, `PipelineDetailView`, `OmEntryCard`), the gate (`shouldRenderMemorySubcard`), the predicate (`isPipelineDetailActive`), and the i18n `catalog`. Module-scope boot check: `if (!isTestEnvironment()) resolveInstalled()` — GUARDED (F4): importing under vitest issues no network request; `resolveInstalled` stays explicitly invokable. See change: add-blackhole-plugin, add-blackhole-session-pipeline. |
| `__tests__/blackhole-api.test.ts` | L1. `getModels` REST client tests: slash-bearing id mapping (E8), no `thinkingLevelMap` consumption (E9), failure classes resolving to unavailable without throw (E27), malformed rows skipped (X3). See change: blackhole-model-picker-chains. |
| `__tests__/BlackholeSettings.test.tsx` | L1. The three states at component level: not-installed from status route (E23, F6), defaults form when installed-but-never-run (E22), parse-error rendering (X1, X3); apply semantics (F7); chain order (E18); payload builder; base-model set/clear (E14-E16), recommended defaults staging/dirty/confirm matrix (E17-E21), server error surface (X1), registry retry (X4). See change: add-blackhole-plugin, add-blackhole-session-pipeline, blackhole-model-picker-chains. |
| `__tests__/ChainEditor.test.tsx` | L1. Ordering + promotion (E19), boundary controls disabled-not-absent (F2), no remove on single-entry chain (E21), accessible names (F1, F3), implicit tail (F4, F5); exact-match slash-id pick (E10), level clamp (E11), non-reasoning off-only (E12), inherit vs off (E13), contextWindow clearing (E22), level drop notice (E23-E24), add flow (E25-E26), registry gating (X2). See change: add-blackhole-plugin, blackhole-model-picker-chains. |
| `__tests__/client-entry.test.ts` | L1 (F4). Imports the ENTRY barrel with `fetch` spied: spy uncalled (guarded kick); `resolveInstalled` still invokable with an injected fetch. See change: add-blackhole-session-pipeline. |
| `__tests__/installed-gate.test.ts` | L1 (F1, F4, X1, X2). Sync fail-closed before resolve; fails ×4 then succeeds → 3 backoff retries + slow-interval attempt, gate flips, exactly ONE `bumpSlotClaimsVersion`, no further polling; 404/500/garbage-body each transient, value never finalized, eventual success still lands; definitive `false` is authoritative + final; parked surface stays false even after an `installed: true` resolve. `isTestEnvironment` override/probe checks. See change: add-blackhole-session-pipeline, park-blackhole-session-card. |
| `__tests__/MemorySubcard.test.tsx` | L1 (E10, E11, F5, F6, F7, F10). State fixtures: healthy row / no-activity / workers-off (+compaction-still-runs, no meter) / cooldown advisory naming model + minutes / pending-batch advisory. Lag: 38 exact not approximate, 0 caught up, stale-cursor indicator with no numeric. Proximity: ≈ label, reachable explanation, no token counts/percent/scale, no alert; omitted when `contextTokens` or `compactAfterTokens` missing while workers+lag persist. Detail affordance in EVERY state. See change: add-blackhole-session-pipeline. |
| `__tests__/OmEntryCard.test.tsx` | L1 (add-custom-entry-renderer-slot; E6/F11–F14/X3). Collapsed counts 0/1/4 for complete bodies, NO count for a head-truncated slice or a non-JSON string; three types → three distinct `data-icon` + text labels; no affordance without `entryId`; expanded records as discrete items; markdown/HTML content renders literally (no `<strong>`/`<a>`/`<img>`); payload error → stored-body fallback. |
| `__tests__/PipelineDetailView.test.tsx` | L1 (F8, F11; 6.3). Predicate false by default, explicit navigation activates THAT session, back affordance resets + fires `onClose`. Provenance: pending-file / cooldown-file / dashboard-accounting attributions; no `consolidationInFlight`/counts/last-error; transcript pointer. Settings surface renders no per-session pipeline state. See change: add-blackhole-session-pipeline. |