-
Notifications
You must be signed in to change notification settings - Fork 0
Settings Panel & Capability Pages
This page covers the settings UI shell: how the cog opens it, how the sidebar/group navigation is composed, how a page turns into a list of sections, and how the per-feature capability pages (Skills / Hooks / Commands / Plugins / Subagents / MCP / Usage) plug into that shell. The internals of those capability pages are documented separately under Capability Settings: MCP, Plugins, Skills, Subagents & Usage and Hook & Slash Command Settings; here we focus on the shell they render inside.
-
Entry point.
CogMenu.tsxis a single presentational button — no menu, no dropdown. The old source-control/settings dropdown was removed: source control now lives in the sidebar, so the gear is a one-click path to the app settings sheet (src/renderer/src/components/CogMenu.tsx#L1-L27). -
Panel shell.
AppSettingsPanel.tsxrenders a full-width sheet with a head row (title + close), a left sidebar nav, and a scrollable content column grouped by domain. It renders its controls with the shared UI kit incomponents/ui/kit.tsx(Button,Card,CardNote,FieldRow,Hint,PrefRow,Row,Section,Select,Status,TextArea,TextInput,Toggle) and is styled by Tailwind v4 utilities with tokens insrc/renderer/src/settings.css(neutral palette — no lime) (src/renderer/src/components/AGENTS.md#L13-L30). -
Capability page bodies.
CapabilityPages.tsxexports one component per capability page —SkillsPage,HooksPage,CommandsPage,PluginsPage,SubagentsPage,McpServersPage,UsagePage— which the panel imports and mounts as sections (src/renderer/src/components/AppSettingsPanel.tsx#L12-L20).
App.tsx owns the mount state and the settings mirror. The toolbar always renders CogMenu; the panel is conditionally mounted, and any settings write is echoed back into the app root so live-settings (e.g. invert-wheel zoom) take effect without reopening the panel (src/renderer/src/App.tsx#L88-L104).
flowchart TD
Toolbar["App toolbar"] --> Cog["CogMenu — one-click gear"]
Cog -->|onOpenSettings| Open["App: settingsOpen = true"]
Open --> Panel["AppSettingsPanel (full-width sheet)"]
Panel -->|onClose / Escape / backdrop| Close["App: settingsOpen = false"]
Panel -->|onSettingsChange| Sync["App: setSettings"]
Sync --> Theme["applyTheme(settings.theme)"]
Sync --> Home["useBrowserHome.setHomeUrl(browserHomeUrl)"]
Sync --> Canvas["Canvas: invertWheelZoom"]
Key nodes:
-
CogMenuis stateless; it only signalsonOpenSettings. There is no menu to keep in sync. -
AppSettingsPanelPropsdeclaresonClose(required) andonSettingsChange(optional), with the doc comment spelling out the live-settings contract (src/renderer/src/components/AppSettingsPanel.tsx#L22-L27). - On app boot,
Apploads settings once viawindow.termsprawl.settings.get()and callsapplyTheme(...); the samesettingsobject is handed to the panel as the initialSectionCtx.settings(src/renderer/src/App.tsx#L40-L47).
The sidebar is data-driven. NAV_GROUPS is an ordered list of { label, pages }, each page a SettingsPage with id, title, optional description, an icon, and optional editions. The full PageId union is the page inventory: general, appearance, models, browser, skills, hooks, commands, plugins, subagents, mcp, accounts, a2a, usage, cloud, connections, updates (src/renderer/src/components/AppSettingsPanel.tsx#L146-L165, #L123-L139).
flowchart TD
Groups["NAV_GROUPS"] --> Pages["SettingsPage: id, title, description?, icon, editions?"]
Pages --> Gate1{"editions omits current EditionKind?"}
Gate1 -->|yes| DropPage["page omitted from sidebar"]
Gate1 -->|no| Nav["sidebar nav item — selects active PageId"]
Nav --> Body["scrollable content column"]
Body --> Sections["SettingsSection[] for this page"]
Sections --> Gate2{"section.editions omits current edition?"}
Gate2 -->|yes| DropSec["section not rendered"]
Gate2 -->|no| Bare{"section.bare?"}
Bare -->|yes| Own["render(ctx) supplies its own cards — CapabilityPages exports"]
Bare -->|no| Wrapped["render(ctx) inside the panel's card wrapper"]
Own --> Ctx["SectionCtx"]
Wrapped --> Ctx
Key nodes:
-
EditionKindis'desktop' | 'server', read from the bridge's runtime hint. A page likebrowseris markededitions: ['desktop']and is dropped from the Server Edition panel — the canvas shows only what the server actually implements (src/renderer/src/components/AppSettingsPanel.tsx#L140-L155,#L213-L225). -
bareexists for pages that already lay out their own titled groups (the code names Skills, Hooks, Commands, MCP, Usage) — those sections skip the panel's card wrapper (src/renderer/src/components/AppSettingsPanel.tsx#L67-L77). - The first two groups are visible in the evidence: Basics (
general,models,browser) and Agent capabilities (skills,hooks,commands,mcp,subagents,plugins). The remaining union members (appearance,accounts,a2a,usage,cloud,connections,updates) are declared inPageIdand described incomponents/AGENTS.mdas General / User / Agents / Connections / Updates at a higher level (src/renderer/src/components/AppSettingsPanel.tsx#L167-L300,src/renderer/src/components/AGENTS.md#L13-L25).
A SettingsSection is { id, title?, bare?, editions?, render(ctx) }. The in-code comment calls this out explicitly: adding one to a page's render array is the extension point for future settings, and editions is the mechanism that drops a section from one edition (src/renderer/src/components/AppSettingsPanel.tsx#L63-L77).
Everything a section can read or do arrives through SectionCtx — the single plumbing door:
| Group | Fields |
|---|---|
| Core settings |
settings: AppSettings, update(patch): Promise<AppSettings>, permissionSupported
|
| Accounts |
addAccount, deleteAccount, setActive, setPermissionMode, loginInto, newLabel/setNewLabel, confirmDelete/setConfirmDelete
|
| Cloud / space |
cloudUser, cloudBusy, device, lastBackup, space, spaceBusy, spaceError, spaceNote, spaceLoaded, cloudSignIn, cloudSignOut, cloudBackupNow, cloudOpenSpace, cloudOpenSnapshot, cloudSyncProject
|
| GitHub |
ghConnected, ghBusy, ghNote, ghConnect, ghDisconnect
|
| Workspace bundle |
workspaceExportBundle, workspaceImportBundle
|
(src/renderer/src/components/AppSettingsPanel.tsx#L79-L121.)
The save path is a round-trip through the panel's update(patch) contract and back out through onSettingsChange:
sequenceDiagram
participant UI as PrefRow / capability page
participant Panel as AppSettingsPanel
participant Bridge as preload settings channel
participant App as App (root)
UI->>Panel: ctx.update(patch)
Panel->>Bridge: persist patch
Bridge-->>Panel: new AppSettings
Panel->>App: onSettingsChange(settings)
App->>App: setSettings + applyTheme
Note on the diagram: the persistence hop is shaped by the declared update(patch) => Promise<AppSettings> signature plus the sibling write pattern used elsewhere in App (window.termsprawl.settings.set(...).then(setSettings)); the exact internal call inside AppSettingsPanel was not part of the read excerpt.
CapabilityPages.tsx is the body library for the agent-capability pages. Each export corresponds to exactly one PageId and is mounted as a bare section: the page owns its own titled cards, its own lists, and its own per-item actions, while the surrounding nav, page title/description, close behavior, and edit/close affordances all stay in AppSettingsPanel (src/renderer/src/components/AppSettingsPanel.tsx#L12-L20, #L63-L77).
Related panel-adjacent helpers that the shell reaches for directly:
-
HelpBadge.tsx— a portaled?explanation next to titles, so node overflow cannot clip it (src/renderer/src/components/AGENTS.md#L29-L30). -
relay-trust.ts—trustState/TrustState, the trust-decision helper consumed by the Connections/Relay surface (paired withrelay-trust.test.ts) (src/renderer/src/components/AppSettingsPanel.tsx#L10). -
discoverAgentCardfromcore/a2a/client— A2A peer discovery invoked from the A2A section. -
parseRelayTermFrame/RelayTermFramefromcore/relay-term— relay terminal-frame handling used in the Relay surface. -
useProjectsanduseCanvasRequests— the panel reaches into the project store and canvas request bus for cloud/space and bundle actions (src/renderer/src/components/AppSettingsPanel.tsx#L6-L9).
Agent ordering is a panel-level constant: PRIMARY_AGENTS = ['codex', 'grok'], with Claude registered but shown as optional/secondary (src/renderer/src/components/AppSettingsPanel.tsx#L29-L31).
-
App root:
settings: AppSettings | null,settingsOpen: boolean(plusversion/error). Settings can benullduring boot; the panel is only mounted on user action, at which point the panel expects a concreteAppSettings(src/renderer/src/App.tsx#L22-L25,#L102-L104). -
Panel: the active
PageId, plus local draft/async state folded intoSectionCtx—newLabel,confirmDelete, and the per-integration status fields (cloudBusy,spaceBusy,spaceError,spaceNote,spaceLoaded,ghBusy,ghNote). -
Persisted and applied outside the panel: theme choices go through
state/theme.ts(applyTheme); accent resolution happens inAppviaresolveAccentand is also the never-purple guard (src/renderer/src/App.tsx#L34-L37).
-
Edition split.
EditionKindgating is the only mechanism that removes entire pages/sections on Server Edition;browseris explicitly desktop-only (src/renderer/src/components/AppSettingsPanel.tsx#L140-L155,#L213-L225). -
Runtime capability flags.
permissionSupportedandspaceLoadedare boolean gates a section must respect:permissionSupportedreflects whether the active CLI supports permission-mode control, andspaceLoadedis false until the panel has learned whether the user has a cloud space — sections must not render "no space" prematurely (src/renderer/src/components/AppSettingsPanel.tsx#L79-L103). -
Close behavior. The sheet closes on Escape, backdrop click, or the header close button (
src/renderer/src/components/AGENTS.md#L24-L25). -
Confirmation UI. Any destructive action inside settings must use the in-app
.confirm-overlaypattern (asTabBardoes);window.confirmis forbidden because Electron silently no-ops it (src/renderer/src/components/AGENTS.md#L7-L9,#L32-L35). -
Null-safety at the seam.
Appreadssettings?.theme,settings?.invertWheelZoom, andsettings?.browserHomeUrlwith optional chaining before the panel ever opens; the panel itself can safely assume a concrete object (src/renderer/src/App.tsx#L37-L52,#L128). -
Onboarding is adjacent but separate.
ShouldShowOnboarding(settings, activeProjectCount)is evaluated inApp, not the panel; dismissing it writesonboardedAtthrough the same settings channel and thensetSettings(src/renderer/src/App.tsx#L107-L113).
-
Add a top-level page: add a
PageIdvariant, add aSettingsPageentry to the rightNavGroup, then add the render branch for that id in the content column. Markeditionsif the page is desktop- or server-only (src/renderer/src/components/AppSettingsPanel.tsx#L146-L165). -
Add an in-page section: push a
SettingsSectiononto that page's render array. This is the documented extension point for future settings (src/renderer/src/components/AppSettingsPanel.tsx#L63-L77). -
Mark a section
barewhen it owns its card layout — this is how the capability pages mount (src/renderer/src/components/AppSettingsPanel.tsx#L67-L77). -
Extend
SectionCtxwhen new shared state or handlers are needed; the interface is deliberately the aggregate so sections don't reach into stores individually. -
Add a capability page body in
CapabilityPages.tsxand export it; the shell import list at the top ofAppSettingsPanel.tsxis the wire-up point (src/renderer/src/components/AppSettingsPanel.tsx#L12-L20).
Sources: src/renderer/src/components/AppSettingsPanel.tsx, src/renderer/src/components/CapabilityPages.tsx, src/renderer/src/components/CogMenu.tsx, src/renderer/src/App.tsx, src/renderer/src/components/AGENTS.md.
Generated from termsprawl at 0d4393be54c6200beedd91bb636e5296c30472c5.
App Shell & Platform Foundations
- Electron Main Process & Window Lifecycle
- Preload Bridge & IPC Contract
- Shared Domain Types and File/URL Helpers
- Renderer Bootstrap & App Composition
- Build Targets & TypeScript Configuration
Canvas, Nodes & Renderer State
- Infinite Canvas Surface & Viewport Interaction
- Workspace, Project & Tab State
- Node Links, Edges & Link Inspector
- Sticky, Group, Editor & Diff Nodes
- Keyboard Canvas Navigation & Cross-Panel Requests
- Theme, Accent & Visual Language
- Boot Overlay, Onboarding & Shared UI Kit
Terminals & Session Continuity
- PTY Lifecycle & Terminal Sessions
- tmux Session Naming & Reattach
- Scrollback Snapshots & Cold Replay
- Terminal Node Rendering (xterm.js)
- SSH Remote Projects, Terminals & Files
Persistence, Projects & Files
- Workspace Store & Project File Layout
- Project Scope, Deletion & Worktree Registry
- Workspace Bundle Export/Import
- File Service & File Tree UI
Agent Runtime & Tooling
- Agent Status Model & Hook Normalization
- Hook Server & CLI Hook Installers
- Agent Launch, CLI Probing & Managed Accounts
- Agent Tool Protocol & In-Process Server
- Agent Tool Client, CLI & MCP Entry
- Transcripts, Context Discovery & Context CLI
- Agent Canvas State & Status Badges
Chat Nodes & Model Providers
- Chat Runtime, Conversation & Cost
- Model Provider Adapters & Streaming
- Chat Tool Calling & Project Tools
- Chat Node UI
Git & Source Control
Embedded Browser Nodes
- Browser Manager & Guest Runtime
- CDP Facade & Browser Agent Server
- Browser Navigation Policy & Node UI
Server Edition
- Server Bootstrap & HTTP/WebSocket Entry
- RPC Dispatch, Handlers & Service Bridges
- Renderer Shim & Server Boundary
- Server Auth & Security Boundary
Relay & Remote Access
- Relay Hub & WebSocket Frame Routing
- Relay End-to-End Cryptography
- Relay Auth, Invites, Store & Admin API
- Relay Client, Pairing & Terminal Tunneling
- Relay Trust UI
Integrations & Secondary Surfaces
- Telegram Bot, Commands & Pairing
- A2A Peers: Protocol, Client & Server
- Node Link Engine, Registry & Scheduler
- Cloud Spaces, Snapshots & Sync
Settings, Updates & Maintenance