Files
orca/src/shared/constants.ts
T
Neil fc64baf7d1 feat(terminal): expose FORCE_HYPERLINK as a user setting (default on) (#963)
* fix(terminal): drop FORCE_HYPERLINK=1 from PTY spawn env

FORCE_HYPERLINK=1 was set on every local PTY. It is read by
oh-my-zsh's supports_hyperlinks(), the Rust supports-hyperlinks
crate, GNU coreutils, and other tooling — so forcing it on
makes every subprocess invoked during shell init take extra
branches and emit OSC-8 escapes.

Reproduced locally with a heavy zshrc (oh-my-zsh + p10k + nvm +
pyenv): time-to-prompt jumps from ~600 ms without the variable
to ~2000 ms with it (3.2×) on a clean sandbox. Stacked with the
user-reported configuration (conda init, gcloud completions,
compaudit over Homebrew paths, corporate network calls) that
multiplier applied to a long serial chain of forks can stretch
into "terminal is effectively never ready."

xterm still renders OSC-8 hyperlinks when tools emit them on
their own detection, so link support in Orca is preserved. VS
Code does not set this variable either.

* feat(terminal): make FORCE_HYPERLINK opt-out via settings, default on

The previous commit on this branch removed FORCE_HYPERLINK=1 from PTY
spawn env entirely. Revise: keep the historical default (on) so
existing users don't silently lose OSC-8 hyperlink emission from
tooling that checks this flag, but expose a toggle in Settings →
Terminal → Advanced so users with heavy rc files (oh-my-zsh +
powerlevel10k + nvm + pyenv + conda, etc.) can turn it off and reclaim
the shell-startup time that the extra subprocess branching and escape
emission was costing them.

- Add `terminalForceHyperlink: boolean` to GlobalSettings (default true)
- LocalPtyProvider gains an `isForceHyperlinkEnabled` hook; IPC wires
  it from the live settings snapshot
- New SearchableSetting row in TerminalPane with zshrc-adjacent search
  keywords so users troubleshooting slow startup find it
- Unit tests cover both default-on and opt-out paths
2026-04-22 18:20:57 -07:00

226 lines
7.4 KiB
TypeScript

import type {
GlobalSettings,
NotificationSettings,
PersistedState,
PersistedUIState,
RepoHookSettings,
StatusBarItem,
WorkspaceSessionState,
WorktreeCardProperty
} from './types'
import { DEFAULT_TERMINAL_FONT_WEIGHT } from './terminal-fonts'
export const SCHEMA_VERSION = 1
export const ORCA_BROWSER_PARTITION = 'persist:orca-browser'
// Why: blank browser tabs must start from an inert guest URL that does not
// navigate the privileged main window to about:blank. Renderer and main both
// need the exact same value so the attach policy can allow only this one safe
// data URL while still rejecting arbitrary renderer-provided data URLs.
export const ORCA_BROWSER_BLANK_URL = 'data:text/html,'
export const BROWSER_FAMILY_LABELS: Record<string, string> = {
chrome: 'Google Chrome',
chromium: 'Chromium',
arc: 'Arc',
edge: 'Microsoft Edge',
brave: 'Brave',
firefox: 'Firefox',
safari: 'Safari',
manual: 'File'
}
// Pick a default terminal font that is likely to exist on the current OS.
// buildFontFamily() adds the full cross-platform fallback chain, so this only
// affects what users see in Settings as the initial value.
function defaultTerminalFontFamily(): string {
const platform = typeof process !== 'undefined' ? process.platform : ''
if (platform === 'win32') {
return 'Cascadia Mono'
}
if (platform === 'linux') {
return 'DejaVu Sans Mono'
}
return 'SF Mono' // macOS default
}
/**
* Why: ProseMirror builds an in-memory tree for the entire document, so large
* markdown files cause noticeable typing lag in the rich editor. Files above
* this threshold fall back to source mode (Monaco) which handles large files
* efficiently via virtualized line rendering.
*/
export const RICH_MARKDOWN_MAX_SIZE_BYTES = 300 * 1024
export const DEFAULT_EDITOR_AUTO_SAVE_DELAY_MS = 1000
export const MIN_EDITOR_AUTO_SAVE_DELAY_MS = 250
export const MAX_EDITOR_AUTO_SAVE_DELAY_MS = 10_000
// Why: initial threshold of agents spawned (since last update) before we show
// the star-on-GitHub notification. Doubles each time the user dismisses
// without starring — e.g. 50 → 100 → 200 → 400. Past dismissals are encoded
// in starNagNextThreshold, so this constant is only the first-time seed.
export const STAR_NAG_INITIAL_THRESHOLD = 50
export const DEFAULT_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = [
'status',
'unread',
'ci',
'issue',
'pr',
'comment'
]
export const DEFAULT_STATUS_BAR_ITEMS: StatusBarItem[] = ['claude', 'codex', 'ssh', 'sessions']
export const REPO_COLORS = [
'#737373', // neutral
'#ef4444', // red
'#f97316', // orange
'#eab308', // yellow
'#22c55e', // green
'#14b8a6', // teal
'#8b5cf6', // purple
'#ec4899' // pink
] as const
export function getDefaultNotificationSettings(): NotificationSettings {
return {
enabled: true,
agentTaskComplete: true,
terminalBell: false,
suppressWhenFocused: true
}
}
export function getDefaultSettings(homedir: string): GlobalSettings {
return {
workspaceDir: `${homedir}/orca/workspaces`,
nestWorkspaces: true,
refreshLocalBaseRefOnWorktreeCreate: false,
branchPrefix: 'git-username',
branchPrefixCustom: '',
theme: 'system',
editorAutoSave: false,
editorAutoSaveDelayMs: DEFAULT_EDITOR_AUTO_SAVE_DELAY_MS,
terminalFontSize: 14,
terminalFontFamily: defaultTerminalFontFamily(),
terminalFontWeight: DEFAULT_TERMINAL_FONT_WEIGHT,
terminalLineHeight: 1,
terminalCursorStyle: 'bar',
terminalCursorBlink: true,
terminalThemeDark: 'Ghostty Default Style Dark',
terminalDividerColorDark: '#3f3f46',
terminalUseSeparateLightTheme: false,
terminalThemeLight: 'Builtin Tango Light',
terminalDividerColorLight: '#d4d4d8',
terminalInactivePaneOpacity: 0.8,
terminalActivePaneOpacity: 1,
terminalPaneOpacityTransitionMs: 140,
terminalDividerThicknessPx: 3,
// Default true so Windows users get native right-click paste out of the
// box. Other platforms ignore this field because the UI never exposes it,
// and Ctrl+right-click still opens the context menu when paste is enabled.
terminalRightClickToPaste: true,
// Default false: opt-in only (matches Ghostty's default). Existing users
// on upgrade inherit this default via persistence.ts's
// { ...defaults.settings, ...parsed.settings } merge, so enabling
// focus-follows-mouse never happens unexpectedly.
terminalFocusFollowsMouse: false,
terminalClipboardOnSelect: false,
terminalAllowOsc52Clipboard: false,
setupScriptLaunchMode: 'new-tab',
terminalScrollbackBytes: 10_000_000,
openLinksInApp: true,
rightSidebarOpenByDefault: true,
showTitlebarAgentActivity: true,
notifications: getDefaultNotificationSettings(),
diffDefaultView: 'inline',
promptCacheTimerEnabled: false,
promptCacheTtlMs: 300_000,
codexManagedAccounts: [],
activeCodexManagedAccountId: null,
terminalScopeHistoryByWorktree: true,
defaultTuiAgent: null,
skipDeleteWorktreeConfirm: false,
defaultTaskViewPreset: 'all',
defaultRepoSelection: null,
agentCmdOverrides: {},
// Why: 'auto' runs a layout-aware probe at boot (see
// src/renderer/src/lib/keyboard-layout/*) that picks 'true' for US and
// US-International and 'false' for every other layout. This mirrors
// Ghostty's detectOptionAsAlt() and ensures users on Turkish, German,
// French, etc. can type Option+Q/L/E characters like @, €, [, ] out of
// the box (issue #903) while US users keep Option-as-Alt readline chords.
terminalMacOptionAsAlt: 'auto',
terminalMacOptionAsAltMigrated: false,
experimentalTerminalDaemon: false,
experimentalTerminalDaemonNoticeShown: false,
// Why: keep the historical default (on) so no existing user's terminal
// loses clickable-link emission on upgrade. Users with heavy zshrc setups
// can disable it from Settings → Terminal → Advanced to reclaim startup
// time.
terminalForceHyperlink: true
}
}
export function getDefaultRepoHookSettings(): RepoHookSettings {
return {
mode: 'auto',
setupRunPolicy: 'run-by-default',
scripts: {
setup: '',
archive: ''
}
}
}
export function getDefaultPersistedState(homedir: string): PersistedState {
return {
schemaVersion: SCHEMA_VERSION,
repos: [],
worktreeMeta: {},
settings: getDefaultSettings(homedir),
ui: getDefaultUIState(),
githubCache: { pr: {}, issue: {} },
workspaceSession: getDefaultWorkspaceSession(),
sshTargets: []
}
}
export function getDefaultUIState(): PersistedUIState {
return {
lastActiveRepoId: null,
lastActiveWorktreeId: null,
sidebarWidth: 280,
rightSidebarWidth: 350,
groupBy: 'none',
sortBy: 'name',
showActiveOnly: false,
filterRepoIds: [],
collapsedGroups: [],
uiZoomLevel: 0,
editorFontZoomLevel: 0,
worktreeCardProperties: [...DEFAULT_WORKTREE_CARD_PROPERTIES],
statusBarItems: [...DEFAULT_STATUS_BAR_ITEMS],
statusBarVisible: true,
dismissedUpdateVersion: null,
lastUpdateCheckAt: null
}
}
export function getDefaultWorkspaceSession(): WorkspaceSessionState {
return {
activeRepoId: null,
activeWorktreeId: null,
activeTabId: null,
tabsByWorktree: {},
terminalLayoutsByTabId: {},
openFilesByWorktree: {},
browserTabsByWorktree: {},
browserPagesByWorkspace: {},
activeBrowserTabIdByWorktree: {},
activeFileIdByWorktree: {},
activeTabTypeByWorktree: {},
browserUrlHistory: []
}
}