AGENTS.md@packages/client/src/components/settings · git:20260928.559f0ed · 2026-09-28 · sha256 4a2bb554ea240e02
AGENTS.md@packages/client/src/components/settings git:20260928.559f0edA
Immutable. This exact content never changes and is served at /api/v1/blob/4a2bb554ea240e02.
# DOX — packages/client/src/components/settings
Files in this directory. One row per source file. See change: fold-oversized-agents-directories.
| File | Purpose |
|------|---------|
| `AccessPromptsSection.tsx` | Settings ▸ *Access* prompt surfaces, rendered ABOVE and as a sibling of `AccessSection` (keeps that section's revoke-only contract). Exports `AccessPromptsSection({store?})`. S4 banners from `prompting.blockers` (`access-prompts-banner-{report-mode,kill-switch,disabled}`; report-mode links `/settings/security`) + `no-channel` when `useHasGrantChannel()` false. S5 `role="switch"` toggle `access-prompts-toggle` → `setPromptEnabled` (PUT `/api/config`), inert (disabled + reason) never hidden under report-mode/kill-switch; kill switch reads off. S3 pending rows answerable inline (allow-once only when `copy.mode === "held"`), recent verdicts (operator: store written + `widenedFrom`; yolo: "no human answered"), remembered refusals with Clear. Refetch on mount, on `grantPromptStore` version bump, and 10 s poll. See change: add-access-grant-dialog (tasks 8.1-8.3). Prop `selectedCwd?` → `YoloAccessCard` base; every fetch publishes `view.yolo` to `yoloStatus`; YOLO verdict rows carry `access-verdict-yolo-badge` + "no human answered", listed after the session ends. See change: add-access-grant-dialog (tasks 8b.7, 8b.7a, 8b.8) |
| `AccessSection.tsx` | Settings ▸ Network ▸ *Access* — read-and-revoke surface over the eight in-scope grant stores (design D6, change: add-access-grants-and-review). Mount-only `fetchAccessSnapshot()` GET (zero store writes on render, task 7.7); groups `aggregateAccessEntries` output under per-store headings (`access-store-<store>` testids); per-entry Revoke → `revokeAccessEntry` → refetch (revocation visible without restart); empty state (`access-empty-state`) when no store holds a grant; legacy hash-only KB entries render the opaque hash + `hash only` badge; session grants render all four D17 fields. NO grant-creation control exists by construction (design D12, task 7.9 — pinned by composition test: every button is `access-revoke`, no textbox/combobox). Theme tokens only. i18n keys `access.*` + `settings.access` (zh-CN, hu, en-source). Tests: `__tests__/access-page-composition.test.tsx`. See change: add-access-grants-and-review. Path-grant rows show `access-entry-via-prompt` ("via prompt") when `via === "prompt"` and `access-entry-widened` when `widenedFrom` set. See change: add-access-grant-dialog (tasks 8.2) |
| `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. |
| `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. |
| `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` |
| `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. |
| `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. |
| `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). |
| `PluginSettingsPage.tsx` | Host-owned chrome for the per-plugin settings page `/settings/plugins/<id>`: identity, status pill, enable toggle, metadata chips, error + missing-requirement banners, then the plugin body via `SettingsSectionByPluginSlot`. No opt-out prop. Provides `PluginSettingsPageProvider` so draft sources file under `plugins/<id>`. Disabled plugin → chrome + disabled notice + re-enable, no body mount. Also exports `PluginNotFoundNotice`. See change: plugin-settings-pages. Optional `promotion` prop → COMPACT chrome: `nav.label` title, lede `plugin-page-lede`, provenance `plugin-page-provenance` ("Provided by the <X> plugin", Cc/Cf stripped) + toggle, `CompactHealthPill` (`plugin-page-pill`, only when unhealthy incl. missing requirements / unknown), id/dependsOn/dependents/slots in `<details>` `plugin-page-details`. Body wrapped in `plugin-page-body-<id>`. See change: promote-model-roles-settings. |
| `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. |
| `ProviderAuthSection.tsx` | Settings → Providers CONNECTED LIST (redesigned). Exports `ProviderAuthSection`, `customEndpointConfigured`. → see `ProviderAuthSection.tsx.AGENTS.md` |
| `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. |
| `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). |
| `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` |