chore: remove unused code

This commit is contained in:
Neil
2026-05-30 11:05:58 -07:00
committed by GitHub
parent 05b78b8f2f
commit c99eb29bb0
37 changed files with 21 additions and 829 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ import {
type TerminalShortcutSpecialKey
} from '../terminal/terminal-accessory-keys'
export const CUSTOM_ACCESSORY_KEYS_STORAGE_KEY = 'orca:custom-accessory-keys'
const CUSTOM_ACCESSORY_KEYS_STORAGE_KEY = 'orca:custom-accessory-keys'
export type CustomKey = {
id: string
@@ -13,7 +13,7 @@ function stripTrailingSeparators(p: string): string {
// Why: cross-platform path basename — handles both POSIX ("/") and Windows
// ("\\") separators, mirroring src/renderer/src/lib/path.ts so the mobile
// suggestion logic agrees with the desktop's collision check.
export function pathBasename(p: string): string {
function pathBasename(p: string): string {
const normalized = stripTrailingSeparators(p)
const idx = Math.max(normalized.lastIndexOf('/'), normalized.lastIndexOf('\\'))
return idx === -1 ? normalized : normalized.slice(idx + 1)
+1 -1
View File
@@ -1,6 +1,6 @@
export type TaskProvider = 'github' | 'gitlab' | 'linear'
export const MOBILE_TASK_PROVIDERS: readonly TaskProvider[] = ['github', 'gitlab', 'linear']
const MOBILE_TASK_PROVIDERS: readonly TaskProvider[] = ['github', 'gitlab', 'linear']
const TASK_PROVIDER_SET = new Set<TaskProvider>(MOBILE_TASK_PROVIDERS)
+2 -1
View File
@@ -7,6 +7,7 @@ export const MOBILE_TUI_AGENT_AUTO_PICK_ORDER = [
'claude',
'openclaude',
'codex',
'openclaude',
'grok',
'copilot',
'opencode',
@@ -135,7 +136,7 @@ export function isMobileTuiAgent(value: unknown): value is TuiAgent {
return MOBILE_TUI_AGENT_AUTO_PICK_ORDER.includes(value as TuiAgent)
}
export function normalizeDisabledMobileTuiAgents(value: unknown): TuiAgent[] {
function normalizeDisabledMobileTuiAgents(value: unknown): TuiAgent[] {
if (!Array.isArray(value)) {
return []
}
+1 -1
View File
@@ -3,7 +3,7 @@
export const PER_REPO_FETCH_LIMIT = 36
export const CROSS_REPO_DISPLAY_LIMIT = 100
export const GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE =
const GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE =
'GitHub work items require a GitHub remote for SSH repositories'
export function isGitHubWorkItemsSshRemoteRequiredError(error: unknown): boolean {
+1 -1
View File
@@ -5,7 +5,7 @@ export function resolveMobileWorkspaceCreateName(args: {
return args.draft?.trim() || args.fallback
}
export function slugifyForWorkspaceName(input: string): string {
function slugifyForWorkspaceName(input: string): string {
return input
.trim()
.toLowerCase()
+1 -1
View File
@@ -7,7 +7,7 @@ export type WorkspaceSshGate = {
error: string | null
}
export function isWorkspaceSshConnectInProgress(status: SshConnectionStatus | null): boolean {
function isWorkspaceSshConnectInProgress(status: SshConnectionStatus | null): boolean {
return status === 'connecting' || status === 'deploying-relay' || status === 'reconnecting'
}
+3 -13
View File
@@ -16,9 +16,9 @@ import type { ConnectionState } from './types'
// session but haven't been for ≥ 1 minute despite the retry loop
// spinning, treat the same as never-connected. Catches the case
// where the desktop's IP changed mid-session.
export const WARNING_ATTEMPTS = 3
export const UNREACHABLE_ATTEMPTS = 12
export const STALE_SINCE_LAST_CONNECT_MS = 60_000
const WARNING_ATTEMPTS = 3
const UNREACHABLE_ATTEMPTS = 12
const STALE_SINCE_LAST_CONNECT_MS = 60_000
export type ConnectionVerdict =
| { kind: 'normal'; label: string }
@@ -76,13 +76,3 @@ export function classifyConnection(args: {
return { kind: 'normal', label: 'Reconnecting…' }
}
// Why: the message under the banner explains what likely happened so the
// user understands why we're suggesting Re-pair. Tuned to be specific
// about IP/port without being technical (we don't want to leak
// "ws://192.168.x.y:port" unless someone is debugging).
export function unreachableHint(reason: 'never-connected' | 'stale'): string {
return reason === 'never-connected'
? "Can't reach this Orca desktop. Its network address may have changed since pairing — try re-pairing from the desktop's Settings → Mobile screen."
: 'Lost contact with the Orca desktop. If your network changed (different Wi-Fi, IP renewed, or desktop restarted), try re-pairing.'
}
+1 -1
View File
@@ -73,7 +73,7 @@ export function decrypt(encrypted: string, sharedKey: Uint8Array): string | null
return plaintext ? new TextDecoder().decode(plaintext) : null
}
export function encryptBytes(plaintext: Uint8Array, sharedKey: Uint8Array): Uint8Array {
function encryptBytes(plaintext: Uint8Array, sharedKey: Uint8Array): Uint8Array {
const nonce = u8(nacl.randomBytes(nacl.box.nonceLength))
const ciphertext = nacl.box.after(u8(plaintext), nonce, u8(sharedKey))
-19
View File
@@ -1,19 +0,0 @@
export { connect, type RpcClient } from './rpc-client'
export {
loadHosts,
saveHost,
removeHost,
renameHost,
getNextHostName,
updateLastConnected
} from './host-store'
export type {
RpcRequest,
RpcResponse,
RpcSuccess,
RpcFailure,
ConnectionState,
HostProfile,
PairingOffer
} from './types'
export { PairingOfferSchema, PAIRING_OFFER_VERSION } from './types'
+1 -1
View File
@@ -24,7 +24,7 @@ export type RpcFailure = {
export type RpcResponse = RpcSuccess | RpcFailure
export const PAIRING_OFFER_VERSION = 2
const PAIRING_OFFER_VERSION = 2
export const PairingOfferSchema = z.object({
v: z.literal(PAIRING_OFFER_VERSION),
-2
View File
@@ -60,7 +60,6 @@
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0",
"@linear/sdk": "^82.1.0",
@@ -125,7 +124,6 @@
"remark-parse": "^11.0.0",
"shadcn": "^4.7.0",
"sherpa-onnx": "1.12.37",
"simple-git": "^3.36.0",
"sonner": "^2.0.7",
"ssh2": "^1.17.0",
"tailwind-merge": "^3.5.0",
-45
View File
@@ -25,9 +25,6 @@ importers:
'@dnd-kit/sortable':
specifier: ^10.0.0
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)
'@dnd-kit/utilities':
specifier: ^3.2.2
version: 3.2.2(react@19.2.5)
'@electron-toolkit/preload':
specifier: ^3.0.2
version: 3.0.2(electron@41.5.0)
@@ -220,9 +217,6 @@ importers:
sherpa-onnx:
specifier: 1.12.37
version: 1.12.37
simple-git:
specifier: ^3.36.0
version: 3.36.0
sonner:
specifier: ^2.0.7
version: 2.0.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -1006,12 +1000,6 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@kwsites/file-exists@1.1.1':
resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==}
'@kwsites/promise-deferred@1.1.1':
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
'@linear/sdk@82.1.0':
resolution: {integrity: sha512-Ok7o+LqXaenx6Um58NQqjQoQanDsCgAIe9yNgpVbqRSh5APz3Ds1kZUz2vWmSNTNATFZm1zDQtEktTMga2X7UQ==}
engines: {node: '>=18.x'}
@@ -2365,12 +2353,6 @@ packages:
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
'@simple-git/args-pathspec@1.0.3':
resolution: {integrity: sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==}
'@simple-git/argv-parser@1.1.1':
resolution: {integrity: sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==}
'@sindresorhus/is@4.6.0':
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'}
@@ -5793,9 +5775,6 @@ packages:
simple-get@4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
simple-git@3.36.0:
resolution: {integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==}
simple-update-notifier@2.0.0:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
engines: {node: '>=10'}
@@ -7054,14 +7033,6 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@kwsites/file-exists@1.1.1':
dependencies:
debug: 4.4.3
transitivePeerDependencies:
- supports-color
'@kwsites/promise-deferred@1.1.1': {}
'@linear/sdk@82.1.0(graphql@16.13.2)':
dependencies:
'@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2)
@@ -8244,12 +8215,6 @@ snapshots:
'@sec-ant/readable-stream@0.4.1': {}
'@simple-git/args-pathspec@1.0.3': {}
'@simple-git/argv-parser@1.1.1':
dependencies:
'@simple-git/args-pathspec': 1.0.3
'@sindresorhus/is@4.6.0': {}
'@sindresorhus/merge-streams@4.0.0': {}
@@ -12349,16 +12314,6 @@ snapshots:
once: 1.4.0
simple-concat: 1.0.1
simple-git@3.36.0:
dependencies:
'@kwsites/file-exists': 1.1.1
'@kwsites/promise-deferred': 1.1.1
'@simple-git/args-pathspec': 1.0.3
'@simple-git/argv-parser': 1.1.1
debug: 4.4.3
transitivePeerDependencies:
- supports-color
simple-update-notifier@2.0.0:
dependencies:
semver: 7.7.4
-8
View File
@@ -665,14 +665,6 @@ export async function scanClaudeUsageFiles(
}
}
export function getClaudeProjectsDirectory(): string {
return CLAUDE_PROJECTS_DIR
}
export function getClaudeTranscriptsDirectory(): string {
return CLAUDE_TRANSCRIPTS_DIR
}
export function createWorktreeRefs(
repos: Repo[],
worktreesByRepo: Map<string, { path: string; worktreeId: string; displayName: string }[]>
+1 -20
View File
@@ -1,29 +1,10 @@
import { Notification, shell, systemPreferences } from 'electron'
import { Notification, shell } from 'electron'
const ACCESSIBILITY_SETTINGS_URL =
'x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility'
const DEFAULT_ACCESSIBILITY_INSTRUCTIONS =
'System Settings -> Privacy & Security -> Accessibility -> enable Orca'
const activePermissionNotifications = new Set<Notification>()
/** Probe accessibility permissions; lazy -- invoked only on first failure path. */
export async function checkAccessibilityPermission(): Promise<{
ok: boolean
instructions?: string
}> {
if (process.platform !== 'darwin') {
return { ok: true }
}
try {
const ok = systemPreferences.isTrustedAccessibilityClient(false)
return ok ? { ok: true } : { ok: false, instructions: DEFAULT_ACCESSIBILITY_INSTRUCTIONS }
} catch {
return { ok: false, instructions: DEFAULT_ACCESSIBILITY_INSTRUCTIONS }
}
}
/** Surface a notification through Orca's existing notification system (do not duplicate UI). */
export function notifyPermissionRequired(instructions: string): void {
if (!Notification.isSupported()) {
-8
View File
@@ -113,14 +113,6 @@ function ownerScopeKey(owner: string, ownerType: GitHubProjectOwnerType): string
return `${owner}\u0000${ownerType}`
}
/** @internal — test-only */
export function _resetProjectViewModuleState(): void {
ownerTypeCache.clear()
parentFieldRetriedByOwner.clear()
parentFieldWarningLoggedByOwner.clear()
parentFieldProbeInFlight.clear()
}
// ─── Normalizers ───────────────────────────────────────────────────────
type RawProjectV2Field = {
-8
View File
@@ -368,14 +368,6 @@ export async function getWorkItemByProjectRef(
}
}
// Why: combined MR + issue list for the Tasks-screen and picker
// surfaces. Centralizes the merge logic that TaskPage previously did
// inline so the IPC layer has a single function to call. Pagination is
// approximate — the v1 contract is "page 1 of perPage MRs + perPage
// issues, mixed by updatedAt desc" which is good enough for a typical
// project's <100 active items.
export type ListWorkItemsState = MRListState
function mrStateToIssueState(state: MRListState): IssueListState | null {
// Why: GitLab issues don't have a 'merged' state. When the user is
// filtering MRs to merged, return null so listWorkItems can skip the
-131
View File
@@ -1,131 +0,0 @@
// Why: extracted from worktrees.ts to keep the main IPC module under the
// max-lines threshold. Hooks IPC handlers (check, readIssueCommand,
// writeIssueCommand) are self-contained and don't interact with worktree
// creation or removal state.
import { ipcMain } from 'electron'
import { join } from 'path'
import type { Store } from '../persistence'
import { isFolderRepo } from '../../shared/repo-kind'
import { getSshFilesystemProvider } from '../providers/ssh-filesystem-dispatch'
import {
hasHooksFile,
hasUnrecognizedOrcaYamlKeys,
loadHooks,
readIssueCommand,
writeIssueCommand
} from '../hooks'
export function registerHooksHandlers(store: Store): void {
ipcMain.removeHandler('hooks:check')
ipcMain.removeHandler('hooks:readIssueCommand')
ipcMain.removeHandler('hooks:writeIssueCommand')
ipcMain.handle('hooks:check', async (_event, args: { repoId: string }) => {
const repo = store.getRepo(args.repoId)
if (!repo || isFolderRepo(repo)) {
return { hasHooks: false, hooks: null, mayNeedUpdate: false }
}
// Why: remote repos read orca.yaml via the SSH filesystem provider.
// Parsing happens in the main process since it's CPU-cheap and avoids
// adding YAML parsing to the relay.
if (repo.connectionId) {
const fsProvider = getSshFilesystemProvider(repo.connectionId)
if (!fsProvider) {
return { hasHooks: false, hooks: null, mayNeedUpdate: false }
}
try {
const result = await fsProvider.readFile(join(repo.path, '.orca.yaml'))
if (result.isBinary) {
return { hasHooks: false, hooks: null, mayNeedUpdate: false }
}
const { parse } = await import('yaml')
const parsed = parse(result.content)
return { hasHooks: true, hooks: parsed, mayNeedUpdate: false }
} catch {
return { hasHooks: false, hooks: null, mayNeedUpdate: false }
}
}
const has = hasHooksFile(repo.path)
const hooks = has ? loadHooks(repo.path) : null
// Why: when a newer Orca version adds a top-level key to `orca.yaml`, older
// versions that don't recognise it return null and show "could not be parsed".
// Detecting well-formed but unrecognised keys lets the UI suggest updating
// instead of implying the file is broken.
const mayNeedUpdate = has && !hooks && hasUnrecognizedOrcaYamlKeys(repo.path)
return {
hasHooks: has,
hooks,
mayNeedUpdate
}
})
ipcMain.handle('hooks:readIssueCommand', async (_event, args: { repoId: string }) => {
const repo = store.getRepo(args.repoId)
if (!repo || isFolderRepo(repo)) {
return {
localContent: null,
sharedContent: null,
effectiveContent: null,
localFilePath: '',
source: 'none' as const
}
}
if (repo.connectionId) {
const fsProvider = getSshFilesystemProvider(repo.connectionId)
if (!fsProvider) {
return {
localContent: null,
sharedContent: null,
effectiveContent: null,
localFilePath: '',
source: 'none' as const
}
}
try {
const result = await fsProvider.readFile(join(repo.path, '.orca', 'issue-command'))
return {
localContent: result.isBinary ? null : result.content,
sharedContent: null,
effectiveContent: result.isBinary ? null : result.content,
localFilePath: join(repo.path, '.orca', 'issue-command'),
source: 'local' as const
}
} catch {
return {
localContent: null,
sharedContent: null,
effectiveContent: null,
localFilePath: '',
source: 'none' as const
}
}
}
return readIssueCommand(repo.path)
})
ipcMain.handle(
'hooks:writeIssueCommand',
async (_event, args: { repoId: string; content: string }) => {
const repo = store.getRepo(args.repoId)
if (!repo || isFolderRepo(repo)) {
return
}
if (repo.connectionId) {
const fsProvider = getSshFilesystemProvider(repo.connectionId)
if (!fsProvider) {
return
}
await fsProvider.writeFile(join(repo.path, '.orca', 'issue-command'), args.content)
return
}
writeIssueCommand(repo.path, args.content)
}
)
}
-6
View File
@@ -114,12 +114,6 @@ export function setActiveSink(sink: TracerSink | null): void {
activeSink = sink
}
/** Read the currently-active sink. Used by the bundle path to flush before
* collecting traces, and by tests for assertion. */
export function getActiveSink(): TracerSink | null {
return activeSink
}
/** Get the current parent context, or `undefined` if we are at the top of
* the trace tree. Renderer-IPC entry points capture this, embed it in
* span-event attributes (so cross-process spans can be visually linked
@@ -7,14 +7,6 @@ export function makeResponse(body: unknown, status = 200): Response {
} as Response
}
export function makeDirent(name: string, isDir: boolean) {
return {
name,
isDirectory: () => isDir,
isFile: () => !isDir
}
}
export const authJsonGoogle = {
google: {
type: 'oauth',
@@ -45,11 +37,6 @@ export const expiredCreds = {
expiry_date: new Date('2026-04-24T11:00:00.000Z').getTime()
}
export const oauth2JsContent = `
const OAUTH_CLIENT_ID = 'client-id-123';
const OAUTH_CLIENT_SECRET = 'client-secret-456';
`
export const quotaResponse = [
{ remainingFraction: 0.75, resetTime: '2026-04-24T13:00:00.000Z', modelId: 'gemini-2.5-pro' },
{ remainingFraction: 0.9, resetTime: '2026-04-24T14:00:00.000Z', modelId: 'gemini-2.5-flash' }
-4
View File
@@ -22,10 +22,6 @@ export const MessageType = {
export const KEEPALIVE_SEND_MS = 5_000
export const TIMEOUT_MS = 20_000
/** PTY flow control watermarks (VS Code FlowControlConstants). */
export const PTY_FLOW_HIGH_WATERMARK = 100_000
export const PTY_FLOW_LOW_WATERMARK = 5_000
/** Reconnection grace period (default, overridable by relay --grace-time). */
export const DEFAULT_GRACE_TIME_MS = 3 * 60 * 60 * 1000 // 3 hours
@@ -1,30 +0,0 @@
import type React from 'react'
export function SettingsToggleSwitchButton({
checked,
onToggle,
ariaLabel
}: {
checked: boolean
onToggle: () => void
ariaLabel?: string
}): React.JSX.Element {
return (
<button
type="button"
role="switch"
aria-checked={checked}
aria-label={ariaLabel}
onClick={onToggle}
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors ${
checked ? 'bg-foreground' : 'bg-muted-foreground/30'
}`}
>
<span
className={`pointer-events-none block size-3.5 rounded-full bg-background shadow-sm transition-transform ${
checked ? 'translate-x-4' : 'translate-x-0.5'
}`}
/>
</button>
)
}
@@ -1,28 +0,0 @@
import type { Worktree } from '../../../../shared/types'
export function LinkedWorktreeItem({
worktree,
onOpen
}: {
worktree: Worktree
onOpen: () => void
}): React.JSX.Element {
const branchLabel = worktree.branch.replace(/^refs\/heads\//, '')
return (
<button
className="group flex items-center justify-between gap-3 w-full rounded-md border border-border/60 bg-secondary/30 px-3 py-2 text-left transition-colors hover:bg-accent cursor-pointer"
onClick={onOpen}
>
<div className="min-w-0">
<p className="text-sm font-medium text-foreground truncate">{worktree.displayName}</p>
{branchLabel !== worktree.displayName && (
<p className="text-xs text-muted-foreground truncate mt-0.5">{branchLabel}</p>
)}
</div>
<span className="shrink-0 text-xs font-medium text-muted-foreground group-hover:text-foreground transition-colors">
Open
</span>
</button>
)
}
@@ -123,108 +123,6 @@ export function barColor(leftPct: number): string {
return 'bg-red-500'
}
function TooltipWindowSection({
w,
label
}: {
w: RateLimitWindow | null
label: string
}): React.JSX.Element | null {
if (!w) {
return null
}
const leftPct = Math.max(0, Math.round(100 - w.usedPercent))
const resetLabel = w.resetsAt ? formatResetCountdown(w.resetsAt - Date.now()) : null
return (
<div className="space-y-1">
<div className="font-medium text-background">{label}</div>
<div className="w-full h-[6px] rounded-full bg-background/20 overflow-hidden">
<div
className={`h-full rounded-full ${barColor(leftPct)} transition-all duration-300`}
style={{ width: `${Math.min(100, Math.max(0, leftPct))}%` }}
/>
</div>
<div className="flex justify-between text-background/60">
<span>{leftPct}% left</span>
{resetLabel && <span>{resetLabel}</span>}
</div>
</div>
)
}
// ---------------------------------------------------------------------------
// Tooltip content
// ---------------------------------------------------------------------------
export function ProviderTooltip({ p }: { p: ProviderRateLimits | null }): React.JSX.Element {
if (!p) {
return <span className="text-xs text-background/60">No data available</span>
}
const name =
p.provider === 'claude'
? 'Claude'
: p.provider === 'codex'
? 'Codex'
: p.provider === 'gemini'
? 'Gemini'
: p.provider === 'opencode-go'
? 'OpenCode Go'
: p.provider
if (p.status === 'unavailable') {
return (
<div className="text-xs w-[200px]">
<div className="flex items-center gap-1.5 font-medium text-background">
<ProviderIcon provider={p.provider} />
{name}
</div>
<div className="text-background/60">{p.error ?? 'Unavailable'}</div>
</div>
)
}
if (p.status === 'error' && !p.session && !p.weekly && !p.monthly) {
return (
<div className="text-xs w-[200px]">
<div className="flex items-center gap-1.5 font-medium text-background">
<ProviderIcon provider={p.provider} />
{name}
</div>
<div className="text-background/60">{p.error ?? 'Unable to fetch usage'}</div>
</div>
)
}
const updatedAgo = p.updatedAt ? `Updated ${formatTimeAgo(p.updatedAt)}` : 'Not yet updated'
return (
<div className="text-xs w-[200px] space-y-3">
{/* Header */}
<div>
<div className="flex items-center gap-1.5 font-medium text-background text-[13px]">
<ProviderIcon provider={p.provider} />
{name}
</div>
<div className="text-background/50">{updatedAgo}</div>
</div>
{/* Divider */}
<div className="border-t border-background/15" />
{getWindowSections(p).map((s) => (
<TooltipWindowSection key={s.label} w={s.window} label={s.label} />
))}
{/* Stale data warning — softer label when prior data is still shown */}
{p.error ? (
<ErrorMessage message={p.error} stale={!!(p.session || p.weekly || p.monthly)} inverted />
) : null}
</div>
)
}
export function ProviderPanel({
p,
inverted = false,
@@ -1,47 +0,0 @@
import {
TerminalStreamOpcode,
encodeTerminalStreamFrame,
encodeTerminalStreamJson,
encodeTerminalStreamText
} from '../../../../shared/terminal-stream-protocol'
export type RemoteRuntimeBinarySender = (bytes: Uint8Array<ArrayBufferLike>) => void
export function sendRemoteRuntimeTerminalInputFrame(
sendBinary: RemoteRuntimeBinarySender | null,
streamId: number | null,
text: string
): boolean {
if (!sendBinary || streamId === null) {
return false
}
sendBinary(
encodeTerminalStreamFrame({
opcode: TerminalStreamOpcode.Input,
streamId,
seq: 0,
payload: encodeTerminalStreamText(text)
})
)
return true
}
export function sendRemoteRuntimeTerminalResizeFrame(
sendBinary: RemoteRuntimeBinarySender | null,
streamId: number | null,
cols: number,
rows: number
): boolean {
if (!sendBinary || streamId === null) {
return false
}
sendBinary(
encodeTerminalStreamFrame({
opcode: TerminalStreamOpcode.Resize,
streamId,
seq: 0,
payload: encodeTerminalStreamJson({ cols, rows })
})
)
return true
}
@@ -50,58 +50,6 @@ export function createNewTerminalTab(
state.setTabBarOrder(activeWorktreeId, order)
}
export function closeTerminalTab(tabId: string): void {
const state = useAppStore.getState()
const owningWorktreeEntry = Object.entries(state.tabsByWorktree).find(([, worktreeTabs]) =>
worktreeTabs.some((tab) => tab.id === tabId)
)
const owningWorktreeId = owningWorktreeEntry?.[0] ?? null
if (!owningWorktreeId) {
return
}
const runtimeEnvironmentId = state.settings?.activeRuntimeEnvironmentId?.trim()
if (isWebRuntimeSessionActive(runtimeEnvironmentId)) {
// Why: paired web tabs are host-owned. Closing locally leaves the host to
// re-publish the same stale surface on the next session-tabs snapshot.
void closeWebRuntimeSessionTab({
worktreeId: owningWorktreeId,
tabId,
environmentId: runtimeEnvironmentId
})
return
}
const currentTabs = state.tabsByWorktree[owningWorktreeId] ?? []
if (currentTabs.length <= 1) {
state.closeTab(tabId)
if (state.activeWorktreeId === owningWorktreeId) {
// Why: only deactivate the worktree when no tabs of any kind remain.
// Editor files are a separate tab type; closing the last terminal tab
// should switch to the editor view instead of tearing down the workspace.
const worktreeFile = state.openFiles.find((f) => f.worktreeId === owningWorktreeId)
if (worktreeFile) {
state.setActiveFile(worktreeFile.id)
state.setActiveTabType('editor')
} else {
state.setActiveWorktree(null)
}
}
return
}
if (state.activeWorktreeId === owningWorktreeId && tabId === state.activeTabId) {
const currentIndex = currentTabs.findIndex((tab) => tab.id === tabId)
const nextTab = currentTabs[currentIndex + 1] ?? currentTabs[currentIndex - 1]
if (nextTab) {
state.setActiveTab(nextTab.id)
}
}
state.closeTab(tabId)
}
export function closeOtherTerminalTabs(tabId: string, activeWorktreeId: string | null): void {
if (!activeWorktreeId) {
return
@@ -190,12 +138,6 @@ export function activateTerminalTab(tabId: string): void {
s.setActiveTabType('terminal')
}
export function activateEditorFile(fileId: string): void {
const s = useAppStore.getState()
s.setActiveFile(fileId)
s.setActiveTabType('editor')
}
export function toggleTerminalPaneExpand(tabId: string): void {
useAppStore.getState().setActiveTab(tabId)
requestAnimationFrame(() => {
+1 -87
View File
@@ -7,7 +7,7 @@ export const BUILTIN_TERMINAL_THEME_NAMES = getThemeNames()
export const DEFAULT_TERMINAL_THEME_DARK = 'Ghostty Default Style Dark'
export const DEFAULT_TERMINAL_THEME_LIGHT = 'Builtin Tango Light'
export const DEFAULT_TERMINAL_DIVIDER_DARK = '#3f3f46'
export const DEFAULT_TERMINAL_DIVIDER_LIGHT = '#d4d4d8'
const DEFAULT_TERMINAL_DIVIDER_LIGHT = '#d4d4d8'
export type EffectiveTerminalAppearance = {
mode: 'dark' | 'light'
@@ -77,25 +77,6 @@ export function normalizeColor(value: string | undefined, fallback: string): str
return trimmed
}
export function buildTerminalFontMatchers(fontFamily: string): string[] {
const trimmed = fontFamily.trim()
const normalized = trimmed.toLowerCase()
const matchers = trimmed ? [trimmed, normalized] : []
return Array.from(
new Set([
...matchers,
'sf mono',
'sfmono-regular',
'menlo',
'menlo regular',
'dejavu sans mono',
'liberation mono',
'ubuntu mono',
'monospace'
])
)
}
export function clampNumber(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value))
}
@@ -120,71 +101,4 @@ export function resolvePaneStyleOptions(
}
}
export function getCursorStyleSequence(
style: 'bar' | 'block' | 'underline',
blinking: boolean
): string {
const code =
style === 'block'
? blinking
? 1
: 2
: style === 'underline'
? blinking
? 3
: 4
: blinking
? 5
: 6
return `\u001b[${code} q`
}
export function colorToCss(
color: { r: number; g: number; b: number; a?: number } | string | undefined,
fallback: string
): string {
if (!color) {
return fallback
}
if (typeof color === 'string') {
return color
}
const alpha = typeof color.a === 'number' ? color.a / 255 : 1
return `rgba(${color.r}, ${color.g}, ${color.b}, ${alpha})`
}
export { isTerminalBackgroundLight } from './terminal-title-contrast'
const PALETTE_KEYS = [
'black',
'red',
'green',
'yellow',
'blue',
'magenta',
'cyan',
'white',
'brightBlack',
'brightRed',
'brightGreen',
'brightYellow',
'brightBlue',
'brightMagenta',
'brightCyan',
'brightWhite'
] as const
export function terminalPalettePreview(theme: ITheme | null): string[] {
if (!theme) {
return []
}
const swatches: string[] = []
for (const key of PALETTE_KEYS) {
const color = theme[key]
if (color) {
swatches.push(color)
}
}
return swatches
}
@@ -1,22 +0,0 @@
import type { GlobalSettings } from '../../../shared/types'
import { readRuntimeFileContent, type RuntimeReadableFileContent } from './runtime-file-client'
export type RemoteReadableFile = {
worktreeId: string
relativePath: string
filePath?: string
}
export type RemoteFileContent = RuntimeReadableFileContent
export async function readFileFromActiveRuntime(
settings: Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> | null | undefined,
file: RemoteReadableFile
): Promise<RemoteFileContent> {
return readRuntimeFileContent({
settings,
filePath: file.filePath ?? file.relativePath,
relativePath: file.relativePath,
worktreeId: file.worktreeId
})
}
@@ -1,8 +1,3 @@
import {
TerminalStreamOpcode,
decodeTerminalStreamFrame,
decodeTerminalStreamText
} from '../../../shared/terminal-stream-protocol'
import type { GlobalSettings } from '../../../shared/types'
import { RuntimeRpcCallError, getActiveRuntimeTarget } from './runtime-rpc-client'
import { getRemoteRuntimeTerminalMultiplexer } from './remote-runtime-terminal-multiplexer'
@@ -15,20 +10,6 @@ export type RemoteRuntimePtyIdParts = {
handle: string
}
export type RuntimeTerminalSubscribeEvent =
| {
type: 'scrollback' | 'subscribed'
streamId?: number | null
lines?: string[]
truncated?: boolean
serialized?: string
cols?: number
rows?: number
}
| { type: 'data'; chunk: string }
| { type: 'end' }
| { type: string; [key: string]: unknown }
export function toRemoteRuntimePtyId(handle: string, environmentId?: string | null): string {
const owner = environmentId?.trim()
if (!owner) {
@@ -60,18 +41,6 @@ export function getRemoteRuntimePtyEnvironmentId(ptyId: string): string | null {
return parseRemoteRuntimePtyId(ptyId)?.environmentId ?? null
}
export function isRuntimeTerminalScrollbackEvent(
event: RuntimeTerminalSubscribeEvent
): event is Extract<RuntimeTerminalSubscribeEvent, { type: 'scrollback' | 'subscribed' }> {
return event.type === 'scrollback' || event.type === 'subscribed'
}
export function isRuntimeTerminalDataEvent(
event: RuntimeTerminalSubscribeEvent
): event is Extract<RuntimeTerminalSubscribeEvent, { type: 'data' }> {
return event.type === 'data' && typeof (event as { chunk?: unknown }).chunk === 'string'
}
export function runtimeTerminalErrorMessage(error: unknown): string {
if (error instanceof RuntimeRpcCallError) {
return error.message
@@ -79,62 +48,6 @@ export function runtimeTerminalErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error)
}
export function readRuntimeTerminalScrollback(event: {
serialized?: string
lines?: string[]
}): string | null {
if (event.serialized) {
return event.serialized
}
if (event.lines && event.lines.length > 0) {
return `${event.lines.join('\r\n')}\r\n`
}
return null
}
function concatBytes(chunks: Uint8Array<ArrayBufferLike>[]): Uint8Array<ArrayBufferLike> {
const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0)
const out = new Uint8Array(total)
let offset = 0
for (const chunk of chunks) {
out.set(chunk, offset)
offset += chunk.byteLength
}
return out
}
export function createRuntimeTerminalBinaryReader(callbacks: {
onData: (data: string) => void
onSnapshot: (data: string) => void
onEnd?: () => void
}): (bytes: Uint8Array<ArrayBufferLike>) => void {
let snapshotChunks: Uint8Array<ArrayBufferLike>[] = []
return (bytes) => {
const frame = decodeTerminalStreamFrame(bytes)
if (!frame) {
return
}
if (frame.opcode === TerminalStreamOpcode.Output) {
callbacks.onData(decodeTerminalStreamText(frame.payload))
return
}
if (frame.opcode === TerminalStreamOpcode.SnapshotStart) {
snapshotChunks = []
return
}
if (frame.opcode === TerminalStreamOpcode.SnapshotChunk) {
snapshotChunks.push(frame.payload)
return
}
if (frame.opcode === TerminalStreamOpcode.SnapshotEnd) {
callbacks.onSnapshot(decodeTerminalStreamText(concatBytes(snapshotChunks)))
snapshotChunks = []
callbacks.onEnd?.()
}
}
}
export async function subscribeToRuntimeTerminalData(
settings: Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> | null | undefined,
ptyId: string,
+1 -1
View File
@@ -2,7 +2,7 @@
// renderer (preview/tests) and main (actual generation) reach the exact same
// string without duplicating the wording.
export const COMMIT_MESSAGE_BASE_PROMPT = `You are generating a single git commit message.
const COMMIT_MESSAGE_BASE_PROMPT = `You are generating a single git commit message.
Read the staged diff below and produce the message.
Rules:
-4
View File
@@ -82,8 +82,4 @@ export function getFeatureWallMediaTile(id: FeatureWallMediaTileId): FeatureWall
return TILE_BY_ID.get(id) ?? null
}
export function getFeatureWallWorkflow(id: FeatureWallWorkflowId): FeatureWallWorkflow | null {
return FEATURE_WALL_WORKFLOWS.find((w) => w.id === id) ?? null
}
export const DEFAULT_FEATURE_WALL_WORKFLOW_ID: FeatureWallWorkflowId = 'workspaces'
+1 -1
View File
@@ -1,6 +1,6 @@
import { z } from 'zod'
export const APP_STAR_SOURCE_VALUES = ['star_nag', 'settings', 'landing'] as const
const APP_STAR_SOURCE_VALUES = ['star_nag', 'settings', 'landing'] as const
// Why: renderer-originated IPC is untrusted, so main validates against this
// closed enum before attaching source context to successful star telemetry.
-64
View File
@@ -991,12 +991,6 @@ function normalizeKeybindingArrayWithOptions(
return normalized
}
export function normalizeKeybindingArray(
input: readonly string[]
): KeybindingValidationResult | string[] {
return normalizeKeybindingArrayWithOptions(input)
}
function normalizeOptionsForAction(actionId: KeybindingActionId): NormalizeKeybindingOptions {
return {
allowBareKeybindings: DEFINITIONS_BY_ID.get(actionId)?.allowBareKeybindings === true
@@ -1438,31 +1432,6 @@ export function formatKeybindingList(
.join(', ')
}
export function formatElectronAccelerator(binding: string): string | null {
const parsed = parseKeybinding(binding)
if (!parsed) {
return null
}
const parts: string[] = []
if (parsed.mod) {
parts.push('CmdOrCtrl')
}
if (parsed.meta) {
parts.push('Command')
}
if (parsed.control) {
parts.push('Control')
}
if (parsed.alt) {
parts.push('Alt')
}
if (parsed.shift) {
parts.push('Shift')
}
parts.push(formatElectronKeyToken(parsed.key))
return parts.join('+')
}
function formatKeyToken(token: string): string {
const labels: Record<string, string> = {
BracketLeft: '[',
@@ -1497,35 +1466,6 @@ function formatKeyToken(token: string): string {
return labels[token] ?? token
}
function formatElectronKeyToken(token: string): string {
const labels: Record<string, string> = {
BracketLeft: '[',
BracketRight: ']',
Minus: '-',
Underscore: '_',
Equal: '=',
Plus: 'Plus',
Comma: ',',
Period: '.',
Slash: '/',
Backslash: '\\',
Semicolon: ';',
Quote: "'",
Backquote: '`',
ArrowLeft: 'Left',
ArrowRight: 'Right',
ArrowUp: 'Up',
ArrowDown: 'Down',
PageUp: 'PageUp',
PageDown: 'PageDown',
NumpadAdd: 'numadd',
NumpadSubtract: 'numsub',
Escape: 'Esc',
Space: 'Space'
}
return labels[token] ?? token
}
export function findKeybindingConflicts(
platform: NodeJS.Platform,
overrides?: KeybindingOverrides
@@ -1563,7 +1503,3 @@ export function findKeybindingConflicts(
actionIds
}))
}
export function getDefaultKeybindingOverrides(): KeybindingOverrides {
return {}
}
+1 -4
View File
@@ -8,9 +8,6 @@ export const NATIVE_FILE_DROP_TARGET = {
projectSidebar: 'project-sidebar'
} as const
export type NativeFileDropTarget =
(typeof NATIVE_FILE_DROP_TARGET)[keyof typeof NATIVE_FILE_DROP_TARGET]
export type NativeDropResolution =
| { target: typeof NATIVE_FILE_DROP_TARGET.editor }
| { target: typeof NATIVE_FILE_DROP_TARGET.terminal; tabId?: string }
@@ -36,7 +33,7 @@ export type NativeFileDropPathEntry = {
terminalTabId?: string
}
export function getDataTransferTypes(
function getDataTransferTypes(
types: Iterable<string> | ArrayLike<string> | null | undefined
): string[] {
return types ? Array.from(types) : []
+1 -1
View File
@@ -2,7 +2,7 @@ export const DEFAULT_TERMINAL_FONT_WEIGHT = 500
export const TERMINAL_FONT_WEIGHT_MIN = 100
export const TERMINAL_FONT_WEIGHT_MAX = 900
export const TERMINAL_FONT_WEIGHT_STEP = 100
export const DEFAULT_TERMINAL_FONT_WEIGHT_BOLD = 700
const DEFAULT_TERMINAL_FONT_WEIGHT_BOLD = 700
export function normalizeTerminalFontWeight(fontWeight: number | null | undefined): number {
const numericFontWeight = typeof fontWeight === 'number' ? fontWeight : NaN
+1 -7
View File
@@ -13,7 +13,7 @@ const MAX_QUICK_COMMAND_REPO_ID_LENGTH = 200
const MAX_QUICK_COMMAND_TEXT_LENGTH = 4000
const REMOVED_PRESET_IDS = new Set(['default-pwd', 'default-git-status'])
export const DEFAULT_TERMINAL_QUICK_COMMANDS: TerminalQuickCommand[] = []
const DEFAULT_TERMINAL_QUICK_COMMANDS: TerminalQuickCommand[] = []
export function getDefaultTerminalQuickCommands(): TerminalQuickCommand[] {
return DEFAULT_TERMINAL_QUICK_COMMANDS.map((command) => ({ ...command }))
@@ -60,12 +60,6 @@ export function isTerminalAgentQuickCommand(
return getTerminalQuickCommandAction(command) === 'agent-prompt'
}
export function isTerminalCommandQuickCommand(
command: TerminalQuickCommand
): command is TerminalCommandQuickCommand {
return getTerminalQuickCommandAction(command) === 'terminal-command'
}
export function supportsTerminalAgentQuickCommand(
agent: unknown
): agent is TerminalAgentQuickCommand['agent'] {
+1
View File
@@ -7,6 +7,7 @@ export const TUI_AGENT_AUTO_PICK_ORDER = [
'claude',
'openclaude',
'codex',
'openclaude',
'grok',
'copilot',
'opencode',