import { HOSTED_APP_CHANNEL } from "~/branding"; import { isElectron, isSecretsFeatureEnabled } from "~/env"; import { isMacPlatform, isWindowsPlatform, normalizeSearchText } from "~/lib/utils"; import { STATIC_KEYBINDING_COMMANDS, type KeybindingCommand } from "@t3tools/contracts"; import type { EnvironmentId } from "@t3tools/contracts"; import type { EnvironmentConnectionPhase } from "@t3tools/client-runtime/connection"; import { sourceControlClients, type SourceControlDefinition, } from "@t3tools/client-runtime/source-control-clients"; import { DEFAULT_KEYBINDINGS } from "@t3tools/shared/keybindings"; import * as Schema from "effect/Schema"; import { TYPOGRAPHY_ADVANCED_STORAGE_KEY } from "../../appearanceFonts"; import { writeLocalStorageValue } from "../../hooks/useLocalStorage"; import { commandLabel } from "./KeybindingsSettings.logic"; import type { ResolvedSettingsScope } from "./settingsScope"; export type SettingsPath = | "/settings/projects" | "/settings/general" | "/settings/diagnostics" | "/settings/voice" | "/settings/appearance" | "/settings/chat" | "/settings/diffs" | "/settings/keybindings" | "/settings/snap-shot" | "/settings/providers" | "/settings/threads" | "/settings/browser" | "/settings/devices" | "/settings/scheduled-tasks" | "/settings/source-control" | "/settings/worktrees" | "/settings/connections" | "/settings/access" | "/settings/secrets" | "/settings/maintenance" | "/settings/labs" | "/settings/about" | "/settings/archived"; export type SettingsSearchScope = | "environment" | "environment-defaults" | "project-defaults" | "project" | "checkout" | "connections"; export interface SettingsSearchItem { readonly id: string; readonly title: string; readonly to: SettingsPath; readonly targetId?: string; readonly searchTerms?: ReadonlyArray; readonly scope?: SettingsSearchScope; readonly desktopOnly?: boolean; readonly macOnly?: boolean; readonly windowsOnly?: boolean; readonly releaseChannelOnly?: boolean; readonly secretsFeatureOnly?: boolean; readonly cloudOnly?: boolean; readonly environmentOnly?: boolean; readonly providerSettingsOnly?: boolean; readonly localBackendManagementOnly?: boolean; readonly localEnvironmentOnly?: boolean; readonly wslAvailableOnly?: boolean; readonly managedTunnelOnly?: boolean; readonly peerLinksOnly?: boolean; readonly secondary?: boolean; readonly requiresThreadAutoSettlement?: boolean; } export interface SettingsSearchAvailability { readonly localEnvironmentDisabled?: boolean; readonly hasCloudPublicConfig: boolean; readonly hasEnvironment: boolean; readonly hasProviderSettingsEnvironment: boolean; readonly canManageLocalBackend: boolean; readonly isWslSettingsRowVisible: boolean; readonly hasThreadAutoSettlement: boolean; readonly managedTunnelActive?: boolean; readonly hasPeerLinkHost?: boolean; } export const SETTINGS_SECTION_LABELS: Readonly> = { "/settings/diagnostics": "Diagnostics", "/settings/projects": "Projects", "/settings/general": "General", "/settings/appearance": "Appearance", "/settings/chat": "Chat", "/settings/diffs": "Diffs & pull requests", "/settings/keybindings": "Keybindings", "/settings/snap-shot": "SnapShots", "/settings/providers": "Providers", "/settings/threads": "Threads", "/settings/browser": "Browser", "/settings/devices": "Devices", "/settings/scheduled-tasks": "Scheduled tasks", "/settings/source-control": "Source control", "/settings/worktrees": "Worktrees", "/settings/connections": "Environments", "/settings/access": "Sharing & access", "/settings/secrets": "Secrets", "/settings/maintenance": "Maintenance", "/settings/labs": "Labs", "/settings/about": "About", "/settings/voice": "Voice", "/settings/archived": "Archive", }; /** Sidebar order. A group without a heading is set apart by space alone. */ export const SETTINGS_NAV_GROUPS: ReadonlyArray<{ readonly heading: string | null; readonly paths: ReadonlyArray; }> = [ { heading: null, paths: [ "/settings/general", "/settings/appearance", "/settings/chat", "/settings/diffs", "/settings/keybindings", "/settings/voice", "/settings/snap-shot", ], }, { heading: "Agents", paths: [ "/settings/providers", "/settings/threads", "/settings/browser", "/settings/devices", "/settings/scheduled-tasks", ], }, { heading: "Code", paths: ["/settings/projects", "/settings/source-control", "/settings/worktrees"], }, { heading: "Connections", paths: [ "/settings/connections", "/settings/access", ...(isSecretsFeatureEnabled ? (["/settings/secrets"] as const) : []), ], }, { heading: null, paths: ["/settings/maintenance", "/settings/labs", "/settings/about", "/settings/archived"], }, ]; export function keybindingSearchAnchorId(command: Command) { return `keybinding-${command}` as const; } const KEYBINDING_SEARCH_ITEMS = STATIC_KEYBINDING_COMMANDS.toSorted((left, right) => commandLabel(left).localeCompare(commandLabel(right)), ).map((command) => { const defaultKeys = DEFAULT_KEYBINDINGS.filter((binding) => binding.command === command).map( (binding) => binding.key, ); return { id: keybindingSearchAnchorId(command), title: commandLabel(command), to: "/settings/keybindings" as const, searchTerms: [command, ...defaultKeys], secondary: true, ...(defaultKeys.length === 0 ? { targetId: "keybindings" } : {}), }; }); export function sourceControlHostSettingsSearchId(kind: string) { return `source-control-host-${kind}` as const; } /** Each host's own settings: its accounts and token, or the fields of its settings form. */ export function sourceControlHostSearchItems( definitions: ReadonlyArray, ): ReadonlyArray< SettingsSearchItem & { readonly id: ReturnType } > { return definitions.flatMap((definition) => { if (!definition.settings) return []; const names = [definition.label, ...(definition.searchKeywords ?? [])].join(" "); const { credentials } = definition; return [ { id: sourceControlHostSettingsSearchId(definition.kind), title: credentials ? `${definition.label} accounts and token` : `${definition.label} credentials`, to: "/settings/source-control" as const, searchTerms: [ credentials ? `${names} ${credentials.cliName} account login user host switch multiple accounts disable sign in token personal access token pat api key credential` : `${names} ${Object.keys(definition.settings.fields).join(" ")} token credentials sign in`, ], environmentOnly: true, scope: "environment-defaults" as const, }, ]; }); } /** For settings that apply to every host. */ const SOURCE_CONTROL_HOST_LABELS = sourceControlClients.definitions .map((definition) => definition.label) .join(" "); const SOURCE_CONTROL_HOST_TERMS = sourceControlClients.definitions .flatMap((definition) => [definition.label, ...(definition.searchKeywords ?? [])]) .join(" "); export const SETTINGS_SEARCH_ITEMS = [ { id: "storage-worktree-keep-when", targetId: "storage-worktrees", title: "Keep worktrees with local changes", to: "/settings/worktrees", scope: "project-defaults", searchTerms: [ "any local files uncommitted changes edited tracked files ignored env build output cleanup", ], }, { id: "storage-delete-now", title: "Delete now", to: "/settings/maintenance", scope: "environment-defaults", searchTerms: ["storage cleanup run now results report removed kept failed"], }, { id: "storage-worktrees", title: "Worktree cleanup", to: "/settings/worktrees", scope: "project-defaults", searchTerms: [ "disk storage automatic delete deleted archived threads old inactive merged settled unchanged worktrees retention days project inherit off custom", ], }, { id: "storage-worktrees-location", title: "Worktree location", to: "/settings/worktrees", scope: "environment-defaults", searchTerms: ["worktree location folder directory path drive external disk"], }, { id: "storage-artifacts", title: "Artifacts and logs", to: "/settings/maintenance", scope: "environment-defaults", searchTerms: [ "disk storage delete old browser artifacts screenshots captures rotated logs cleanup retention", ], }, { id: "project-defaults", title: "Project defaults and overrides", to: "/settings/threads", scope: "project-defaults", searchTerms: ["model workspace environments projects inheritance checkout"], }, { id: "project-overview", title: "Project overview", to: "/settings/projects", searchTerms: ["name icon emoji image checkout remove delete"], }, { id: "default-model", title: "Default model", to: "/settings/threads", scope: "project-defaults", searchTerms: ["new thread project provider reasoning effort"], }, { id: "fallback-model", title: "Fallback model", to: "/settings/threads", scope: "environment-defaults", searchTerms: ["fallback agent provider out of usage new threads"], environmentOnly: true, }, { id: "default-permissions", title: "Permissions", to: "/settings/threads", scope: "project-defaults", searchTerms: [ "new thread default runtime mode supervised approvals auto accept edits full access", ], }, { id: "color-scheme", title: "Color scheme", to: "/settings/appearance", searchTerms: ["appearance light dark system mode"], targetId: "appearance", }, { id: "theme", title: "Themes", to: "/settings/appearance", searchTerms: ["appearance colors palette custom import"], targetId: "appearance", }, { // Prefixed because the slider control already owns the `appearance-contrast` id. id: "setting-appearance-contrast", title: "Contrast", to: "/settings/appearance", searchTerms: ["colors borders interface"], }, { // Prefixed because the slider control already owns the `glass-opacity` id. id: "setting-glass-opacity", title: "Glass opacity", to: "/settings/appearance", searchTerms: ["transparent transparency solid menus dialogs composer"], }, { id: "diff-color-scheme", title: "Diff colors", to: "/settings/diffs", searchTerms: ["red green blue orange additions deletions changes counts palette colorblind"], }, { id: "chat-width", title: "Chat width", to: "/settings/appearance", searchTerms: ["wide full width column layout messages composer monitor"], }, { id: "thread-details-items", title: "Thread details", to: "/settings/appearance", searchTerms: [ "customize show hide panel card environment editor scripts branch pull request git changes servers automations lineage usage limits 5 hour weekly quota tokens context window", ], }, { id: "effort-display", title: "Effort display", to: "/settings/appearance", searchTerms: ["reasoning effort thinking bars graph word label level composer"], }, { id: "math-rendering", title: "Render math", to: "/settings/appearance", searchTerms: ["latex tex katex equations formulas markdown"], }, { id: "panel-animations", title: "Panel animations", to: "/settings/appearance", }, { id: "environment-identification", title: "Environment identification", to: "/settings/appearance", searchTerms: ["dev nightly latest channel artwork pill label hide none"], targetId: "appearance-interface", }, { id: "interface-font", title: "Interface font", to: "/settings/appearance", searchTerms: ["typography family size system sans"], }, { id: "prompt-font", title: "Prompt font", to: "/settings/appearance", searchTerms: ["typography family size composer input"], }, { id: "code-font", title: "Code font", to: "/settings/appearance", searchTerms: ["typography family size monospace code blocks diffs file previews"], }, { id: "terminal-font", title: "Terminal font", to: "/settings/appearance", searchTerms: ["typography family size monospace output"], }, { id: "separate-fonts", title: "Separate fonts", to: "/settings/appearance", searchTerms: ["typography advanced prompt code terminal font family size individually"], }, { id: "font-smoothing", title: "Font smoothing", to: "/settings/appearance", searchTerms: ["typography text grayscale anti aliasing macos thin"], macOnly: true, }, { id: "word-wrap", title: "Word wrap", to: "/settings/appearance", searchTerms: ["long lines code blocks diffs file previews"], }, { id: "composer-context", title: "Composer context", to: "/settings/appearance", }, { id: "project-grouping", title: "Project grouping", to: "/settings/general", searchTerms: ["combine matching repositories environments sidebar"], }, { id: "project-order", title: "Project order", to: "/settings/general", searchTerms: ["sort projects sidebar manual created recent"], }, { id: "snooze-options", title: "Snooze options", to: "/settings/threads", searchTerms: ["snooze presets times later tonight tomorrow next week remind"], environmentOnly: true, scope: "environment-defaults", }, { id: "snooze-limited-threads", title: "Snooze until limits reset", to: "/settings/threads", searchTerms: ["usage quota rate limit reset wake recover continue"], }, { id: "auto-resume-limited-threads", title: "Resume after usage limits", to: "/settings/threads", searchTerms: ["usage quota rate limit reset recover continue"], }, { id: "working-shelf", title: "Working section", to: "/settings/general", searchTerms: ["hide fold running monitoring threads inbox sidebar shelf"], }, { id: "boring-sidebar", title: "Boring sidebar (group by project)", to: "/settings/labs", searchTerms: ["beta folders project tree legacy old classic sidebar"], }, { id: "boring-sidebar-multiline", title: "Two-line folder rows", to: "/settings/labs", searchTerms: ["beta boring sidebar folder rows multiline branch pull request details"], }, { id: "tool-call-visibility", title: "Tool calls in chat", to: "/settings/labs", searchTerms: [ "beta hide tool calls commands edits searches work log activity quiet chat zen final answers only", ], }, { id: "guest-links", title: "Guest links", to: "/settings/labs", scope: "environment", searchTerms: ["beta share guest invite link teammate watch t3 connect tunnel"], }, { id: "linked-environments-beta", title: "Linked environments", to: "/settings/labs", scope: "environment", searchTerms: ["beta link another machine vps peer agents work there delegate"], }, { id: "simlock-hosts", title: "Simlock device hosts (alpha)", to: "/settings/labs", scope: "environment", searchTerms: ["beta alpha simlock callstack simulator emulator ios android pool lease gateway"], }, { id: "voice-transcription-destination", title: "Transcribe with", to: "/settings/voice", searchTerms: [ "send microphone audio dictation destination speech voice stt transcription environment machine consent privacy cloud OpenAI Grok OpenRouter", ], }, { id: "voice-microphone", title: "Microphone", to: "/settings/voice", searchTerms: ["mic input audio device"], }, { id: "dictation-shortcut-mode", title: "Dictation shortcut", to: "/settings/voice", searchTerms: ["speech voice keyboard hotkey hold toggle auto"], }, { id: "speech-language", title: "Transcription language", to: "/settings/voice", searchTerms: ["speech voice translate english auto detect"], }, { id: "speech-dictionary", title: "Dictionary", to: "/settings/voice", searchTerms: ["speech voice transcription vocabulary names glossary alias"], }, { id: "speech-project-dictionary", title: "Project dictionary", to: "/settings/voice", scope: "project-defaults", searchTerms: [ "speech voice transcription vocabulary shared dictionary team environment glossary", ], }, { id: "speech-post-processing-model", title: "Cleanup model", to: "/settings/voice", scope: "environment", searchTerms: ["speech voice transcript cleanup provider model post-processing"], }, { id: "speech-correction-word", title: "Correction cue", to: "/settings/voice", searchTerms: ["speech voice cleanup sorry scratch that fix mistake"], }, { id: "speech-silence-filtering", title: "Silence filtering", to: "/settings/voice", searchTerms: ["voice speech vad silero pauses noise"], }, { id: "remove-filler-words", title: "Remove filler words", to: "/settings/voice", searchTerms: ["voice speech transcription hesitation um uh cleanup"], }, { id: "speech-post-processing", title: "Clean up transcripts with a provider", to: "/settings/voice", searchTerms: ["speech transcription polish clean provider prompt post-processing"], }, { id: "speech-models", title: "Transcription models", to: "/settings/voice", searchTerms: [ "speech voice stt download whisper parakeet canary moonshine accuracy speed benchmark languages", ], }, { id: "speech-hardware", title: "Transcription hardware and model unloading", to: "/settings/voice", searchTerms: ["speech voice stt advanced gpu cpu acceleration memory unload idle"], }, { id: "microphone-test", title: "Test microphone", to: "/settings/voice", searchTerms: ["test mic audio playback loopback record input level"], }, { id: "transcription-test", title: "Test model", to: "/settings/voice", searchTerms: ["test voice speech model transcription recognition"], }, { id: "auto-settle-inactive-threads", title: "Auto-settle inactive threads", to: "/settings/threads", searchTerms: ["sidebar inactivity days no activity automatically"], requiresThreadAutoSettlement: true, scope: "project-defaults", }, { id: "compact-idle-threads", title: "Compact idle threads", to: "/settings/providers", scope: "environment-defaults", searchTerms: ["claude context compaction idle cache tokens usage summary resume"], }, { id: "auto-settle-merged-threads", title: "Auto-settle merged threads", to: "/settings/threads", searchTerms: ["pull request merge closed automatically sidebar"], requiresThreadAutoSettlement: true, scope: "project-defaults", }, { id: "keep-threads-with-pull-requests", title: "Keep threads with a PR", to: "/settings/threads", targetId: "auto-settle-inactive-threads", searchTerms: ["pull request inactivity auto-settle merge close sidebar"], requiresThreadAutoSettlement: true, scope: "project-defaults", }, { id: "days-before-auto-settle", title: "Days of inactivity before auto-settle", to: "/settings/threads", targetId: "auto-settle-inactive-threads", searchTerms: ["thread timeout activity sidebar"], requiresThreadAutoSettlement: true, scope: "project-defaults", }, { id: "thread-notifications", title: "Thread notifications", to: "/settings/general", searchTerms: ["notification sound alert completion input approval desktop"], }, { id: "in-app-notifications", title: "In-app notifications", to: "/settings/general", searchTerms: ["notification toast popup completion input approval failure"], }, { id: "time-format", title: "Time format", to: "/settings/general", searchTerms: ["timestamp clock locale system browser os 12 hour 24 hour"], }, { id: "history-swipe-navigation", title: "Swipe to navigate", to: "/settings/general", searchTerms: ["trackpad two finger horizontal back forward history gesture desktop"], }, { id: "response-streaming", title: "Response streaming", to: "/settings/threads", scope: "project-defaults", searchTerms: ["output token paragraph buffered wait turn legacy"], }, { id: "hide-whitespace-changes", title: "Hide whitespace changes", to: "/settings/diffs", searchTerms: ["diff ignore spaces edits default"], }, { id: "default-diff-file-state", title: "Default diff file state", to: "/settings/diffs", searchTerms: ["collapsed expanded collapse expand files pull request pr code tab"], }, { id: "diff-layout", title: "Diff layout", to: "/settings/diffs", searchTerms: ["stacked split side by side unified inline view"], }, { id: "proactive-panels", title: "Open pull requests and changes panels proactively", to: "/settings/diffs", searchTerms: ["proactive automatically open diff pull request pr right panel agent completion"], }, { id: "skills-in-slash-menu", title: "Show skills in slash menu", to: "/settings/chat", searchTerms: ["command menu dollar $ slash /"], }, { id: "composer-rich-text", title: "Rich text composer", to: "/settings/chat", searchTerms: ["composer rich text tiptap bold italic markdown styled wysiwyg"], }, { id: "send-shortcut", title: "Send shortcut", to: "/settings/chat", searchTerms: ["enter return command ctrl multiline prompt new line composer"], }, { id: "follow-up-behavior", title: "Follow-up behavior", to: "/settings/chat", searchTerms: ["queue steer running turn send default behavior composer"], }, { id: "provider-update-checks", title: "Provider update checks", to: "/settings/maintenance", searchTerms: [ "installed cli versions newer available codex claude cursor grok fx kiro opencode", ], scope: "environment-defaults", }, { id: "continue-threads-after-server-update", title: "Continue threads after restarts", to: "/settings/maintenance", scope: "project-defaults", searchTerms: [ "resume running active interrupted work restart reboot machine crash desktop update automatically", ], }, { id: "background-activity", title: "Background activity", to: "/settings/maintenance", scope: "environment-defaults", searchTerms: [ "balanced performance battery saver advanced git fetch provider health refresh host power monitor idle policy", ], }, { id: "new-threads", title: "New threads", to: "/settings/threads", scope: "project-defaults", searchTerms: ["default workspace mode draft local worktree"], }, { id: "worktree-submodules", title: "Submodules", to: "/settings/worktrees", scope: "project-defaults", searchTerms: ["git submodule init recursive top-level none worktree t3.json"], }, { id: "start-from-origin", title: "Start from remote", to: "/settings/worktrees", scope: "project-defaults", searchTerms: ["new worktrees start remote origin latest fetched branch local"], }, { id: "add-project-starts-in", title: "Add project starts in", to: "/settings/threads", scope: "environment-defaults", searchTerms: ["base directory folder browser path home"], }, { id: "ghq-clone-layout", title: "Clone into host folders", to: "/settings/threads", scope: "environment-defaults", searchTerms: ["ghq clone destination host owner repository root layout"], }, { id: "unpin-confirmation", title: "Unpin confirmation", to: "/settings/general", searchTerms: ["ask before thread pinned section"], }, { id: "archive-confirmation", title: "Archive confirmation", to: "/settings/general", searchTerms: ["ask before thread second click inline action"], }, { id: "delete-confirmation", title: "Delete confirmation", to: "/settings/general", searchTerms: ["ask before thread chat history"], }, { id: "quit-confirmation", title: "Quit shortcut", to: "/settings/general", searchTerms: ["confirmation desktop app exit direct hold double click press twice"], desktopOnly: true, }, { id: "text-generation-model", title: "Text generation model", to: "/settings/threads", scope: "project-defaults", searchTerms: ["generated thread titles source control content default provider"], }, { id: "thread-title-instructions", title: "Thread title instructions", to: "/settings/threads", scope: "project-defaults", searchTerms: ["generated thread titles prompt custom style naming rename regenerate"], }, { id: "app-version", title: "Version", to: "/settings/about", searchTerms: ["about check for updates download install upgrade release"], }, { id: "update-track", title: "Update track", to: "/settings/about", searchTerms: ["release channel stable latest nightly prerelease"], releaseChannelOnly: true, }, { id: "cli-command", title: "t3 command", to: "/settings/about", searchTerms: ["cli terminal shell path install command line"], desktopOnly: true, }, { id: "mobile-app", title: "Mobile app", to: "/settings/about", searchTerms: ["phone tablet iPhone iPad iOS Android App Store Google Play QR"], }, { id: "privacy-policy", title: "Privacy policy", to: "/settings/about", searchTerms: ["telemetry analytics usage data tracking legal opt out"], }, { id: "telemetry-export", title: "OpenTelemetry export", to: "/settings/diagnostics", targetId: "telemetry-export", scope: "environment", searchTerms: ["OTel OTLP telemetry export endpoints logs traces metrics Grafana"], }, { id: "diagnostics", title: "Diagnostics", to: "/settings/maintenance", searchTerms: ["logs traces processes resource history failures spans cpu memory"], }, { id: "open-source-licenses", title: "Open source licenses", to: "/settings/about", }, { id: "legacy-plan-mode", title: "Plan mode (legacy)", to: "/settings/labs", searchTerms: ["build plan composer old"], }, { id: "legacy-context-window-indicator", title: "Context window indicator (legacy)", to: "/settings/labs", searchTerms: ["composer meter usage tokens circle old"], }, { id: "keybindings", title: "Keybindings", to: "/settings/keybindings", searchTerms: ["keyboard shortcuts hotkeys commands bindings json"], }, ...KEYBINDING_SEARCH_ITEMS, { id: "snap-shot-enabled", title: "SnapShots", searchTerms: ["window capture screenshot"], to: "/settings/snap-shot", }, { id: "snap-shot-accessibility", title: "Include app text", to: "/settings/snap-shot", targetId: "snap-shot-enabled", searchTerms: [ "capture accessibility data text UI structure elements privacy omit agent context", ], }, { id: "snap-shot-shortcut", title: "Capture shortcut", to: "/settings/snap-shot", targetId: "snap-shot-enabled", }, { id: "snap-shot-sound", title: "Capture sound", to: "/settings/snap-shot", targetId: "snap-shot-enabled", }, { id: "snap-shot-flash", title: "Capture flash", to: "/settings/snap-shot", targetId: "snap-shot-enabled", }, { id: "snap-shot-animations", title: "Capture animations", to: "/settings/snap-shot", targetId: "snap-shot-enabled", }, { id: "providers", title: "Providers", to: "/settings/providers", searchTerms: [ "agents cli codex claude cursor grok fx kiro opencode antigravity google sign in sign out install subscription instances authentication api key models configuration binary path config directory codex_home shadow home endpoint arguments environment variables display name accent color custom favorite hidden auto compact native sessions import resume", ], }, { id: "usage-providers", title: "Usage providers", to: "/settings/providers", searchTerms: [ "usage sources CLIProxyAPI CLI proxy hub quota subscription limits management key add remove", ], providerSettingsOnly: true, }, { id: "provider-health-check-interval", title: "Health check interval", to: "/settings/providers", searchTerms: ["refresh availability versions auth state models background probes seconds off"], providerSettingsOnly: true, }, { id: "agent-browser-access", title: "Agent browser access", to: "/settings/browser", scope: "project-defaults", searchTerms: ["allow disable enable open drive preview tools sessions project override"], }, { id: "device-sources", title: "Device sources", to: "/settings/devices", searchTerms: [ "device hosts ssh remote mac mini identity key connection eas expo cloud simlock pool gateway local simulators", ], }, { id: "agent-device-access", title: "Agents can use devices", to: "/settings/devices", targetId: "devices", searchTerms: ["allow simulator emulator ios android drive tools sessions"], }, { id: "device-hub", title: "Use devices", to: "/settings/devices", targetId: "devices", searchTerms: ["device hub simulator emulator ios android install start version"], }, { id: "device-platform-support", title: "Local simulators", to: "/settings/devices", targetId: "device-sources", searchTerms: ["simulator support xcode android studio sdk avd runtime"], }, { id: "browser-profiles", title: "Browser profiles", to: "/settings/browser", targetId: "browser", }, { id: "browser-default-profile", title: "Default browser profile", to: "/settings/browser", targetId: "browser-profiles", }, { id: "browser-default-viewport", title: "Default browser viewport", to: "/settings/browser", searchTerms: ["preview size width height device desktop mobile rotate"], }, { id: "browser-default-zoom", title: "Default browser zoom", to: "/settings/browser", searchTerms: ["preview page scale tabs percent"], }, { id: "browser-default-appearance", title: "Default browser appearance", to: "/settings/browser", searchTerms: ["preview color scheme light dark system os"], }, { id: "browser-recording-frame-rate", title: "Browser recording frame rate", to: "/settings/browser", }, { id: "browser-recording-key-presses", title: "Show key presses in recordings", to: "/settings/browser", searchTerms: ["browser preview keyboard shortcuts keystrokes overlay capture"], }, { id: "browser-recording-mouse-presses", title: "Show mouse presses in recordings", to: "/settings/browser", searchTerms: ["browser preview clicks buttons drag overlay capture"], }, { id: "browser-link-target", title: "Open links in", to: "/settings/browser", searchTerms: ["links default browser in-app browser external open"], }, { id: "browser-search-engine", title: "Search engine", to: "/settings/browser", searchTerms: ["address bar duckduckgo google bing brave kagi ecosia custom url"], }, { id: "pull-request-links-open-in-app", title: "Open pull requests in T3 Code", to: "/settings/diffs", searchTerms: [ `pr commit links ${SOURCE_CONTROL_HOST_LABELS} browser external click default pull request view panel`, ], }, { id: "browser-auto-show-floating-preview", title: "Auto-show floating preview", to: "/settings/browser", searchTerms: ["agent opens browser device simulator pop into view hide"], }, { id: "automatic-pull", title: "Automatically pull", to: "/settings/source-control", scope: "project-defaults", searchTerms: ["auto pull default branch current checkout fast forward upstream"], }, { id: "remove-agent-credits-on-merge", title: "Remove agent credits when merging", to: "/settings/source-control", scope: "project-defaults", searchTerms: [ `pull request ${SOURCE_CONTROL_HOST_LABELS} squash co-authored-by attribution claude codex generated`, ], }, { id: "pull-request-merge-method", title: "Default merge method", to: "/settings/source-control", scope: "project-defaults", searchTerms: ["pull request merge squash rebase last selected"], }, { id: "source-control", title: "Source control", to: "/settings/source-control", scope: "environment-defaults", searchTerms: [ `version control git ${SOURCE_CONTROL_HOST_TERMS} hosting integrations credentials scan server environment`, ], }, { id: "git-fetch-interval", title: "Git fetch interval", to: "/settings/maintenance", targetId: "background-activity", searchTerms: [ "automatic remote branch refresh background credentials security keys seconds off", ], environmentOnly: true, scope: "environment-defaults", }, { id: "worktree-branch-naming", title: "Worktree branch naming", to: "/settings/worktrees", searchTerms: ["static semantic prefix custom prompt instructions feat fix refactor chore"], environmentOnly: true, scope: "project-defaults", }, ...sourceControlHostSearchItems(sourceControlClients.definitions), { id: "source-control-writing-style", title: "Source control writing style", to: "/settings/source-control", searchTerms: [ "repository conventions conventional commits custom instructions change descriptions request titles", ], environmentOnly: true, }, { id: "follow-change-request-templates", title: "Follow change request templates", to: "/settings/source-control", searchTerms: ["repository pr pull request description structure"], environmentOnly: true, }, { id: "source-control-writer-model", title: "Source control writer model", to: "/settings/source-control", searchTerms: [ "override generated commit change request pr titles descriptions branch bookmark", ], environmentOnly: true, scope: "project-defaults", }, { id: "review-guides", title: "Review guides (beta)", to: "/settings/source-control", searchTerms: ["pull request pr guided review guide tour beta experimental"], environmentOnly: true, }, { id: "review-guide-model", title: "Review guide model", to: "/settings/source-control", // Hidden until review guides are on, so a search lands on the switch that shows it. targetId: "review-guides", searchTerms: ["pull request pr guided review guide brief maintainer"], environmentOnly: true, scope: "project-defaults", }, { id: "review-guide-instructions", title: "Review guide instructions", to: "/settings/source-control", targetId: "review-guides", searchTerms: ["pull request pr guided review guide preferences taste prompt custom"], environmentOnly: true, scope: "project-defaults", }, { id: "project-actions", title: "Actions", to: "/settings/projects", searchTerms: ["commands scripts setup run dev server checkout worktree t3.json import"], }, { id: "environment-icon", title: "Environment icon", to: "/settings/access", targetId: "connections-environment", searchTerms: ["machine glyph sidebar mac mini studio laptop desktop server cloud vm"], }, { id: "local-environment", title: "Local environment", to: "/settings/connections", searchTerms: ["turn off on disable enable local server agents remote only restart"], desktopOnly: true, }, { id: "network-access", title: "Network access", to: "/settings/access", targetId: "connections-environment", searchTerms: ["expose backend remote pairing local machine interfaces host restart"], localBackendManagementOnly: true, }, { id: "tailscale-https", title: "Tailscale HTTPS", to: "/settings/access", targetId: "connections-environment", searchTerms: ["serve magicdns endpoint remote secure network"], desktopOnly: true, localBackendManagementOnly: true, }, { id: "wsl-backend", title: "WSL backend", to: "/settings/access", searchTerms: [ "windows subsystem linux distro second server projects stop windows backend restart wsl only", ], desktopOnly: true, windowsOnly: true, localBackendManagementOnly: true, wslAvailableOnly: true, }, { id: "t3-connect", localEnvironmentOnly: true, title: "T3 Connect", to: "/settings/access", targetId: "connections-environment", searchTerms: ["managed tunnel cloud other devices remote tunnels limit t3 chat"], desktopOnly: true, cloudOnly: true, }, { id: "hold-webhooks-while-offline", localEnvironmentOnly: true, title: "Hold webhooks while offline", to: "/settings/access", targetId: "connections-environment", searchTerms: ["webhook automations offline queue mailbox t3 connect"], cloudOnly: true, managedTunnelOnly: true, }, { id: "publish-agent-activity", localEnvironmentOnly: true, title: "Publish agent activity", to: "/settings/access", targetId: "connections-environment", searchTerms: ["mobile push notifications live activities cloud tunnel"], cloudOnly: true, }, { id: "connections-environment", title: "This machine", to: "/settings/access", searchTerms: [ "connections server backend local remote access administrative permissions scope pairing links qr code authorized clients sessions revoke endpoint", ], }, { id: "linked-environments", peerLinksOnly: true, title: "Linked environments", to: "/settings/connections", targetId: "linked-environments", searchTerms: ["link peer another machine vps agents work there handoff pairing code"], }, { id: "remote-environments", title: "Environments", to: "/settings/connections", searchTerms: ["add pair backend host code ssh config agent tunnel saved t3 connect"], }, { id: "load-balancing", title: "Load balancing", to: "/settings/connections", searchTerms: [ "automatic machine environment resources cpu memory capacity preference weight shared projects", ], }, { id: "pull-request-routing", title: "Pull request sharing", to: "/settings/connections", searchTerms: [ `pull request routing trusted environments shared credentials permissions read actions source control ${SOURCE_CONTROL_HOST_LABELS}`, ], }, { id: "secret-providers", title: "Secret providers", to: "/settings/secrets", searchTerms: ["1password vault credentials api key"], secretsFeatureOnly: true, }, { id: "secret-providers-add", title: "Add secret provider", to: "/settings/secrets", targetId: "secret-providers", searchTerms: ["new 1password vault op cli account"], secretsFeatureOnly: true, }, { id: "archive", title: "Archived threads", to: "/settings/archived", searchTerms: ["restore reopen deleted history projects"], }, ] as const satisfies ReadonlyArray; export type SettingsSearchItemId = (typeof SETTINGS_SEARCH_ITEMS)[number]["id"]; const SEARCH_ITEMS_BY_ID = new Map(SETTINGS_SEARCH_ITEMS.map((item) => [item.id, item] as const)); const SETTINGS_CATEGORY_SCOPES: Readonly> = { "/settings/diagnostics": "environment", "/settings/projects": "project", "/settings/general": null, "/settings/voice": null, "/settings/appearance": null, "/settings/chat": null, "/settings/diffs": null, "/settings/snap-shot": null, "/settings/keybindings": null, "/settings/providers": null, "/settings/threads": null, "/settings/browser": null, "/settings/devices": null, "/settings/source-control": "environment-defaults", "/settings/worktrees": "project-defaults", "/settings/connections": "connections", "/settings/access": "connections", "/settings/secrets": null, "/settings/scheduled-tasks": null, "/settings/maintenance": null, "/settings/labs": null, "/settings/about": null, "/settings/archived": "project-defaults", }; const RENAMED_SETTINGS_PATHS: Readonly> = { "/settings/integrations": "/settings/browser", "/settings/storage": "/settings/maintenance", "/settings/beta": "/settings/labs", }; const SPLIT_SETTINGS_PATHS: ReadonlySet = new Set([ "/settings/general", "/settings/appearance", "/settings/source-control", "/settings/connections", ...Object.keys(RENAMED_SETTINGS_PATHS), ]); // Section anchors that moved but are not search results themselves. const MOVED_SECTION_ANCHORS: Readonly> = { "authorized-clients": "/settings/access", "legacy-features": "/settings/labs", "text-generation": "/settings/threads", about: "/settings/about", }; /** Where an old settings link now lives. Its anchor finds the page its row moved to. */ export function movedSettingsPath(pathname: string, hash: string): SettingsPath | null { if (!SPLIT_SETTINGS_PATHS.has(pathname)) return null; const items: readonly SettingsSearchItem[] = SETTINGS_SEARCH_ITEMS; const anchored = hash ? (items.find((item) => item.id === hash) ?? items.find((item) => item.targetId === hash)) : undefined; const to = anchored?.to ?? MOVED_SECTION_ANCHORS[hash] ?? RENAMED_SETTINGS_PATHS[pathname] ?? null; return to === pathname ? null : to; } export function getSettingsSearchTargetScope(targetId: string) { const items: readonly SettingsSearchItem[] = SETTINGS_SEARCH_ITEMS; const item = items.find((candidate) => candidate.id === targetId) ?? items.find((candidate) => candidate.targetId === targetId); return item ? { title: item.title, scope: item.scope ?? SETTINGS_CATEGORY_SCOPES[item.to], ...(item.requiresThreadAutoSettlement ? { requiresThreadAutoSettlement: true } : {}), } : null; } interface AutoSettlementSearchEnvironment { readonly environmentId: EnvironmentId; readonly connection: { readonly phase: EnvironmentConnectionPhase }; readonly serverConfig: object | null; } export function getThreadAutoSettlementSearchAvailability( environments: readonly AutoSettlementSearchEnvironment[], scope?: Pick, ) { const connected = environments.filter( (environment) => environment.connection.phase === "connected" && environment.serverConfig !== null, ); const eligibleEnvironmentIds = connected.map((environment) => environment.environmentId); const selected = connected.filter((environment) => scope?.environmentIds.includes(environment.environmentId), ); return { eligibleEnvironmentIds, isTargetAvailable: scope !== undefined && scope.kind !== "unavailable" && selected.length > 0 && selected.every((environment) => eligibleEnvironmentIds.includes(environment.environmentId)), }; } export function isSettingsSearchScopeAvailable( requiredScope: SettingsSearchScope | null, scopeKind: ResolvedSettingsScope["kind"], ): boolean { switch (requiredScope) { case null: case "connections": return true; case "environment": case "checkout": return requiredScope === scopeKind; case "project": return scopeKind === "project" || scopeKind === "checkout"; case "environment-defaults": return scopeKind === "environment" || scopeKind === "all"; case "project-defaults": return ( scopeKind === "environment" || scopeKind === "all" || scopeKind === "project" || scopeKind === "checkout" ); } } export function searchableSetting(id: SettingsSearchItemId): { readonly id: string; readonly title: string; } { const { id: anchorId, title } = SEARCH_ITEMS_BY_ID.get(id)!; return { id: anchorId, title }; } export function filterAvailableSettingsSearchItems( availability: SettingsSearchAvailability, ): ReadonlyArray { const items: ReadonlyArray = SETTINGS_SEARCH_ITEMS; return items.filter( (item) => (!item.cloudOnly || availability.hasCloudPublicConfig) && (!item.environmentOnly || availability.hasEnvironment) && (!item.providerSettingsOnly || availability.hasProviderSettingsEnvironment) && (!item.localBackendManagementOnly || availability.canManageLocalBackend) && (!item.localEnvironmentOnly || !availability.localEnvironmentDisabled) && (!item.wslAvailableOnly || availability.isWslSettingsRowVisible) && (!item.requiresThreadAutoSettlement || availability.hasThreadAutoSettlement) && (!item.managedTunnelOnly || availability.managedTunnelActive === true) && (!item.peerLinksOnly || availability.hasPeerLinkHost === true), ); } /** The settings that exist on this platform and app channel. */ export function settingsForThisPlatform( items: ReadonlyArray, ): ReadonlyArray { const platform = typeof navigator === "undefined" ? "" : navigator.platform; return items.filter( (item) => (isElectron || item.desktopOnly !== true) && (!item.releaseChannelOnly || isElectron || HOSTED_APP_CHANNEL !== null) && (!item.secretsFeatureOnly || isSecretsFeatureEnabled) && (!item.macOnly || isMacPlatform(platform)) && (!item.windowsOnly || isWindowsPlatform(platform)), ); } export function searchSettings( query: string, items: ReadonlyArray = SETTINGS_SEARCH_ITEMS, ): ReadonlyArray { const normalizedQuery = normalizeSearchText(query); if (normalizedQuery.length === 0) return []; const queryTokens = normalizedQuery.split(" "); return settingsForThisPlatform(items) .flatMap((item, index) => { const title = normalizeSearchText(item.title); const fields = [ title, normalizeSearchText(SETTINGS_SECTION_LABELS[item.to]), ...(item.searchTerms ?? []).map(normalizeSearchText), ]; if (!queryTokens.every((token) => fields.some((field) => field.includes(token)))) return []; const exactPhraseField = fields.findIndex((field) => field.includes(normalizedQuery)); const rank = title === normalizedQuery ? 5 : title.startsWith(normalizedQuery) ? 4 : title.includes(normalizedQuery) ? 3 : queryTokens.every((token) => title.includes(token)) ? 2 : exactPhraseField >= 0 ? 1 : 0; return [{ item, index, rank }]; }) .toSorted( (left, right) => Number(left.item.secondary ?? false) - Number(right.item.secondary ?? false) || right.rank - left.rank || left.index - right.index, ) .map(({ item }) => item); } // These rows only exist with separate fonts on, so a search jump to one turns them on. const ADVANCED_TYPOGRAPHY_TARGET_IDS: ReadonlySet = new Set([ "prompt-font", "terminal-font", ]); export function isAdvancedTypographyTarget(targetId: string): boolean { return ADVANCED_TYPOGRAPHY_TARGET_IDS.has(targetId); } /** * Opens the persisted section a search result lives in before jumping to it, so * its row is mounted to scroll to. The terminal reads the same flag, so it must * stay on after the jump. */ export function revealSettingsSearchTarget(targetId: string): void { if (isAdvancedTypographyTarget(targetId)) { writeLocalStorageValue(TYPOGRAPHY_ADVANCED_STORAGE_KEY, true, false, Schema.Boolean); } }