# DOX — packages/client/src/components/settings

Files in this directory. One row per source file. See change: fold-oversized-agents-directories.

| File | Purpose |
|------|---------|
| `AccessProvisioningSection.tsx` | Settings ▸ Access: pending provisioning requests (S4/S5). Exports `AccessProvisioningSection({store?})`, mounted by `AccessPromptsSection` above the denial `Pending requests` (NOT in `AccessSection`, revoke-only contract). `GET /api/access/provision` on mount + `provisionPromptStore.getVersion()` + 10 s poll; 404 → empty, not error. Cards reuse `ProvisionLines`/`ProvisionRemoteGoals`/`ProvisionConsequence`; nothing preselected; answers over `POST …/:id/settle` (capability header auto-attached), then `store.claim`. `Dismiss all pending` → `POST …/dismiss-all`, no confirm, cooldown hint. No capability (`useHasGrantChannel()` false) → read-only `Read-only` banner (`access-provision-readonly`) + inert controls, `Open Remote access` stays enabled. Action error kept apart from load error. See change: add-access-provisioning (tasks 3.4, 3.8, 3.9) |
| `AccessPromptsSection.tsx` | Settings ▸ Access prompt surfaces (banners, prompting toggle, YOLO card, provisioning requests, pending denials, verdicts, refusals); sibling above `AccessSection`. See change: add-access-provisioning. → see `AccessPromptsSection.tsx.AGENTS.md` |
| `AccessSection.tsx` | Settings ▸ Access read-and-revoke view over the eight grant stores; labels `via` provision / agent-prompt / prompt rows. See change: add-access-provisioning. → see `AccessSection.tsx.AGENTS.md` |
| `AgentPathGateSection.tsx` | Settings ▸ Security ▸ *Agent file access*: draft-bound `agentPathGate.enabled` checkbox (inert + reason under `PI_DASHBOARD_AGENT_PATH_GATE`, via computed `agentPathGateEnvOverride`) + timeout seconds field; copy states 'approval prompt, not a sandbox'. See change: ask-agent-file-access-in-chat. |
| `AllowedHostsSection.tsx` | Settings ▸ Security ▸ *Allowed hostnames* — the operator surface of the Host gate (design D8, change: add-host-allowlist-admission). Four parts: report/enforce mode control (segmented `GatewayProviderSection` shape, inline consequence, disabled + `PI_DASHBOARD_HOST_GATE` reason when `envOverridden`); read-only *Currently admitted* from `GET /api/host-gate` (source pills, derived rows link to their source page, NEVER re-derived client-side); *Additional hostnames* textarea (one per line, blur-validated with the shared `isValidBareHostname`, exact-fix message); *Recent refusals* with per-entry outcome pill + Allow that appends to the DRAFT `allowedHosts` (hidden when already in the draft, re-applied each fetch). Issues no write of its own; persists via the panel Save. Admitted source `tls-listener` (pill `TLS certificate`, group gateway, link to Gateway, no remove). See change: add-tailscale-cert-tls. |
| `CanvasTypesSettingsSection.tsx` | Canvas-type registry settings: 8 per-kind checkboxes + global/project scope switch. Toggle PATCHes the full 8-key map for the scope then refreshes. Project scope needs a session cwd (disabled with hint when none). See change: auto-canvas. |
| `DefaultGroupingField.tsx` | Settings ▸ Sessions ▸ Session list "Default grouping": 3-segment `role=radiogroup` (None/Status/Location), ←/→ wrap, instant-apply via `onChange` (WS `set_default_group_by`, outside Save-bar draft). See change: session-list-group-by. |
| `CardSectionsSection.tsx` | Settings › General › Session card sections: `role=switch` per offered section (global default; ON sends null to stay sparse, OFF sends false), each row shows `N folder(s) override this` from `countFolderOverrides`. Instant-apply WS, outside unified Save draft. See change: configurable-session-card-sections. Adds the `Directory card` group and the global-only `Effects` group (`fx-*`); the `Default (…)` label resolves through the legacy parent; mounts `<FocusNotice/>` while focus mode is on. Switching a row ON writes an EXPLICIT `true` when a hidden legacy parent would otherwise keep it hidden — a `null` (inherit) write there is a silent no-op the user reads as a broken switch. See change: add-focus-mode-and-card-block-toggles. |
| `DiagnosticsSection.tsx` | Settings → Diagnostics. Fetches `/api/doctor`. Groups by section in fixed order, omits empty sections (no n/a… → see `DiagnosticsSection.tsx.AGENTS.md` Also fetches the retained auto-naming outcomes on mount (`autoNameFetcher` injectable) and renders them under `auto-naming-diagnostics`; `starved` is labelled distinctly from `waiting` (output-cap truncation vs no nameable topic) because conflating them is the bug this readout exists to expose. Best-effort: a failed outcomes fetch never blocks the doctor report. See change: fix-auto-naming-reasoning-model. |
| `FirstLaunchDisplayModal.tsx` | One-shot preset picker (simple / standard / everything) shown when `/api/preferences/display.global ===… → see `FirstLaunchDisplayModal.tsx.AGENTS.md` |
| `FocusNotice.tsx` | Inline notice on card-section settings surfaces while focus mode is on: focus profile outranks per-block switches. Exports `FocusNotice`; testid `focus-notice`, `focus-notice-off` turns focus off in place. See change: add-focus-mode-and-card-block-toggles. |
| `FocusSettingsSection.tsx` | Settings › Sessions › Focus. Exports `FocusSettingsSection`. On/off switch (`focus-settings-switch`), Built-in vs Custom profile label (`focus-profile-label`), `Save current` snapshots live visibility as a custom profile, `Reset` returns to built-in, collapsed `Customize profile` `<details>` holds per-row show/hide radios (`focus-row-<id>-<value>`). Instant-apply WS, outside unified Save draft. See change: add-focus-mode-and-card-block-toggles. |
| `LazySettingsPanel.tsx` | `React.lazy` wrapper of `SettingsPanel` (type-only import of the real module). `Suspense` in-panel spinner (`settings-loading`); `ErrorBoundary` error state (`settings-load-error`) with Retry that mints a fresh lazy (drops memoized rejection). Module-level lazy → one fetch per page. See change: shrink-client-entry-lazy-islands. |
| `ModelProxySection.tsx` | Settings panel section for model proxy. Exports `ModelProxySection`, `ModelProxyConfig`. → see `ModelProxySection.tsx.AGENTS.md` |
| `ModelSelector.tsx` | Variant C: grouped by provider only (no separate favorites group), per-row star toggle, capability badges (🧠/👁… → see `ModelSelector.tsx.AGENTS.md` Footer refresh button + `refreshing` state REMOVED; `onRefresh` now fires on the dropdown open transition (sole trigger); new `refreshErrors?` prop renders a non-blocking footer notice (`data-testid model-refresh-errors`), never a toast. See change: upgrade-model-selector-primitives. Favorites now the component DEFAULT: no `favorites`/`onToggleFavorite` props → both read from `useModelConfigOptional()`; explicit either prop → caller owns both. No resolvable toggle → ★ buttons + `favs-only-toggle` hidden, persisted `favOnly` ignored. See change: model-picker-everywhere-favorites. Change: `allowRoles` Model|Role tabs (`useRoleRows` fetches `/api/roles` on mount + each open; tab only on 2xx), `RoleList` (unassigned marker), `viaRole` hint, trigger shows `@role → model · level`. RoleTabs ArrowLeft/Right moves focus with selection (WAI-ARIA). See change: add-role-aware-model-refs. |
| `NetworkTrustParts.tsx` | Security ▸ Network access parts: `NetworkTrustModeRadio` (native radios in `role=radiogroup`; testids `network-trust-mode-pair`/`-legacy`), `NetworkTrustNotices` (consequence `network-trust-mode-consequence`, migration `network-trust-migration`, LAN-off info `network-trust-lan-off` + checkbox `network-trust-accept-lan`, unrecognised-value warning `network-trust-unrecognised`), `PairRefusedPeer` (info row `block-event-<ip>`, action `block-event-allow-requests-<ip>`), pure `canRequestAccess`, `deviceAddresses`. Draft only — every handler edits the panel draft; panel Save writes. See change: retire-trusted-networks-bypass. |
| `OAuthFlowView.tsx` | Generic OAuth flow body extracted from `SignInPane`: authUrl link, device code + countdown (`useDeviceCodeRemaining`), `manual_code`/`text` paste field, `select` options, Cancel; one-answer-per-step latch. Props `UiOAuthFlowViewProps` `{flow,onSendInput,onCancel}`; renders only while `phase` starting/waiting. Registered as `ui:oauth-flow`; test ids `dialog-flow-waiting`, `dialog-input-field`, `dialog-input-submit`, `dialog-cancel`. See change: expose-plugin-credential-and-oauth-seams. Copy via `copyText` (non-secure LAN origin safe). See change: add-lan-knock-pairing. |
| `PeerAutoInstallStatus.tsx` | Content-only status for the auto peer install: deciding/running (info, in-progress), installed (info), failed (warning: plain sentence first, secondary Retry, raw output in collapsed `LogBlock`). Exports `PeerAutoInstallStatus`, `SECONDARY_BUTTON_CLASS`. Caller owns the `role="status"` wrapper. Shared by the Anthropic row + Landing Step ①. See change: streamline-first-run. |
| `PiSettingsJsonEditor.tsx` | Lazy Monaco JSON editor (`language="json"`) for the whole pi `settings.json`; shared `monaco-setup` + dashboard theme. See change: edit-pi-agent-settings. |
| `PiSettingsPage.tsx` | Settings → pi → pi settings page (`activeTab:"pi-settings"`): schema-driven Form (every typed pi key, categories, filter/changed-only/jump chips, link + Edit-in-JSON rows) / JSON modes, ONE draft at a time via `useSettingsDraftSource` (`id`/`page:"pi-settings"`, no private Save). `commit()`: validate → precompute patch → legacy cleanup → patch with the cleanup token (rebase, never `adopt`). 409 banner + reload, 400 field errors, drift action (`deriveMirror`). Injectable: `load`/`save`/`migrate`/`loadCatalogue`/`syncDefaults`/`onNavigate`/`JsonEditor`. See change: edit-pi-agent-settings; pi-settings-full-coverage. |
| `PiSettingsRows.tsx` | Rows of the pi settings page: `SettingRow` (segmented ≤3 values, select, number/text, theme select + Custom…), `EditorRow`, `ExtraSettingRow` (link / Edit in JSON / default-model), `Segmented`. Changed pill carries text; inline error tied via `aria-describedby`. See change: pi-settings-full-coverage. |
| `pi-settings-form.ts` | Pure form logic of the pi settings page: `toForm`/`parseField`/`fieldError`/`changedFieldErrors`/`buildFormPatch` (`""` = unset), `EXTRA_ROWS` (link/JSON-only keys), `DASHBOARD_MANAGED_KEYS`. See change: pi-settings-full-coverage. |
| `ModelScopeEditor.tsx` | `enabledModels` editor: All/Selected switch, provider + model toggles, patterns, in-scope count, JSON preview, roles/auto-append notes, catalogue-failure notice. Logic in `lib/model-scope.ts`. See change: pi-settings-full-coverage. |
| `PiLegacyCredentialsWarning.tsx` | Masked plain-text API-key warning with per-key queued Move / Remove / manual-step (Form mode only, undo until Save). See change: pi-settings-full-coverage. |
| `PiRuntimeSection.tsx` | Settings → Developer "Pi runtime" section, mounted directly above `<ToolsSection />` (design D12 correction: Tools left General in `reorganize-settings-pages-and-descriptions`; adjacency is the load-bearing half of the rationale). Two consumer lanes (Sessions spawn = blue / Server imports = purple) over one candidate list with two selection columns. "Keep both in sync" is DERIVED from realpath'd package-dir equality and persisted NOWHERE — a stored flag would open "checked" on a pre-existing single-consumer pin and one click would clobber it. Apply issues ONE `POST /api/pi/runtime`; the confirm dialog restates a resulting mismatch BEFORE the write and does not claim one when the lanes agree. Below-floor rows disabled with the required minimum named; unknown-version rows warn but stay selectable. Restart offered only when the IMPORT consumer changed; running sessions on the previous version counted, unknown-runtime sessions reported separately. Collapses below 680px to metadata-over-two-cells with ≥44px hit areas. See change: select-pi-runtime-install. See change: align-ui-with-theme-tokens: copy "New sessions" lane/column, "Custom start entry (pi)", "session-runtime change" (no spawn wording; i18n keys unchanged). |
| `PiRuntimeStatusRow.tsx` | Settings → General ALWAYS-visible READ-ONLY pi runtime summary (the conditional advisory's permanent counterpart). Props: `piRuntime` (`PiRuntimeHealth` or null) + required `onChangeRuntime` callback. Renders `piRuntime.title` + both consumers (`piRuntime.laneSpawn`/`laneImport`) with per-consumer version; null consumer version → `piRuntime.unknownVersion` fallback, never fabricated; `consumerDiverged && consumerMessage != null` → message VERBATIM in a warning; `Change…` button (`piRuntime.changeRuntime`) → `onChangeRuntime` (panel navigates to the Developer picker + scrolls `pi-runtime-section`). STRICTLY read-only: no fetch of its own (host panel owns the single `/api/health` poll), no `POST /api/pi/runtime`, no `PUT`/`DELETE /api/tools/:name`, no `CONFIG_FIELD_PAGE` entry, no Save Bar contribution; no automatic-vs-pinned labels (the health shape carries versions + divergence only — D2 gate). `piRuntime` null/absent (older server, discovery failure) → renders nothing. Testids: `pi-runtime-status-row`, `-spawn`, `-import`, `-warning`, `-change`. See change: surface-pi-runtime-on-general (D1/D2). See change: align-ui-with-theme-tokens: lane label "New sessions" (key `piRuntime.laneSpawn` unchanged). |
| `PiTrustMirrorSection.tsx` | Settings → Access mirror UI: switches, effective/live rows, choices, live-query, reason legend. → see `PiTrustMirrorSection.tsx.AGENTS.md` |
| `PluginSettingsPage.tsx` | Host-owned chrome for `/settings/plugins/<id>`. Breadcrumb `plugin-page-back-to-index` (`tap-target`) ABOVE card `plugin-page-chrome` (`rounded-xl`, title `text-lg`, id `plugin-page-id`); kit `ToggleField` `role="switch"` "Enabled" (`plugin-page-toggle-<id>`); `PluginHealthPill` (`plugin-page-pill`, ONLY unhealthy: Off/Not loaded/Failed to load/Needs setup/Status unknown); `SettingsNotice` banners (load error = sentence + copyable `plugin-status-error-<id>`, missing reqs `plugin-page-missing-requirements`, restart, disabled, not-found); "Needs"/"Used by" chips always; slot claims ONLY when `useShowPluginInternals()` on: full = collapsed `plugin-page-internals` details, compact = inside `plugin-page-details`. Body `plugin-page-body-<id>` = `grid grid-cols-1 gap-3`. No opt-out prop; `PluginSettingsPageProvider` files drafts under `plugins/<id>`; disabled → notice + re-enable, no body. Optional `promotion` → COMPACT chrome (`plugin-page-compact-header`, lede, provenance, `plugin-page-details`). Exports `PluginNotFoundNotice`. See change: plugin-settings-pages, promote-model-roles-settings, retro-ux-plugin-settings-wave-2. |
| `ProviderAddDialog.tsx` | Add-provider dialog — PRESENTATION ONLY (D4): picker + per-`flowType` panes; the SECTION owns flows and write outcomes. Exports `ProviderAuthSection`-fed `ProviderAddDialog`, plus `AddDialogFlowState`, `CustomEndpointInput`, `isConfiguredRow` (`configured ?? authenticated` — E9), `buildPickerEntries` (picker = unconfigured rows, grouped Subscriptions/API keys + PINNED Custom endpoint entry; cross-type suppression = api_key row whose OAuth sibling is configured, or OAuth row whose `<id>-api` twin carries `maskedKey` — visible, non-selectable, remove-first reason), `countSelectable` (E10 label count), `API_TYPE_OPTIONS`. Panes: auth_code (sign-in + copyable authUrl fallback), device_code (code + URL + EXPLICIT Open Registration Page, never auto-opens; github-copilot prompts Enterprise domain BEFORE start), api_key (envVar hint), custom endpoint (name/baseUrl/api/key + Test). Validates BEFORE write: empty key / blank name → visible message, NO request; failed write keeps dialog open with inline error, dispatches nothing. Built on `SearchableSelectDialog` + `Dialog` (portal). Tests: `src/__tests__/ProviderAddDialog.test.tsx`. See change: redesign-providers-settings-page. `SignInPane` keeps provider prop, Copilot pre-prompt, `PaneShell` + error/Try-Again; renders the flow body via `OAuthFlowView` (direct import). See change: expose-plugin-credential-and-oauth-seams. |
| `ProviderHealthPill.tsx` | Health pill for custom-endpoint surfaces (dialog pane + row). Exports `ProviderHealthPill`, `derivePillView`, `LiveTestResult`. Registers: testing / pending (`providers.pendingProbe` — write landed, detached probe not reconciled) / ok (model count) / error (HTTP status) / unreachable / not-tested; non-ok renders verbatim error on monospace second line (`provider-error-line`). Credential rows render NO pill (D8 scope). `data-testid health-pill` + `data-state`. Unit tests: `__tests__/ProviderHealthPill.test.tsx` (register matrix + `derivePillView` live-over-health precedence); probe/Test-action behaviours live in `src/__tests__/ProviderAddDialog.test.tsx` (pane, NEW provider) + `src/__tests__/ProviderAuthSection.test.tsx` (row, SAVED provider). Moved from the deleted `LlmProviderCard` in SettingsPanel. See change: redesign-providers-settings-page. Registers render through kit `SettingsStatusPill` in a `data-testid=health-pill data-state` wrapper (testing/pending spin `mdiLoading`); `provider-error-line` = kit `SettingsActionError` (one sentence `providers.healthErrorLine`, raw error only in Copy details). See change: align-ui-contract-recipes. |
| `ProviderAuthSection.tsx` | Settings → Providers CONNECTED LIST (redesigned). Exports `ProviderAuthSection`, `customEndpointConfigured`. → see `ProviderAuthSection.tsx.AGENTS.md` Warning notice (`legacy-provider-rename-notice`) from `GET /api/provider-auth/legacy-provider-ids` naming locations, pi's fallback/prompt-cache consequence and the fix; absent/failed → no notice. See change: update-pi-core-1-1-adopt-apis. |
| `PushNotificationsSection.tsx` | Settings ▸ Sessions ▸ Push notifications. `usePushSubscription` availability (non-ok vapid key → "Push not enabled on this server", no controls); device `role="switch"` only in secure context (else https notice); iOS install-to-home-screen hint; token list by server `display` with Send test + Remove; "Add webhook URL" form (works without Web Push), 400 shown inline via `aria-describedby`. i18n keys `settings.push.*`. See change: add-server-push-notifications. |
| `RadiusMcpOffer.tsx` | Post-sign-in "Configure Radius MCP?" inline offer (`InlineMessage`, info). Exports `RadiusMcpOffer`, `fetchRadiusMcpOffer()` (GET `/api/provider-auth/radius/mcp` → `{name,path}` or null), `radiusMcpErrorMessage(body)` (known `provider_auth.radius_mcp_*` codes → i18n, else server `error`). Accept = one POST; decline = no request. See change: add-radius-provider-login. |
| `RetrySettingsSection.tsx` | Editor for pi's OWN retry policy — ALL SIX fields (`retry.{enabled,maxRetries,baseDelayMs}` + `retry.provider.{timeoutMs,maxRetries,maxRetryDelayMs}`) via `pi-retry-api`. Exports `RetrySettingsSection`; `load`/`save` props injectable for tests. Mounted in SettingsPanel **`sessions`** tab (turn-level fields dominate; the observable effect is on a session), section titled "Retry" — the body renders NO heading of its own. NO private Save button: registers via `useSettingsDraftSource({id:"pi-retry", page:"sessions", isDirty, commit, reset})` so the panel's unified Save commits it, the nav rail shows a Sessions dirty dot, and the leave guard covers it; `commit` THROWS on invalid input or a failed PUT so the host keeps it dirty and names it in `savePartialFail` (never a false success); `reset` restores the loaded policy for Discard. `isDirty` = string-compare vs the loaded snapshot, so an invalid in-progress edit still counts. Renders enabled toggle (off → greys the turn-level numerics), a live `SchedulePreview` (delay progression + total, since pi's delay is `baseDelayMs * 2^(n-1)` uncapped), a NON-BLOCKING long-tail warning above `LONG_TAIL_ATTEMPTS=20` (warned, never capped — a UI cap is bypassed by editing the file), a global-scope disclosure, the `retry.provider.*` trio under its own "Provider / SDK request controls" subhead with an invisible-wait warning (that layer emits no event → renders as ordinary streaming), and a status line reporting `reloadedSessions`. Blank `provider.timeoutMs` is OMITTED, never `0`/`null`. Client-side validation mirrors the server's. See change: retry-forever-with-stop-control, unify-settings-save-contract (draft-source rewiring + Sessions placement). |
| `RolePickNotice.tsx` | `RolePickNoticeCallout`: warning callout (icon, `role="status"`, keeps host `data-testid` + `data-kind`) for one-shot role-pick notices; shared by the composer and the OpenSpec run-config row. See change: retro-ux-model-selector. |
| `SettingsPanel.tsx` | Settings UI: left-nav rail + page content… → see `SettingsPanel.tsx.AGENTS.md` **Provider credentials and custom endpoints are NOT a Save-bar source** (settings-panel REMOVED requirement): the LLM-providers draft state, baseline, save task, `LlmProviderCard`, `HealthPill`/`derivePillView`/`TestState`, and the `GET /api/providers` load fetch are DELETED — the Providers page renders `ProviderAuthSection` (which owns its own two-source reads + per-write PATCH/DELETE) plus the API Proxy section; `modelProxy` keeps the page's dirty chip; the `PROVIDER_AUTH_EVENT` dispatch, `refetchCatalogue` and post-write health refetch moved onto the section's write paths (`onCredentialsChanged={refetchCatalogue}` preserved). See change: redesign-providers-settings-page. Network group gains the `access` page (client route whitelist only, like `gateway`; `VALID_PAGES` + nav item + `{activeTab === "access" && <AccessSection />}` branch before security). See change: add-access-grants-and-review. Trusted Networks gains the unreachable-bind advisory (live region, above the block-event banner) with an inline `0.0.0.0` remediation and a Server-page link; the header Restart affordance signals a saved-but-unapplied `bindHost`; the Add Local Network dropdown renders deduped `suggestions`. See change: warn-unreachable-trusted-networks. Sessions page renders a `ThinkingLevelSelector` inside the Default Model `--severity-info-*` callout, bound to `config.defaultThinkingLevel`; levels derive from the selected model's `supportedThinkingLevels` (App threads it through `availableModels`), lock to `["off"]` (persistence no-op, field stays `""`) when no model is selected, and persist via the `PUT /api/config` partial. See change: add-default-thinking-level. The Sessions-page auto-name toggle carries `auto-name-model-pointer`, naming the `@naming` role (configured in the Roles panel) and its `@fast` fallback — the naming model has ONE source of truth (the roles map), so the toggle points at it rather than duplicating a selector. See change: fix-auto-naming-reasoning-model. Root is `flex-1 flex flex-col min-w-0 min-h-0` (NOT `h-full`) — the flush Dialog panel is a capped flex column with no definite height, and this root is shared with the MobileShell detail panel. See change: fix-flush-dialog-scroll-and-close-collision. Security page's `Pair a device` section is a LINK, not a pairing UI (`security-pair-link` testid → navigate `/settings/gateway` + rAF-scroll `#connect-a-device` into view; body names destination+act; button mirrors `Open Security →` shape); no QR, copy-string, or approve control renders on Security. See change: collapse-pairing-into-gateway. Memory Limits gains a `replayWindowMode` SELECT, DISABLED (not hidden) at `maxReplayEvents: 0` with the dependency stated — hiding it would make the dependency invisible. Its hint carries both the tradeoff (opening messages omitted) and the SCOPE (affects every client of this server). `MemoryLimitsConfig` is no longer all-numeric, so the field-level diff writes through a `Record<string, unknown>` cast; the write stays FIELD-level, never whole-object. See change: add-tail-only-replay-window (D10). Chat display gains the `reasoningInlineFlow` toggle (inside the reasoning GatedGroup, visible-but-disabled when reasoning off) and — replacing the removed single `customEntryFallback` toggle — one toggle per configured custom event group under a "Custom event groups" subhead (labels from `useCustomEventGroups()`; absent id falls to the group's configured default; draft patch merges `customEventGroups` field-by-field). See changes: render-inline-reasoning-and-custom-entries, add-custom-event-group-filters. Composes `AllowedHostsSection` between Trusted Networks and Pair a device; `computeConfigPartial` diffs `allowedHosts`/`hostGate` (each written whole) and `CONFIG_FIELD_PAGE` maps both to `security`. See change: add-host-allowlist-admission. Sessions page configures auto-archive policy (`sessionList.archiveAfterDays`, `sessionList.archiveSweepIntervalMinutes`) with numeric validation and field-level partial diffs. See change: archive-sessions-lazy-load. Adds `HeapMbField` (bespoke, NOT `NumberField`: below `MIN_HEAP_MB` is REFUSED and never reaches the draft, above `HEAP_WARN_ABOVE_MB` is WARNED and still saved — a shared NumberField would flatten both into hint text). Sessions page gains Session memory (V8 heap) with `sessionHeap.maxOldSpaceMb`, `maxConcurrentSubagents`, and the `subagentHeapBudget` coupling warning; Server page gains Server memory (V8 heap) with `serverHeap.maxOldSpaceMb`, cold-start copy, and a configured-vs-effective divergence note fed by `/api/health` `server.effectiveMaxOldSpaceMb` on the existing health read. `CONFIG_FIELD_PAGE` gains `sessionHeap`→sessions, `serverHeap`→server, `maxConcurrentSubagents`→sessions; `computeConfigPartial` diffs both heap blocks FIELD-level (whole-object write would pin materialized defaults). Server copy avoids the phrase the generic restart banner owns. See change: bound-session-heap-and-gc-telemetry. |
| `ThemePicker.tsx` | Palette dropdown for theme selection. Lists `THEMES` with color swatches, flip-aware (`usePopoverFlip`), outside-click close. Reads/writes `useThemeContext`. Exports `ThemePicker`. See change: fix-popover-container-clip — documented structurally immune (no narrow offset `overflow` pane above it → keeps viewport measurement, no `boundaryRef`). See change: fix-popover-pane-bounded-height — applies BOTH `minHeight` and `maxHeight` (default 120 floor; 4 themes ≈ 112px content, so 260 would render dead space); immunity re-audited and CONFIRMED, correcting the stale "settings header" claim — its only mount is `SessionList.tsx` header bar, a sibling ABOVE that view's scroll list. |
| `ThemeProvider.tsx` | React context provider wrapping `useTheme` hook. Exports `ThemeProvider`, `useThemeContext` (throws outside provider). |
| `ThemeToggle.tsx` | Exports `ThemeToggle`. Three-button light/system/dark switcher; reads `preference`/`setPreference` from `useThemeContext`. Renders mdi icons, `data-testid="theme-toggle"`. |
| `ThinkingLevelSelector.tsx` | Thinking-level picker. Optional prop `supportedLevels` filters `THINKING_LEVELS` to supported set (canonical… → see `ThinkingLevelSelector.tsx.AGENTS.md` |
| `NodeRuntimeSection.tsx` | Settings → Developer Node family picker (mounted between `PiRuntimeSection` and `ToolsSection`). Fetches `/api/node/installs`; renders candidates (entry-bearing only), coherence report (mismatch names deviator+root via `node-runtime-mismatch`; hand-set overrides listed), migration-adopted selection. Apply = ONE `POST /api/node/installs/select` (whole family written atomically); confirm block restates hand-set deviations pre-write, preserved unless explicitly discarded. See change: add-node-runtime-family-selection. |
| `ToolsSection.tsx` | Settings → General → **Tools** section. One row per registered tool: status badge, source, truncated path,… → see `ToolsSection.tsx.AGENTS.md` |
| `__tests__/AccessProvisioningSection.test.tsx` | E26 `Provisioned` + `requested for /w/app` + revoke vs agent-prompt/prompt labels; F3 no capability → listed read-only, no approve controls, reason shown; answerable cards (settle wire shape, reject, dismiss-all, 409 error, refetch on queue change, 404 = none). See change: add-access-provisioning (tasks 3.8, 3.9). |
| `__tests__/PiTrustMirrorSection.test.tsx` | #F1 live rows/legend/live-query; #F2 effective list, choice PUT. See change: mirror-dashboard-trust-to-pi, mirror-dashboard-trust-live-remote. |
