# Settings and Preferences Settings rows and actions, the native Preferences window and its project context, Providers/Web search, Stats, notifications and bug reports. ## Agent conventions - Settings → Providers separates Models and Web search into two shared `.settings-disclosure` groups (no icons): Models starts open, Web search folded, and the folded Web search mounts nothing, so its connection metadata is read only once opened. Each starts with a provider selector, Connect action and configured-provider list; Web search then shows its global default and project override. Reuse provider rows, selectors, inputs and action styles; do not add an API-key disclosure or a separate card design. Search-only integrations stay out of the Models group. Web search configuration lives entirely here. Read connection metadata while this Settings surface is visible, never credential secret values; keep API-key entry explicit and clear drafts on context changes. Use the authorized Global/Project document operations shared with plugin controls and `opencode/native-setting-document.ts` for targeted JSONC edits. Preserve unrelated fields, native discovery roots, WSL atomic writes and conflict/connection/deletion fences. Group/search styling lives in `styles/components/websearch-settings.css`; credentials use native integration methods and remain global. - Stats preferences explicitly aggregate the entire connected OpenCode service via `opencode/service-usage.ts`. Reserve the Usage label for the provider subscription panel. Native project IDs span independent clones, so never label native `session.stats` as physical-folder/Git-family scoped. Keep connection/workspace fences, `tools: "none"` and periods bounded to 366 days; never load transcripts for statistics or imply billing/quota precision. Last snapshots and period choices restore through backend-owned `state.ui.serviceUsage` (eight each/1 MiB, validated service/channel/folder identity), never a Preferences writer to main-window client state or a workspace-UUID cache. Native aggregation must not await optional discovery/cache reads; cached responses cannot replace fresh data. Backend authorized admission order, not completion order or client time, chooses the period across windows; optional publication is bounded and shutdown-fenced. Keep last-known-owner totals explicitly stale through disconnect/errors and revoke them on verified owner/discovery changes. Read while mounted and document-visible; hide cancels queued/trailing demand, activation revalidates once and terminal hints use the shared activity cooldown. Never treat document visibility as native minimize/occlusion detection or equate blur with hidden. See `dev-docs/CACHE_REFRESH_CONVENTIONS.md` for the remaining native scope. Styles live in `styles/components/usage-dashboard.css`. - Settings rows keep only a title unless a caption is essential; secondary explanations go behind `components/settings/settings-info-tip.tsx` (a focusable "i" with a hover/focus tooltip that a tap toggles on touch screens, a span so it never becomes a row label's control or toggles it), styled in `styles/components/settings-info-tip.css`. Behavior settings opt in with `infoKey` instead of `subtitleKey`. Folded settings groups use the one `.settings-disclosure` (`styles/components/settings-disclosure.css`; a summary may hold an inline heading that keeps the summary's look): Missions gathers every agent/model choice under "Agents and models" with the current request in its summary, and Advanced ends with the environment-variable editor folded under a single title. - Settings update/install actions (OpenCode and Info), OpenCode's post-update service restart, and Remote Control start/pairing-link actions use the shared `.selector-button-primary` accent treatment. The shared `OpenCodeSetupPanel` keeps those same primary actions in startup recovery. Remote Control's header refresh is an icon-only `.window-icon-button`, retaining its localized accessible name, tooltip, disabled state and loading spinner. - The native Preferences window never owns client state. Its General startup card (`settings/opener-startup-state-settings-card.tsx`) targets the exact local window that opened Preferences: the host records that opener and an epoch (Electron `PreferencesWindowRegistry`, Tauri `PreferencesWindow`), forwards only the fixed commands in `lib/native/startup-state-command.ts` to that renderer, which runs them through its own `stores/client-state` transitions. Mutations carry the displayed epoch and degrade to a read after the opener changes. Startup state is per window: when the opener is unknown (Preferences restored at launch) or closed, the host adopts the most recently used local window as the opener (`startupStateTargetId` + `adoptOpener` / `startup_state_target` + `set_opener`), rotating the epoch so a change prepared for another window is only a read. The card re-reads a few times while the adopted window starts and stays disabled only when no local window can answer. - Native Preferences resolves its project context through `settings/preferences-project-context.ts` before Providers, Missions or Stats read project data. Backend workspace IDs are ephemeral: use the exact requested record while it exists; otherwise bind only one unambiguous exact-folder match from the backend's metadata-only inventory. Requests persist the opened backend-host folder as `workspacePath`, separate from the selected native `location.directory` (WSL/worktrees); keep the native directory for requests and drop any stale native workspace selector when rebinding. Requests without the backend folder match only their exact directory, never through guessed WSL translations or ancestors. A starting, missing or ambiguous project stays inactive. Read only on section demand, matching workspace lifecycle events or reconnect; never hydrate other workspaces, discover worktrees, create projects or poll. Record all deletion tombstones before filtering by the current binding so late inventory responses cannot publish deleted candidates, and revalidate when deletion resolves an ambiguity. Invalidation and newer native requests fence late reads, explicitly deleted occurrences never turn into sibling tabs, and rebinding persists through the existing Preferences request queue without resetting the section or newer scrolling. The shared renderer and request contract cover both Electron and Tauri; startup regressions live in `tests/browser/preferences-startup.test.ts`. - Settings → Info's Report a bug opens the `bug_report.yml` issue form with its text fields prefilled by id (`BUG_REPORT_FIELDS` in `lib/bug-report.ts`). When the template changes, keep those ids and their input/textarea types in sync: `lib/bug-report.test.ts` checks the repository's template. The section warms a bounded read of the form published on the default branch; the click never waits for it (WebKit drops user activation after about 1 s), using the settled assessment or else the form while a background read retries. Only a proven incompatible form (missing, unparsable, prefilled field removed or retyped) falls back to a blank issue carrying the same Markdown report; an unreachable read keeps the form. Never prefill listeners, addresses, paths, credentials or conversation data. - Notification icon badges mirror the existing per-window bell/history unread count, never session status or native OS notification delivery. The browser favicon uses a dot; native Electron/Tauri hosts aggregate live primary-window counts and clear contributions on reload/close, keeping preferences and preview guests outside the badge authority. Keep UI projection in `lib/notification-icon-badge.ts` and host dispatch in `lib/native/notification-badge.ts`; do not add discovery scans or polling. - Notifications have one routing table (`lib/notification-routing.ts`): every `showToastNotification` declares a category, and Settings → Notifications chooses per category On screen / History / System (`components/settings/notification-routing-table.tsx`, `styles/components/notification-routing.css`). The history, and therefore the icon badges, holds only what is routed there; errors always reach the screen and the history; `transient` live feedback (progress, retry countdowns, "up to date") stays on screen only. The routing table is the only system-notification control: checking a System box requests permission, and saved `osNotificationsEnabled`, `notifyOnNeedsInput` and `notifyOnIdle` preferences only seed the routing until it is first saved. The bell (tab bar, or title bar in the macOS desktop app) is the history, independent of system notifications. System delivery is held back only while the window has focus (visible and `document.hasFocus()`), unless **Notify when the app is focused** (`osNotificationsAllowWhenVisible`) is on; a window left open behind another application or on another screen still notifies (`tests/browser/system-notification-focus.test.ts`). Classify new notifications by what the user would want to find later, not by their source file; a warning that must be seen once by default (the incomplete-recovery warning) uses a category that shows a bubble (`projects`), never `needsInput`, whose default is System only.