From 4c2a10d15782fcdca63435d0bad81b7b3f01c969 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:11:34 -0700 Subject: [PATCH 001/100] Fix smart sort ranking of done agents by completion time (#13899) * Fix smart sort ranking of done agents by completion time Completed entries stayed in the Done sort class indefinitely when same-state writes refreshed updatedAt without moving stateStartedAt. Introduce agentEntryCompletionAt() to use actual completion time for both age display and sort eligibility, ensuring consistent aging regardless of hook updates. * Fix smart sort ranking of done agents by completion time Co-authored-by: Orca --------- Co-authored-by: Orca --- .../agent-finished-timestamp.test.ts | 15 ++ .../dashboard/agent-finished-timestamp.ts | 13 +- .../sidebar/smart-attention.test.ts | 64 ++++++++ .../src/components/sidebar/smart-attention.ts | 44 ++---- .../src/components/sidebar/smart-sort.test.ts | 71 ++++++++- .../src/runtime/web-session-tabs-sync.test.ts | 58 ++++++++ .../src/runtime/web-session-tabs-sync.ts | 6 + .../agent-status-freshness-scheduler.test.ts | 139 ++++++++++++++++++ .../agent-status-freshness-scheduler.ts | 27 +++- .../src/store/slices/agent-status.test.ts | 23 +++ src/renderer/src/store/slices/agent-status.ts | 8 +- src/shared/agent-completion-time.ts | 43 ++++++ 12 files changed, 473 insertions(+), 38 deletions(-) create mode 100644 src/renderer/src/store/slices/agent-status-freshness-scheduler.test.ts create mode 100644 src/shared/agent-completion-time.ts diff --git a/src/renderer/src/components/dashboard/agent-finished-timestamp.test.ts b/src/renderer/src/components/dashboard/agent-finished-timestamp.test.ts index 92df277ef9d..84116333a1c 100644 --- a/src/renderer/src/components/dashboard/agent-finished-timestamp.test.ts +++ b/src/renderer/src/components/dashboard/agent-finished-timestamp.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { agentEntryCompletionAt } from '../../../../shared/agent-completion-time' import type { AgentStatusEntry } from '../../../../shared/agent-status-types' import { lastEnteredDoneAt } from './agent-finished-timestamp' @@ -37,3 +38,17 @@ describe('lastEnteredDoneAt session boundaries (STA-3386)', () => { ).toBe(1_000) }) }) + +describe('lastEnteredDoneAt shares the Smart Sort completion clock', () => { + it('times an ordinary completion from stateStartedAt, not updatedAt', () => { + const entry = doneEntry({ stateStartedAt: 2_000, updatedAt: 190_000 }) + expect(lastEnteredDoneAt(row(entry))).toBe(2_000) + expect(agentEntryCompletionAt(entry)).toBe(2_000) + }) + + it('still shows when an interrupted turn stopped, though it never ranks as done', () => { + const entry = doneEntry({ interrupted: true, stateStartedAt: 2_000 }) + expect(lastEnteredDoneAt(row(entry))).toBe(2_000) + expect(agentEntryCompletionAt(entry)).toBeNull() + }) +}) diff --git a/src/renderer/src/components/dashboard/agent-finished-timestamp.ts b/src/renderer/src/components/dashboard/agent-finished-timestamp.ts index 6449e570fe1..599a07d118a 100644 --- a/src/renderer/src/components/dashboard/agent-finished-timestamp.ts +++ b/src/renderer/src/components/dashboard/agent-finished-timestamp.ts @@ -1,3 +1,4 @@ +import { agentEntryCompletionAt } from '../../../../shared/agent-completion-time' import type { DashboardAgentRow } from './useDashboardData' /** @@ -15,9 +16,15 @@ export function lastEnteredDoneAt( return null } const entry = agent.entry - // Why: a session-boundary done means the session connected idle (STA-3386) — nothing - // finished; fall through to history (which never records session boundaries). - if (entry.state === 'done' && entry.sessionBoundary !== true) { + // Why: same primitive Smart Sort ranks on, so the displayed age and Done eligibility share a clock. + // (Session-boundary `done` means the session connected idle — STA-3386 — so it resolves to the real + // completion it displaced, if any.) + const completedAt = agentEntryCompletionAt(entry) + if (completedAt !== null) { + return completedAt + } + // Why: display is looser than ranking — an interrupted turn still shows when it stopped. + if (entry.state === 'done' && entry.interrupted === true && entry.sessionBoundary !== true) { return entry.stateStartedAt } for (let i = (entry.stateHistory?.length ?? 0) - 1; i >= 0; i--) { diff --git a/src/renderer/src/components/sidebar/smart-attention.test.ts b/src/renderer/src/components/sidebar/smart-attention.test.ts index 2c4324938cd..8723cf937fc 100644 --- a/src/renderer/src/components/sidebar/smart-attention.test.ts +++ b/src/renderer/src/components/sidebar/smart-attention.test.ts @@ -195,6 +195,70 @@ describe('resolveAttention', () => { }) }) + it('drops a done pane out of Class 2 once the completion itself ages out', () => { + const justInside = makeEntry({ + paneKey: 't:1', + state: 'done', + stateStartedAt: NOW - AGENT_STATUS_STALE_AFTER_MS, + updatedAt: NOW - 1_000 + }) + expect(resolveAttention([hookPane(justInside)], NOW)).toEqual({ + cls: 2, + attentionTimestamp: NOW - AGENT_STATUS_STALE_AFTER_MS + }) + + const justOutside = makeEntry({ + paneKey: 't:1', + state: 'done', + stateStartedAt: NOW - AGENT_STATUS_STALE_AFTER_MS - 1, + updatedAt: NOW - 1_000 + }) + expect(resolveAttention([hookPane(justOutside)], NOW)).toEqual(IDLE) + }) + + it('does not let same-state done writes extend Class 2 eligibility', () => { + // The captured regression: updatedAt was 3m04s newer than the completion, keeping a 32m-old + // "done" row above two spinners. + const entry = makeEntry({ + paneKey: 't:1', + state: 'done', + stateStartedAt: NOW - 32 * 60_000, + updatedAt: NOW - 29 * 60_000 + }) + expect(resolveAttention([hookPane(entry)], NOW)).toEqual(IDLE) + }) + + it('keeps an expired done pane from masking a live working sibling', () => { + const expiredDone = makeEntry({ + paneKey: 't:1', + state: 'done', + stateStartedAt: NOW - AGENT_STATUS_STALE_AFTER_MS - 60_000, + updatedAt: NOW - 1_000 + }) + const working = makeEntry({ + paneKey: 't:2', + state: 'working', + stateStartedAt: NOW - 10_000, + updatedAt: NOW - 1_000 + }) + expect(resolveAttention(hookPanes([expiredDone, working]), NOW)).toEqual({ + cls: 3, + attentionTimestamp: NOW - 10_000 + }) + }) + + it('ages out a session-boundary completion from its real completion time', () => { + const boundary = makeEntry({ + paneKey: 't:1', + state: 'done', + sessionBoundary: true, + stateStartedAt: NOW - 1_000, + updatedAt: NOW - 500, + stateHistory: [makeHistory('done', NOW - AGENT_STATUS_STALE_AFTER_MS - 1)] + }) + expect(resolveAttention([hookPane(boundary)], NOW)).toEqual(IDLE) + }) + it('classifies a working pane with prior done as Class 3 with the prior timestamp', () => { const entry = makeEntry({ paneKey: 't:1', diff --git a/src/renderer/src/components/sidebar/smart-attention.ts b/src/renderer/src/components/sidebar/smart-attention.ts index d1a182cb224..5238a9091eb 100644 --- a/src/renderer/src/components/sidebar/smart-attention.ts +++ b/src/renderer/src/components/sidebar/smart-attention.ts @@ -1,4 +1,5 @@ import { classifyTitleActivity, isExplicitAgentStatusFresh } from '@/lib/pane-agent-evidence' +import { agentEntryCompletionAt } from '../../../../shared/agent-completion-time' import { migrationUnsupportedToAgentStatusEntry } from '@/lib/migration-unsupported-agent-entry' import { tabHasLivePty } from '@/lib/tab-has-live-pty' import { resolveRuntimePaneTitleLeafId } from '@/lib/runtime-pane-title-leaf-id' @@ -15,9 +16,9 @@ import { parsePaneKey } from '../../../../shared/stable-pane-id' /** * Ordinal class for the "Smart" sort. Lower number = more attention-demanding. * 1 — Needs you (`blocked` / `waiting`) - * 2 — Done (`done`, not interrupted) + * 2 — Done (`done`, not interrupted, completed within AGENT_STATUS_STALE_AFTER_MS) * 3 — Working (`working`) - * 4 — Idle (no live entry, stale entry, or interrupted `done`) + * 4 — Idle (no live entry, stale entry, interrupted `done`, or an aged-out completion) * * Primary sort key; ties fall back to the attention timestamp. See docs/smart-worktree-order-redesign.md. */ @@ -34,7 +35,7 @@ export type AttentionCause = 'blocked' | 'waiting' | 'title-heuristic' * Per-worktree resolution computed once before sorting. * * `attentionTimestamp` by class: - * - Class 1 / 2: `stateStartedAt` of the current entry. + * - Class 1: `stateStartedAt` of the current entry. Class 2: the entry's completion time. * - Class 3: `stateStartedAt` of the most recent prior `done`/`blocked`/`waiting` entry, * falling back to the current `working` `stateStartedAt`. * - Class 4: `0` — comparator drops to `effectiveRecentActivity` for idle ordering. @@ -98,21 +99,6 @@ export function mostRecentAttentionInHistory(history: AgentStateHistoryEntry[]): return max > 0 ? max : null } -function mostRecentCompletedTurnInHistory(history: AgentStateHistoryEntry[]): number | null { - let max = 0 - for (const entry of history) { - if ( - entry.state === 'done' && - entry.interrupted !== true && - Number.isFinite(entry.startedAt) && - entry.startedAt > max - ) { - max = entry.startedAt - } - } - return max > 0 ? max : null -} - /** * One pane's contribution to a worktree's attention class. Fresh hook entries win; hookless * panes fall back to the title heuristic (design doc Edge case 9). Authority is per-pane, not per-worktree. @@ -152,21 +138,19 @@ export function resolveAttention(panes: PaneInput[], now: number): WorktreeAtten ts = entry.stateStartedAt cause = entry.state } else if (entry.state === 'done') { - // Why: interrupted `done` (Ctrl+C) means the user is done with the turn; treat as idle, not Class 2. - if (entry.interrupted) { + // Why: null covers interrupted `done` (Ctrl+C — user is finished with it) and idle session + // boundaries; neither is attention. + const completedAt = agentEntryCompletionAt(entry) + if (completedAt === null) { + continue + } + // Why: same-state `done` writes advance updatedAt without moving the completion, so the hook + // freshness gate alone can keep a row in Class 2 long after the UI shows it aged out. + if (now - completedAt > AGENT_STATUS_STALE_AFTER_MS) { continue } cls = 2 - if (entry.sessionBoundary) { - // Why: idle connect is not new attention; only preserve a real completion it displaced. - const completedAt = mostRecentCompletedTurnInHistory(entry.stateHistory) - if (completedAt === null) { - continue - } - ts = completedAt - } else { - ts = entry.stateStartedAt - } + ts = completedAt } else { // working cls = 3 diff --git a/src/renderer/src/components/sidebar/smart-sort.test.ts b/src/renderer/src/components/sidebar/smart-sort.test.ts index 47c437f5199..75af8fae1cc 100644 --- a/src/renderer/src/components/sidebar/smart-sort.test.ts +++ b/src/renderer/src/components/sidebar/smart-sort.test.ts @@ -109,7 +109,8 @@ function ptyMapForTabs(tabsByWorktree: Record): Record, agentStatusByPaneKey: Record @@ -120,9 +121,17 @@ function sortSmart( agentStatusByPaneKey, {}, ptyMapForTabs(tabsByWorktree), - NOW + now ) - return [...worktrees].sort(buildWorktreeComparator('smart', repoMap, NOW, attention)) + return [...worktrees].sort(buildWorktreeComparator('smart', repoMap, now, attention)) +} + +function sortSmart( + worktrees: Worktree[], + tabsByWorktree: Record, + agentStatusByPaneKey: Record +): Worktree[] { + return sortSmartAt(NOW, worktrees, tabsByWorktree, agentStatusByPaneKey) } describe('smart sort — class invariants', () => { @@ -354,6 +363,62 @@ describe('smart sort — interrupted and stale handling', () => { }) }) +describe('smart sort — completed-agent eligibility clock', () => { + // Captured regression (docs/smart-sort-agent-activity-findings.md): a `done` row whose + // updatedAt was 3m04s newer than its completion outranked two live spinners. + const SCREENSHOT_AT = new Date('2026-03-27T21:44:39.000Z').getTime() + const at = (hhmmss: string): number => new Date(`2026-03-27T${hhmmss}.000Z`).getTime() + + function capturedFixture(workingUpdatedAt = at('21:44:30')) { + const done = makeWorktree({ id: 'fix-linear-persistent', displayName: 'fix-linear-persistent' }) + const workingA = makeWorktree({ id: 'allow-editing', displayName: 'allow-editing' }) + const workingB = makeWorktree({ id: 'resume-terminal', displayName: 'resume-terminal' }) + const tabs = { + [done.id]: [makeTab({ id: 'tab-done', worktreeId: done.id })], + [workingA.id]: [makeTab({ id: 'tab-a', worktreeId: workingA.id })], + [workingB.id]: [makeTab({ id: 'tab-b', worktreeId: workingB.id })] + } + const entries = { + [paneKey('tab-done', '1')]: makeEntry({ + paneKey: paneKey('tab-done', '1'), + state: 'done', + stateStartedAt: at('21:12:09'), + // Same-state `done` writes pushed updatedAt 3m04s past the completion. + updatedAt: at('21:15:13') + }), + [paneKey('tab-a', '1')]: makeEntry({ + paneKey: paneKey('tab-a', '1'), + state: 'working', + stateStartedAt: workingUpdatedAt - 68_000, + updatedAt: workingUpdatedAt + }), + [paneKey('tab-b', '1')]: makeEntry({ + paneKey: paneKey('tab-b', '1'), + state: 'working', + stateStartedAt: workingUpdatedAt - 145_000, + updatedAt: workingUpdatedAt - 5_000 + }) + } + return { worktrees: [done, workingA, workingB], tabs, entries } + } + + it('ranks the two spinners above the 32-minute-old completion', () => { + const { worktrees, tabs, entries } = capturedFixture() + const sorted = sortSmartAt(SCREENSHOT_AT, worktrees, tabs, entries) + expect(sorted.map((w) => w.id)).toEqual([ + 'allow-editing', + 'resume-terminal', + 'fix-linear-persistent' + ]) + }) + + it('still ranks that completion above the spinners inside its own window', () => { + const { worktrees, tabs, entries } = capturedFixture(at('21:39:50')) + const sorted = sortSmartAt(at('21:40:00'), worktrees, tabs, entries) + expect(sorted[0].id).toBe('fix-linear-persistent') + }) +}) + describe('smart sort — Class 4 ordering', () => { it('breaks ties on effectiveRecentActivity, then displayName', () => { const recentlyActive = makeWorktree({ diff --git a/src/renderer/src/runtime/web-session-tabs-sync.test.ts b/src/renderer/src/runtime/web-session-tabs-sync.test.ts index 4d94c99e728..526833a5969 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync.test.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync.test.ts @@ -2300,6 +2300,64 @@ describe('applyWebSessionTabsSnapshot', () => { expect(patch.sortEpoch).toBe((initialState.sortEpoch ?? 0) + 1) }) + it('bumps sort epoch when a mirrored same-state done update becomes a completion', () => { + const hostPaneKey = makePaneKey('host-tab-1', LEAF_ID) + const initialSnapshot = makeSnapshot([ + { + type: 'terminal', + id: HOST_SURFACE_ID, + title: 'Codex', + parentTabId: 'host-tab-1', + leafId: LEAF_ID, + isActive: true, + status: 'ready', + terminal: 'terminal-1', + agentStatus: { + state: 'done', + prompt: 'same prompt', + updatedAt: NOW - 1_000, + stateStartedAt: NOW - 2_000, + agentType: 'codex', + paneKey: hostPaneKey, + stateHistory: [], + interrupted: true + } + } + ]) + const initialPatch = applyWebSessionTabsSnapshot( + makeState(), + initialSnapshot, + ENV, + NOW + ) as Partial + const initialState = { ...makeState(), ...initialPatch } + + const patch = applyWebSessionTabsSnapshot( + initialState, + { + ...initialSnapshot, + snapshotVersion: 2, + tabs: initialSnapshot.tabs.map((tab) => + tab.type === 'terminal' && tab.agentStatus + ? { + ...tab, + agentStatus: { + ...tab.agentStatus, + updatedAt: NOW, + interrupted: undefined + } + } + : tab + ) + }, + ENV, + NOW + ) as Partial + + expect(patch.agentStatusEpoch).toBe((initialState.agentStatusEpoch ?? 0) + 1) + expect(patch.sortEpoch).toBe((initialState.sortEpoch ?? 0) + 1) + }) + it('hydrates multiple initial host snapshots in one merged patch', () => { const secondWorktree = 'repo::/other-worktree' const patch = applyWebSessionTabsSnapshots( diff --git a/src/renderer/src/runtime/web-session-tabs-sync.ts b/src/renderer/src/runtime/web-session-tabs-sync.ts index 174a239ac5a..2e28113c2d8 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync.ts @@ -8,6 +8,7 @@ import { AGENT_STATUS_STALE_AFTER_MS, type AgentStatusEntry } from '../../../shared/agent-status-types' +import { agentEntryCompletionAt } from '../../../shared/agent-completion-time' import { agentProviderSessionsEqual } from '../../../shared/agent-session-resume' import type { RuntimeMobileSessionTabsResult, @@ -1278,12 +1279,17 @@ function buildMirroredAgentStatusPatch( existing?.worktreeId !== entry.worktreeId || existing?.tabId !== entry.tabId const entryFreshnessChanged = !!existing && isAgentStatusFresh(existing, now) !== isAgentStatusFresh(entry, now) + const doneAttentionChanged = + existing?.state === 'done' && + entry.state === 'done' && + agentEntryCompletionAt(existing) !== agentEntryCompletionAt(entry) const entrySortRelevantChange = !existing || existing.state !== entry.state || !isAgentStatusFresh(existing, now) || entryFreshnessChanged || entryAttributionChanged || + doneAttentionChanged || isMirroredCommandCodeTurnBump(existing, entry) aggregateRelevantChange = aggregateRelevantChange || entrySortRelevantChange sortRelevantChange = sortRelevantChange || entrySortRelevantChange diff --git a/src/renderer/src/store/slices/agent-status-freshness-scheduler.test.ts b/src/renderer/src/store/slices/agent-status-freshness-scheduler.test.ts new file mode 100644 index 00000000000..93d49d79404 --- /dev/null +++ b/src/renderer/src/store/slices/agent-status-freshness-scheduler.test.ts @@ -0,0 +1,139 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + AGENT_STATUS_STALE_AFTER_MS, + type AgentStatusEntry +} from '../../../../shared/agent-status-types' +import { createFreshnessScheduler } from './agent-status-freshness-scheduler' + +const NOW = new Date('2026-04-09T12:00:00.000Z').getTime() + +function doneEntry(overrides: Partial = {}): AgentStatusEntry { + return { + paneKey: 'tab-1:11111111-1111-4111-8111-111111111111', + state: 'done', + prompt: '', + updatedAt: NOW, + stateStartedAt: NOW, + stateHistory: [], + agentType: 'claude', + ...overrides + } +} + +function setup(entries: AgentStatusEntry[]) { + const bumpEpochs = vi.fn() + const scheduler = createFreshnessScheduler({ getEntries: () => entries, bumpEpochs }) + return { bumpEpochs, scheduler } +} + +describe('freshness scheduler completion deadlines', () => { + afterEach(() => { + vi.useRealTimers() + }) + + it('wakes at the completion deadline even while the hook row stays fresh', () => { + vi.useFakeTimers() + vi.setSystemTime(NOW) + // Completion is 25m old; same-state `done` writes have kept updatedAt at now. + const completedAt = NOW - 25 * 60_000 + const { bumpEpochs, scheduler } = setup([ + doneEntry({ stateStartedAt: completedAt, updatedAt: NOW }) + ]) + + scheduler.schedule() + vi.advanceTimersByTime(completedAt + AGENT_STATUS_STALE_AFTER_MS - NOW) + expect(bumpEpochs).not.toHaveBeenCalled() + + vi.advanceTimersByTime(1) + expect(bumpEpochs).toHaveBeenCalledTimes(1) + + scheduler.dispose() + }) + + it('keeps the strict-expiry wake armed when rescheduled exactly at the boundary', () => { + vi.useFakeTimers() + vi.setSystemTime(NOW) + const { bumpEpochs, scheduler } = setup([ + doneEntry({ + stateStartedAt: NOW - AGENT_STATUS_STALE_AFTER_MS, + updatedAt: NOW + }) + ]) + + scheduler.schedule() + vi.advanceTimersByTime(1) + expect(bumpEpochs).toHaveBeenCalledTimes(1) + + scheduler.dispose() + }) + + it('does not lose a completion deadline when an overdue timer is rescheduled after sleep', () => { + vi.useFakeTimers() + vi.setSystemTime(NOW) + const completedAt = NOW - 25 * 60_000 + const entries = [doneEntry({ stateStartedAt: completedAt, updatedAt: NOW })] + const { bumpEpochs, scheduler } = setup(entries) + + scheduler.schedule() + vi.setSystemTime(completedAt + AGENT_STATUS_STALE_AFTER_MS + 1) + entries[0] = doneEntry({ stateStartedAt: completedAt, updatedAt: Date.now() }) + scheduler.schedule() + + expect(bumpEpochs).toHaveBeenCalledTimes(1) + scheduler.schedule() + expect(bumpEpochs).toHaveBeenCalledTimes(1) + scheduler.dispose() + }) + + it('does not rearm an expired completion deadline in a zero-delay loop', () => { + vi.useFakeTimers() + vi.setSystemTime(NOW) + const completedAt = NOW - 25 * 60_000 + const { bumpEpochs, scheduler } = setup([ + doneEntry({ stateStartedAt: completedAt, updatedAt: NOW }) + ]) + + scheduler.schedule() + // Past both the completion deadline and the hook-freshness deadline. + vi.advanceTimersByTime(AGENT_STATUS_STALE_AFTER_MS + 60_000) + + // One bump for the completion boundary, one for hook freshness — never a spin. + expect(bumpEpochs).toHaveBeenCalledTimes(2) + expect(vi.getTimerCount()).toBe(0) + + scheduler.dispose() + }) + + it('ignores completions that cannot demand attention', () => { + vi.useFakeTimers() + vi.setSystemTime(NOW) + const completedAt = NOW - 25 * 60_000 + const { bumpEpochs, scheduler } = setup([ + doneEntry({ stateStartedAt: completedAt, updatedAt: NOW, interrupted: true }) + ]) + + scheduler.schedule() + vi.advanceTimersByTime(completedAt + AGENT_STATUS_STALE_AFTER_MS - NOW + 1) + expect(bumpEpochs).not.toHaveBeenCalled() + + // Only the hook-freshness deadline remains. + vi.advanceTimersByTime(AGENT_STATUS_STALE_AFTER_MS) + expect(bumpEpochs).toHaveBeenCalledTimes(1) + + scheduler.dispose() + }) + + it('still arms hook freshness for a working entry', () => { + vi.useFakeTimers() + vi.setSystemTime(NOW) + const { bumpEpochs, scheduler } = setup([ + doneEntry({ state: 'working', stateStartedAt: NOW, updatedAt: NOW }) + ]) + + scheduler.schedule() + vi.advanceTimersByTime(AGENT_STATUS_STALE_AFTER_MS + 1) + expect(bumpEpochs).toHaveBeenCalledTimes(1) + + scheduler.dispose() + }) +}) diff --git a/src/renderer/src/store/slices/agent-status-freshness-scheduler.ts b/src/renderer/src/store/slices/agent-status-freshness-scheduler.ts index 628529dd007..93c3e54fee1 100644 --- a/src/renderer/src/store/slices/agent-status-freshness-scheduler.ts +++ b/src/renderer/src/store/slices/agent-status-freshness-scheduler.ts @@ -1,3 +1,4 @@ +import { agentEntryCompletionAt } from '../../../../shared/agent-completion-time' import type { AgentStatusEntry } from '../../../../shared/agent-status-types' import { AGENT_STATUS_STALE_AFTER_MS } from '../../../../shared/agent-status-types' @@ -21,6 +22,7 @@ export function createFreshnessScheduler(deps: FreshnessSchedulerDeps): Freshnes // `dispose()` in teardown — otherwise a real 30-minute setTimeout leaks // into the test process. let timer: ReturnType | null = null + let lastCheckedAt: number | null = null const clear = (): void => { if (timer !== null) { @@ -33,10 +35,12 @@ export function createFreshnessScheduler(deps: FreshnessSchedulerDeps): Freshnes clear() const entries = deps.getEntries() if (entries.length === 0) { + lastCheckedAt = null return } const now = Date.now() let nextExpiryAt = Number.POSITIVE_INFINITY + let crossedCompletionDeadline = false // Why: skip entries already past the stale boundary — they each contribute // exactly one epoch bump at crossing, and rescheduling on them would spin // the timer forever because the bump doesn't clear them from the map @@ -47,9 +51,29 @@ export function createFreshnessScheduler(deps: FreshnessSchedulerDeps): Freshnes // freshness-aware selectors can decay them immediately on that render. for (const entry of entries) { const expiryAt = entry.updatedAt + AGENT_STATUS_STALE_AFTER_MS - if (expiryAt > now) { + if (expiryAt >= now) { nextExpiryAt = Math.min(nextExpiryAt, expiryAt) } + // Completion and hook freshness have independent expiry times. + const completedAt = agentEntryCompletionAt(entry) + if (completedAt !== null) { + const completionExpiryAt = completedAt + AGENT_STATUS_STALE_AFTER_MS + // Detect a missed completion expiry before a same-state update extends hook freshness. + if ( + lastCheckedAt !== null && + completionExpiryAt >= lastCheckedAt && + completionExpiryAt < now + ) { + crossedCompletionDeadline = true + } + if (completionExpiryAt >= now) { + nextExpiryAt = Math.min(nextExpiryAt, completionExpiryAt) + } + } + } + lastCheckedAt = now + if (crossedCompletionDeadline) { + deps.bumpEpochs() } if (!Number.isFinite(nextExpiryAt)) { return @@ -62,6 +86,7 @@ export function createFreshnessScheduler(deps: FreshnessSchedulerDeps): Freshnes timer = setTimeout(() => { timer = null deps.bumpEpochs() + lastCheckedAt = Date.now() schedule() }, delayMs) } diff --git a/src/renderer/src/store/slices/agent-status.test.ts b/src/renderer/src/store/slices/agent-status.test.ts index 6965ca8bbb2..d937c3af53b 100644 --- a/src/renderer/src/store/slices/agent-status.test.ts +++ b/src/renderer/src/store/slices/agent-status.test.ts @@ -805,6 +805,29 @@ describe('agent status tool + assistant fields', () => { expect(store.getState().sortEpoch).toBe(firstSortEpoch) }) + it('bumps sort epoch when a same-state done update changes completion eligibility', () => { + vi.useFakeTimers() + const store = createTestStore() + store + .getState() + .setAgentStatus( + 'tab-1:1', + { state: 'done', prompt: 'p1', agentType: 'claude', interrupted: true }, + 'claude', + { updatedAt: 1_000, stateStartedAt: 1_000 } + ) + const firstSortEpoch = store.getState().sortEpoch + + store + .getState() + .setAgentStatus('tab-1:1', { state: 'done', prompt: 'p1', agentType: 'claude' }, 'claude', { + updatedAt: 2_000, + stateStartedAt: 1_000 + }) + + expect(store.getState().sortEpoch).toBe(firstSortEpoch + 1) + }) + it('bumps global epochs when a stale same-state entry refreshes', () => { vi.useFakeTimers() const store = createTestStore() diff --git a/src/renderer/src/store/slices/agent-status.ts b/src/renderer/src/store/slices/agent-status.ts index 271ab70829a..6dd2e60df1e 100644 --- a/src/renderer/src/store/slices/agent-status.ts +++ b/src/renderer/src/store/slices/agent-status.ts @@ -26,6 +26,7 @@ import { shouldSuppressInheritedTerminalStatus } from '../../../../shared/agent-status-identity' import { isCommandCodeNewTurnWhileWorking } from '../../../../shared/command-code-turn-boundary' +import { agentEntryCompletionAt } from '../../../../shared/agent-completion-time' import type { TerminalPaneLayoutNode, TerminalTab } from '../../../../shared/types' import { getRepoExecutionHostId, @@ -1982,13 +1983,18 @@ export const createAgentStatusSlice: StateCreator + +function mostRecentCompletedTurnInHistory( + history: readonly AgentStateHistoryEntry[] | undefined +): number | null { + let max = 0 + for (const row of history ?? []) { + if ( + row.state === 'done' && + row.interrupted !== true && + Number.isFinite(row.startedAt) && + row.startedAt > max + ) { + max = row.startedAt + } + } + return max > 0 ? max : null +} + +/** + * When the entry's agent last actually COMPLETED a turn, or null when nothing qualifies. + * One clock for both the displayed completion age and Smart Sort's Done eligibility, so a row + * can't rank as freshly done while showing an age past the staleness threshold. + * + * A completion is only: + * - a non-interrupted `done` (its `stateStartedAt` — unmoved by same-state tool/prompt pings); or + * - for a session-boundary `done` (connected idle, not a turn), the real completion it displaced. + */ +export function agentEntryCompletionAt(entry: AgentCompletionSource): number | null { + if (entry.state !== 'done' || entry.interrupted === true) { + return null + } + if (entry.sessionBoundary === true) { + return mostRecentCompletedTurnInHistory(entry.stateHistory) + } + return Number.isFinite(entry.stateStartedAt) ? entry.stateStartedAt : null +} From 991a3fe9637df160a2b420b4d3c6466cf0af09dc Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:19:43 -0700 Subject: [PATCH 002/100] chore(lint): update oxlint to 1.77 and enable no-op cleanup rules (#13901) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable eleven oxlint rules that simplify code without changing behavior, and fix every existing violation. Each candidate was gated on measured cost rather than assumption, so rules that regressed runtime performance or type checking were dropped instead of suppressed. typescript/no-redundant-type-constituents is the largest addition: 113 sites, no autofix. Dead constituents are deleted. Where the redundant literal existed to document intent (`string | 'all'`), it is preserved as `(string & {})`, which keeps the autocomplete hint the original code was reaching for instead of flattening it away. The rule also caught a broken import — remote-shared-control-retirement-probe.ts pulled RuntimeStatus from src/shared/types, which does not export it, so the type silently degraded to `any`; no tsconfig covers that file, so tsc never saw it. oxlint stays at 1.77.0 rather than 1.78.0 because .npmrc sets minimum-release-age=4320 and 1.78.0 is younger than that window. Rules evaluated and rejected, with what disqualified each: - prefer-string-raw: String.raw is a runtime call, not a literal (184x slower) - prefer-string-replace-all: 26% slower - text-encoding-identifier-case: ~5% slower, reproducible - prefer-spread: [...str] is 110% slower than split('') and differs on surrogates - no-implicit-coercion: `!!x` narrows types and `Boolean(x)` does not (22 tsc errors) - prefer-arrow-callback: arrows are not constructible, breaking `new` on mocks - object-shorthand: rewrites source text asserted by a tracked reliability gate - switch-case-braces: pushes ten files past max-lines, which cannot be suppressed - no-useless-switch-case: drops `case undefined:` that switch-exhaustiveness-check needs - arrow-body-style: 115 violations have no fix, and it breaks max-lines - newline-after-import: false-positives on the leading-semicolon ASI idiom electron-vite-output-contract asserted on the literal Object.prototype.hasOwnProperty.call text; retarget it to Object.hasOwn, which rejects inherited keys identically. --- .oxlintrc.json | 10 ++ config/electron-vite-target.config.ts | 2 +- .../oxlint-code-quality-native-plugins.json | 1 + config/oxlint-code-quality-type-aware.json | 1 + .../build-notification-status-macos.mjs | 2 +- config/scripts/build-windows-cli-launcher.mjs | 2 +- .../computer-use-modifier-safety.test.mjs | 2 +- .../electron-vite-output-contract.test.ts | 3 +- config/scripts/ensure-native-runtime.mjs | 2 +- .../generate-skill-bundle-manifest.mjs | 6 +- ...-worker-recovery-persistence-benchmark.mjs | 4 +- ...s-computer-helper-owner-loss-benchmark.mjs | 2 +- .../mjpeg-frame-parser-alloc-benchmark.mjs | 8 +- .../scripts/relay-watcher-fault-harness.mjs | 4 +- .../remote-shared-control-retirement-probe.ts | 3 +- .../run-ssh-staged-upload-reliability.mjs | 4 +- config/scripts/terminal-e2e-helpers.mjs | 4 +- .../verify-agent-hook-stdin-lifecycle.mjs | 2 +- config/scripts/verify-computer-native.mjs | 8 +- config/scripts/wsl-git-shell-benchmark.mjs | 2 +- .../mobile-markdown-preview-html.ts | 2 +- .../mobile-markdown-preview-tag-stripper.ts | 6 +- mobile/src/files/file-tree.ts | 2 +- .../src/hooks/use-active-worktree-scroll.ts | 2 +- .../mobile-diff-review-screen-model.ts | 2 +- .../mobile-native-chat-draft-reconcile.ts | 2 +- mobile/src/transport/host-store.ts | 8 +- .../mobile-relay-host-overlay-store.ts | 2 +- mobile/src/transport/rpc-response-shape.ts | 2 +- package.json | 2 +- pnpm-lock.yaml | 162 +++++++++--------- .../managed-hook-owner-identity.ts | 2 +- .../session-scanner-service-client.ts | 4 +- .../ai-vault/session-scanner-worker-client.ts | 2 +- src/main/automations/hermes-cron-output.ts | 4 +- src/main/browser/agent-browser-bridge.test.ts | 4 +- src/main/browser/browser-cookie-import.ts | 2 +- ...owser-session-registry.persistence.test.ts | 2 +- src/main/browser/browser-webauthn-access.ts | 2 +- .../browser/cdp-bridge-integration.test.ts | 2 +- src/main/cli/wsl-cli-installer.ts | 2 +- src/main/cli/wsl-cli-registration-registry.ts | 2 +- src/main/codex-accounts/service.ts | 2 +- src/main/codex/codex-real-home-path.ts | 2 +- .../codex/codex-session-resume-home.test.ts | 4 +- .../macos-native-provider-transport.ts | 2 +- .../daemon/daemon-incarnation-evidence.ts | 2 +- src/main/daemon/daemon-server.ts | 2 +- .../daemon/macos-login-session-death-watch.ts | 4 +- src/main/daemon/terminal-session-teardown.ts | 3 +- .../terminal-view-attribute-responder.ts | 2 +- src/main/daemon/xterm-env-polyfill.ts | 2 +- src/main/emulator/android/adb-devices.ts | 2 +- src/main/emulator/mjpeg-frame-parser.ts | 4 +- src/main/git/repo.ts | 2 +- src/main/git/wsl-git-read-environment.ts | 2 +- src/main/github/stacked-pr-creation.ts | 2 +- src/main/ipc/ephemeral-vm.test.ts | 2 +- src/main/ipc/filesystem-import.test.ts | 4 +- .../ipc/filesystem-search-rg-timeout.test.ts | 2 +- .../filesystem-watcher-dormant-rearm.test.ts | 2 +- .../filesystem-watcher-large-batch.test.ts | 2 +- ...lesystem-watcher-local-unsubscribe.test.ts | 2 +- ...filesystem-watcher-native-capacity.test.ts | 2 +- src/main/ipc/filesystem-watcher-real.test.ts | 2 +- .../filesystem-watcher-remote-batch.test.ts | 2 +- ...system-watcher-remote-cancellation.test.ts | 2 +- .../filesystem-watcher-remote-rearm.test.ts | 2 +- ...ilesystem-watcher-removal-deadline.test.ts | 2 +- ...filesystem-watcher-terminal-resync.test.ts | 2 +- ...lesystem-watcher-unwatchable-roots.test.ts | 2 +- src/main/ipc/filesystem-watcher.test.ts | 2 +- src/main/ipc/filesystem.test.ts | 2 +- src/main/ipc/notebook.test.ts | 2 +- .../ipc/runtime-watcher-quarantine-queue.ts | 2 +- .../ipc/ssh-pty-legacy-projection-record.ts | 2 +- src/main/ipc/ssh.ts | 4 +- src/main/ipc/workspace-ports.test.ts | 2 +- src/main/ipc/worktree-common-git-directory.ts | 2 +- src/main/ipc/worktree-git-common-polling.ts | 2 +- src/main/jira/attachment-images.ts | 2 +- src/main/jira/client.ts | 2 +- src/main/keybindings/keybinding-file.ts | 6 +- src/main/linear/issue-context-client.ts | 18 +- src/main/linear/mcp-issue-list.ts | 2 +- .../windows-process-resource-collector.ts | 2 +- src/main/minimax/minimax-cookie-store.ts | 2 +- .../native-chat/transcript-tail-reader.ts | 2 +- .../network/macos-tailscale-dns-diagnostic.ts | 2 +- src/main/observability/bundle.test.ts | 2 +- src/main/observability/redactor.test.ts | 2 +- src/main/observability/redactor.ts | 2 +- src/main/persistence.ts | 13 +- src/main/pi/legacy-omp-overlay-migration.ts | 2 +- src/main/plugins/plugin-dev-watcher.ts | 2 +- src/main/plugins/plugin-host-runtime.ts | 8 +- .../plugins/plugin-worker-output-buffer.ts | 10 +- src/main/plugins/plugin-worker-slot-pool.ts | 2 +- .../windows-foreground-process-rows.ts | 2 +- src/main/pty/posix-pty-foreground-group.ts | 2 +- .../rate-limits/minimax-request-context.ts | 2 +- .../rate-limits/opencode-go-usage-fetcher.ts | 2 +- src/main/runtime/device-registry.ts | 8 +- src/main/runtime/fetch-remote-cache.test.ts | 2 +- src/main/runtime/linear-save-issue.test.ts | 2 +- src/main/runtime/orca-runtime.ts | 23 ++- src/main/runtime/orchestration/db.ts | 6 +- .../orchestration/setup-completion-signal.ts | 2 +- src/main/runtime/rpc/core.ts | 4 +- src/main/runtime/rpc/methods/client-ui.ts | 2 +- .../rpc/methods/linear-agent-access.test.ts | 8 +- .../linear-agent-project-access.test.ts | 2 +- src/main/runtime/rpc/methods/native-chat.ts | 4 +- .../rpc/methods/worktree-schemas.test.ts | 4 +- .../rpc/orchestration-legacy-compatibility.ts | 2 +- .../rpc/orchestration-legacy-lifecycle.ts | 2 +- .../runtime/rpc/orchestration-legacy-mail.ts | 4 +- .../rpc/orchestration-legacy-question.ts | 2 +- .../rpc/orchestration-mutation-executor.ts | 2 +- .../rpc/orchestration-mutation-ledger.test.ts | 2 +- src/main/runtime/runtime-rpc.test.ts | 2 +- src/main/runtime/runtime-rpc.ts | 6 +- .../runtime/windows-firewall-remote-scope.ts | 6 +- ...workspace-session-failed-write-rollback.ts | 2 +- src/main/source-control/forge-provider.ts | 2 +- .../stacked-hosted-review-creation.ts | 3 +- .../ssh/sftp-namespace-resolution.test.ts | 5 +- src/main/ssh/ssh-channel-multiplexer.ts | 2 +- src/main/ssh/ssh-relay-session.ts | 4 +- .../ssh/ssh-remote-node-toolchain-probe.ts | 2 +- src/main/startup/hydrate-shell-path.ts | 4 +- src/main/warp-themes/discovery.ts | 2 +- src/main/window/createMainWindow.ts | 2 +- src/preload/index.ts | 5 +- src/relay/agent-hook-server.ts | 4 +- src/relay/ai-vault-service-client.ts | 4 +- src/relay/dispatcher-client-writer.test.ts | 2 +- src/relay/external-automations-handler.ts | 4 +- src/relay/git-handler-utils.ts | 2 +- src/relay/pty-handler.ts | 8 +- src/relay/relay-watcher-event-emitter.test.ts | 2 +- src/relay/relay.ts | 2 +- src/renderer/src/App.tsx | 21 ++- .../src/components/GitHubItemDialog.tsx | 2 +- .../src/components/PullRequestPage.tsx | 2 +- src/renderer/src/components/TaskPage.tsx | 2 +- .../src/components/WorktreeJumpPalette.tsx | 4 +- .../automations/AutomationsPage.tsx | 4 +- .../components/browser-pane/BrowserPane.tsx | 2 +- .../browser-address-bar-suggestions.ts | 2 +- .../context-menu-positioning.test.ts | 10 +- .../contextual-tours/contextual-tour-gate.ts | 2 +- .../dashboard-agent-status-patch.ts | 2 +- .../editor/CheckRunDetailsPanel.tsx | 4 +- .../components/editor/CombinedDiffViewer.tsx | 5 +- .../src/components/editor/IpynbViewer.tsx | 2 +- .../src/components/editor/editor-autosave.ts | 2 +- .../editor/editor-panel-draft-selector.ts | 2 +- .../editor/monaco-context-menu-paste.ts | 2 +- .../editor/monaco-markdown-doc-completions.ts | 2 +- .../rich-markdown-slash-command-catalog.tsx | 1 - .../rich-markdown-terminal-path-paste.ts | 2 +- .../src/components/editor/useLocalImageSrc.ts | 2 +- .../floating-terminal-panel-bounds.ts | 2 +- .../floating-terminal-trigger-position.ts | 2 +- .../claude-model-switch-confirmation.ts | 4 +- .../claude-terminal-session-options.ts | 6 +- .../native-chat-pending-occurrence.ts | 2 +- .../native-chat/native-chat-pending.ts | 6 +- .../project-combobox-matching.ts | 6 +- .../use-jira-url-source.test.tsx | 2 +- .../new-workspace/use-jira-url-source.ts | 2 +- .../checks-panel-review-creation.ts | 2 +- ...source-control-text-generation-defaults.ts | 2 +- .../src/components/settings/AccountsPane.tsx | 4 +- .../components/settings/SettingsConstants.ts | 2 +- .../settings/keybinding-override-edits.ts | 2 +- .../repository-source-control-ai-draft.ts | 2 +- .../sidebar/worktree-lineage-projection.ts | 2 +- .../sidebar/worktree-list-groups.ts | 4 +- .../worktree-title-derived-agent-rows.ts | 2 +- .../SourceControlActionVariableChips.tsx | 2 +- .../status-bar/mergeSnapshotAndSessions.ts | 4 +- .../tab-create-entry-url-classification.ts | 6 +- .../tab-bar/terminal-tab-activity-status.ts | 6 +- ...page-github-work-item-authority-refresh.ts | 2 +- ...e-github-work-item-mutation-composition.ts | 2 +- ...page-github-work-item-mutation-registry.ts | 2 +- ...-page-github-work-item-quiet-revalidate.ts | 6 +- .../task-page-linear-in-orca-issues.ts | 2 +- .../agent-process-inspection-queue.ts | 2 +- .../terminal-agent-session-fork.ts | 4 +- .../terminal-ime-linux-candidate-state.ts | 2 +- .../terminal-layout-leaf-detach.ts | 2 +- .../terminal-pane/terminal-layout-leaf-ids.ts | 4 +- .../terminal-layout-pty-ownership.ts | 6 +- .../terminal-paste-diagnostics.ts | 2 +- .../use-terminal-pane-global-effects.ts | 22 +-- .../workspace-cleanup-background-removal.ts | 2 +- ...pace-cleanup-post-batch-late-settlement.ts | 2 +- ...eanup-skipped-ancestor-reclassification.ts | 2 +- ...ssh-reconnect-coordinator-stabilization.ts | 2 +- .../src/lib/agent-hibernation-planner.ts | 5 +- src/renderer/src/lib/agent-status.ts | 4 +- src/renderer/src/lib/crash-diagnostics.ts | 2 +- .../src/lib/feature-education-telemetry.ts | 6 +- .../src/lib/markdown-document-templates.ts | 2 +- src/renderer/src/lib/native-chat-telemetry.ts | 2 +- src/renderer/src/lib/pane-manager/pane-fit.ts | 1 + .../pane-manager/pane-identity-registry.ts | 6 +- .../src/lib/pane-manager/pane-manager.ts | 2 +- .../pane-tree-reparent-frame.test.ts | 2 +- .../src/lib/runtime-pane-title-leaf-id.ts | 2 +- src/renderer/src/lib/setup-script-prompt.ts | 2 +- .../lib/simulator-pane-shutdown-scheduler.ts | 2 +- .../source-control-launch-agent-selection.ts | 8 +- .../lib/terminal-contrast-correction.test.ts | 2 +- .../src/lib/terminal-layout-equality.ts | 4 +- .../src/lib/terminal-quick-command-search.ts | 4 +- .../src/lib/terminal-themes/shared.ts | 2 +- .../src/lib/terminal-title-contrast.ts | 2 +- src/renderer/src/lib/ui-zoom.ts | 4 +- .../src/lib/window-visibility-interval.ts | 2 +- .../src/lib/workspace-emoji-shortcodes.ts | 2 +- .../src/runtime/web-session-tabs-sync.test.ts | 2 +- .../src/runtime/web-session-tabs-sync.ts | 3 +- .../store/running-terminal-close-confirm.ts | 2 +- src/renderer/src/store/slices/browser.ts | 2 +- src/renderer/src/store/slices/diffComments.ts | 2 +- src/renderer/src/store/slices/editor.ts | 2 +- .../src/store/slices/repo-host-identity.ts | 2 +- .../store/slices/repo-identity-reconcile.ts | 6 +- src/renderer/src/store/slices/repos.ts | 4 +- .../src/store/slices/ssh-target-cleanup.ts | 20 +-- .../src/store/slices/worktree-helpers.ts | 4 +- .../src/store/slices/worktree-nav-history.ts | 2 +- src/renderer/src/store/slices/worktrees.ts | 13 +- .../web/web-clipboard-copy-fallback.test.ts | 2 +- src/renderer/src/web/web-preload-api.ts | 5 +- src/shared/agent-hook-listener.ts | 2 +- src/shared/automation-schedules.ts | 2 +- src/shared/browser-page-zoom.ts | 2 +- src/shared/external-worktree-visibility.ts | 2 +- src/shared/gitlab-types.ts | 2 +- src/shared/jira-types.ts | 2 +- src/shared/linear-agent-access.ts | 4 +- src/shared/linear-agent-result-types.ts | 8 +- src/shared/linear-issue-view-resume-state.ts | 4 +- src/shared/linear-links.ts | 2 +- src/shared/linear-mcp-issue-list.ts | 4 +- src/shared/mcp-config.ts | 2 +- src/shared/mcp-server-inspection.ts | 2 +- .../native-chat-image-transcript-markers.ts | 2 +- .../native-chat-session-option-commands.ts | 2 +- src/shared/native-chat-tool-summary.ts | 2 +- src/shared/persisted-ui-equality.ts | 2 +- .../remote-runtime-shared-control-ready.ts | 4 +- .../setup-script-import-codex-environment.ts | 2 +- src/shared/setup-script-imports.ts | 4 +- src/shared/source-control-ai-actions.ts | 2 +- src/shared/source-control-ai.ts | 10 +- src/shared/terminal-file-link-conformance.ts | 2 +- src/shared/text-search.ts | 2 +- src/shared/tui-agent-config.ts | 2 +- src/shared/tui-agent-launch-defaults.ts | 4 +- src/shared/types.ts | 6 +- ...pace-session-schema.sleeping-agent.test.ts | 9 +- .../e2e/chinese-ime-chat-input-repro.spec.ts | 2 +- .../e2e/codex-composer-echo-latency-probe.ts | 2 +- .../daemon-generation-legacy-close-client.ts | 2 +- .../daemon-generation-reconnect-client.ts | 2 +- .../fixtures/visible-tui-scroll-fixture.cjs | 2 +- .../completed-worker-retirement-fixture.ts | 2 +- ...an-ime-terminal-shift-enter-commit.spec.ts | 2 +- ...mote-agent-session-focus-authority.spec.ts | 2 +- ...l-link-hover-after-worktree-return.spec.ts | 2 +- tests/e2e/terminal-paste-ownership.spec.ts | 4 +- .../tools/win-crash-survival-e2e/cli-args.mjs | 2 +- tests/tools/win-update-e2e/cli-args.mjs | 2 +- .../tools/win-update-e2e/daemon-processes.mjs | 4 +- 280 files changed, 530 insertions(+), 571 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 0f679f17d52..97a2b68701a 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -56,6 +56,7 @@ "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "typescript/array-type": "error", + "typescript/consistent-generic-constructors": "error", "typescript/consistent-indexed-object-style": "error", "typescript/consistent-type-assertions": "error", "typescript/consistent-type-definitions": ["error", "type"], @@ -68,12 +69,16 @@ "typescript/prefer-function-type": "error", "typescript/prefer-includes": "error", "typescript/prefer-optional-chain": "error", + "typescript/prefer-ts-expect-error": "error", "typescript/switch-exhaustiveness-check": [ "error", { "allowDefaultCaseForExhaustiveSwitch": false } ], "curly": "error", "no-unneeded-ternary": "error", + "no-else-return": "error", + "prefer-exponentiation-operator": "error", + "prefer-object-has-own": "error", "no-restricted-imports": [ "error", { @@ -90,11 +95,16 @@ "no-useless-return": "error", "prefer-template": "error", "unicorn/consistent-empty-array-spread": "error", + "unicorn/consistent-existence-index-check": "error", + "unicorn/empty-brace-spaces": "error", "unicorn/error-message": "error", "unicorn/no-array-fill-with-reference-type": "warn", "unicorn/no-array-reverse": "error", "unicorn/no-instanceof-builtins": "error", + "unicorn/no-typeof-undefined": "error", + "unicorn/no-unnecessary-array-splice-count": "error", "unicorn/no-useless-promise-resolve-reject": "error", + "unicorn/no-zero-fractions": "error", "unicorn/prefer-array-find": "error", "unicorn/prefer-array-flat-map": "warn", "unicorn/prefer-array-index-of": "error", diff --git a/config/electron-vite-target.config.ts b/config/electron-vite-target.config.ts index 60651dd6590..490fa654f5b 100644 --- a/config/electron-vite-target.config.ts +++ b/config/electron-vite-target.config.ts @@ -8,7 +8,7 @@ const configByTarget = { renderer: { renderer: electronViteConfig.renderer } } -if (!target || !Object.prototype.hasOwnProperty.call(configByTarget, target)) { +if (!target || !Object.hasOwn(configByTarget, target)) { throw new Error(`Invalid ORCA_ELECTRON_VITE_TARGET: ${target ?? ''}`) } diff --git a/config/oxlint-code-quality-native-plugins.json b/config/oxlint-code-quality-native-plugins.json index 5d9cd118e74..5f2fb031a51 100644 --- a/config/oxlint-code-quality-native-plugins.json +++ b/config/oxlint-code-quality-native-plugins.json @@ -16,6 +16,7 @@ "import/namespace": "warn", "import/no-cycle": ["warn", { "maxDepth": 3 }], "import/no-duplicates": "warn", + "import/no-empty-named-blocks": "warn", "import/no-self-import": "warn" }, "overrides": [ diff --git a/config/oxlint-code-quality-type-aware.json b/config/oxlint-code-quality-type-aware.json index 4c0e01fa748..8ae3cbab60e 100644 --- a/config/oxlint-code-quality-type-aware.json +++ b/config/oxlint-code-quality-type-aware.json @@ -12,6 +12,7 @@ }, "rules": { "typescript/await-thenable": "warn", + "typescript/no-redundant-type-constituents": "warn", "typescript/restrict-plus-operands": "warn", "typescript/restrict-template-expressions": "warn", "typescript/switch-exhaustiveness-check": [ diff --git a/config/scripts/build-notification-status-macos.mjs b/config/scripts/build-notification-status-macos.mjs index e8150e32b6f..41d4c84a4af 100644 --- a/config/scripts/build-notification-status-macos.mjs +++ b/config/scripts/build-notification-status-macos.mjs @@ -79,7 +79,7 @@ try { function readArg(name) { const index = args.indexOf(name) - return index >= 0 ? args[index + 1] : undefined + return index !== -1 ? args[index + 1] : undefined } function embeddedInfoPlist(identifier) { diff --git a/config/scripts/build-windows-cli-launcher.mjs b/config/scripts/build-windows-cli-launcher.mjs index 6f9b86bdb4b..27c17b3d15c 100644 --- a/config/scripts/build-windows-cli-launcher.mjs +++ b/config/scripts/build-windows-cli-launcher.mjs @@ -56,5 +56,5 @@ function findFrameworkCompiler(env) { function readArg(name) { const index = process.argv.indexOf(name) - return index >= 0 ? process.argv[index + 1] : undefined + return index !== -1 ? process.argv[index + 1] : undefined } diff --git a/config/scripts/computer-use-modifier-safety.test.mjs b/config/scripts/computer-use-modifier-safety.test.mjs index 725f17fdf4c..2a3234d3d9c 100644 --- a/config/scripts/computer-use-modifier-safety.test.mjs +++ b/config/scripts/computer-use-modifier-safety.test.mjs @@ -11,7 +11,7 @@ function source(path) { function sourceBetween(contents, startMarker, endMarker) { const start = contents.indexOf(startMarker) const end = contents.indexOf(endMarker, start + startMarker.length) - if (start < 0 || end < 0) { + if (start === -1 || end === -1) { throw new Error(`Missing source boundary: ${startMarker} → ${endMarker}`) } return contents.slice(start, end) diff --git a/config/scripts/electron-vite-output-contract.test.ts b/config/scripts/electron-vite-output-contract.test.ts index f59f4a80f88..818b380fc2f 100644 --- a/config/scripts/electron-vite-output-contract.test.ts +++ b/config/scripts/electron-vite-output-contract.test.ts @@ -196,7 +196,8 @@ describe('Electron Vite output contract', () => { }) it('rejects prototype properties as build targets', () => { - expect(targetConfig).toContain('Object.prototype.hasOwnProperty.call(configByTarget, target)') + // Own-property check only: an inherited key like `constructor` must not select a build target. + expect(targetConfig).toContain('Object.hasOwn(configByTarget, target)') }) it('gives the dev terminal daemon helper the TCC identity watched by Orca', () => { diff --git a/config/scripts/ensure-native-runtime.mjs b/config/scripts/ensure-native-runtime.mjs index 98b064cd30d..44261e968a9 100644 --- a/config/scripts/ensure-native-runtime.mjs +++ b/config/scripts/ensure-native-runtime.mjs @@ -45,7 +45,7 @@ function readRuntimeArg() { } const runtimeIndex = process.argv.indexOf('--runtime') - if (runtimeIndex >= 0) { + if (runtimeIndex !== -1) { return process.argv[runtimeIndex + 1] } diff --git a/config/scripts/generate-skill-bundle-manifest.mjs b/config/scripts/generate-skill-bundle-manifest.mjs index 899cb6436df..226ea74c4fb 100644 --- a/config/scripts/generate-skill-bundle-manifest.mjs +++ b/config/scripts/generate-skill-bundle-manifest.mjs @@ -227,7 +227,7 @@ function readGitBlobs(objectShas) { let offset = 0 for (const requestedSha of uniqueShas) { const headerEnd = output.indexOf(10, offset) - if (headerEnd < 0) { + if (headerEnd === -1) { throw new Error(`Missing git cat-file header for ${requestedSha}`) } const header = output.subarray(offset, headerEnd).toString('utf8') @@ -648,8 +648,8 @@ async function main() { const argv = process.argv.slice(2) const rebuildFromTags = argv.includes('--rebuild-from-tags') const releaseIndex = argv.indexOf('--release') - const releaseVersion = releaseIndex >= 0 ? argv[releaseIndex + 1] : null - if (releaseIndex >= 0 && !releaseVersion) { + const releaseVersion = releaseIndex !== -1 ? argv[releaseIndex + 1] : null + if (releaseIndex !== -1 && !releaseVersion) { throw new Error('--release requires a version argument, e.g. --release 1.4.160') } diff --git a/config/scripts/legacy-worker-recovery-persistence-benchmark.mjs b/config/scripts/legacy-worker-recovery-persistence-benchmark.mjs index 5f4160600cc..76555d4e78e 100644 --- a/config/scripts/legacy-worker-recovery-persistence-benchmark.mjs +++ b/config/scripts/legacy-worker-recovery-persistence-benchmark.mjs @@ -30,8 +30,8 @@ const recoveryEnd = runtimeSource.indexOf( ) const recoverySource = runtimeSource.slice(recoveryStart, recoveryEnd) if ( - recoveryStart < 0 || - recoveryEnd < 0 || + recoveryStart === -1 || + recoveryEnd === -1 || !recoverySource.includes('await this.flushWorkspaceSessionOrThrowAsync()') || recoverySource.includes('flushOrThrow()') ) { diff --git a/config/scripts/macos-computer-helper-owner-loss-benchmark.mjs b/config/scripts/macos-computer-helper-owner-loss-benchmark.mjs index 8724cf8faf4..b6f1d9571a5 100644 --- a/config/scripts/macos-computer-helper-owner-loss-benchmark.mjs +++ b/config/scripts/macos-computer-helper-owner-loss-benchmark.mjs @@ -210,7 +210,7 @@ function connectInvalidPeer(socketPath) { const onData = (chunk) => { buffer += chunk const newline = buffer.indexOf('\n') - if (newline < 0) { + if (newline === -1) { return } let response diff --git a/config/scripts/mjpeg-frame-parser-alloc-benchmark.mjs b/config/scripts/mjpeg-frame-parser-alloc-benchmark.mjs index 86791745c10..5e350bb24f8 100644 --- a/config/scripts/mjpeg-frame-parser-alloc-benchmark.mjs +++ b/config/scripts/mjpeg-frame-parser-alloc-benchmark.mjs @@ -54,7 +54,7 @@ function extractOld(pending, chunk, maxPendingBytes = 2 * 1024 * 1024) { const frames = [] while (cursor.length > 0) { const frameStart = cursor.indexOf(JPEG_START) - if (frameStart < 0) { + if (frameStart === -1) { const keepLastByte = cursor.at(-1) === 0xff return { frames, pending: keepLastByte ? Buffer.from([0xff]) : Buffer.alloc(0) } } @@ -62,7 +62,7 @@ function extractOld(pending, chunk, maxPendingBytes = 2 * 1024 * 1024) { cursor = cursor.subarray(frameStart) } const frameEnd = cursor.indexOf(JPEG_END, JPEG_START.length) - if (frameEnd < 0) { + if (frameEnd === -1) { const tail = cursor.length <= maxPendingBytes ? cursor : cursor.subarray(-maxPendingBytes) return { frames, pending: trackedCopy(tail) } } @@ -79,7 +79,7 @@ function extractNew(pending, chunk, maxPendingBytes = 2 * 1024 * 1024) { const frames = [] while (cursor.length > 0) { const frameStart = cursor.indexOf(JPEG_START) - if (frameStart < 0) { + if (frameStart === -1) { const keepLastByte = cursor.at(-1) === 0xff return { frames, pending: keepLastByte ? Buffer.from([0xff]) : Buffer.alloc(0) } } @@ -87,7 +87,7 @@ function extractNew(pending, chunk, maxPendingBytes = 2 * 1024 * 1024) { cursor = cursor.subarray(frameStart) } const frameEnd = cursor.indexOf(JPEG_END, JPEG_START.length) - if (frameEnd < 0) { + if (frameEnd === -1) { const tail = cursor.length <= maxPendingBytes ? cursor : cursor.subarray(-maxPendingBytes) return { frames, pending: trackedCopy(tail) } } diff --git a/config/scripts/relay-watcher-fault-harness.mjs b/config/scripts/relay-watcher-fault-harness.mjs index b27a1f945d9..784abfc4850 100644 --- a/config/scripts/relay-watcher-fault-harness.mjs +++ b/config/scripts/relay-watcher-fault-harness.mjs @@ -94,7 +94,7 @@ function waitForStdoutSentinel(proc, protocol, stderr) { stdoutBuffer = Buffer.concat([stdoutBuffer, chunk]) const sentinel = Buffer.from(protocol.RELAY_SENTINEL) const index = stdoutBuffer.indexOf(sentinel) - if (index < 0) { + if (index === -1) { return } settled = true @@ -148,7 +148,7 @@ function createRelayClient(entryPath, args, env, protocol) { stdoutBuffer = Buffer.concat([stdoutBuffer, chunk]) const sentinel = Buffer.from(protocol.RELAY_SENTINEL) const index = stdoutBuffer.indexOf(sentinel) - if (index < 0) { + if (index === -1) { return } ready = true diff --git a/config/scripts/remote-shared-control-retirement-probe.ts b/config/scripts/remote-shared-control-retirement-probe.ts index 63abd01e376..30edeb9802a 100644 --- a/config/scripts/remote-shared-control-retirement-probe.ts +++ b/config/scripts/remote-shared-control-retirement-probe.ts @@ -5,7 +5,8 @@ import { resolveEnvironment, resolveEnvironmentPairingOffer } from '../../src/shared/runtime-environment-store' -import type { MemorySnapshot, RuntimeStatus } from '../../src/shared/types' +import type { MemorySnapshot } from '../../src/shared/types' +import type { RuntimeStatus } from '../../src/shared/runtime-types' async function main(): Promise { const environmentName = process.env.ORCA_PROBE_ENVIRONMENT_NAME diff --git a/config/scripts/run-ssh-staged-upload-reliability.mjs b/config/scripts/run-ssh-staged-upload-reliability.mjs index 4fadf6f3001..fb3d0040350 100644 --- a/config/scripts/run-ssh-staged-upload-reliability.mjs +++ b/config/scripts/run-ssh-staged-upload-reliability.mjs @@ -17,8 +17,8 @@ const defaultFiles = [ const cliArguments = process.argv.slice(2) const powerShellFlag = cliArguments.indexOf('--powershell') const configuredPowerShell = - powerShellFlag >= 0 ? cliArguments[powerShellFlag + 1] : process.env.ORCA_POWERSHELL_EXECUTABLE -if (powerShellFlag >= 0 && !configuredPowerShell) { + powerShellFlag !== -1 ? cliArguments[powerShellFlag + 1] : process.env.ORCA_POWERSHELL_EXECUTABLE +if (powerShellFlag !== -1 && !configuredPowerShell) { console.error('--powershell requires an executable path') process.exit(2) } diff --git a/config/scripts/terminal-e2e-helpers.mjs b/config/scripts/terminal-e2e-helpers.mjs index 2f89f1a7f90..8b698416326 100644 --- a/config/scripts/terminal-e2e-helpers.mjs +++ b/config/scripts/terminal-e2e-helpers.mjs @@ -222,8 +222,8 @@ if (process.argv[1]?.endsWith('terminal-e2e-helpers.mjs')) { const args = process.argv.slice(2) const portIdx = args.indexOf('--port') const cmdIdx = args.indexOf('--command') - const port = portIdx >= 0 ? Number(args[portIdx + 1]) : 9444 - const command = cmdIdx >= 0 ? args[cmdIdx + 1] : null + const port = portIdx !== -1 ? Number(args[portIdx + 1]) : 9444 + const command = cmdIdx !== -1 ? args[cmdIdx + 1] : null const term = new OrcaTerminal(port) console.log('Connecting to Orca on CDP port', port, '...') diff --git a/config/scripts/verify-agent-hook-stdin-lifecycle.mjs b/config/scripts/verify-agent-hook-stdin-lifecycle.mjs index 079735c41ec..187b5aa3fc2 100644 --- a/config/scripts/verify-agent-hook-stdin-lifecycle.mjs +++ b/config/scripts/verify-agent-hook-stdin-lifecycle.mjs @@ -142,7 +142,7 @@ function readGeneratedScripts(home, minMtime) { const body = readFileSync(path, 'utf8') const captureIndex = body.indexOf('payload=$(cat)') const firstExitIndex = body.indexOf('exit 0') - if (captureIndex < 0 || firstExitIndex <= captureIndex) { + if (captureIndex === -1 || firstExitIndex <= captureIndex) { throw new Error([fileName, ' can exit before capturing stdin'].join('')) } return { body, fileName, path, source } diff --git a/config/scripts/verify-computer-native.mjs b/config/scripts/verify-computer-native.mjs index ba23a49cdd0..56efeb33521 100644 --- a/config/scripts/verify-computer-native.mjs +++ b/config/scripts/verify-computer-native.mjs @@ -489,11 +489,11 @@ function verifyNativeArgumentGuardrails() { function powerShellFunctionBody(source, name) { const start = source.indexOf(`function ${name}`) - if (start < 0) { + if (start === -1) { return null } const bodyStart = source.indexOf('{', start) - if (bodyStart < 0) { + if (bodyStart === -1) { return null } let depth = 0 @@ -513,11 +513,11 @@ function powerShellFunctionBody(source, name) { function swiftFunctionBody(source, name) { const start = source.indexOf(`func ${name}`) - if (start < 0) { + if (start === -1) { return null } const bodyStart = source.indexOf('{', start) - if (bodyStart < 0) { + if (bodyStart === -1) { return null } let depth = 0 diff --git a/config/scripts/wsl-git-shell-benchmark.mjs b/config/scripts/wsl-git-shell-benchmark.mjs index 00ea47ded0a..d29aaea2824 100644 --- a/config/scripts/wsl-git-shell-benchmark.mjs +++ b/config/scripts/wsl-git-shell-benchmark.mjs @@ -175,7 +175,7 @@ async function main() { wslShellArgs(distro, ['/bin/sh', '-lc', escapeWslShCommandForWindows(script)]) ) const markerOffset = result.stdout.indexOf(outputMarker) - if (markerOffset < 0) { + if (markerOffset === -1) { throw new Error('Login shell did not emit the Git output marker') } result.stdout = result.stdout.subarray(markerOffset + Buffer.byteLength(outputMarker)) diff --git a/mobile/src/components/mobile-markdown-preview-html.ts b/mobile/src/components/mobile-markdown-preview-html.ts index 8b2d247f8f2..8bb5fca0934 100644 --- a/mobile/src/components/mobile-markdown-preview-html.ts +++ b/mobile/src/components/mobile-markdown-preview-html.ts @@ -97,7 +97,7 @@ function attrValue(tag: string, name: string): string { const valueStart = cursor if (quote) { const valueEnd = tag.indexOf(quote, cursor) - if (valueEnd < 0) { + if (valueEnd === -1) { return '' } cursor = valueEnd + 1 diff --git a/mobile/src/components/mobile-markdown-preview-tag-stripper.ts b/mobile/src/components/mobile-markdown-preview-tag-stripper.ts index 90e3c3cc3cc..6eeca2aa612 100644 --- a/mobile/src/components/mobile-markdown-preview-tag-stripper.ts +++ b/mobile/src/components/mobile-markdown-preview-tag-stripper.ts @@ -191,7 +191,7 @@ function closingMarkupTagNames(value: string): Set { let cursor = 0 while (cursor < value.length) { const start = value.indexOf('<', cursor) - if (start < 0) { + if (start === -1) { return names } const end = findMobileMarkdownMarkupTagEnd(value, start + 1) @@ -228,7 +228,7 @@ export function stripMobileMarkdownMarkupTags(value: string): string { const closingTagNames = closingMarkupTagNames(value) while (cursor < value.length) { const start = value.indexOf('<', cursor) - if (start < 0) { + if (start === -1) { return output + value.slice(cursor) } output += value.slice(cursor, start) @@ -261,7 +261,7 @@ export function stripMobileMarkdownMarkupTags(value: string): string { !/[-:]/.test(name) && !knownMarkupTagNames.has(lowerName) && !closingTagNames.has(lowerName) - if (isNestedGeneric && nestedStart >= 0) { + if (isNestedGeneric && nestedStart !== -1) { output += value.slice(start, nestedStart) cursor = nestedStart continue diff --git a/mobile/src/files/file-tree.ts b/mobile/src/files/file-tree.ts index 71f26ca155b..8a3e795ac2d 100644 --- a/mobile/src/files/file-tree.ts +++ b/mobile/src/files/file-tree.ts @@ -132,7 +132,7 @@ export function getDirectoryCacheState( ): DirectoryState | undefined { // Why: repository paths are arbitrary object keys; inherited keys like // "constructor" must not masquerade as loaded directory state. - return Object.prototype.hasOwnProperty.call(cache, relativePath) ? cache[relativePath] : undefined + return Object.hasOwn(cache, relativePath) ? cache[relativePath] : undefined } export function joinRelativePath(parentPath: string, name: string): string { diff --git a/mobile/src/hooks/use-active-worktree-scroll.ts b/mobile/src/hooks/use-active-worktree-scroll.ts index 7600f3ceafe..ff0441c85a8 100644 --- a/mobile/src/hooks/use-active-worktree-scroll.ts +++ b/mobile/src/hooks/use-active-worktree-scroll.ts @@ -37,7 +37,7 @@ export function useActiveWorktreeScroll( (worktreeId: string): boolean => { for (let sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) { const itemIndex = sections[sectionIndex].data.findIndex((w) => w.worktreeId === worktreeId) - if (itemIndex >= 0) { + if (itemIndex !== -1) { sectionListRef.current?.scrollToLocation({ sectionIndex, itemIndex, diff --git a/mobile/src/session/mobile-diff-review-screen-model.ts b/mobile/src/session/mobile-diff-review-screen-model.ts index 3dcaed35716..f8f1ceedf99 100644 --- a/mobile/src/session/mobile-diff-review-screen-model.ts +++ b/mobile/src/session/mobile-diff-review-screen-model.ts @@ -100,7 +100,7 @@ export function nextReviewIndexAfterMarkReviewed({ const wrappedIndex = filteredQueue.findIndex( (item) => item.key !== currentItemKey && !item.isReviewed ) - const targetIndex = nextIndex >= 0 ? nextIndex : wrappedIndex >= 0 ? wrappedIndex : null + const targetIndex = nextIndex !== -1 ? nextIndex : wrappedIndex !== -1 ? wrappedIndex : null if (targetIndex === null) { return null } diff --git a/mobile/src/session/mobile-native-chat-draft-reconcile.ts b/mobile/src/session/mobile-native-chat-draft-reconcile.ts index 360fd22587b..a6f599b7458 100644 --- a/mobile/src/session/mobile-native-chat-draft-reconcile.ts +++ b/mobile/src/session/mobile-native-chat-draft-reconcile.ts @@ -87,7 +87,7 @@ export function mergeLandedImagePreviewEchoes( const entries = Object.entries(previous[sessionKey] ?? {}) for (const preview of landed) { const existingIndex = entries.findIndex(([messageId]) => messageId === preview.messageId) - if (existingIndex >= 0) { + if (existingIndex !== -1) { entries.splice(existingIndex, 1) } entries.push([preview.messageId, preview.images]) diff --git a/mobile/src/transport/host-store.ts b/mobile/src/transport/host-store.ts index 99421ffd192..c32ac44ac77 100644 --- a/mobile/src/transport/host-store.ts +++ b/mobile/src/transport/host-store.ts @@ -182,7 +182,7 @@ async function persistHost(host: HostProfile, requireExisting: boolean): Promise } } let next: StoredHostProfile[] - if (index >= 0) { + if (index !== -1) { updatedExistingHost = true // Why: an authoritative save is the safe point to collapse pre-existing duplicate rows to the preserved host id. next = hosts @@ -195,7 +195,7 @@ async function persistHost(host: HostProfile, requireExisting: boolean): Promise next = [...hosts.filter(({ id }) => !duplicateHostIds.has(id)), stored] } if (duplicateHostIds.size > 0) { - if (index < 0) { + if (index === -1) { // Why: process death between the early token write and metadata publication must leave cleanup discoverable. await recordHostCredentialCleanupIntent(stored.id) cleanupIntentRecordedBeforeMetadata = true @@ -304,7 +304,7 @@ export async function updateHostNameAndEndpoint( ): Promise { await mutateStoredHosts((hosts) => { const index = hosts.findIndex((host) => host.id === hostId) - if (index < 0) { + if (index === -1) { throw new Error('Host not found') } const next = hosts.slice() @@ -321,7 +321,7 @@ export async function updateLastConnected(hostId: string): Promise { try { await mutateStoredHosts((hosts) => { const index = hosts.findIndex((h) => h.id === hostId) - if (index < 0) { + if (index === -1) { return hosts } const next = hosts.slice() diff --git a/mobile/src/transport/mobile-relay-host-overlay-store.ts b/mobile/src/transport/mobile-relay-host-overlay-store.ts index 7f1519eb5c9..4960fbec3f0 100644 --- a/mobile/src/transport/mobile-relay-host-overlay-store.ts +++ b/mobile/src/transport/mobile-relay-host-overlay-store.ts @@ -80,7 +80,7 @@ export async function saveMobileRelayHostOverlay(overlay: MobileRelayHostOverlay const validated = MobileRelayHostOverlaySchema.parse(overlay) return mutateOverlays((overlays) => { const index = overlays.findIndex(({ hostId }) => hostId === validated.hostId) - if (index < 0) { + if (index === -1) { return [...overlays, validated] } const next = overlays.slice() diff --git a/mobile/src/transport/rpc-response-shape.ts b/mobile/src/transport/rpc-response-shape.ts index 54903f5f3e1..61ecaf57362 100644 --- a/mobile/src/transport/rpc-response-shape.ts +++ b/mobile/src/transport/rpc-response-shape.ts @@ -13,7 +13,7 @@ export function isRpcResponse(value: unknown): value is RpcResponse { return false } if (response.ok === true) { - return Object.prototype.hasOwnProperty.call(response, 'result') + return Object.hasOwn(response, 'result') } return ( response.ok === false && diff --git a/package.json b/package.json index 83cd7597e5f..6a0e4f33814 100644 --- a/package.json +++ b/package.json @@ -217,7 +217,7 @@ "mermaid": "^11.16.1", "monaco-editor": "^0.55.1", "oxfmt": "^0.52.0", - "oxlint": "^1.75.0", + "oxlint": "^1.77.0", "oxlint-plugin-react-doctor": "0.9.1", "oxlint-tsgolint": "7.0.2001", "pdfjs-dist": "^6.2.108", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1872f9860e8..57885d16e25 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -294,8 +294,8 @@ importers: specifier: ^0.52.0 version: 0.52.0 oxlint: - specifier: ^1.75.0 - version: 1.75.0(oxlint-tsgolint@7.0.2001) + specifier: ^1.77.0 + version: 1.77.0(oxlint-tsgolint@7.0.2001) oxlint-plugin-react-doctor: specifier: 0.9.1 version: 0.9.1 @@ -1488,124 +1488,124 @@ packages: cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.75.0': - resolution: {integrity: sha512-lutovtFzJqlRaqpZrCqSSGaHZzl9nIxxpjLzhSRLunN6dCLylj0uzlCyQGaQDIys7rrv8kVXiFO+R4Zpn0bX7g==} + '@oxlint/binding-android-arm-eabi@1.77.0': + resolution: {integrity: sha512-E06sKWS6PiI6HRxS1wyQg22HvApt01hI7fV+T3wUk3OSbaaP4a3hYGY/MIQDmASqCiRjBdpRQYkgMkqH82cWmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.75.0': - resolution: {integrity: sha512-hXI0hDgHkw4w5nfru72aG7y+2iQJmC4waH/KV6H/hbgA6yAP5jYNx0P9yug15Hs0tWl/+mda3Jjn/2gmDT48tw==} + '@oxlint/binding-android-arm64@1.77.0': + resolution: {integrity: sha512-NvsKz0KZxTp9cYWPLf+FXaSZwB3oO3peAjtukpOMBgse2vhQSoIIVqeO1yR0lEo/UcdZIDL18uq+kL0LzQ0ytA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.75.0': - resolution: {integrity: sha512-D91BWbK/dMYfCcrghspPIuKs2D9LF4Z/OabVSQjw1AO6PWxArD7teDA48bm0ySFqWDaPVqmQRl5GMWNglTXyrQ==} + '@oxlint/binding-darwin-arm64@1.77.0': + resolution: {integrity: sha512-bgjTn6nW4bQCFBvSvuHCpDD+sONvmpo4lGI4PxzMt1quBA+xYxhczk6RiCn3GZ9gY8uhaBbwhj9MdKGfu6T9DA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.75.0': - resolution: {integrity: sha512-02mpwzf12BonZ6PT0TuQoomvEh2kVl2WGBIKWezCyToIS+rYkQZ6GXnARBAl9A4Ovm2V+Xe7M4KretyqmmcnJQ==} + '@oxlint/binding-darwin-x64@1.77.0': + resolution: {integrity: sha512-aotaIttH1R6j1Rwhx0M0htgeZyGtVQqYNTVEYMN/UcgHPquGA6kmk9OyuDc3a2GKUQBC+3C3GVQCcrRPMYqAFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.75.0': - resolution: {integrity: sha512-qZJgLnDaBsiL5YESx2t/TZ8eXkL9fEkKoXEdzegROhlz9A0lgyGnZ0dAzJrh7LJAHQl2K9RdRueN2s/9N7+odg==} + '@oxlint/binding-freebsd-x64@1.77.0': + resolution: {integrity: sha512-nNx/wta7ksRAdYvq+l4AWjXkLxEXHALhENxjj2cYbQAIR4ybaA5L+hCbE63HOmft5czQ6ks+hb8vmEAnn7YGPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.75.0': - resolution: {integrity: sha512-7XlaWA5BJD3XpCfrEqjEe6Zseeb14S7QGa304XfwKignRaKQ+eIj775BQ7nIslggWickl4IsPUFqJ+/gAyNHVg==} + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': + resolution: {integrity: sha512-tMLLjM7xXtzXisVCzkOTXNCy9bZVId2wteNwjohlFDR/jY6WagpEDA1c1wu4xRc20Hojaxj+V6DSR7gbKxijWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.75.0': - resolution: {integrity: sha512-av6Tpv8yrcMMMOadOqENBhlsLRcGFXXwoQ0hzHhsmS9FJ4Wioy8we427GbcMe2XTxmL2e60T67H1Dyr3up+tAA==} + '@oxlint/binding-linux-arm-musleabihf@1.77.0': + resolution: {integrity: sha512-MiAFDFaqR0tmHTAyo0YDcZ5hyLREdYw/RQhc2R3cbT+8O3tB+zqPM2th9TTQ+Uo3jn/embS+DO+HyX9ztCPkOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.75.0': - resolution: {integrity: sha512-WcUhd8fHT5plrA14lANevl+hOl815mVI5t2hU21oFWrZKFXIVV/Sr4rWQV0NzSvzBupbMLNc5ErEA6Ehxh5jMg==} + '@oxlint/binding-linux-arm64-gnu@1.77.0': + resolution: {integrity: sha512-/xqQ3B16i1T4cyt/9Mn+4CpzhUXoBXp7kVpIwzOXNFLj5JmK1bIjsbSnX296Gg8A/o7oDtKWikFgBx0SLwztkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.75.0': - resolution: {integrity: sha512-UWzp5wRHFe/ESO3+eEaxXsTkYTGLYjnTsi/I5neEacXSItQ6WNleapfOAeA4x2b8nyhJ4uQxqvtv9pHv8kWJtQ==} + '@oxlint/binding-linux-arm64-musl@1.77.0': + resolution: {integrity: sha512-LSbwuRKiNCenPDcbARqAZ5RfBy7gmj7vOvfJRLeCDU3gFtSxWbhv/+VTlaUqzUhNj1gFLHB8h7ALnxa/Az6z6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.75.0': - resolution: {integrity: sha512-XEVRwGMLKCUKrvhLAz4F6AIh8MJrQVdSZtAmPpRZt9tGPsUnamPOcl3dS/ZQzJnar/Ymgc//+xho0L60Emzuxg==} + '@oxlint/binding-linux-ppc64-gnu@1.77.0': + resolution: {integrity: sha512-QWdcH31mXEUe5Nq1s0CfCpceaKjIo9uZtwDjAuL681g1axf+5x8xrg/eXWaw//4NCxYZ4V4e5Hu5tvdR+pTBlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.75.0': - resolution: {integrity: sha512-mAG4DUXqfLC8cTjMD2kt3jDmVzFREYtDyeLNdLdsCcBc4Zbl2EMuiFektGBilQwkNjYnMvCqJs55U+Hyb+b+jw==} + '@oxlint/binding-linux-riscv64-gnu@1.77.0': + resolution: {integrity: sha512-GnOfYgJxbcElOiPZaDFDl406ONddwvOWk2jvAAAEjwAl4GofNoHF+/HHUIBYa6bFCArlcGPi0XjC4cU1pkgF/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.75.0': - resolution: {integrity: sha512-95hrAvriAlI+pekSomTFIn0+bawMDlDwTNVmdjsFusTHyL2JWh7TWvRNG/Lkim72uN8OiCcO9wcaC6omLP5E3w==} + '@oxlint/binding-linux-riscv64-musl@1.77.0': + resolution: {integrity: sha512-AyEMTUCf0xY+hHF+IxqXFQIX0yQOIR8ykpY0lJNOw9xYqOzUX8dyZfRvlG0RfXwuQn2eonf/8NrMmDSZJjdqsA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.75.0': - resolution: {integrity: sha512-4b6f2+FrtruAESrCqIKcrarzfrSx+wk2QNcp+RT91/Prc+pMQMAfyZ1rG1c3tFQNl8Bc616tx40uNXyxNBRPbQ==} + '@oxlint/binding-linux-s390x-gnu@1.77.0': + resolution: {integrity: sha512-sPLzEcNvxd/oyVQ5oZo92CiHkFkpBeRop13E/P3TPY+hZfXHKCOWKI70TE2RYwMKFJDc20EMjH16L7NZICtKTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.75.0': - resolution: {integrity: sha512-nshAhrUvXFUWOvqQ2soIw7HFNWvpvEV4o0cYSqPtzLiPF5gKyYTDOOTJ6Rn8g8K/iGvPIrbDA4v8+5MvnjJrrg==} + '@oxlint/binding-linux-x64-gnu@1.77.0': + resolution: {integrity: sha512-1Oh2ssH2L7lwyvkdSqaMUfsGfwU2Wfvew+obBUYjRVqhpBcUpwnsPSEr1IzVi9XqkuY10geiLsNKecqaZC34Dw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.75.0': - resolution: {integrity: sha512-e4jNxLKnxLC6sYBQRxrI2pgIIxnmMtF8U/VwNYcjTT/CLS+spH624cYVnj07bTKwaEWT37/e025isOs6j/0xqA==} + '@oxlint/binding-linux-x64-musl@1.77.0': + resolution: {integrity: sha512-0j/2wRgNGO+Qj/M1uu/p57h/hFTTWWcfie0ufkbabeus2s5+/QqkCflnMOwLLN5m2GsNeWp4xdl4cPa4n7QCOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.75.0': - resolution: {integrity: sha512-hZ2lH+1qLf/DiEP9UWuQTK2JWj/BgvMB4jhIV4SmNU1wfEiYYX4TynQyAZXx0j9X4qRYizAL042SKaV+8ynh4w==} + '@oxlint/binding-openharmony-arm64@1.77.0': + resolution: {integrity: sha512-BJ/j54qS0usEnyDkLYURMj2iiD9h5Cyy+ppzeMSXBGRXaGRNWnj1Mw14NqWMR5E/PzdgB30OOCCzLzbRoduafw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.75.0': - resolution: {integrity: sha512-Ilj6PNzGDS3bCU0MSJH7Msh0NhH+T/mRp2shwg+q+GHeVlPwP5LEboW96aW+3kVKFk6zYZy1Xi5pZkqZh6X8KQ==} + '@oxlint/binding-win32-arm64-msvc@1.77.0': + resolution: {integrity: sha512-Yh8w+g2Lpx7StrvtYkoz9JJvXjB9wxgFChFNb85nrXm/wj/XTwGWS1hve9+900HL7llrntYB3YP+y32E3tRqzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.75.0': - resolution: {integrity: sha512-QVit2nOEOiPhkmsrksPSkoGCdnZRNkspt8fwoYyP09te1VEbnSj4LAxua4rc8FKTmWkySVe05j8iz9GXYfF1AQ==} + '@oxlint/binding-win32-ia32-msvc@1.77.0': + resolution: {integrity: sha512-zja5b7+6a7UsRFgAQSrnax5vrzliEyNPLCjfXONu/vTWswaIVZGFajJZptaeRvPE4LghtFdAzVFlexTm7MVTGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.75.0': - resolution: {integrity: sha512-DSxnNkBUAYARPwJtR12Ig3deWr8w0H997xP6jy33i+e0SyYJw8FKuz4+cZtpmPEhQmvlPJE3X/2vNxDmLkd/rA==} + '@oxlint/binding-win32-x64-msvc@1.77.0': + resolution: {integrity: sha512-+teyvPDZ2RjUvo+SuCqS/UhaJl1QtdW5fWT5NJTV61V5MIuIS90Db9LixmtEGvXixyttiK62P96MSu3UlpviBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -5541,8 +5541,8 @@ packages: resolution: {integrity: sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg==} hasBin: true - oxlint@1.75.0: - resolution: {integrity: sha512-m9WzjRcRYA/uqIZDa9tclrieoPJ/ln1QYTKdFx6NUOs8uY5DiHlIwRQoCrHT6OM6O3ww3l2skY5gO7G7ZphE7g==} + oxlint@1.77.0: + resolution: {integrity: sha512-qnGh8XJHaQ0dprrDXNQZgS0FgjI6v+V3+X8DwmaV++5Aamy6jGKfDdQ1TUvhUxtmKFAbEf4/WeO5QZX+5WSngg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -7809,61 +7809,61 @@ snapshots: '@oxlint-tsgolint/win32-x64@7.0.2001': optional: true - '@oxlint/binding-android-arm-eabi@1.75.0': + '@oxlint/binding-android-arm-eabi@1.77.0': optional: true - '@oxlint/binding-android-arm64@1.75.0': + '@oxlint/binding-android-arm64@1.77.0': optional: true - '@oxlint/binding-darwin-arm64@1.75.0': + '@oxlint/binding-darwin-arm64@1.77.0': optional: true - '@oxlint/binding-darwin-x64@1.75.0': + '@oxlint/binding-darwin-x64@1.77.0': optional: true - '@oxlint/binding-freebsd-x64@1.75.0': + '@oxlint/binding-freebsd-x64@1.77.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.75.0': + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.75.0': + '@oxlint/binding-linux-arm-musleabihf@1.77.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.75.0': + '@oxlint/binding-linux-arm64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.75.0': + '@oxlint/binding-linux-arm64-musl@1.77.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.75.0': + '@oxlint/binding-linux-ppc64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.75.0': + '@oxlint/binding-linux-riscv64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.75.0': + '@oxlint/binding-linux-riscv64-musl@1.77.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.75.0': + '@oxlint/binding-linux-s390x-gnu@1.77.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.75.0': + '@oxlint/binding-linux-x64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-x64-musl@1.75.0': + '@oxlint/binding-linux-x64-musl@1.77.0': optional: true - '@oxlint/binding-openharmony-arm64@1.75.0': + '@oxlint/binding-openharmony-arm64@1.77.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.75.0': + '@oxlint/binding-win32-arm64-msvc@1.77.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.75.0': + '@oxlint/binding-win32-ia32-msvc@1.77.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.75.0': + '@oxlint/binding-win32-x64-msvc@1.77.0': optional: true '@parcel/watcher-android-arm64@2.5.6': @@ -12174,27 +12174,27 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 7.0.2001 '@oxlint-tsgolint/win32-x64': 7.0.2001 - oxlint@1.75.0(oxlint-tsgolint@7.0.2001): + oxlint@1.77.0(oxlint-tsgolint@7.0.2001): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.75.0 - '@oxlint/binding-android-arm64': 1.75.0 - '@oxlint/binding-darwin-arm64': 1.75.0 - '@oxlint/binding-darwin-x64': 1.75.0 - '@oxlint/binding-freebsd-x64': 1.75.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.75.0 - '@oxlint/binding-linux-arm-musleabihf': 1.75.0 - '@oxlint/binding-linux-arm64-gnu': 1.75.0 - '@oxlint/binding-linux-arm64-musl': 1.75.0 - '@oxlint/binding-linux-ppc64-gnu': 1.75.0 - '@oxlint/binding-linux-riscv64-gnu': 1.75.0 - '@oxlint/binding-linux-riscv64-musl': 1.75.0 - '@oxlint/binding-linux-s390x-gnu': 1.75.0 - '@oxlint/binding-linux-x64-gnu': 1.75.0 - '@oxlint/binding-linux-x64-musl': 1.75.0 - '@oxlint/binding-openharmony-arm64': 1.75.0 - '@oxlint/binding-win32-arm64-msvc': 1.75.0 - '@oxlint/binding-win32-ia32-msvc': 1.75.0 - '@oxlint/binding-win32-x64-msvc': 1.75.0 + '@oxlint/binding-android-arm-eabi': 1.77.0 + '@oxlint/binding-android-arm64': 1.77.0 + '@oxlint/binding-darwin-arm64': 1.77.0 + '@oxlint/binding-darwin-x64': 1.77.0 + '@oxlint/binding-freebsd-x64': 1.77.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.77.0 + '@oxlint/binding-linux-arm-musleabihf': 1.77.0 + '@oxlint/binding-linux-arm64-gnu': 1.77.0 + '@oxlint/binding-linux-arm64-musl': 1.77.0 + '@oxlint/binding-linux-ppc64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-musl': 1.77.0 + '@oxlint/binding-linux-s390x-gnu': 1.77.0 + '@oxlint/binding-linux-x64-gnu': 1.77.0 + '@oxlint/binding-linux-x64-musl': 1.77.0 + '@oxlint/binding-openharmony-arm64': 1.77.0 + '@oxlint/binding-win32-arm64-msvc': 1.77.0 + '@oxlint/binding-win32-ia32-msvc': 1.77.0 + '@oxlint/binding-win32-x64-msvc': 1.77.0 oxlint-tsgolint: 7.0.2001 p-cancelable@2.1.1: {} diff --git a/src/main/agent-hooks/managed-hook-owner-identity.ts b/src/main/agent-hooks/managed-hook-owner-identity.ts index 7f60956e6e4..51f299595cd 100644 --- a/src/main/agent-hooks/managed-hook-owner-identity.ts +++ b/src/main/agent-hooks/managed-hook-owner-identity.ts @@ -32,7 +32,7 @@ function hasCode(error: unknown, code: string): boolean { export function parseLinuxStartTicks(statLine: string): string | null { const commandEnd = statLine.lastIndexOf(')') - if (commandEnd < 0) { + if (commandEnd === -1) { return null } // Field 22 is index 19 after removing pid and the parenthesized command. diff --git a/src/main/ai-vault/session-scanner-service-client.ts b/src/main/ai-vault/session-scanner-service-client.ts index cfcee4c2240..4f46dc643c5 100644 --- a/src/main/ai-vault/session-scanner-service-client.ts +++ b/src/main/ai-vault/session-scanner-service-client.ts @@ -130,7 +130,7 @@ export class AiVaultScannerServiceClient { continue } const index = this.queue.findIndex((call) => call.lane === lane) - if (index < 0) { + if (index === -1) { continue } const call = this.queue.splice(index, 1)[0]! @@ -257,7 +257,7 @@ export class AiVaultScannerServiceClient { call.cancelled = true call.reject(createAiVaultScanCancelledError()) const queuedIndex = this.queue.indexOf(call) - if (queuedIndex >= 0) { + if (queuedIndex !== -1) { this.queue.splice(queuedIndex, 1) clearAiVaultServiceCall(call) this.pump() diff --git a/src/main/ai-vault/session-scanner-worker-client.ts b/src/main/ai-vault/session-scanner-worker-client.ts index 6b780c5086d..70e75c4f982 100644 --- a/src/main/ai-vault/session-scanner-worker-client.ts +++ b/src/main/ai-vault/session-scanner-worker-client.ts @@ -176,7 +176,7 @@ export class AiVaultScannerWorkerClient { return } const index = this.queue.indexOf(call) - if (index >= 0) { + if (index !== -1) { this.queue.splice(index, 1) this.clearCall(call) } diff --git a/src/main/automations/hermes-cron-output.ts b/src/main/automations/hermes-cron-output.ts index db1e054f7e8..581b7f998d1 100644 --- a/src/main/automations/hermes-cron-output.ts +++ b/src/main/automations/hermes-cron-output.ts @@ -470,7 +470,7 @@ function findMatchingSessionRunIndex( (sessionRun, index) => !usedSessionRunIndexes.has(index) && getRunKey(sessionRun) === outputRunKey ) - if (exactMatchIndex >= 0) { + if (exactMatchIndex !== -1) { return exactMatchIndex } @@ -798,7 +798,7 @@ function readHermesSessionDbRunRefs(jobId: string): HermesSessionRunRef[] { } } -function readHermesSessionDbRunById(jobId: string, runId: string): unknown | null { +function readHermesSessionDbRunById(jobId: string, runId: string): unknown { if (!existsSync(HERMES_STATE_DB)) { return null } diff --git a/src/main/browser/agent-browser-bridge.test.ts b/src/main/browser/agent-browser-bridge.test.ts index b9f5a9bd92d..125949f3a50 100644 --- a/src/main/browser/agent-browser-bridge.test.ts +++ b/src/main/browser/agent-browser-bridge.test.ts @@ -74,8 +74,8 @@ type ExecFileCallback = (error: unknown, stdout?: string, stderr?: string) => vo } function mockBrowserManager( - tabs: Map = new Map([['tab-1', 100]]), - worktrees: Map = new Map(), + tabs = new Map([['tab-1', 100]]), + worktrees = new Map(), overrides: Partial = {} ): BrowserManager { return { diff --git a/src/main/browser/browser-cookie-import.ts b/src/main/browser/browser-cookie-import.ts index bffb12cfa02..6d5f1661703 100644 --- a/src/main/browser/browser-cookie-import.ts +++ b/src/main/browser/browser-cookie-import.ts @@ -911,7 +911,7 @@ export function buildChromiumCookieInsertParams( return decryptedValue } - const sourceHasColumn = Object.prototype.hasOwnProperty.call(sourceRow, column.name) + const sourceHasColumn = Object.hasOwn(sourceRow, column.name) const sourceValue = sourceHasColumn ? normalizeSqliteCookieValue(sourceRow[column.name]) : null if (sourceValue !== null) { return sourceValue diff --git a/src/main/browser/browser-session-registry.persistence.test.ts b/src/main/browser/browser-session-registry.persistence.test.ts index f5f9bb7a7b2..f27a861a45c 100644 --- a/src/main/browser/browser-session-registry.persistence.test.ts +++ b/src/main/browser/browser-session-registry.persistence.test.ts @@ -24,7 +24,7 @@ function seedMeta(fsState: FsState, meta: unknown): void { function installModuleMocks( fsState: FsState, - copyFailures: Set = new Set() + copyFailures = new Set() ): { sessionFromPartitionMock: ReturnType setupClientHintsOverrideMock: ReturnType diff --git a/src/main/browser/browser-webauthn-access.ts b/src/main/browser/browser-webauthn-access.ts index 4f04065e36c..60da5b1e297 100644 --- a/src/main/browser/browser-webauthn-access.ts +++ b/src/main/browser/browser-webauthn-access.ts @@ -15,7 +15,7 @@ function isSecureBrowserOrigin(rawOrigin: string | undefined): boolean { } } -function isFidoHidDevice(device: Electron.HIDDevice | unknown): device is Electron.HIDDevice { +function isFidoHidDevice(device: unknown): device is Electron.HIDDevice { if (!device || typeof device !== 'object') { return false } diff --git a/src/main/browser/cdp-bridge-integration.test.ts b/src/main/browser/cdp-bridge-integration.test.ts index b3b1fae9401..b3b836d07b0 100644 --- a/src/main/browser/cdp-bridge-integration.test.ts +++ b/src/main/browser/cdp-bridge-integration.test.ts @@ -212,7 +212,7 @@ function createMockGuest( removeListener: vi.fn((event: string, handler: (...args: unknown[]) => void) => { const handlers = debuggerListeners.get(event) ?? [] const idx = handlers.indexOf(handler) - if (idx >= 0) { + if (idx !== -1) { handlers.splice(idx, 1) } }), diff --git a/src/main/cli/wsl-cli-installer.ts b/src/main/cli/wsl-cli-installer.ts index f6a1684e19c..3dee00a871b 100644 --- a/src/main/cli/wsl-cli-installer.ts +++ b/src/main/cli/wsl-cli-installer.ts @@ -379,7 +379,7 @@ export class WslCliInstaller { private async readCommandFile( distro: string, commandPath: string - ): Promise { + ): Promise<(string & {}) | 'not_file' | null> { const output = await this.run( distro, [ diff --git a/src/main/cli/wsl-cli-registration-registry.ts b/src/main/cli/wsl-cli-registration-registry.ts index d1a84a42f52..99d3faff5d4 100644 --- a/src/main/cli/wsl-cli-registration-registry.ts +++ b/src/main/cli/wsl-cli-registration-registry.ts @@ -138,7 +138,7 @@ async function readState(userDataPath: string): Promise normalizeWslDistroKey(entry) === key) - if (existingIndex < 0) { + if (existingIndex === -1) { return [...distros, distro.trim()] } return distros.map((entry, index) => (index === existingIndex ? distro.trim() : entry)) diff --git a/src/main/codex-accounts/service.ts b/src/main/codex-accounts/service.ts index b55c9b50315..eef834af323 100644 --- a/src/main/codex-accounts/service.ts +++ b/src/main/codex-accounts/service.ts @@ -1311,7 +1311,7 @@ export class CodexAccountService { const managedRootMarker = '/.local/share/orca/codex-accounts/' const markerIndex = wslInfo.linuxPath.indexOf(managedRootMarker) - if (markerIndex < 0) { + if (markerIndex === -1) { return null } const wslHome = wslInfo.linuxPath.slice(0, markerIndex) diff --git a/src/main/codex/codex-real-home-path.ts b/src/main/codex/codex-real-home-path.ts index 700359d4f37..b964f5ba518 100644 --- a/src/main/codex/codex-real-home-path.ts +++ b/src/main/codex/codex-real-home-path.ts @@ -109,7 +109,7 @@ function getLaunchEnvValue( launchEnv: NodeJS.ProcessEnv, key: 'CODEX_HOME' | 'ORCA_CODEX_HOME' | 'HOME' | 'SHELL' ): string | undefined { - return Object.prototype.hasOwnProperty.call(launchEnv, key) ? launchEnv[key] : process.env[key] + return Object.hasOwn(launchEnv, key) ? launchEnv[key] : process.env[key] } function normalizePathForComparison(value: string): string { diff --git a/src/main/codex/codex-session-resume-home.test.ts b/src/main/codex/codex-session-resume-home.test.ts index 75d91d7ab34..2f0aa5429c6 100644 --- a/src/main/codex/codex-session-resume-home.test.ts +++ b/src/main/codex/codex-session-resume-home.test.ts @@ -140,7 +140,7 @@ describe('resolveTrustedCodexSessionResumeHome', () => { trustedCodexHomes: [homePath], ...withoutHomeRanking, fileIsRegular: () => true, - listSessionFiles: async function* (): AsyncIterable { + async *listSessionFiles(): AsyncIterable { yield extendedEntry } }) @@ -153,7 +153,7 @@ describe('resolveTrustedCodexSessionResumeHome', () => { trustedCodexHomes: [homePath], ...withoutHomeRanking, fileIsRegular: () => true, - listSessionFiles: async function* (): AsyncIterable { + async *listSessionFiles(): AsyncIterable { yield `\\\\.\\C:\\Users\\Example\\.codex\\sessions\\2026\\07\\20\\rollout-${sessionId}.jsonl` } }) diff --git a/src/main/computer/macos-native-provider-transport.ts b/src/main/computer/macos-native-provider-transport.ts index 54c4cf773aa..1048ce5a94c 100644 --- a/src/main/computer/macos-native-provider-transport.ts +++ b/src/main/computer/macos-native-provider-transport.ts @@ -52,7 +52,7 @@ export function consumeNativeProviderLines( let remaining = buffer while (true) { const newline = remaining.indexOf('\n') - if (newline < 0) { + if (newline === -1) { return remaining } const line = remaining.slice(0, newline) diff --git a/src/main/daemon/daemon-incarnation-evidence.ts b/src/main/daemon/daemon-incarnation-evidence.ts index 01d9ffc97c5..f5c88935998 100644 --- a/src/main/daemon/daemon-incarnation-evidence.ts +++ b/src/main/daemon/daemon-incarnation-evidence.ts @@ -237,7 +237,7 @@ async function probeWindowsProcess( export function parseLinuxProcessState(statLine: string): string | null { const commandEnd = statLine.lastIndexOf(')') - if (commandEnd < 0) { + if (commandEnd === -1) { return null } return ( diff --git a/src/main/daemon/daemon-server.ts b/src/main/daemon/daemon-server.ts index 51d9d0913a2..731da9eefd5 100644 --- a/src/main/daemon/daemon-server.ts +++ b/src/main/daemon/daemon-server.ts @@ -145,7 +145,7 @@ export class DaemonServer { private transportSockets = new Set() private createOrAttachInFlight = 0 private idleShutdownState: 'running' | 'idle-shutdown-pending' | 'shutting-down' = 'running' - private initialAdoptionTimer: unknown | null = null + private initialAdoptionTimer: unknown = null private initialAdoptionDeadlineMs: number | null = null private retirementRequested = false private shutdownPromise: Promise | null = null diff --git a/src/main/daemon/macos-login-session-death-watch.ts b/src/main/daemon/macos-login-session-death-watch.ts index 1f529ff3e00..e2df486e38f 100644 --- a/src/main/daemon/macos-login-session-death-watch.ts +++ b/src/main/daemon/macos-login-session-death-watch.ts @@ -66,9 +66,9 @@ export class MacosLoginSessionDeathWatch { private probeInFlight = false private stopped = false private retired = false - private scheduledProbeTimer: unknown | null = null + private scheduledProbeTimer: unknown = null private scheduledProbeAtMs: number | null = null - private ptyExitDebounceTimer: unknown | null = null + private ptyExitDebounceTimer: unknown = null private pendingProbeTrigger: string | null = null private probeAbortController: AbortController | null = null diff --git a/src/main/daemon/terminal-session-teardown.ts b/src/main/daemon/terminal-session-teardown.ts index 1ba58393721..352927a5dfd 100644 --- a/src/main/daemon/terminal-session-teardown.ts +++ b/src/main/daemon/terminal-session-teardown.ts @@ -39,9 +39,8 @@ export class TerminalSessionTeardown { } if (immediate) { return this.forceKillPlainShellSession(sessionId, session) - } else { - session.kill() } + session.kill() } /** diff --git a/src/main/daemon/terminal-view-attribute-responder.ts b/src/main/daemon/terminal-view-attribute-responder.ts index ddd04bf11dc..decca3d3f99 100644 --- a/src/main/daemon/terminal-view-attribute-responder.ts +++ b/src/main/daemon/terminal-view-attribute-responder.ts @@ -57,7 +57,7 @@ function isValidColorIndex(value: number): boolean { function relativeLuminance([r, g, b]: TerminalViewRgb): number { const linear = (channel: number): number => { const c = channel / 255 - return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4) + return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4 } return linear(r) * 0.2126 + linear(g) * 0.7152 + linear(b) * 0.0722 } diff --git a/src/main/daemon/xterm-env-polyfill.ts b/src/main/daemon/xterm-env-polyfill.ts index 9ab12627299..27605b8fa9a 100644 --- a/src/main/daemon/xterm-env-polyfill.ts +++ b/src/main/daemon/xterm-env-polyfill.ts @@ -1,6 +1,6 @@ // @xterm/headless checks for `window` to detect browser vs node environment. // In ELECTRON_RUN_AS_NODE mode, `window` is undefined. This polyfill must be // imported before any @xterm/headless import. -if (typeof globalThis.window === 'undefined') { +if (globalThis.window === undefined) { ;(globalThis as Record).window = globalThis } diff --git a/src/main/emulator/android/adb-devices.ts b/src/main/emulator/android/adb-devices.ts index 692bbb4d0fb..7264a825310 100644 --- a/src/main/emulator/android/adb-devices.ts +++ b/src/main/emulator/android/adb-devices.ts @@ -8,7 +8,7 @@ export type AndroidAdbDeviceState = | 'bootloader' | 'recovery' | 'no permissions' - | string + | (string & {}) export type AndroidAdbDevice = { serial: string diff --git a/src/main/emulator/mjpeg-frame-parser.ts b/src/main/emulator/mjpeg-frame-parser.ts index 841bc6a8c91..90733e2c1fe 100644 --- a/src/main/emulator/mjpeg-frame-parser.ts +++ b/src/main/emulator/mjpeg-frame-parser.ts @@ -32,7 +32,7 @@ export function extractJpegFrames( while (cursor.length > 0) { const frameStart = cursor.indexOf(JPEG_START) - if (frameStart < 0) { + if (frameStart === -1) { const keepLastByte = cursor.at(-1) === 0xff return { frames, pending: keepLastByte ? Buffer.from([0xff]) : Buffer.alloc(0) } } @@ -41,7 +41,7 @@ export function extractJpegFrames( } const frameEnd = cursor.indexOf(JPEG_END, JPEG_START.length) - if (frameEnd < 0) { + if (frameEnd === -1) { return { frames, pending: trimPendingBuffer(cursor, maxPendingBytes) } } diff --git a/src/main/git/repo.ts b/src/main/git/repo.ts index e609d90a981..26291ec5d39 100644 --- a/src/main/git/repo.ts +++ b/src/main/git/repo.ts @@ -932,7 +932,7 @@ export function parseAndFilterSearchRefDetails( .filter((line) => line.length > 0) .map((line) => { const nul = line.indexOf('\0') - if (nul < 0) { + if (nul === -1) { // Why: no NUL means an unexpected %(refname) format; drop it rather than hand callers an unusable "short" ref. return null } diff --git a/src/main/git/wsl-git-read-environment.ts b/src/main/git/wsl-git-read-environment.ts index f67d696a37b..7df86a44a94 100644 --- a/src/main/git/wsl-git-read-environment.ts +++ b/src/main/git/wsl-git-read-environment.ts @@ -26,7 +26,7 @@ function parseProbe(stdout: string): WslGitReadEnvironment | null { const gitPath = fields[markerIndex + 2] ?? '' const home = fields[markerIndex + 3] ?? '' if ( - markerIndex < 0 || + markerIndex === -1 || !path.includes('/') || path.length > 32_768 || !gitPath.startsWith('/') || diff --git a/src/main/github/stacked-pr-creation.ts b/src/main/github/stacked-pr-creation.ts index 357f78bcbc6..e34752df6d9 100644 --- a/src/main/github/stacked-pr-creation.ts +++ b/src/main/github/stacked-pr-creation.ts @@ -209,7 +209,7 @@ function registeredStackNumber( ) // Why: a miss is -1, and -1 + 1 reads the first entry — which reports "already // registered" whenever the current PR heads a stack the parent has left. - if (parentPosition < 0) { + if (parentPosition === -1) { return null } return parentStack.pull_requests[parentPosition + 1]?.number === currentReview.number diff --git a/src/main/ipc/ephemeral-vm.test.ts b/src/main/ipc/ephemeral-vm.test.ts index 8df189a4abb..bbccfb41e93 100644 --- a/src/main/ipc/ephemeral-vm.test.ts +++ b/src/main/ipc/ephemeral-vm.test.ts @@ -6,7 +6,7 @@ import { encodePairingOffer, PAIRING_OFFER_VERSION } from '../../shared/pairing' import { listEnvironments } from '../../shared/runtime-environment-store' import { upsertEphemeralVmRuntime } from '../../shared/ephemeral-vm-runtime-store' -const handlers = new Map Promise | unknown>() +const handlers = new Map unknown>() const { handleMock, removeHandlerMock, diff --git a/src/main/ipc/filesystem-import.test.ts b/src/main/ipc/filesystem-import.test.ts index fcd004dcd06..bb143987c96 100644 --- a/src/main/ipc/filesystem-import.test.ts +++ b/src/main/ipc/filesystem-import.test.ts @@ -645,7 +645,7 @@ describe('fs:importExternalPaths', () => { ) const mib = 1024 * 1024 const regularSize = 25 * mib - const overflowSize = 1 * mib + const overflowSize = Number(mib) const readFileMock = vi.fn().mockResolvedValue(Buffer.from('chunk')) lstatMock.mockImplementation(async (p: string) => { @@ -683,7 +683,7 @@ describe('fs:importExternalPaths', () => { ) openMock.mockImplementation(async (p: string) => { const fileIndex = filePaths.indexOf(p) - if (fileIndex >= 0 && fileIndex < filePaths.length - 1) { + if (fileIndex !== -1 && fileIndex < filePaths.length - 1) { return { stat: vi.fn().mockResolvedValue({ size: regularSize, diff --git a/src/main/ipc/filesystem-search-rg-timeout.test.ts b/src/main/ipc/filesystem-search-rg-timeout.test.ts index 9e0a259d2d3..6c677c73d6d 100644 --- a/src/main/ipc/filesystem-search-rg-timeout.test.ts +++ b/src/main/ipc/filesystem-search-rg-timeout.test.ts @@ -20,7 +20,7 @@ const { toWindowsWslPathMock: vi.fn((value: string) => value) })) -const handlers = new Map Promise | unknown>() +const handlers = new Map unknown>() vi.mock('electron', () => ({ ipcMain: { diff --git a/src/main/ipc/filesystem-watcher-dormant-rearm.test.ts b/src/main/ipc/filesystem-watcher-dormant-rearm.test.ts index f557ebca379..7d27d4ca780 100644 --- a/src/main/ipc/filesystem-watcher-dormant-rearm.test.ts +++ b/src/main/ipc/filesystem-watcher-dormant-rearm.test.ts @@ -22,7 +22,7 @@ vi.mock('../providers/ssh-filesystem-dispatch', () => ({ import { closeAllWatchers, registerFilesystemWatcherHandlers } from './filesystem-watcher' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> const WORKTREE_PATH = '/home/me/repo' const ARGS = { worktreePath: WORKTREE_PATH, connectionId: 'conn-1' } diff --git a/src/main/ipc/filesystem-watcher-large-batch.test.ts b/src/main/ipc/filesystem-watcher-large-batch.test.ts index 80d45c90e43..e605cd6167c 100644 --- a/src/main/ipc/filesystem-watcher-large-batch.test.ts +++ b/src/main/ipc/filesystem-watcher-large-batch.test.ts @@ -38,7 +38,7 @@ import { WATCH_BATCH_TRAILING_MS } from '../../shared/filesystem-watch-batch-window' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> describe('local filesystem watcher large batches', () => { const handlers: HandlerMap = {} diff --git a/src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts b/src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts index 0e15ad43fc7..eb161c1ba30 100644 --- a/src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts +++ b/src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts @@ -47,7 +47,7 @@ import { subscribe as subscribeParcelWatcher } from '@parcel/watcher' import { subscribeViaWatcherProcess } from './parcel-watcher-process' import { WatcherProcessFailure } from './parcel-watcher-process-failure' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> describe('local filesystem watcher unsubscribe cleanup', () => { const handlers: HandlerMap = {} diff --git a/src/main/ipc/filesystem-watcher-native-capacity.test.ts b/src/main/ipc/filesystem-watcher-native-capacity.test.ts index 41db2d35e52..670b7eb2af3 100644 --- a/src/main/ipc/filesystem-watcher-native-capacity.test.ts +++ b/src/main/ipc/filesystem-watcher-native-capacity.test.ts @@ -28,7 +28,7 @@ import { WatcherChildCapacityError } from './parcel-watcher-child-registry' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> function fillWatcherChildCapacity(): (() => void)[] { return Array.from({ length: MAX_PHYSICAL_WATCHER_CHILDREN }, () => { diff --git a/src/main/ipc/filesystem-watcher-real.test.ts b/src/main/ipc/filesystem-watcher-real.test.ts index 34f2bcc91e1..a813be69e28 100644 --- a/src/main/ipc/filesystem-watcher-real.test.ts +++ b/src/main/ipc/filesystem-watcher-real.test.ts @@ -26,7 +26,7 @@ vi.mock('electron', () => ({ import { closeAllWatchers, registerFilesystemWatcherHandlers } from './filesystem-watcher' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> type FsChangedCall = { worktreePath: string; events: { kind: string; absolutePath: string }[] } diff --git a/src/main/ipc/filesystem-watcher-remote-batch.test.ts b/src/main/ipc/filesystem-watcher-remote-batch.test.ts index 6d964c2b2d4..8666a40c906 100644 --- a/src/main/ipc/filesystem-watcher-remote-batch.test.ts +++ b/src/main/ipc/filesystem-watcher-remote-batch.test.ts @@ -23,7 +23,7 @@ vi.mock('../providers/ssh-filesystem-dispatch', () => ({ import { closeAllWatchers, registerFilesystemWatcherHandlers } from './filesystem-watcher' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> type WatchCallback = (events: FsChangeEvent[]) => void const WORKTREE_PATH = '/home/me/repo' diff --git a/src/main/ipc/filesystem-watcher-remote-cancellation.test.ts b/src/main/ipc/filesystem-watcher-remote-cancellation.test.ts index 6a67f3a8fb7..60e25db9040 100644 --- a/src/main/ipc/filesystem-watcher-remote-cancellation.test.ts +++ b/src/main/ipc/filesystem-watcher-remote-cancellation.test.ts @@ -19,7 +19,7 @@ vi.mock('../providers/ssh-filesystem-dispatch', () => ({ import { closeAllWatchers, registerFilesystemWatcherHandlers } from './filesystem-watcher' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> describe('remote filesystem watcher cancellation', () => { const handlers: HandlerMap = {} diff --git a/src/main/ipc/filesystem-watcher-remote-rearm.test.ts b/src/main/ipc/filesystem-watcher-remote-rearm.test.ts index 869c8bebf45..0eecfbd82e9 100644 --- a/src/main/ipc/filesystem-watcher-remote-rearm.test.ts +++ b/src/main/ipc/filesystem-watcher-remote-rearm.test.ts @@ -29,7 +29,7 @@ vi.mock('../providers/ssh-filesystem-dispatch', () => ({ import { closeAllWatchers, registerFilesystemWatcherHandlers } from './filesystem-watcher' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> describe('remote filesystem watcher re-arm', () => { const handlers: HandlerMap = {} diff --git a/src/main/ipc/filesystem-watcher-removal-deadline.test.ts b/src/main/ipc/filesystem-watcher-removal-deadline.test.ts index 52a48e9a06d..9eabdb8964e 100644 --- a/src/main/ipc/filesystem-watcher-removal-deadline.test.ts +++ b/src/main/ipc/filesystem-watcher-removal-deadline.test.ts @@ -52,7 +52,7 @@ import { stat } from 'node:fs/promises' import { subscribe as subscribeParcelWatcher } from '@parcel/watcher' import { subscribeViaWatcherProcess } from './parcel-watcher-process' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> describe('local filesystem watcher removal deadline', () => { const handlers: HandlerMap = {} diff --git a/src/main/ipc/filesystem-watcher-terminal-resync.test.ts b/src/main/ipc/filesystem-watcher-terminal-resync.test.ts index 8a08c5f07d5..31e87d7a4c9 100644 --- a/src/main/ipc/filesystem-watcher-terminal-resync.test.ts +++ b/src/main/ipc/filesystem-watcher-terminal-resync.test.ts @@ -28,7 +28,7 @@ import { restoreRemoteWatcherAfterFailedRemoval } from './filesystem-watcher' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> type TerminalErrorHandler = (error: Error) => void const WORKTREE_PATH = '/home/me/repo' diff --git a/src/main/ipc/filesystem-watcher-unwatchable-roots.test.ts b/src/main/ipc/filesystem-watcher-unwatchable-roots.test.ts index d629d74dc02..3473caa8a73 100644 --- a/src/main/ipc/filesystem-watcher-unwatchable-roots.test.ts +++ b/src/main/ipc/filesystem-watcher-unwatchable-roots.test.ts @@ -30,7 +30,7 @@ vi.mock('../providers/ssh-filesystem-dispatch', () => ({ import { closeAllWatchers, registerFilesystemWatcherHandlers } from './filesystem-watcher' import { stat } from 'node:fs/promises' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> describe('filesystem watcher unwatchable root cache', () => { const handlers: HandlerMap = {} diff --git a/src/main/ipc/filesystem-watcher.test.ts b/src/main/ipc/filesystem-watcher.test.ts index 1e9e3cf509a..6fff613e0d9 100644 --- a/src/main/ipc/filesystem-watcher.test.ts +++ b/src/main/ipc/filesystem-watcher.test.ts @@ -60,7 +60,7 @@ import { import { acquireWatcherRemovalGate } from './watcher-removal-gate' import { WATCH_BATCH_TRAILING_MS } from '../../shared/filesystem-watch-batch-window' -type HandlerMap = Record Promise | unknown> +type HandlerMap = Record unknown> /** Remote fs:changed rides the shared debounce window, so drain it before asserting sends. */ const emitRemote = async (onEvents: (e: unknown[]) => void, events: unknown[]) => { diff --git a/src/main/ipc/filesystem.test.ts b/src/main/ipc/filesystem.test.ts index 2c66ab9ad09..f96667751d2 100644 --- a/src/main/ipc/filesystem.test.ts +++ b/src/main/ipc/filesystem.test.ts @@ -3,7 +3,7 @@ import path from 'node:path' import { EventEmitter } from 'node:events' import { beforeEach, describe, expect, it, vi } from 'vitest' -const handlers = new Map Promise | unknown>() +const handlers = new Map unknown>() const { handleMock, showSaveDialogMock, diff --git a/src/main/ipc/notebook.test.ts b/src/main/ipc/notebook.test.ts index d68668038bd..cdffcb8b913 100644 --- a/src/main/ipc/notebook.test.ts +++ b/src/main/ipc/notebook.test.ts @@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { ChildProcessWithoutNullStreams } from 'node:child_process' -const handlers = new Map Promise | unknown>() +const handlers = new Map unknown>() const { spawnMock, handleMock, resolveAuthorizedPathMock } = vi.hoisted(() => ({ spawnMock: vi.fn(), handleMock: vi.fn((channel: string, handler: (event: unknown, args: unknown) => unknown) => { diff --git a/src/main/ipc/runtime-watcher-quarantine-queue.ts b/src/main/ipc/runtime-watcher-quarantine-queue.ts index e326b299943..bc2b4cf5fd4 100644 --- a/src/main/ipc/runtime-watcher-quarantine-queue.ts +++ b/src/main/ipc/runtime-watcher-quarantine-queue.ts @@ -33,7 +33,7 @@ export class RuntimeWatcherQuarantineQueue { } settled = true const index = this.waiters.indexOf(waiter) - if (index >= 0) { + if (index !== -1) { this.waiters.splice(index, 1) } callback() diff --git a/src/main/ipc/ssh-pty-legacy-projection-record.ts b/src/main/ipc/ssh-pty-legacy-projection-record.ts index 45c666dc09a..6d9843d888c 100644 --- a/src/main/ipc/ssh-pty-legacy-projection-record.ts +++ b/src/main/ipc/ssh-pty-legacy-projection-record.ts @@ -117,7 +117,7 @@ export function reclaimProjectionRecord( return } const index = ids.indexOf(id) - if (index >= 0) { + if (index !== -1) { ids.splice(index, 1) } if (ids.length === 0) { diff --git a/src/main/ipc/ssh.ts b/src/main/ipc/ssh.ts index 59ffcfbc9a9..0c504c5f0a0 100644 --- a/src/main/ipc/ssh.ts +++ b/src/main/ipc/ssh.ts @@ -177,7 +177,7 @@ export async function requestActiveSshAiVaultSessionList( targetId: string, params: SshAiVaultRelayListParams, options: { signal?: AbortSignal; timeoutMs?: number } = {} -): Promise { +): Promise { if (isRuntimeOwnedSshTargetId(targetId)) { return null } @@ -192,7 +192,7 @@ export async function requestActiveSshAiVaultSessionTitles( targetId: string, params: SshAiVaultRelayTitleParams, options: { signal?: AbortSignal; timeoutMs?: number } = {} -): Promise { +): Promise { if (isRuntimeOwnedSshTargetId(targetId)) { return null } diff --git a/src/main/ipc/workspace-ports.test.ts b/src/main/ipc/workspace-ports.test.ts index f5afe03628b..9c847b14ad8 100644 --- a/src/main/ipc/workspace-ports.test.ts +++ b/src/main/ipc/workspace-ports.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import type { WorkspacePort, WorkspacePortScanResult } from '../../shared/workspace-ports' -const handlers = new Map Promise | unknown>() +const handlers = new Map unknown>() const { handleMock, removeHandlerMock, scanWorkspacePortsMock, processKillMock } = vi.hoisted( () => ({ handleMock: vi.fn(), diff --git a/src/main/ipc/worktree-common-git-directory.ts b/src/main/ipc/worktree-common-git-directory.ts index a2ec6b0e990..7fb464f3c94 100644 --- a/src/main/ipc/worktree-common-git-directory.ts +++ b/src/main/ipc/worktree-common-git-directory.ts @@ -26,7 +26,7 @@ function isFileStat(value: GitDirectoryStat): boolean { function runtimeDirname(pathValue: string): string { const normalized = normalizeRuntimePathSeparators(pathValue).replace(/\/+$/, '') const index = normalized.lastIndexOf('/') - if (index < 0) { + if (index === -1) { return '.' } if (index === 0) { diff --git a/src/main/ipc/worktree-git-common-polling.ts b/src/main/ipc/worktree-git-common-polling.ts index dae122943a3..86cc06e1420 100644 --- a/src/main/ipc/worktree-git-common-polling.ts +++ b/src/main/ipc/worktree-git-common-polling.ts @@ -74,7 +74,7 @@ async function snapshotGitCommon( previous?: GitCommonSnapshot, includePrimary = true, forceFullScan = false, - statusRefPaths: Set = new Set() + statusRefPaths = new Set() ): Promise { const worktreesDir = join(commonDirPath, 'worktrees') const [worktreesDirSignature, primarySignatures, statusRefSignatures] = await Promise.all([ diff --git a/src/main/jira/attachment-images.ts b/src/main/jira/attachment-images.ts index ce2e682b872..d1cfb2ab079 100644 --- a/src/main/jira/attachment-images.ts +++ b/src/main/jira/attachment-images.ts @@ -191,7 +191,7 @@ export function createMediaMarkdownResolver( } const index = queue.findIndex((entry) => entry.id === image.id) // Why: already-consumed images must not re-emit; fall through to positional pairing. - if (index < 0) { + if (index === -1) { return null } queue.splice(index, 1) diff --git a/src/main/jira/client.ts b/src/main/jira/client.ts index 70f45628461..cd3497fb2be 100644 --- a/src/main/jira/client.ts +++ b/src/main/jira/client.ts @@ -61,7 +61,7 @@ export function acquire(signal?: AbortSignal): Promise { signal, onAbort: () => { const index = queue.indexOf(entry) - if (index < 0) { + if (index === -1) { return } queue.splice(index, 1) diff --git a/src/main/keybindings/keybinding-file.ts b/src/main/keybindings/keybinding-file.ts index 4d41a42ba87..7a357010406 100644 --- a/src/main/keybindings/keybinding-file.ts +++ b/src/main/keybindings/keybinding-file.ts @@ -221,7 +221,7 @@ function removeConflictingOverrides( for (const conflict of conflicts) { for (const actionId of conflict.actionIds) { - if (Object.prototype.hasOwnProperty.call(next, actionId)) { + if (Object.hasOwn(next, actionId)) { conflictingOverrides.add(actionId) } } @@ -346,8 +346,8 @@ export function seedLegacyTabSwitchBindings( // common + active-platform sections directly so the seed never replaces it. const toSeed = actionIds.filter( (actionId) => - !Object.prototype.hasOwnProperty.call(current.commonOverrides, actionId) && - !Object.prototype.hasOwnProperty.call(activePlatformOverrides, actionId) + !Object.hasOwn(current.commonOverrides, actionId) && + !Object.hasOwn(activePlatformOverrides, actionId) ) if (toSeed.length === 0) { return { seeded: false, snapshot: current } diff --git a/src/main/linear/issue-context-client.ts b/src/main/linear/issue-context-client.ts index 1f4863d235a..c9e5085f3bb 100644 --- a/src/main/linear/issue-context-client.ts +++ b/src/main/linear/issue-context-client.ts @@ -43,7 +43,7 @@ export type ResolvedIssue = { export async function searchLinearIssuesForAgents(args: { query: string limit?: number - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' }): Promise { const limit = clampLinearSearchLimit(args.limit) const workspaceId = resolveSearchWorkspaceId(args.workspaceId) @@ -144,14 +144,18 @@ function getExplicitClientEntries(workspaceId?: string): { } } -function resolveSearchWorkspaceId(workspaceId?: string | 'all'): string | 'all' | undefined { +function resolveSearchWorkspaceId( + workspaceId?: (string & {}) | 'all' +): (string & {}) | 'all' | undefined { if (!workspaceId || workspaceId === 'all') { return workspaceId } return resolveWorkspaceSelector({ workspaceId }, getConnectedWorkspaces())?.id ?? workspaceId } -function throwIfExplicitWorkspaceHasConnectedAlternatives(workspaceId?: string | 'all'): void { +function throwIfExplicitWorkspaceHasConnectedAlternatives( + workspaceId?: (string & {}) | 'all' +): void { if (!workspaceId || workspaceId === 'all') { return } @@ -169,7 +173,7 @@ function throwIfExplicitWorkspaceHasConnectedAlternatives(workspaceId?: string | export async function withLinearRead( entry: LinearClientForWorkspace, read: () => Promise, - selection?: string | 'all' + selection?: (string & {}) | 'all' ): Promise { void selection await acquire() @@ -206,7 +210,7 @@ async function readIssueWorkspace( async function readIssueWorkspaces( entries: LinearClientForWorkspace[], identifier: string, - selection: string | 'all', + selection: (string & {}) | 'all', initialFailures: WorkspaceReadFailure[] = [] ): Promise { if (selection !== 'all') { @@ -243,7 +247,7 @@ async function readSearchWorkspace( entry: LinearClientForWorkspace, query: string, limit: number, - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' ): Promise { const response = await withLinearRead( entry, @@ -269,7 +273,7 @@ async function readSearchWorkspaces( entries: LinearClientForWorkspace[], query: string, limit: number, - workspaceId?: string | 'all', + workspaceId?: (string & {}) | 'all', initialFailures: WorkspaceReadFailure[] = [] ): Promise<{ results: LinearSearchIssueSummary[][]; failures: WorkspaceReadFailure[] }> { if (workspaceId && workspaceId !== 'all') { diff --git a/src/main/linear/mcp-issue-list.ts b/src/main/linear/mcp-issue-list.ts index 3906442879e..09c5fff3336 100644 --- a/src/main/linear/mcp-issue-list.ts +++ b/src/main/linear/mcp-issue-list.ts @@ -106,7 +106,7 @@ export async function listMcpIssues( } } -function getIssueListEntries(workspaceId?: string | 'all'): { +function getIssueListEntries(workspaceId?: (string & {}) | 'all'): { entries: LinearClientForWorkspace[] failures: WorkspaceReadFailure[] } { diff --git a/src/main/memory/windows-process-resource-collector.ts b/src/main/memory/windows-process-resource-collector.ts index d73ae95b8c9..527c4678fc9 100644 --- a/src/main/memory/windows-process-resource-collector.ts +++ b/src/main/memory/windows-process-resource-collector.ts @@ -286,7 +286,7 @@ export function parseTypeperfProcessOutput(stdout: string): WindowsProcessResour function parseTypeperfCounterPath(path: string): { instance: string; counter: string } | null { const processStart = path.lastIndexOf('\\Process(') const counterStart = path.lastIndexOf(')\\') - if (processStart < 0 || counterStart <= processStart + 9) { + if (processStart === -1 || counterStart <= processStart + 9) { return null } return { diff --git a/src/main/minimax/minimax-cookie-store.ts b/src/main/minimax/minimax-cookie-store.ts index 3864f892bfb..1b98a1a81c6 100644 --- a/src/main/minimax/minimax-cookie-store.ts +++ b/src/main/minimax/minimax-cookie-store.ts @@ -33,7 +33,7 @@ function decodeCookieEnvelope(raw: Buffer): MiniMaxCookieEnvelope | null { } const rest = text.slice(COOKIE_ENVELOPE_PREFIX.length) const separator = rest.indexOf(':') - if (separator < 0) { + if (separator === -1) { throw new Error('MiniMax session cookie could not be decrypted') } const kind = rest.slice(0, separator) diff --git a/src/main/native-chat/transcript-tail-reader.ts b/src/main/native-chat/transcript-tail-reader.ts index c7b1527858a..fd590855ac7 100644 --- a/src/main/native-chat/transcript-tail-reader.ts +++ b/src/main/native-chat/transcript-tail-reader.ts @@ -202,7 +202,7 @@ async function findLastCompleteLineEnd( const { bytesRead } = await handle.read(buffer, 0, buffer.length, start) signal?.throwIfAborted() const newline = buffer.subarray(0, bytesRead).lastIndexOf(0x0a) - if (newline >= 0) { + if (newline !== -1) { return start + newline + 1 } cursor = start diff --git a/src/main/network/macos-tailscale-dns-diagnostic.ts b/src/main/network/macos-tailscale-dns-diagnostic.ts index 5ec5501525e..a4dc948c0bd 100644 --- a/src/main/network/macos-tailscale-dns-diagnostic.ts +++ b/src/main/network/macos-tailscale-dns-diagnostic.ts @@ -19,7 +19,7 @@ const NETWORK_LOOKUP_FAILURE_RE = function globalDnsSection(scutilOutput: string): string { const scopedStart = scutilOutput.indexOf('\nDNS configuration (for scoped queries)') - return scopedStart >= 0 ? scutilOutput.slice(0, scopedStart) : scutilOutput + return scopedStart !== -1 ? scutilOutput.slice(0, scopedStart) : scutilOutput } export function parseMacTailscaleDnsDiagnostic(scutilOutput: string): DnsDiagnostic | null { diff --git a/src/main/observability/bundle.test.ts b/src/main/observability/bundle.test.ts index 347e1025893..3c6644a7d43 100644 --- a/src/main/observability/bundle.test.ts +++ b/src/main/observability/bundle.test.ts @@ -35,7 +35,7 @@ function makeSpan(overrides: Record = {}): Record): RedactableSpan { kind: 'internal', startTimeUnixNano: '1000', endTimeUnixNano: '2000', - durationMs: 1.0, + durationMs: 1, attributes: {}, events: [], exit: { _tag: 'Success' }, diff --git a/src/main/observability/redactor.ts b/src/main/observability/redactor.ts index cd5570f8577..da8feb0a8b0 100644 --- a/src/main/observability/redactor.ts +++ b/src/main/observability/redactor.ts @@ -123,7 +123,7 @@ export function redactString(input: string): string { export function redactValue( value: unknown, mode: RedactorMode = 'client', - seen: WeakSet = new WeakSet() + seen = new WeakSet() ): unknown { if (value === null || value === undefined) { return value diff --git a/src/main/persistence.ts b/src/main/persistence.ts index c15cab49aad..14565e4a698 100644 --- a/src/main/persistence.ts +++ b/src/main/persistence.ts @@ -673,11 +673,8 @@ function migrateTerminalScrollbackRows(settings: unknown): { needsSave: boolean } { const legacySettings = readLegacyTerminalScrollbackSettings(settings) - const hasRows = Object.prototype.hasOwnProperty.call(legacySettings, 'terminalScrollbackRows') - const hasLegacyBytes = Object.prototype.hasOwnProperty.call( - legacySettings, - 'terminalScrollbackBytes' - ) + const hasRows = Object.hasOwn(legacySettings, 'terminalScrollbackRows') + const hasLegacyBytes = Object.hasOwn(legacySettings, 'terminalScrollbackBytes') const rows = hasRows ? normalizeDesktopTerminalScrollbackRows(legacySettings.terminalScrollbackRows) : legacyTerminalScrollbackBytesToRows(legacySettings.terminalScrollbackBytes) @@ -1685,7 +1682,7 @@ type LayoutLeafNormalization = { function collectLayoutLeafCounts( node: TerminalPaneLayoutNode, - counts: Map = new Map() + counts = new Map() ): Map { if (node.type === 'leaf') { counts.set(node.leafId, (counts.get(node.leafId) ?? 0) + 1) @@ -7226,14 +7223,14 @@ export class Store { (entry) => entry.targetId === normalizedLease.targetId && entry.ptyId === normalizedLease.ptyId ) - const existing = existingIndex >= 0 ? this.state.sshRemotePtyLeases[existingIndex] : undefined + const existing = existingIndex !== -1 ? this.state.sshRemotePtyLeases[existingIndex] : undefined const next: SshRemotePtyLease = { ...existing, ...normalizedLease, createdAt: existing?.createdAt ?? normalizedLease.createdAt ?? now, updatedAt: normalizedLease.updatedAt ?? now } - if (existingIndex >= 0) { + if (existingIndex !== -1) { this.state.sshRemotePtyLeases[existingIndex] = next } else { this.state.sshRemotePtyLeases.push(next) diff --git a/src/main/pi/legacy-omp-overlay-migration.ts b/src/main/pi/legacy-omp-overlay-migration.ts index 88baf78ea9f..74d6039fcb5 100644 --- a/src/main/pi/legacy-omp-overlay-migration.ts +++ b/src/main/pi/legacy-omp-overlay-migration.ts @@ -146,7 +146,7 @@ function copyMissingLegacyOmpOverlayEntries( overlayDir: string, sourceAgentDir: string, pathSegments: string[] = [], - copiedFilePaths: Set = new Set() + copiedFilePaths = new Set() ): boolean { let completed = true let entries: Dirent[] diff --git a/src/main/plugins/plugin-dev-watcher.ts b/src/main/plugins/plugin-dev-watcher.ts index c6aefa12699..6e7f6f908f0 100644 --- a/src/main/plugins/plugin-dev-watcher.ts +++ b/src/main/plugins/plugin-dev-watcher.ts @@ -98,7 +98,7 @@ export class PluginDevWatcher { private removeSubscription(subscription: WatcherProcessSubscription): void { const index = this.subscriptions.indexOf(subscription) - if (index >= 0) { + if (index !== -1) { this.subscriptions.splice(index, 1) } } diff --git a/src/main/plugins/plugin-host-runtime.ts b/src/main/plugins/plugin-host-runtime.ts index 2a2f5f9fcab..b7f3eb9b8a1 100644 --- a/src/main/plugins/plugin-host-runtime.ts +++ b/src/main/plugins/plugin-host-runtime.ts @@ -20,7 +20,7 @@ export type PluginHostCallError = Error & { code?: string } export type PluginWorkerOrcaApi = { /** Register the handler for a command declared in the manifest. */ commands: { - register(commandId: string, handler: (args: unknown) => unknown | Promise): void + register(commandId: string, handler: (args: unknown) => unknown): void } /** Handle an event the manifest subscribed to (`contributes.events`). */ events: { @@ -55,7 +55,7 @@ export function createPluginWorkerRuntime( const send = options.send const importModule = options.importModule ?? ((specifier: string) => import(specifier)) const exit = options.exit ?? ((code: number) => process.exit(code)) - const commandHandlers = new Map unknown | Promise>() + const commandHandlers = new Map unknown>() const eventHandlers = new Map void | Promise)[]>() const pendingHostCalls = new Map< number, @@ -64,7 +64,7 @@ export function createPluginWorkerRuntime( let nextHostCallId = 0 let initialized = false let shuttingDown = false - let deactivate: (() => unknown | Promise) | null = null + let deactivate: (() => unknown) | null = null async function handleInit(input: { pluginRoot: string @@ -88,7 +88,7 @@ export function createPluginWorkerRuntime( if (module.deactivate !== undefined && typeof module.deactivate !== 'function') { throw new Error(`plugin entry ${input.mainEntry} has a non-function deactivate export`) } - deactivate = (module.deactivate as (() => unknown | Promise) | undefined) ?? null + deactivate = (module.deactivate as (() => unknown) | undefined) ?? null const orca: PluginWorkerOrcaApi = { commands: { register(commandId, handler) { diff --git a/src/main/plugins/plugin-worker-output-buffer.ts b/src/main/plugins/plugin-worker-output-buffer.ts index dfbe0478c28..836330c8799 100644 --- a/src/main/plugins/plugin-worker-output-buffer.ts +++ b/src/main/plugins/plugin-worker-output-buffer.ts @@ -34,7 +34,7 @@ export function pipePluginWorkerOutput( while (remaining.length > 0) { if (discarding) { const newline = remaining.indexOf('\n') - if (newline < 0) { + if (newline === -1) { return } discarding = false @@ -42,20 +42,20 @@ export function pipePluginWorkerOutput( continue } const newline = remaining.indexOf('\n') - const segment = newline < 0 ? remaining : remaining.slice(0, newline) + const segment = newline === -1 ? remaining : remaining.slice(0, newline) const available = PLUGIN_WORKER_OUTPUT_LINE_LIMIT - buffered.length if (segment.length > available) { emit(buffered + segment.slice(0, available), true) buffered = '' - discarding = newline < 0 + discarding = newline === -1 } else { buffered += segment - if (newline >= 0) { + if (newline !== -1) { emit(buffered) buffered = '' } } - if (newline < 0) { + if (newline === -1) { return } remaining = remaining.slice(newline + 1) diff --git a/src/main/plugins/plugin-worker-slot-pool.ts b/src/main/plugins/plugin-worker-slot-pool.ts index 5b86acf38d7..7cb6c79d6f1 100644 --- a/src/main/plugins/plugin-worker-slot-pool.ts +++ b/src/main/plugins/plugin-worker-slot-pool.ts @@ -49,7 +49,7 @@ export class PluginWorkerSlotPool { } cancelled = true const index = this.waiters.indexOf(waiter) - if (index >= 0) { + if (index !== -1) { this.waiters.splice(index, 1) } signal.removeEventListener('abort', waiter.onAbort) diff --git a/src/main/providers/windows-foreground-process-rows.ts b/src/main/providers/windows-foreground-process-rows.ts index 7e4cdd2e48d..d5e8f8185cd 100644 --- a/src/main/providers/windows-foreground-process-rows.ts +++ b/src/main/providers/windows-foreground-process-rows.ts @@ -112,7 +112,7 @@ function parseWindowsProcessValueRows(stdout: string): WindowsProcessRow[] { continue } const eq = line.indexOf('=') - if (eq < 0) { + if (eq === -1) { continue } const key = line.slice(0, eq) diff --git a/src/main/pty/posix-pty-foreground-group.ts b/src/main/pty/posix-pty-foreground-group.ts index 124978c269d..8b3e375dbff 100644 --- a/src/main/pty/posix-pty-foreground-group.ts +++ b/src/main/pty/posix-pty-foreground-group.ts @@ -103,7 +103,7 @@ export function getPosixPtyForegroundGroup( export function signalPosixPtyForegroundGroup( rootPid: number, ptsName: string | undefined, - signal: NodeJS.Signals | string, + signal: NodeJS.Signals | (string & {}), fallback: () => void, deps: PosixPtyForegroundGroupDeps = {} ): void { diff --git a/src/main/rate-limits/minimax-request-context.ts b/src/main/rate-limits/minimax-request-context.ts index 34dcd47130b..ecda8329a2e 100644 --- a/src/main/rate-limits/minimax-request-context.ts +++ b/src/main/rate-limits/minimax-request-context.ts @@ -45,7 +45,7 @@ function parseCookiePairs(cookie: string): { name: string; value: string }[] { .map((part) => { const normalizedPart = part.replace(/^Cookie:\s*/i, '') const eq = normalizedPart.indexOf('=') - if (eq < 0) { + if (eq === -1) { return null } return { diff --git a/src/main/rate-limits/opencode-go-usage-fetcher.ts b/src/main/rate-limits/opencode-go-usage-fetcher.ts index 934d1e72b42..f2ea68b7588 100644 --- a/src/main/rate-limits/opencode-go-usage-fetcher.ts +++ b/src/main/rate-limits/opencode-go-usage-fetcher.ts @@ -47,7 +47,7 @@ function parseAuthCookies(raw: string): { name: string; value: string }[] { .map((p) => p.trim()) .map((pair) => { const eq = pair.indexOf('=') - if (eq < 0) { + if (eq === -1) { return null } const name = pair.slice(0, eq).trim() diff --git a/src/main/runtime/device-registry.ts b/src/main/runtime/device-registry.ts index 88818b3cb1d..b5f0c7a6c8d 100644 --- a/src/main/runtime/device-registry.ts +++ b/src/main/runtime/device-registry.ts @@ -162,7 +162,7 @@ export class DeviceRegistry { setRelayBinding(deviceId: string, binding: RelayDeviceBinding): boolean { const index = this.devices.findIndex((candidate) => candidate.deviceId === deviceId) - if (index < 0 || binding.relayDeviceId !== deviceId) { + if (index === -1 || binding.relayDeviceId !== deviceId) { return false } const nextDevices = this.devices.map((device, candidateIndex) => @@ -175,7 +175,7 @@ export class DeviceRegistry { setMobilePairingConnectionMode(deviceId: string, mode: MobilePairingConnectionMode): boolean { const index = this.devices.findIndex((candidate) => candidate.deviceId === deviceId) - if (index < 0 || this.devices[index]?.scope !== 'mobile') { + if (index === -1 || this.devices[index]?.scope !== 'mobile') { return false } // Why: persist before swapping memory so a failed write does not leave a @@ -208,7 +208,7 @@ export class DeviceRegistry { updateLastSeen(deviceId: string): void { const index = this.devices.findIndex((d) => d.deviceId === deviceId) - if (index < 0) { + if (index === -1) { return } // Why: persist before memory swap so a failed write cannot leave a scanned @@ -230,7 +230,7 @@ export class DeviceRegistry { */ updateLastSeenDeferred(deviceId: string): void { const index = this.devices.findIndex((d) => d.deviceId === deviceId) - if (index < 0) { + if (index === -1) { return } if (this.devices[index]!.lastSeenAt === 0) { diff --git a/src/main/runtime/fetch-remote-cache.test.ts b/src/main/runtime/fetch-remote-cache.test.ts index 14c18115636..fc2d761c059 100644 --- a/src/main/runtime/fetch-remote-cache.test.ts +++ b/src/main/runtime/fetch-remote-cache.test.ts @@ -66,7 +66,7 @@ function fullRemoteFetchOptions(cwd: string): { cwd: string; timeout: number } { return { cwd, timeout: 60_000 } } -function mockFetchResults(results: (Promise | unknown)[]): void { +function mockFetchResults(results: unknown[]): void { let fetchIndex = 0 gitExecFileAsyncMock.mockImplementation((argv: string[]) => { if (argv[0] === 'rev-parse') { diff --git a/src/main/runtime/linear-save-issue.test.ts b/src/main/runtime/linear-save-issue.test.ts index add8a54d1ab..eb8ed4093d2 100644 --- a/src/main/runtime/linear-save-issue.test.ts +++ b/src/main/runtime/linear-save-issue.test.ts @@ -23,7 +23,7 @@ const issue = { type SaveIssueInternals = { resolveLinearAssignee(input: string, teamId: string, workspaceId: string): Promise - resolveLinearAgentState(input: string, states: unknown[]): unknown | null + resolveLinearAgentState(input: string, states: unknown[]): unknown buildLinearSaveUpdate( params: { labels?: string[] }, current: typeof issue, diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index 0b234b24160..4f285c690a1 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -23664,8 +23664,7 @@ export class OrcaRuntimeService { this.invalidateWorktreeScanCacheForRepo(worktree.repoId) } const shouldClearPushTarget = - Object.prototype.hasOwnProperty.call(metaUpdates, 'pushTarget') && - metaUpdates.pushTarget === null + Object.hasOwn(metaUpdates, 'pushTarget') && metaUpdates.pushTarget === null const normalizedMetaUpdates: Partial = shouldClearPushTarget ? { ...metaUpdates, pushTarget: undefined } : (metaUpdates as Partial) @@ -27788,7 +27787,7 @@ export class OrcaRuntimeService { committedPtyIds, terminalHandlesByPtyId }) - if (failedStopIndex >= 0) { + if (failedStopIndex !== -1) { const failedStop = stopResults[failedStopIndex] throw Object.assign(new Error('terminal_worktree_sleep_failed'), { ptyId: orderedLivePtyIds[failedStopIndex], @@ -27815,7 +27814,7 @@ export class OrcaRuntimeService { committedPtyIds, terminalHandlesByPtyId }) - if (failedStopIndex >= 0 && remainingLivePtyIds.size > 0) { + if (failedStopIndex !== -1 && remainingLivePtyIds.size > 0) { const failedStop = stopResults[failedStopIndex] console.error('[runtime] worktree terminal sleep physical stop failed', { worktreeId: worktree.id, @@ -29834,7 +29833,7 @@ export class OrcaRuntimeService { private syncMobileSessionTabs( snapshots: RuntimeMobileSessionTabsSnapshot[] | undefined, unchangedWorktreeIds?: string[], - resyncWorktreeIds: Set = new Set() + resyncWorktreeIds = new Set() ): Set { const changedWorktreeIds = new Set() if (snapshots === undefined) { @@ -32494,7 +32493,7 @@ export class OrcaRuntimeService { linearSearchForAgents(args: { query: string limit?: number - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' }): ReturnType { return searchLinearIssuesForAgents(args) } @@ -32504,7 +32503,7 @@ export class OrcaRuntimeService { } async linearTeamListForAgents(params: { - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' }): Promise { try { const result = await listLinearTeamsForAgent(params.workspaceId) @@ -32583,7 +32582,7 @@ export class OrcaRuntimeService { async linearProjectListForAgents(params: { query?: string limit?: number - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' }): Promise { const limit = clampLinearSearchLimit(params.limit) try { @@ -32622,7 +32621,7 @@ export class OrcaRuntimeService { filter?: LinearIssueListFilter teamInput?: string limit?: number - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' }): Promise { const filter = params.filter ?? 'assigned' const limit = clampLinearIssueListLimit(params.limit) @@ -34305,7 +34304,7 @@ export class OrcaRuntimeService { private async resolveLinearTeamInput( teamInput: string, - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' ): Promise<{ id: string key: string @@ -36213,12 +36212,12 @@ function applyTerminalLineControls(line: string): { hadControl: boolean } { const carriageIndex = line.lastIndexOf('\r') - const latestRedraw = carriageIndex >= 0 ? line.slice(carriageIndex + 1) : line + const latestRedraw = carriageIndex !== -1 ? line.slice(carriageIndex + 1) : line if (!latestRedraw.includes('\u0008') && !latestRedraw.includes('\u001b')) { return { text: latestRedraw, cursorColumn: latestRedraw.length, - hadControl: carriageIndex >= 0 + hadControl: carriageIndex !== -1 } } diff --git a/src/main/runtime/orchestration/db.ts b/src/main/runtime/orchestration/db.ts index 259664c5544..3fdc2b5011f 100644 --- a/src/main/runtime/orchestration/db.ts +++ b/src/main/runtime/orchestration/db.ts @@ -81,7 +81,7 @@ const DISPATCH_PANE_KEY_MATCH_SUFFIX_SQL = function paneKeyMatchSuffix(paneKey: string): string { const colon = paneKey.indexOf(':') - return colon < 0 ? paneKey : paneKey.slice(colon + 1) + return colon === -1 ? paneKey : paneKey.slice(colon + 1) } export type { @@ -285,7 +285,7 @@ type RunListCursor = { // Schema versions: v2 'heartbeat'+last_heartbeat_at, v3 delivered_at, v4 task-creator terminal, v5 task_title/display_name, v6 pane identity, v7 lightweight Runs, v8 crash-safe Run deliveries, v9 durable question threads, v10 Dispatch capabilities, v11 durable mutation receipts, v12 composed worker state, v18 post-v6 version-skew repair, v19 adopted legacy Runs and compatibility receipts, v20 legacy question backfill, v21 legacy scheduler-loss provenance, v22 dispatch assignee lookup, v23 worker terminal resource ownership, v24 creator-incarnation authority, v25 active Dispatch handle lookup, v26 indexed mutation receipt capacity. const SCHEMA_VERSION = 26 -function hardenOrchestrationDatabaseFiles(dbPath: string | ':memory:'): void { +function hardenOrchestrationDatabaseFiles(dbPath: (string & {}) | ':memory:'): void { if (dbPath === ':memory:' || process.platform === 'win32') { // Why: Windows protects these files through Orca's current-user-only userData DACL; POSIX mode bits are inert there. return @@ -308,7 +308,7 @@ export class OrchestrationDb { // per-terminal fan-out. Only createDispatchContext flips this false→true. private hasAnyDispatchContextsCache: boolean | undefined - constructor(dbPath: string | ':memory:') { + constructor(dbPath: (string & {}) | ':memory:') { this.db = new Database(dbPath) this.db.pragma('journal_mode = WAL') this.db.pragma('synchronous = NORMAL') diff --git a/src/main/runtime/orchestration/setup-completion-signal.ts b/src/main/runtime/orchestration/setup-completion-signal.ts index 23e22008387..15c74d2294e 100644 --- a/src/main/runtime/orchestration/setup-completion-signal.ts +++ b/src/main/runtime/orchestration/setup-completion-signal.ts @@ -61,7 +61,7 @@ export function createSetupCompletionScanner( } const combined = `${carry}${data}` const markerIndex = combined.lastIndexOf(expectedPrefix) - if (markerIndex >= 0) { + if (markerIndex !== -1) { const suffix = combined.slice(markerIndex + expectedPrefix.length) const match = suffix.match(/^(-?\d+)\r?\n/) if (match) { diff --git a/src/main/runtime/rpc/core.ts b/src/main/runtime/rpc/core.ts index 8e09e942a08..b415ddd0e71 100644 --- a/src/main/runtime/rpc/core.ts +++ b/src/main/runtime/rpc/core.ts @@ -107,13 +107,13 @@ export type RpcContext = { ) => () => void } -export type RpcHandler = (params: TParams, ctx: RpcContext) => Promise | unknown +export type RpcHandler = (params: TParams, ctx: RpcContext) => unknown // Why: RpcMethod erases the param type; centralizing the cast in defineMethod sidesteps RpcHandler's contravariance. export type RpcMethod = { readonly name: string readonly params: ZodType | null - readonly handler: (params: unknown, ctx: RpcContext) => Promise | unknown + readonly handler: (params: unknown, ctx: RpcContext) => unknown } type DefineMethodSpec = { diff --git a/src/main/runtime/rpc/methods/client-ui.ts b/src/main/runtime/rpc/methods/client-ui.ts index 540333bf5ac..73326756cd9 100644 --- a/src/main/runtime/rpc/methods/client-ui.ts +++ b/src/main/runtime/rpc/methods/client-ui.ts @@ -8,7 +8,7 @@ import { } from './client-ui-schemas' // Type-only side effect: keeps the schema/PersistedUIState parity assertions in // the typecheck graph so drift fails the build instead of a paired client. -import type {} from './ui-state-schema-parity-checks' + import { TerminalQuickCommandsUpdate } from './terminal-quick-command-rpc-schema' export const CLIENT_UI_METHODS: RpcMethod[] = [ diff --git a/src/main/runtime/rpc/methods/linear-agent-access.test.ts b/src/main/runtime/rpc/methods/linear-agent-access.test.ts index 945eadb20b8..56c32168460 100644 --- a/src/main/runtime/rpc/methods/linear-agent-access.test.ts +++ b/src/main/runtime/rpc/methods/linear-agent-access.test.ts @@ -355,7 +355,7 @@ type LinearUnconfirmedBuilder = { target: unknown, extra?: unknown ): Error & { data?: { cause?: string; nextSteps?: string[] } } - resolveLinearAgentState(input: string, states: unknown[]): unknown | null + resolveLinearAgentState(input: string, states: unknown[]): unknown linearCreatedIssueMatchesIntent(issue: unknown, intent: unknown): boolean linearSavedIssueMatchesIntent(issue: unknown, intent: unknown): boolean notifyLinearLinkedIssueUpdated( @@ -372,13 +372,13 @@ type LinearRetryLookupTester = { parentId: string | null, workspaceId: string, required: boolean - ): Promise + ): Promise getMatchingLinearAttachmentWrite( writeId: string, issueId: string, workspaceId: string, required: boolean - ): Promise + ): Promise getMatchingLinearCreatedIssue( writeId: string, teamId: string, @@ -386,7 +386,7 @@ type LinearRetryLookupTester = { workspaceId: string, required: boolean, intent?: unknown - ): Promise + ): Promise refetchLinearCommentAfterDuplicate( writeId: string, issueId: string, diff --git a/src/main/runtime/rpc/methods/linear-agent-project-access.test.ts b/src/main/runtime/rpc/methods/linear-agent-project-access.test.ts index cd51e8a0798..9e0f1568050 100644 --- a/src/main/runtime/rpc/methods/linear-agent-project-access.test.ts +++ b/src/main/runtime/rpc/methods/linear-agent-project-access.test.ts @@ -10,7 +10,7 @@ type LinearProjectResolverTester = { id: string name: string }> - readLinearProjectByIdForCreate(id: string, workspaceId: string): Promise + readLinearProjectByIdForCreate(id: string, workspaceId: string): Promise readLinearProjectsForCreate(query: string, workspaceId: string): Promise readLinearProjectsByExactNameForCreate(name: string, workspaceId: string): Promise } diff --git a/src/main/runtime/rpc/methods/native-chat.ts b/src/main/runtime/rpc/methods/native-chat.ts index 5a15b3a8d8f..3b8d42287de 100644 --- a/src/main/runtime/rpc/methods/native-chat.ts +++ b/src/main/runtime/rpc/methods/native-chat.ts @@ -138,7 +138,7 @@ function sanitizeToolInput( const result: Record = {} let count = 0 for (const key in value) { - if (!Object.prototype.hasOwnProperty.call(value, key)) { + if (!Object.hasOwn(value, key)) { continue } if (count >= MOBILE_TOOL_INPUT_ITEMS_CAP || budget.remaining <= 0) { @@ -148,7 +148,7 @@ function sanitizeToolInput( let boundedKey = key.slice(0, Math.min(key.length, budget.remaining, 128)) // Why: sibling keys sharing a >=128-char (or budget-truncated) prefix collapse // to the same bounded key; suffix collisions so neither field is silently lost. - if (Object.prototype.hasOwnProperty.call(result, boundedKey)) { + if (Object.hasOwn(result, boundedKey)) { boundedKey = `${boundedKey}~${count}` } budget.remaining -= boundedKey.length diff --git a/src/main/runtime/rpc/methods/worktree-schemas.test.ts b/src/main/runtime/rpc/methods/worktree-schemas.test.ts index e1eb22f2c52..5737e2c4c8d 100644 --- a/src/main/runtime/rpc/methods/worktree-schemas.test.ts +++ b/src/main/runtime/rpc/methods/worktree-schemas.test.ts @@ -109,14 +109,14 @@ describe('worktree RPC schemas', () => { const parsed = WorktreeSet.parse({ worktree: 'id:r1::/repos/wt', displayName: '' }) expect(parsed.displayName).toBe('') - expect(Object.prototype.hasOwnProperty.call(parsed, 'displayName')).toBe(true) + expect(Object.hasOwn(parsed, 'displayName')).toBe(true) }) it('still omits a display name that was never sent', () => { const parsed = WorktreeSet.parse({ worktree: 'id:r1::/repos/wt', comment: 'note' }) expect(parsed.displayName).toBeUndefined() - expect(Object.prototype.hasOwnProperty.call(parsed, 'displayName')).toBe(false) + expect(Object.hasOwn(parsed, 'displayName')).toBe(false) }) it('ignores a non-string display name rather than persisting it', () => { diff --git a/src/main/runtime/rpc/orchestration-legacy-compatibility.ts b/src/main/runtime/rpc/orchestration-legacy-compatibility.ts index 291d35b895f..31d3b5b1952 100644 --- a/src/main/runtime/rpc/orchestration-legacy-compatibility.ts +++ b/src/main/runtime/rpc/orchestration-legacy-compatibility.ts @@ -206,7 +206,7 @@ export class OrchestrationLegacyCompatibility { request: RpcRequest, params: unknown, signal?: AbortSignal - ): Promise { + ): Promise { if (request.method === 'orchestration.send') { return await handleLegacyLifecycleSend({ runtime: this.runtime, diff --git a/src/main/runtime/rpc/orchestration-legacy-lifecycle.ts b/src/main/runtime/rpc/orchestration-legacy-lifecycle.ts index 9ffe17cca6e..7e76128bd19 100644 --- a/src/main/runtime/rpc/orchestration-legacy-lifecycle.ts +++ b/src/main/runtime/rpc/orchestration-legacy-lifecycle.ts @@ -17,7 +17,7 @@ export async function handleLegacyLifecycleSend(args: { authority: LegacyCompatibilityAuthority request: RpcRequest params: LegacySendParams -}): Promise { +}): Promise { const { runtime, authority, request, params } = args if (!['heartbeat', 'worker_done', 'escalation'].includes(params.type ?? '')) { return undefined diff --git a/src/main/runtime/rpc/orchestration-legacy-mail.ts b/src/main/runtime/rpc/orchestration-legacy-mail.ts index e41e0c11509..c83d7ffdf2f 100644 --- a/src/main/runtime/rpc/orchestration-legacy-mail.ts +++ b/src/main/runtime/rpc/orchestration-legacy-mail.ts @@ -20,7 +20,7 @@ export async function handleLegacyCheck(args: { request: RpcRequest params: LegacyCheckParams signal?: AbortSignal -}): Promise { +}): Promise { const { runtime, authority, request, params, signal } = args if (params.run) { return undefined @@ -138,7 +138,7 @@ export async function handleLegacyReply(args: { authority: LegacyCompatibilityAuthority request: RpcRequest params: LegacyReplyParams -}): Promise { +}): Promise { const { runtime, authority, request, params } = args const db = runtime.getOrchestrationDb() const original = db.getMessageById(params.id) diff --git a/src/main/runtime/rpc/orchestration-legacy-question.ts b/src/main/runtime/rpc/orchestration-legacy-question.ts index 69dec720a3a..cd463155619 100644 --- a/src/main/runtime/rpc/orchestration-legacy-question.ts +++ b/src/main/runtime/rpc/orchestration-legacy-question.ts @@ -17,7 +17,7 @@ export async function handleLegacyAsk(args: { request: RpcRequest params: LegacyAskParams signal?: AbortSignal -}): Promise { +}): Promise { const { runtime, authority, request, params, signal } = args const dispatch = authority.resolveAskDispatch(request, params) if (!dispatch || dispatch.contract_version !== LEGACY_CONTRACT_VERSION) { diff --git a/src/main/runtime/rpc/orchestration-mutation-executor.ts b/src/main/runtime/rpc/orchestration-mutation-executor.ts index 2334cd709eb..701970eb58c 100644 --- a/src/main/runtime/rpc/orchestration-mutation-executor.ts +++ b/src/main/runtime/rpc/orchestration-mutation-executor.ts @@ -22,7 +22,7 @@ export class OrchestrationMutationExecutor { async run( request: RpcRequest, params: unknown, - invoke: (mutation?: DurableMutationInvocation) => Promise | unknown, + invoke: (mutation?: DurableMutationInvocation) => unknown, callerFingerprintOverride?: string ): Promise { const requestId = request.orchestrationRequestId diff --git a/src/main/runtime/rpc/orchestration-mutation-ledger.test.ts b/src/main/runtime/rpc/orchestration-mutation-ledger.test.ts index 4062dacf9a1..a9cbfbc5ab7 100644 --- a/src/main/runtime/rpc/orchestration-mutation-ledger.test.ts +++ b/src/main/runtime/rpc/orchestration-mutation-ledger.test.ts @@ -38,7 +38,7 @@ describe('durable orchestration mutation ledger', () => { } }) - function createHarness(dbPath: string | ':memory:' = ':memory:') { + function createHarness(dbPath: (string & {}) | ':memory:' = ':memory:') { const db = new OrchestrationDb(dbPath) const runtime = new OrcaRuntimeService() runtime.setOrchestrationDb(db) diff --git a/src/main/runtime/runtime-rpc.test.ts b/src/main/runtime/runtime-rpc.test.ts index 2df636da4c0..5ccffe24550 100644 --- a/src/main/runtime/runtime-rpc.test.ts +++ b/src/main/runtime/runtime-rpc.test.ts @@ -6026,7 +6026,7 @@ describe('OrcaRuntimeRpcServer WebSocket bind host (STA-2370)', () => { const wideStartGate = new Promise((resolve) => { releaseWideStart = resolve }) - let wideStopSpy: ReturnType | null = null + let wideStopSpy: ReturnType = null vi.spyOn(target, 'startWebSocketTransport').mockImplementation(async (opts) => { if (opts.host === '0.0.0.0') { // Why: hold the widen mid-flight (loopback already stopped) so stop() must race the rebind. diff --git a/src/main/runtime/runtime-rpc.ts b/src/main/runtime/runtime-rpc.ts index b783382c544..326801c6fa9 100644 --- a/src/main/runtime/runtime-rpc.ts +++ b/src/main/runtime/runtime-rpc.ts @@ -1409,7 +1409,7 @@ export class OrcaRuntimeRpcServer { // never orphan a running 0.0.0.0 listener outside activeTransports (and thus outside stop()). this.activeTransports[index] = widened.transport const metaIndex = this.transports.findIndex((meta) => meta.kind === 'websocket') - if (metaIndex >= 0) { + if (metaIndex !== -1) { this.transports[metaIndex] = { kind: 'websocket', endpoint: widened.endpoint } } try { @@ -1451,7 +1451,7 @@ export class OrcaRuntimeRpcServer { ) this.activeTransports.splice(index, 1) const metaIndex = this.transports.findIndex((meta) => meta.kind === 'websocket') - if (metaIndex >= 0) { + if (metaIndex !== -1) { this.transports.splice(metaIndex, 1) } this.wsBoundHost = null @@ -1466,7 +1466,7 @@ export class OrcaRuntimeRpcServer { // orphan a live transport outside activeTransports (and thus outside stop()). this.activeTransports[index] = restored.transport const metaIndex = this.transports.findIndex((meta) => meta.kind === 'websocket') - if (metaIndex >= 0) { + if (metaIndex !== -1) { this.transports[metaIndex] = { kind: 'websocket', endpoint: restored.endpoint } } try { diff --git a/src/main/runtime/windows-firewall-remote-scope.ts b/src/main/runtime/windows-firewall-remote-scope.ts index 9845413d079..6968435a8aa 100644 --- a/src/main/runtime/windows-firewall-remote-scope.ts +++ b/src/main/runtime/windows-firewall-remote-scope.ts @@ -109,7 +109,7 @@ function maskPrefixLength(maskText: string, version: IpVersion): number | null { function parseIpRange(scope: string): IpRange | null { const trimmed = scope.trim() const dashIndex = trimmed.indexOf('-') - if (dashIndex >= 0) { + if (dashIndex !== -1) { if (dashIndex !== trimmed.lastIndexOf('-')) { return null } @@ -122,7 +122,7 @@ function parseIpRange(scope: string): IpRange | null { } const slashIndex = trimmed.indexOf('/') - if (slashIndex >= 0) { + if (slashIndex !== -1) { if (slashIndex !== trimmed.lastIndexOf('/')) { return null } @@ -195,7 +195,7 @@ function expandEmbeddedIpv4(address: string): string | null { } const lastColon = address.lastIndexOf(':') const ipv4 = parseIpv4(address.slice(lastColon + 1)) - if (lastColon < 0 || !ipv4) { + if (lastColon === -1 || !ipv4) { return null } const high = ((ipv4.value >> 16n) & 0xffffn).toString(16) diff --git a/src/main/runtime/workspace-session-failed-write-rollback.ts b/src/main/runtime/workspace-session-failed-write-rollback.ts index 9460c27b6ed..f770511bb27 100644 --- a/src/main/runtime/workspace-session-failed-write-rollback.ts +++ b/src/main/runtime/workspace-session-failed-write-rollback.ts @@ -2,7 +2,7 @@ import { isDeepStrictEqual } from 'node:util' import type { WorkspaceSessionState } from '../../shared/types' const MISSING = Symbol('missing') -type RollbackValue = unknown | typeof MISSING +type RollbackValue = unknown function isRecord(value: RollbackValue): value is Record { return ( diff --git a/src/main/source-control/forge-provider.ts b/src/main/source-control/forge-provider.ts index 775943052da..a3aef433110 100644 --- a/src/main/source-control/forge-provider.ts +++ b/src/main/source-control/forge-provider.ts @@ -66,7 +66,7 @@ export type ForgeReviewByNumberInput = ForgeProviderRepositoryContext & { export type ForgeProvider = { id: ForgeProviderId supportsReviewCreation: boolean - resolveRepository(context: ForgeProviderRepositoryContext): Promise + resolveRepository(context: ForgeProviderRepositoryContext): Promise getReviewForBranch(input: ForgeReviewForBranchInput): Promise getReviewByNumber(input: ForgeReviewByNumberInput): Promise createReview?( diff --git a/src/main/source-control/stacked-hosted-review-creation.ts b/src/main/source-control/stacked-hosted-review-creation.ts index 0a36588f493..53327cbd404 100644 --- a/src/main/source-control/stacked-hosted-review-creation.ts +++ b/src/main/source-control/stacked-hosted-review-creation.ts @@ -38,9 +38,8 @@ export async function createStackedHostedReview( url: created.existingReview.url } } - } else { - currentReview = { number: created.number, url: created.url } } + currentReview = { number: created.number, url: created.url } } if (!currentReview) { return { diff --git a/src/main/ssh/sftp-namespace-resolution.test.ts b/src/main/ssh/sftp-namespace-resolution.test.ts index 98ba2637c7d..5239e834152 100644 --- a/src/main/ssh/sftp-namespace-resolution.test.ts +++ b/src/main/ssh/sftp-namespace-resolution.test.ts @@ -35,10 +35,7 @@ const MARKER_STATS = { size: 0 } -function makeSftp(options: { - startPath?: string | Error | unknown - lstat?: (path: string) => LstatOutcome -}): { +function makeSftp(options: { startPath?: unknown; lstat?: (path: string) => LstatOutcome }): { sftp: SFTPWrapper realpathCalls: string[] lstatCalls: string[] diff --git a/src/main/ssh/ssh-channel-multiplexer.ts b/src/main/ssh/ssh-channel-multiplexer.ts index 0000f2f353b..d87a7ad8f5b 100644 --- a/src/main/ssh/ssh-channel-multiplexer.ts +++ b/src/main/ssh/ssh-channel-multiplexer.ts @@ -39,7 +39,7 @@ export type SshMultiplexerRequestOptions = { export type NotificationHandler = (method: string, params: Record) => void export type MethodNotificationHandler = (params: Record) => void -export type RequestHandler = (params: Record) => Promise | unknown +export type RequestHandler = (params: Record) => unknown export type MultiplexerDisposeReason = 'shutdown' | 'connection_lost' diff --git a/src/main/ssh/ssh-relay-session.ts b/src/main/ssh/ssh-relay-session.ts index e002c334aad..90cf99ff9db 100644 --- a/src/main/ssh/ssh-relay-session.ts +++ b/src/main/ssh/ssh-relay-session.ts @@ -438,7 +438,7 @@ export class SshRelaySession { async requestAiVaultSessionList( params: SshAiVaultRelayListParams, options: { signal?: AbortSignal; timeoutMs?: number } = {} - ): Promise { + ): Promise { if (this.aiVaultListMethodSupported === false) { return null } @@ -465,7 +465,7 @@ export class SshRelaySession { async requestAiVaultSessionTitles( params: SshAiVaultRelayTitleParams, options: { signal?: AbortSignal; timeoutMs?: number } = {} - ): Promise { + ): Promise { if (this.aiVaultTitleMethodSupported === false) { return null } diff --git a/src/main/ssh/ssh-remote-node-toolchain-probe.ts b/src/main/ssh/ssh-remote-node-toolchain-probe.ts index b1e0b8d544a..d6d7f1d7a06 100644 --- a/src/main/ssh/ssh-remote-node-toolchain-probe.ts +++ b/src/main/ssh/ssh-remote-node-toolchain-probe.ts @@ -50,7 +50,7 @@ export function nodeToolchainVersionsMeetRequirements(versionOutput: string): bo function markedVersionMajor(output: string, marker: string): number | null { const lines = output.split(/\r?\n/) const markerIndex = lines.indexOf(marker) - if (markerIndex < 0) { + if (markerIndex === -1) { return null } for (const line of lines.slice(markerIndex + 1)) { diff --git a/src/main/startup/hydrate-shell-path.ts b/src/main/startup/hydrate-shell-path.ts index 9cf7dc75608..38a16816507 100644 --- a/src/main/startup/hydrate-shell-path.ts +++ b/src/main/startup/hydrate-shell-path.ts @@ -71,11 +71,11 @@ function pickShell(): string | null { function parseCapturedPath(stdout: string, pathDelimiter: string = delimiter): string[] { const cleaned = stdout.replace(ANSI_RE, '') const first = cleaned.indexOf(DELIMITER) - if (first < 0) { + if (first === -1) { return [] } const second = cleaned.indexOf(DELIMITER, first + DELIMITER.length) - if (second < 0) { + if (second === -1) { return [] } const value = cleaned.slice(first + DELIMITER.length, second).trim() diff --git a/src/main/warp-themes/discovery.ts b/src/main/warp-themes/discovery.ts index 6f3f80b51da..fd29419c12c 100644 --- a/src/main/warp-themes/discovery.ts +++ b/src/main/warp-themes/discovery.ts @@ -142,7 +142,7 @@ export function getWarpThemeDirectories(): string[] { export function warpThemeSourceLabelForDirectory(directoryPath: string): string { const parts = directoryPath.split(/[\\/]+/).filter(Boolean) const themesIndex = parts.findLastIndex((part) => part.toLowerCase() === 'themes') - if (themesIndex < 0) { + if (themesIndex === -1) { return parts.at(-1) || 'Warp themes' } diff --git a/src/main/window/createMainWindow.ts b/src/main/window/createMainWindow.ts index d34ef495965..0661b6c3d06 100644 --- a/src/main/window/createMainWindow.ts +++ b/src/main/window/createMainWindow.ts @@ -336,7 +336,7 @@ export function createMainWindow( mainWindow.webContents.setZoomLevel(level) // Why: native traffic lights don't scale with CSS zoom; reposition on startup to stay aligned with the zoomed titlebar. if (process.platform === 'darwin') { - syncTrafficLightPosition(mainWindow, Math.pow(1.2, level)) + syncTrafficLightPosition(mainWindow, 1.2 ** level) } }) diff --git a/src/preload/index.ts b/src/preload/index.ts index 70ce60249b1..a61acccaccb 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1461,7 +1461,7 @@ const api = { checkName?: string url?: string | null prRepo?: GitHubOwnerRepo | null - }): Promise => ipcRenderer.invoke('gh:prCheckDetails', args), + }): Promise => ipcRenderer.invoke('gh:prCheckDetails', args), rerunPRChecks: (args: { repoPath: string @@ -4933,8 +4933,7 @@ if (process.contextIsolated) { console.error(error) } } else { - // @ts-ignore (define in dts) window.electron = electronAPI - // @ts-ignore (define in dts) + // @ts-expect-error (define in dts) window.api = api } diff --git a/src/relay/agent-hook-server.ts b/src/relay/agent-hook-server.ts index 3166c70a2f5..3cfa6e7f636 100644 --- a/src/relay/agent-hook-server.ts +++ b/src/relay/agent-hook-server.ts @@ -99,10 +99,10 @@ export class RelayAgentHookServer { private state: HookListenerState = createHookListenerState() // Why: retain envelope metadata so replays match live POSTs. // Invariant: keys mirror state.lastStatusByPaneKey, populated/cleared in lockstep. - private lastEnvelopeMetaByPaneKey: Map< + private lastEnvelopeMetaByPaneKey = new Map< string, { source: AgentHookSource; env?: string; version?: string } - > = new Map() + >() private assistantMessageRetryTimers = new Map>() private codexSubagentPollTimers = new Map>() private forward: RelayHookForward diff --git a/src/relay/ai-vault-service-client.ts b/src/relay/ai-vault-service-client.ts index 87c0d6e4c84..c873c380506 100644 --- a/src/relay/ai-vault-service-client.ts +++ b/src/relay/ai-vault-service-client.ts @@ -123,7 +123,7 @@ export class RelayAiVaultServiceClient implements RelayAiVaultServiceApi { continue } const index = this.queue.findIndex((call) => call.lane === lane) - if (index < 0) { + if (index === -1) { continue } const call = this.queue.splice(index, 1)[0]! @@ -241,7 +241,7 @@ export class RelayAiVaultServiceClient implements RelayAiVaultServiceApi { private cancel(call: RelayAiVaultServiceCall): void { const index = this.queue.indexOf(call) - if (index >= 0) { + if (index !== -1) { this.queue.splice(index, 1) settleRelayAiVaultServiceCall(call, relayAiVaultAbortError()) this.pump() diff --git a/src/relay/dispatcher-client-writer.test.ts b/src/relay/dispatcher-client-writer.test.ts index 13e49b59e8e..83b5e7960b7 100644 --- a/src/relay/dispatcher-client-writer.test.ts +++ b/src/relay/dispatcher-client-writer.test.ts @@ -51,7 +51,7 @@ function createWriter( sink.drainWaiters.push(callback) return () => { const index = sink.drainWaiters.indexOf(callback) - if (index >= 0) { + if (index !== -1) { sink.drainWaiters.splice(index, 1) } } diff --git a/src/relay/external-automations-handler.ts b/src/relay/external-automations-handler.ts index baaaa99481e..03bb0b9beba 100644 --- a/src/relay/external-automations-handler.ts +++ b/src/relay/external-automations-handler.ts @@ -382,7 +382,7 @@ export class ExternalAutomationsHandler { (sessionRun, index) => !usedSessionRunIndexes.has(index) && this.getRunKey(sessionRun) === outputRunKey ) - if (exactMatchIndex >= 0) { + if (exactMatchIndex !== -1) { return exactMatchIndex } @@ -755,7 +755,7 @@ export class ExternalAutomationsHandler { } } - private readHermesSessionDbRunById(jobId: string, runId: string): unknown | null { + private readHermesSessionDbRunById(jobId: string, runId: string): unknown { if (!existsSync(HERMES_STATE_DB)) { return null } diff --git a/src/relay/git-handler-utils.ts b/src/relay/git-handler-utils.ts index ab3bb61faba..25885cfe4dc 100644 --- a/src/relay/git-handler-utils.ts +++ b/src/relay/git-handler-utils.ts @@ -106,7 +106,7 @@ export function parseUnmergedEntry( */ export function parseBranchDiff( stdout: string, - statsByPath: Map = new Map() + statsByPath = new Map() ): Record[] { const entries: Record[] = [] for (const line of stdout.split(/\r?\n/)) { diff --git a/src/relay/pty-handler.ts b/src/relay/pty-handler.ts index 9471b2a3b9f..42b8a57856c 100644 --- a/src/relay/pty-handler.ts +++ b/src/relay/pty-handler.ts @@ -607,11 +607,7 @@ export class PtyHandler { for (const key of envToDelete) { delete result[key] } - if ( - !envToDelete.includes('TERM') && - rendererEnv && - Object.prototype.hasOwnProperty.call(rendererEnv, 'TERM') - ) { + if (!envToDelete.includes('TERM') && rendererEnv && Object.hasOwn(rendererEnv, 'TERM')) { result.TERM = rendererEnv.TERM } // Why: node-pty defaults missing/empty TERM per-platform; normalize so POSIX and Windows children agree. @@ -1431,7 +1427,7 @@ export class PtyHandler { const explicitTerm = !envToDelete.includes('TERM') && env && - Object.prototype.hasOwnProperty.call(env, 'TERM') && + Object.hasOwn(env, 'TERM') && typeof env.TERM === 'string' && env.TERM.length > 0 ? env.TERM diff --git a/src/relay/relay-watcher-event-emitter.test.ts b/src/relay/relay-watcher-event-emitter.test.ts index acf734acd71..0f0991e5c01 100644 --- a/src/relay/relay-watcher-event-emitter.test.ts +++ b/src/relay/relay-watcher-event-emitter.test.ts @@ -61,7 +61,7 @@ function createRecordingSink(highWaterMark?: number): { drainWaiters.push(callback) return () => { const index = drainWaiters.indexOf(callback) - if (index >= 0) { + if (index !== -1) { drainWaiters.splice(index, 1) } } diff --git a/src/relay/relay.ts b/src/relay/relay.ts index cf4b4808668..1c30e2d61e8 100644 --- a/src/relay/relay.ts +++ b/src/relay/relay.ts @@ -549,7 +549,7 @@ async function main(): Promise { const marker = process.argv.indexOf('--orca-cli') await runOrcaCliMode( sockPath, - marker >= 0 ? process.argv.slice(marker + 1) : [], + marker !== -1 ? process.argv.slice(marker + 1) : [], endpointCredential ) return diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 84b6bb9e155..643b6497e5d 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1476,18 +1476,17 @@ function App(): React.JSX.Element { } else if (settings.theme === 'light') { applyDocumentTheme('light') return undefined - } else { - // system - const mq = window.matchMedia('(prefers-color-scheme: dark)') - applyDocumentTheme('system') - const handler = (): void => { - applyDocumentTheme('system') - // System theme changes don't mutate the store, so mobile terminal colors need an explicit graph republish. - scheduleRuntimeGraphSync() - } - mq.addEventListener('change', handler) - return () => mq.removeEventListener('change', handler) } + // system + const mq = window.matchMedia('(prefers-color-scheme: dark)') + applyDocumentTheme('system') + const handler = (): void => { + applyDocumentTheme('system') + // System theme changes don't mutate the store, so mobile terminal colors need an explicit graph republish. + scheduleRuntimeGraphSync() + } + mq.addEventListener('change', handler) + return () => mq.removeEventListener('change', handler) }, [settings]) useEffect(() => { diff --git a/src/renderer/src/components/GitHubItemDialog.tsx b/src/renderer/src/components/GitHubItemDialog.tsx index eee4a5a106c..5f73fb31bea 100644 --- a/src/renderer/src/components/GitHubItemDialog.tsx +++ b/src/renderer/src/components/GitHubItemDialog.tsx @@ -1240,7 +1240,7 @@ if (typeof window !== 'undefined' && window.api?.gh?.onWorkItemMutated) { invalidateWorkItemDetailsCacheByMatch(payload) }) } -if (typeof import.meta !== 'undefined' && import.meta.hot) { +if (import.meta !== undefined && import.meta.hot) { import.meta.hot.dispose(() => { workItemMutatedUnsub?.() workItemDetailsCacheEventUnsub?.() diff --git a/src/renderer/src/components/PullRequestPage.tsx b/src/renderer/src/components/PullRequestPage.tsx index 69969b5f3bd..aedc7bdd5e0 100644 --- a/src/renderer/src/components/PullRequestPage.tsx +++ b/src/renderer/src/components/PullRequestPage.tsx @@ -1286,7 +1286,7 @@ if (typeof window !== 'undefined' && window.api?.gh?.onWorkItemMutated) { invalidateWorkItemDetailsCacheByMatch(payload) }) } -if (typeof import.meta !== 'undefined' && import.meta.hot) { +if (import.meta !== undefined && import.meta.hot) { import.meta.hot.dispose(() => { workItemMutatedUnsub?.() workItemDetailsCacheEventUnsub?.() diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index 520f2debbdd..29bc0bc4587 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -6960,7 +6960,7 @@ export default function TaskPage(): React.JSX.Element { const authorityPage = pages.findIndex((page) => page?.some((item) => authorityItemKeys.has(taskPageGitHubItemKey(item.repoId, item.id))) ) - const quietPage = authorityPage >= 0 ? authorityPage : currentPage + const quietPage = authorityPage !== -1 ? authorityPage : currentPage const visiblePage = currentPage > quietPage ? currentPage : undefined const pageItemKeys = (page: number): Set => new Set((pages[page] ?? []).map((item) => taskPageGitHubItemKey(item.repoId, item.id))) diff --git a/src/renderer/src/components/WorktreeJumpPalette.tsx b/src/renderer/src/components/WorktreeJumpPalette.tsx index aaccdda3da7..cee7c9bf4ce 100644 --- a/src/renderer/src/components/WorktreeJumpPalette.tsx +++ b/src/renderer/src/components/WorktreeJumpPalette.tsx @@ -321,8 +321,8 @@ function shouldIncludeOpenTabInRecentSection({ worktree: Worktree row: RecentWorkspaceTabRow paneSources: TabPaneInputSources - unreadTerminalTabs: Record - unreadAgentCompletionPanes: Record + unreadTerminalTabs: Record + unreadAgentCompletionPanes: Record now: number }): boolean { if (worktree.isArchived) { diff --git a/src/renderer/src/components/automations/AutomationsPage.tsx b/src/renderer/src/components/automations/AutomationsPage.tsx index 996f474a7c9..c52126ad670 100644 --- a/src/renderer/src/components/automations/AutomationsPage.tsx +++ b/src/renderer/src/components/automations/AutomationsPage.tsx @@ -355,7 +355,7 @@ export default function AutomationsPage(): React.JSX.Element { const loadAutomationYamlHooksForRepo = useCallback( async (repoId: string): Promise => { const key = getAutomationHooksCacheKey(repoId) - if (Object.prototype.hasOwnProperty.call(automationYamlHooksByRepoKey, key)) { + if (Object.hasOwn(automationYamlHooksByRepoKey, key)) { return automationYamlHooksByRepoKey[key] ?? null } const existingPromise = automationHookCheckPromisesRef.current.get(key) @@ -376,7 +376,7 @@ export default function AutomationsPage(): React.JSX.Element { return hooks } setAutomationYamlHooksByRepoKey((current) => - Object.prototype.hasOwnProperty.call(current, key) ? current : { ...current, [key]: hooks } + Object.hasOwn(current, key) ? current : { ...current, [key]: hooks } ) return hooks }, diff --git a/src/renderer/src/components/browser-pane/BrowserPane.tsx b/src/renderer/src/components/browser-pane/BrowserPane.tsx index ada2d7a07ae..d8fd4b91405 100644 --- a/src/renderer/src/components/browser-pane/BrowserPane.tsx +++ b/src/renderer/src/components/browser-pane/BrowserPane.tsx @@ -2883,7 +2883,7 @@ function BrowserPagePane({ return } // Why: convert OS screen cursor coords to renderer CSS pixels — immune to guest/renderer coordinate-space mismatches from zoom/DPI. - const zoomFactor = Math.pow(1.2, window.api.ui.getZoomLevel()) + const zoomFactor = 1.2 ** window.api.ui.getZoomLevel() const x = Math.round((event.screenX - window.screenX) / zoomFactor) const y = Math.round((event.screenY - window.screenY) / zoomFactor) console.debug( diff --git a/src/renderer/src/components/browser-pane/browser-address-bar-suggestions.ts b/src/renderer/src/components/browser-pane/browser-address-bar-suggestions.ts index 5351073140b..6ac0fe12c52 100644 --- a/src/renderer/src/components/browser-pane/browser-address-bar-suggestions.ts +++ b/src/renderer/src/components/browser-pane/browser-address-bar-suggestions.ts @@ -127,7 +127,7 @@ export function buildBrowserAddressBarSuggestions({ // Why: the history row gives Enter the same target while showing real page metadata. const duplicateIdx = historySuggestions.findIndex((h) => h.url === topAction.url) - if (duplicateIdx >= 0) { + if (duplicateIdx !== -1) { return historySuggestions.slice(0, MAX_BROWSER_ADDRESS_BAR_SUGGESTIONS) } diff --git a/src/renderer/src/components/browser-pane/context-menu-positioning.test.ts b/src/renderer/src/components/browser-pane/context-menu-positioning.test.ts index 397647b181f..647b6696907 100644 --- a/src/renderer/src/components/browser-pane/context-menu-positioning.test.ts +++ b/src/renderer/src/components/browser-pane/context-menu-positioning.test.ts @@ -43,7 +43,7 @@ function computeCorrection( describe('screen-to-viewport coordinate conversion', () => { it('converts screen coords to viewport at zoom 1.0', () => { - const result = computeViewportCoords(650, 400, 100, 50, 1.0) + const result = computeViewportCoords(650, 400, 100, 50, 1) expect(result.x).toBe(550) expect(result.y).toBe(350) }) @@ -64,7 +64,7 @@ describe('screen-to-viewport coordinate conversion', () => { }) it('handles cursor at window origin', () => { - const result = computeViewportCoords(100, 50, 100, 50, 1.0) + const result = computeViewportCoords(100, 50, 100, 50, 1) expect(result.x).toBe(0) expect(result.y).toBe(0) }) @@ -203,7 +203,7 @@ describe('full pipeline', () => { screenCursor.y, windowPos.x, windowPos.y, - 1.0 + 1 ) expect(menuPos.x).toBe(550) @@ -236,7 +236,7 @@ describe('full pipeline', () => { screenCursor.y, windowPos.x, windowPos.y, - 1.0 + 1 ) const cssOffset = { x: 60, y: 40 } const measured = { left: menuPos.x + cssOffset.x, top: menuPos.y + cssOffset.y } @@ -255,7 +255,7 @@ describe('full pipeline', () => { screenCursor.y, windowPos.x, windowPos.y, - 1.0 + 1 ) const corrected = computeCorrection(menuPos.x, menuPos.y, menuPos.x, menuPos.y) diff --git a/src/renderer/src/components/contextual-tours/contextual-tour-gate.ts b/src/renderer/src/components/contextual-tours/contextual-tour-gate.ts index 07ffc248c28..155f2bfb5a8 100644 --- a/src/renderer/src/components/contextual-tours/contextual-tour-gate.ts +++ b/src/renderer/src/components/contextual-tours/contextual-tour-gate.ts @@ -173,7 +173,7 @@ export function getContextualTourStepProgress(args: { stepIndex: number }): { current: number; total: number } | null { const visibleIndex = args.visibleStepIndexes.indexOf(args.stepIndex) - if (visibleIndex < 0) { + if (visibleIndex === -1) { return null } return { current: visibleIndex + 1, total: args.visibleStepIndexes.length } diff --git a/src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.ts b/src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.ts index f3d32a9b462..816474eb70f 100644 --- a/src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.ts +++ b/src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.ts @@ -35,7 +35,7 @@ export function patchDashboardSnapshotFromAgentStatus( return { matched: true, snapshot } } const index = snapshot.cards.findIndex((card) => card.paneKey === event.paneKey) - if (index < 0) { + if (index === -1) { return { matched: false, snapshot } } const card = snapshot.cards[index] diff --git a/src/renderer/src/components/editor/CheckRunDetailsPanel.tsx b/src/renderer/src/components/editor/CheckRunDetailsPanel.tsx index 50350fb1b47..6003be19e05 100644 --- a/src/renderer/src/components/editor/CheckRunDetailsPanel.tsx +++ b/src/renderer/src/components/editor/CheckRunDetailsPanel.tsx @@ -27,8 +27,8 @@ function formatCheckTimestamp(value: string | null | undefined): string | null { } type CheckStatusLike = { - status: PRCheckDetail['status'] | string | null | undefined - conclusion: PRCheckDetail['conclusion'] | string | null | undefined + status: PRCheckDetail['status'] | (string & {}) | null | undefined + conclusion: PRCheckDetail['conclusion'] | (string & {}) | null | undefined } function getCheckStatusLabel(check: CheckStatusLike): string { diff --git a/src/renderer/src/components/editor/CombinedDiffViewer.tsx b/src/renderer/src/components/editor/CombinedDiffViewer.tsx index f4bf62d3d54..58898e2d2ed 100644 --- a/src/renderer/src/components/editor/CombinedDiffViewer.tsx +++ b/src/renderer/src/components/editor/CombinedDiffViewer.tsx @@ -1182,10 +1182,7 @@ export default function CombinedDiffViewer({ if (!detail || detail.worktreeId !== file.worktreeId) { return } - const hasRuntimeOwnerFilter = Object.prototype.hasOwnProperty.call( - detail, - 'runtimeEnvironmentId' - ) + const hasRuntimeOwnerFilter = Object.hasOwn(detail, 'runtimeEnvironmentId') const targetRuntimeOwner = detail.runtimeEnvironmentId?.trim() || null const fileRuntimeOwner = file.runtimeEnvironmentId?.trim() || null if (hasRuntimeOwnerFilter && targetRuntimeOwner !== fileRuntimeOwner) { diff --git a/src/renderer/src/components/editor/IpynbViewer.tsx b/src/renderer/src/components/editor/IpynbViewer.tsx index bbf7ded32ed..4aac74ae495 100644 --- a/src/renderer/src/components/editor/IpynbViewer.tsx +++ b/src/renderer/src/components/editor/IpynbViewer.tsx @@ -435,7 +435,7 @@ function getCellKey(cell: IpynbCell, index: number): string { } function hasOwnDraft(drafts: Record, key: string): boolean { - return Object.prototype.hasOwnProperty.call(drafts, key) + return Object.hasOwn(drafts, key) } function EditableTextCell({ diff --git a/src/renderer/src/components/editor/editor-autosave.ts b/src/renderer/src/components/editor/editor-autosave.ts index c6d01a8cdf8..110f358dd99 100644 --- a/src/renderer/src/components/editor/editor-autosave.ts +++ b/src/renderer/src/components/editor/editor-autosave.ts @@ -128,7 +128,7 @@ export function getOpenFilesForExternalFileChange( target: EditorPathMutationTarget ): OpenFile[] { const absolutePath = joinPath(target.worktreePath, target.relativePath) - const hasRuntimeOwnerFilter = Object.prototype.hasOwnProperty.call(target, 'runtimeEnvironmentId') + const hasRuntimeOwnerFilter = Object.hasOwn(target, 'runtimeEnvironmentId') const targetRuntimeOwner = target.runtimeEnvironmentId?.trim() || null return openFiles.filter((file) => { if (file.worktreeId !== target.worktreeId) { diff --git a/src/renderer/src/components/editor/editor-panel-draft-selector.ts b/src/renderer/src/components/editor/editor-panel-draft-selector.ts index 8cc7e07fb27..b455af4e56f 100644 --- a/src/renderer/src/components/editor/editor-panel-draft-selector.ts +++ b/src/renderer/src/components/editor/editor-panel-draft-selector.ts @@ -43,7 +43,7 @@ export function createEditorPanelDraftSelector( const draft = state.editorDrafts[fileId] return ( draft !== previousSelection[fileId] || - (draft === undefined && Object.prototype.hasOwnProperty.call(previousSelection, fileId)) + (draft === undefined && Object.hasOwn(previousSelection, fileId)) ) }) if (!changed) { diff --git a/src/renderer/src/components/editor/monaco-context-menu-paste.ts b/src/renderer/src/components/editor/monaco-context-menu-paste.ts index 4a33fb68352..9b513f8e426 100644 --- a/src/renderer/src/components/editor/monaco-context-menu-paste.ts +++ b/src/renderer/src/components/editor/monaco-context-menu-paste.ts @@ -60,7 +60,7 @@ function resolvePasteMetadata( return { pasteOnNewLine: emptySelectionClipboard && metadata.isFromEmptySelection === true, multicursorText: - typeof metadata.multicursorText !== 'undefined' ? (metadata.multicursorText ?? null) : null, + metadata.multicursorText !== undefined ? (metadata.multicursorText ?? null) : null, mode: metadata.mode ?? null } } diff --git a/src/renderer/src/components/editor/monaco-markdown-doc-completions.ts b/src/renderer/src/components/editor/monaco-markdown-doc-completions.ts index 882b011e9b3..4d33a1f3985 100644 --- a/src/renderer/src/components/editor/monaco-markdown-doc-completions.ts +++ b/src/renderer/src/components/editor/monaco-markdown-doc-completions.ts @@ -9,7 +9,7 @@ import { type MonacoApi = Parameters[1] let provider: IDisposable | null = null -let providerMonaco: MonacoApi | null = null +let providerMonaco: MonacoApi = null const documentsByModel = new Map() export function ensureMarkdownDocCompletionProvider(monaco: MonacoApi): void { diff --git a/src/renderer/src/components/editor/rich-markdown-slash-command-catalog.tsx b/src/renderer/src/components/editor/rich-markdown-slash-command-catalog.tsx index 922e1abe2f1..3cd31be6f6d 100644 --- a/src/renderer/src/components/editor/rich-markdown-slash-command-catalog.tsx +++ b/src/renderer/src/components/editor/rich-markdown-slash-command-catalog.tsx @@ -1,4 +1,3 @@ -import type {} from '@tiptap/extension-mathematics' import { ChevronRight, ImageIcon, diff --git a/src/renderer/src/components/editor/rich-markdown-terminal-path-paste.ts b/src/renderer/src/components/editor/rich-markdown-terminal-path-paste.ts index cfdb7f422fd..037004a5303 100644 --- a/src/renderer/src/components/editor/rich-markdown-terminal-path-paste.ts +++ b/src/renderer/src/components/editor/rich-markdown-terminal-path-paste.ts @@ -14,7 +14,7 @@ function readClipboardText(event: ClipboardEvent, type: string): string { function getWindowsPathBasename(filePath: string): string { const normalized = filePath.replaceAll('/', '\\') const separatorIndex = normalized.lastIndexOf('\\') - return separatorIndex >= 0 ? normalized.slice(separatorIndex + 1) : normalized + return separatorIndex !== -1 ? normalized.slice(separatorIndex + 1) : normalized } function extractClipboardAnchors(html: string): ClipboardAnchor[] { diff --git a/src/renderer/src/components/editor/useLocalImageSrc.ts b/src/renderer/src/components/editor/useLocalImageSrc.ts index 9a8327af090..df174160bb8 100644 --- a/src/renderer/src/components/editor/useLocalImageSrc.ts +++ b/src/renderer/src/components/editor/useLocalImageSrc.ts @@ -130,7 +130,7 @@ if (typeof window !== 'undefined') { window.addEventListener('focus', invalidateImageCache) } -if (typeof import.meta !== 'undefined' && import.meta.hot) { +if (import.meta !== undefined && import.meta.hot) { // Why: Vite can re-evaluate this module without a full renderer reload. // Disposing the module-level listener and blob URLs prevents dev-session leaks. import.meta.hot.dispose(disposeImageCacheModuleState) diff --git a/src/renderer/src/components/floating-terminal/floating-terminal-panel-bounds.ts b/src/renderer/src/components/floating-terminal/floating-terminal-panel-bounds.ts index aef4c99dfe6..91e97c2de79 100644 --- a/src/renderer/src/components/floating-terminal/floating-terminal-panel-bounds.ts +++ b/src/renderer/src/components/floating-terminal/floating-terminal-panel-bounds.ts @@ -60,7 +60,7 @@ function clampValue(value: number, min: number, max: number): number { } function getWindowStorage(): Storage | null { - return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined' + return typeof window !== 'undefined' && window.localStorage !== undefined ? window.localStorage : null } diff --git a/src/renderer/src/components/floating-terminal/floating-terminal-trigger-position.ts b/src/renderer/src/components/floating-terminal/floating-terminal-trigger-position.ts index effee92d52a..fb127b23778 100644 --- a/src/renderer/src/components/floating-terminal/floating-terminal-trigger-position.ts +++ b/src/renderer/src/components/floating-terminal/floating-terminal-trigger-position.ts @@ -48,7 +48,7 @@ function isAnchorY(value: unknown): value is FloatingTerminalAnchorY { } function getWindowStorage(): Storage | null { - return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined' + return typeof window !== 'undefined' && window.localStorage !== undefined ? window.localStorage : null } diff --git a/src/renderer/src/components/native-chat/claude-model-switch-confirmation.ts b/src/renderer/src/components/native-chat/claude-model-switch-confirmation.ts index b96d3fad358..826d70aea9f 100644 --- a/src/renderer/src/components/native-chat/claude-model-switch-confirmation.ts +++ b/src/renderer/src/components/native-chat/claude-model-switch-confirmation.ts @@ -43,14 +43,14 @@ function hasClaudeModelSwitchSuccess(buffer: string, modelLabel: string): boolea // every picker token prevents one context variant from confirming another. const labelTokens = modelLabel.toLowerCase().match(/[a-z]+|\d+[a-z]*/g) ?? [] const successStart = text.lastIndexOf('setmodelto') - if (successStart < 0 || labelTokens.length === 0) { + if (successStart === -1 || labelTokens.length === 0) { return false } const successText = text.slice(successStart) let tokenEnd = 0 for (const token of labelTokens) { const tokenStart = successText.indexOf(token, tokenEnd) - if (tokenStart < 0) { + if (tokenStart === -1) { return false } tokenEnd = tokenStart + token.length diff --git a/src/renderer/src/components/native-chat/claude-terminal-session-options.ts b/src/renderer/src/components/native-chat/claude-terminal-session-options.ts index 3b8ad86a47a..c7e88eaa898 100644 --- a/src/renderer/src/components/native-chat/claude-terminal-session-options.ts +++ b/src/renderer/src/components/native-chat/claude-terminal-session-options.ts @@ -52,7 +52,7 @@ function frameCell(line: string): string { if (line.startsWith(FRAME_COLUMN)) { const afterBorder = line.slice(FRAME_COLUMN.length) const columnEnd = afterBorder.indexOf(FRAME_COLUMN) - return (columnEnd < 0 ? afterBorder : afterBorder.slice(0, columnEnd)).trim() + return (columnEnd === -1 ? afterBorder : afterBorder.slice(0, columnEnd)).trim() } // Unframed buffers put the descriptor on the row directly, behind box art. return line.replace(/^[^A-Za-z0-9]+/, '').trim() @@ -171,7 +171,7 @@ function labelNamesReportedModel(reportedModel: string, label: string): boolean let cursor = 1 for (const token of labelTokens.slice(1)) { const found = nameTokens.indexOf(token, cursor) - if (found < 0) { + if (found === -1) { return false } cursor = found + 1 @@ -212,7 +212,7 @@ export function readClaudeSessionOptionsFromTerminalScreen( } const lines = normalizedScreenLines(screen) const headerIndex = lines.findIndex(isClaudeHeaderRow) - if (headerIndex < 0) { + if (headerIndex === -1) { return null } const descriptorCell = claudeModelDescriptorCell(lines, headerIndex) diff --git a/src/renderer/src/components/native-chat/native-chat-pending-occurrence.ts b/src/renderer/src/components/native-chat/native-chat-pending-occurrence.ts index 7185b6ad1d3..5a4631421bc 100644 --- a/src/renderer/src/components/native-chat/native-chat-pending-occurrence.ts +++ b/src/renderer/src/components/native-chat/native-chat-pending-occurrence.ts @@ -191,7 +191,7 @@ export function selectPendingIndicesRepresentedByUserTexts( } represented.add(entry.index) const at = remaining.findIndex((candidate) => candidate.index === entry.index) - if (at >= 0) { + if (at !== -1) { remaining.splice(at, 1) } } diff --git a/src/renderer/src/components/native-chat/native-chat-pending.ts b/src/renderer/src/components/native-chat/native-chat-pending.ts index 410de8add38..a33b4e27838 100644 --- a/src/renderer/src/components/native-chat/native-chat-pending.ts +++ b/src/renderer/src/components/native-chat/native-chat-pending.ts @@ -99,7 +99,7 @@ function messagesAfterPendingBoundary( return messages.filter((message) => messageIsAfterPendingTimestamp(message, pending)) } const boundaryIndex = messages.findIndex((message) => message.id === pending.afterMessageId) - if (boundaryIndex >= 0) { + if (boundaryIndex !== -1) { return messages.slice(boundaryIndex + 1) } // A bounded authoritative read can page the boundary out. Fall back to the @@ -164,7 +164,7 @@ export function prunePendingSends( return false } const openIndex = stillOpen.indexOf(entry) - return openIndex < 0 || !gluedRepresented.has(openIndex) + return openIndex === -1 || !gluedRepresented.has(openIndex) }) return next.length === pending.length ? pending : next } @@ -208,7 +208,7 @@ export function pendingSendsAsMessages( return false } const openIndex = stillVisible.indexOf(entry) - return openIndex < 0 || !gluedRepresented.has(openIndex) + return openIndex === -1 || !gluedRepresented.has(openIndex) }) .map((entry) => ({ id: `pending:${entry.id}`, diff --git a/src/renderer/src/components/new-workspace/project-combobox-matching.ts b/src/renderer/src/components/new-workspace/project-combobox-matching.ts index ac298ebd6c5..2e3459ac745 100644 --- a/src/renderer/src/components/new-workspace/project-combobox-matching.ts +++ b/src/renderer/src/components/new-workspace/project-combobox-matching.ts @@ -10,7 +10,7 @@ export type ScoredProjectOption = { function substringHits(text: string, query: string): number[] | null { const at = text.toLowerCase().indexOf(query) - return at < 0 ? null : Array.from({ length: query.length }, (_, offset) => at + offset) + return at === -1 ? null : Array.from({ length: query.length }, (_, offset) => at + offset) } /** @@ -27,7 +27,7 @@ function nameHitsFor(name: string, query: string): number[] | null { let cursor = 0 for (const char of query) { const found = haystack.indexOf(char, cursor) - if (found < 0) { + if (found === -1) { return null } hits.push(found) @@ -60,7 +60,7 @@ export function rankProjectOptions( const scored: ScoredProjectOption[] = [] for (const option of options) { const recentAt = recentIds.indexOf(option.id) - const recency = recentAt < 0 ? 0 : 32 - recentAt * 4 + const recency = recentAt === -1 ? 0 : 32 - recentAt * 4 if (query.length === 0) { scored.push({ option, score: recency, nameHits: [], detailHits: [] }) continue diff --git a/src/renderer/src/components/new-workspace/use-jira-url-source.test.tsx b/src/renderer/src/components/new-workspace/use-jira-url-source.test.tsx index fc3a60a5504..1f6df54bfb2 100644 --- a/src/renderer/src/components/new-workspace/use-jira-url-source.test.tsx +++ b/src/renderer/src/components/new-workspace/use-jira-url-source.test.tsx @@ -50,7 +50,7 @@ function site(id: string, email = `${id}@example.com`): JiraSite { function status( sites: JiraSite[], - selectedSiteId: string | 'all' | null = null + selectedSiteId: (string & {}) | 'all' | null = null ): JiraConnectionStatus { return { connected: true, diff --git a/src/renderer/src/components/new-workspace/use-jira-url-source.ts b/src/renderer/src/components/new-workspace/use-jira-url-source.ts index e1dc6efd6a6..0441e37edc9 100644 --- a/src/renderer/src/components/new-workspace/use-jira-url-source.ts +++ b/src/renderer/src/components/new-workspace/use-jira-url-source.ts @@ -54,7 +54,7 @@ function getPreferredSite( matches: JiraSite[], selectedAccount: AccountSelection | null, requestKey: string, - selectedSiteId: string | 'all' | null | undefined, + selectedSiteId: (string & {}) | 'all' | null | undefined, activeSiteId: string | null | undefined ): JiraSite | null { const explicitId = selectedAccount?.requestKey === requestKey ? selectedAccount.siteId : null diff --git a/src/renderer/src/components/right-sidebar/checks-panel-review-creation.ts b/src/renderer/src/components/right-sidebar/checks-panel-review-creation.ts index 95c9ccfde97..d0534124397 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel-review-creation.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel-review-creation.ts @@ -63,7 +63,7 @@ export function isChecksPanelCreateEligibilityConfirmable(input: { * provisional or draft-preserve path. */ export function shouldOpenChecksPanelCreateComposer(input: { - activeReview: unknown | null + activeReview: unknown isFolder: boolean branch: string hostedReviewCreation: HostedReviewCreationEligibility | null diff --git a/src/renderer/src/components/right-sidebar/source-control-text-generation-defaults.ts b/src/renderer/src/components/right-sidebar/source-control-text-generation-defaults.ts index 89212dd6149..08bc9fb6d9d 100644 --- a/src/renderer/src/components/right-sidebar/source-control-text-generation-defaults.ts +++ b/src/renderer/src/components/right-sidebar/source-control-text-generation-defaults.ts @@ -21,7 +21,7 @@ function textGenerationRecipeIsConfigured( actionId: SourceControlTextActionId, recipe: TextGenerationRecipeConfiguration | null | undefined ): boolean { - if (Object.prototype.hasOwnProperty.call(recipe ?? {}, 'agentId')) { + if (Object.hasOwn(recipe ?? {}, 'agentId')) { return true } if ( diff --git a/src/renderer/src/components/settings/AccountsPane.tsx b/src/renderer/src/components/settings/AccountsPane.tsx index 81dd540ff5f..c8500b99cfa 100644 --- a/src/renderer/src/components/settings/AccountsPane.tsx +++ b/src/renderer/src/components/settings/AccountsPane.tsx @@ -383,12 +383,12 @@ export function AccountsPane({ useState(emptyCodexAccountsState) const [codexAccountsLoaded, setCodexAccountsLoaded] = useState(false) const [codexAction, setCodexAction] = useState< - 'idle' | 'adding' | `reauth:${string}` | `remove:${string}` | `select:${string | 'system'}` + 'idle' | 'adding' | `reauth:${string}` | `remove:${string}` | `select:${string}` >('idle') const [claudeAccounts, setClaudeAccounts] = useState(emptyClaudeAccountsState) const [claudeAction, setClaudeAction] = useState< - 'idle' | 'adding' | `reauth:${string}` | `remove:${string}` | `select:${string | 'system'}` + 'idle' | 'adding' | `reauth:${string}` | `remove:${string}` | `select:${string}` >('idle') // Why: capture the account's runtime slot when the dialog opens; the roster // can change underneath an open dialog and lose the slot to diff for restarts. diff --git a/src/renderer/src/components/settings/SettingsConstants.ts b/src/renderer/src/components/settings/SettingsConstants.ts index 260d8b48971..f129bea3265 100644 --- a/src/renderer/src/components/settings/SettingsConstants.ts +++ b/src/renderer/src/components/settings/SettingsConstants.ts @@ -11,7 +11,7 @@ export { } from '../../../../shared/ui-zoom-level' export function zoomLevelToPercent(level: number): number { - return Math.round(100 * Math.pow(1.2, level)) + return Math.round(100 * 1.2 ** level) } export function mergeFontSuggestions( diff --git a/src/renderer/src/components/settings/keybinding-override-edits.ts b/src/renderer/src/components/settings/keybinding-override-edits.ts index e0e88d00782..21b339ba98f 100644 --- a/src/renderer/src/components/settings/keybinding-override-edits.ts +++ b/src/renderer/src/components/settings/keybinding-override-edits.ts @@ -15,7 +15,7 @@ export function hasOwnBindingOverride( overrides: KeybindingOverrides, actionId: KeybindingActionId ): boolean { - return Object.prototype.hasOwnProperty.call(overrides, actionId) + return Object.hasOwn(overrides, actionId) } export function removeBindingOverride( diff --git a/src/renderer/src/components/settings/repository-source-control-ai-draft.ts b/src/renderer/src/components/settings/repository-source-control-ai-draft.ts index 1e516bab7a8..370f60f3d2f 100644 --- a/src/renderer/src/components/settings/repository-source-control-ai-draft.ts +++ b/src/renderer/src/components/settings/repository-source-control-ai-draft.ts @@ -19,7 +19,7 @@ export function hasOwnActionOverride( overrides: RepoSourceControlAiOverrides['actionOverrides'], actionId: SourceControlActionId ): boolean { - return Object.prototype.hasOwnProperty.call(overrides ?? {}, actionId) + return Object.hasOwn(overrides ?? {}, actionId) } export function triStateValue(value: boolean | null | undefined): 'inherit' | 'on' | 'off' { diff --git a/src/renderer/src/components/sidebar/worktree-lineage-projection.ts b/src/renderer/src/components/sidebar/worktree-lineage-projection.ts index 38c8584c064..040a40eb737 100644 --- a/src/renderer/src/components/sidebar/worktree-lineage-projection.ts +++ b/src/renderer/src/components/sidebar/worktree-lineage-projection.ts @@ -15,7 +15,7 @@ export function getProjectedWorktreeLineage( worktree: Worktree, lineageById: Readonly> ): WorktreeLineage | null | undefined { - if (Object.prototype.hasOwnProperty.call(lineageById, worktree.id)) { + if (Object.hasOwn(lineageById, worktree.id)) { return lineageById[worktree.id] } return (worktree as WorktreeWithResolvedLineage).lineage diff --git a/src/renderer/src/components/sidebar/worktree-list-groups.ts b/src/renderer/src/components/sidebar/worktree-list-groups.ts index 777e9fcfbe6..a154eea8263 100644 --- a/src/renderer/src/components/sidebar/worktree-list-groups.ts +++ b/src/renderer/src/components/sidebar/worktree-list-groups.ts @@ -1006,9 +1006,7 @@ export function buildRows( workspaceStatuses: readonly WorkspaceStatusDefinition[] = cloneDefaultWorkspaceStatuses(), projectOrderBy: ProjectOrderBy = 'manual', lineageById: Record = {}, - worktreeMap: Map = new Map( - worktrees.map((worktree) => [worktree.id, worktree]) - ), + worktreeMap = new Map(worktrees.map((worktree) => [worktree.id, worktree])), nestLineage = false, settings?: AppState['settings'], projectGroups: readonly ProjectGroup[] = [], diff --git a/src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.ts b/src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.ts index ceba63d3e07..9224fe2cba3 100644 --- a/src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.ts +++ b/src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.ts @@ -285,7 +285,7 @@ function resolveLeafIdForTitleFallback(args: { } const paneIndex = args.paneTitleEntries.findIndex(([paneId]) => Number(paneId) === args.paneId) - return paneIndex >= 0 ? (leafIds[paneIndex] ?? null) : null + return paneIndex !== -1 ? (leafIds[paneIndex] ?? null) : null } function collectLeafIds(node: TerminalPaneLayoutNode | null): string[] { diff --git a/src/renderer/src/components/source-control/SourceControlActionVariableChips.tsx b/src/renderer/src/components/source-control/SourceControlActionVariableChips.tsx index 7a99c800eb1..cd853cf42a5 100644 --- a/src/renderer/src/components/source-control/SourceControlActionVariableChips.tsx +++ b/src/renderer/src/components/source-control/SourceControlActionVariableChips.tsx @@ -23,7 +23,7 @@ function hasVariablePreview( ): boolean { return Boolean( variablePreviews && - Object.prototype.hasOwnProperty.call(variablePreviews, variable) && + Object.hasOwn(variablePreviews, variable) && variablePreviews[variable] !== undefined && variablePreviews[variable] !== null ) diff --git a/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts b/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts index d02588001b9..baf9c189fde 100644 --- a/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts +++ b/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts @@ -69,7 +69,7 @@ function resolveSnapshotSessionLabel( if (parsed) { const tabs = ctx.tabsByWorktree[worktreeId] ?? [] const tabIndex = tabs.findIndex((t) => t.id === parsed.tabId) - const tab = tabIndex >= 0 ? tabs[tabIndex] : undefined + const tab = tabIndex !== -1 ? tabs[tabIndex] : undefined if (tab) { const custom = tab.customTitle?.trim() if (custom) { @@ -94,7 +94,7 @@ function resolveDaemonSessionLabel( if (tabId && resolvedWorktreeId) { const tabs = ctx.tabsByWorktree[resolvedWorktreeId] ?? [] const tabIndex = tabs.findIndex((t) => t.id === tabId) - const tab = tabIndex >= 0 ? tabs[tabIndex] : undefined + const tab = tabIndex !== -1 ? tabs[tabIndex] : undefined if (tab) { const custom = tab.customTitle?.trim() if (custom) { diff --git a/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.ts b/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.ts index b321dc87f88..67194fbecbf 100644 --- a/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.ts +++ b/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.ts @@ -58,8 +58,8 @@ function splitHostCandidate(query: string): { host: string; port: string | null return null } const colonIndex = query.indexOf(':') - const host = colonIndex < 0 ? query : query.slice(0, colonIndex) - const port = colonIndex < 0 ? null : query.slice(colonIndex + 1) + const host = colonIndex === -1 ? query : query.slice(0, colonIndex) + const port = colonIndex === -1 ? null : query.slice(colonIndex + 1) const extension = host.split('.').pop()?.toLowerCase() ?? '' if (HOST_FILE_EXTENSIONS.has(extension)) { return null @@ -94,7 +94,7 @@ function isPrivateIpv4(host: string): boolean { function hasSourceExtensionBeforeColon(query: string): boolean { const colonIndex = query.indexOf(':') - if (colonIndex < 0) { + if (colonIndex === -1) { return false } const extension = query.slice(0, colonIndex).split('.').pop()?.toLowerCase() ?? '' diff --git a/src/renderer/src/components/tab-bar/terminal-tab-activity-status.ts b/src/renderer/src/components/tab-bar/terminal-tab-activity-status.ts index 26525128517..abdcc584881 100644 --- a/src/renderer/src/components/tab-bar/terminal-tab-activity-status.ts +++ b/src/renderer/src/components/tab-bar/terminal-tab-activity-status.ts @@ -223,8 +223,8 @@ export function terminalTabHasUnreadActivity({ unreadAgentCompletionPanes }: { terminalTabId: string - unreadTerminalTabs: Record - unreadAgentCompletionPanes: Record + unreadTerminalTabs: Record + unreadAgentCompletionPanes: Record }): boolean { return ( unreadTerminalTabs[terminalTabId] === true || @@ -234,7 +234,7 @@ export function terminalTabHasUnreadActivity({ /** Match pane-level unread completion markers to their owning terminal tab. */ export function hasUnreadAgentCompletionForTerminalTab( - unreadAgentCompletionPanes: Record | undefined, + unreadAgentCompletionPanes: Record | undefined, tabId: string ): boolean { for (const [paneKey, unread] of Object.entries(unreadAgentCompletionPanes ?? {})) { diff --git a/src/renderer/src/components/task-page-github-work-item-authority-refresh.ts b/src/renderer/src/components/task-page-github-work-item-authority-refresh.ts index 6a047760195..3f11b3b93b1 100644 --- a/src/renderer/src/components/task-page-github-work-item-authority-refresh.ts +++ b/src/renderer/src/components/task-page-github-work-item-authority-refresh.ts @@ -50,7 +50,7 @@ export function getTaskPageGitHubRevalidatableAuthorityItemKeys( const quiet = getQuietRevalidateState(queryKey) for (const itemKey of getTaskPageGitHubConfirmedAuthorityItemKeys()) { const separator = itemKey.indexOf('\0') - if (separator < 0) { + if (separator === -1) { continue } const repoId = itemKey.slice(0, separator) diff --git a/src/renderer/src/components/task-page-github-work-item-mutation-composition.ts b/src/renderer/src/components/task-page-github-work-item-mutation-composition.ts index 970e777ecd3..25616c3c059 100644 --- a/src/renderer/src/components/task-page-github-work-item-mutation-composition.ts +++ b/src/renderer/src/components/task-page-github-work-item-mutation-composition.ts @@ -53,7 +53,7 @@ export function pendingListOpsForFamily( } const login = pending.listOp.logins[0] const lastIndex = acc.findIndex((op) => op.logins.length === 1 && op.logins[0] === login) - if (lastIndex >= 0) { + if (lastIndex !== -1) { acc[lastIndex] = pending.listOp } else { acc.push(pending.listOp) diff --git a/src/renderer/src/components/task-page-github-work-item-mutation-registry.ts b/src/renderer/src/components/task-page-github-work-item-mutation-registry.ts index 58428e8746e..bfd575ea4ab 100644 --- a/src/renderer/src/components/task-page-github-work-item-mutation-registry.ts +++ b/src/renderer/src/components/task-page-github-work-item-mutation-registry.ts @@ -65,7 +65,7 @@ export function getTaskPageGitHubConfirmedAuthorityItemKeys(): ReadonlySet= 0 && + separator !== -1 && hasConfirmedAuthorityForItem(itemKey.slice(0, separator), itemKey.slice(separator + 1)) ) { keys.add(itemKey) diff --git a/src/renderer/src/components/task-page-github-work-item-quiet-revalidate.ts b/src/renderer/src/components/task-page-github-work-item-quiet-revalidate.ts index 6c33605b569..5895eb28f85 100644 --- a/src/renderer/src/components/task-page-github-work-item-quiet-revalidate.ts +++ b/src/renderer/src/components/task-page-github-work-item-quiet-revalidate.ts @@ -99,7 +99,7 @@ export function settleQuietSearchRevalidate(args: { } const separator = itemKey.indexOf('\0') if ( - separator >= 0 && + separator !== -1 && !hasPendingTaskPageGitHubOpsForItem(itemKey.slice(0, separator), itemKey.slice(separator + 1)) ) { clearConfirmedAuthorityForItem(itemKey.slice(0, separator), itemKey.slice(separator + 1)) @@ -111,7 +111,7 @@ export function settleQuietSearchRevalidate(args: { continue } const sep = itemKey.indexOf('\0') - if (sep < 0) { + if (sep === -1) { continue } const repoId = itemKey.slice(0, sep) @@ -134,7 +134,7 @@ export function settleQuietSearchRevalidate(args: { continue } const sep = itemKey.indexOf('\0') - if (sep < 0) { + if (sep === -1) { continue } if ( diff --git a/src/renderer/src/components/task-page-linear-in-orca-issues.ts b/src/renderer/src/components/task-page-linear-in-orca-issues.ts index 3db29f31846..38f1dbe4802 100644 --- a/src/renderer/src/components/task-page-linear-in-orca-issues.ts +++ b/src/renderer/src/components/task-page-linear-in-orca-issues.ts @@ -101,7 +101,7 @@ export function collectLinkedLinearIssueRefsFromWorktrees( (candidate.sourceContext ? getTaskSourceCacheScope(candidate.sourceContext) : '') === sourceScope ) - if ((workspaceId || organizationUrlKey) && unscopedIndex >= 0) { + if ((workspaceId || organizationUrlKey) && unscopedIndex !== -1) { existing[unscopedIndex] = ref } else if (!workspaceId && !organizationUrlKey) { const hasSameSourceScope = existing.some( diff --git a/src/renderer/src/components/terminal-pane/agent-process-inspection-queue.ts b/src/renderer/src/components/terminal-pane/agent-process-inspection-queue.ts index 797407b69a3..5c581ddc652 100644 --- a/src/renderer/src/components/terminal-pane/agent-process-inspection-queue.ts +++ b/src/renderer/src/components/terminal-pane/agent-process-inspection-queue.ts @@ -45,7 +45,7 @@ function pumpInspectionQueue(): void { const priorityIndex = inspectionQueue.findIndex((task) => task.priority === 'pending-title') const next = - priorityIndex >= 0 ? inspectionQueue.splice(priorityIndex, 1)[0] : inspectionQueue.shift() + priorityIndex !== -1 ? inspectionQueue.splice(priorityIndex, 1)[0] : inspectionQueue.shift() if (!next) { return } diff --git a/src/renderer/src/components/terminal-pane/terminal-agent-session-fork.ts b/src/renderer/src/components/terminal-pane/terminal-agent-session-fork.ts index 1b7c36a0a1f..959b4a9a7e4 100644 --- a/src/renderer/src/components/terminal-pane/terminal-agent-session-fork.ts +++ b/src/renderer/src/components/terminal-pane/terminal-agent-session-fork.ts @@ -36,9 +36,7 @@ function buildForkWorkspaceName(sourceName: string): string { } function resolveTuiAgent(value: string | null | undefined): TuiAgent | null { - return value && Object.prototype.hasOwnProperty.call(TUI_AGENT_CONFIG, value) - ? (value as TuiAgent) - : null + return value && Object.hasOwn(TUI_AGENT_CONFIG, value) ? (value as TuiAgent) : null } function getUsableForkBase( diff --git a/src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.ts b/src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.ts index 5594bcb3cbe..525a8d2b12a 100644 --- a/src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.ts +++ b/src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.ts @@ -118,7 +118,7 @@ function isPlainAsciiDigitKey(event: XtermBypassEvent): boolean { /** Tracks legacy desktop Linux IME candidate-selection event sequences. */ export function createTerminalImeLinuxCandidateState( now: () => number = () => Date.now(), - pendingPlainLetterKeydownsByCode: Set = new Set() + pendingPlainLetterKeydownsByCode = new Set() ): TerminalImeLinuxCandidateState { let candidateDigitUntil = 0 diff --git a/src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.ts b/src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.ts index d43702057f1..7ca537b014d 100644 --- a/src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.ts +++ b/src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.ts @@ -44,7 +44,7 @@ function omitLeafRecord( source: Record | undefined, leafId: string ): Record | undefined { - if (!source || !Object.prototype.hasOwnProperty.call(source, leafId)) { + if (!source || !Object.hasOwn(source, leafId)) { return source } const next = { ...source } diff --git a/src/renderer/src/components/terminal-pane/terminal-layout-leaf-ids.ts b/src/renderer/src/components/terminal-pane/terminal-layout-leaf-ids.ts index bd780e89495..9f21c39056b 100644 --- a/src/renderer/src/components/terminal-pane/terminal-layout-leaf-ids.ts +++ b/src/renderer/src/components/terminal-pane/terminal-layout-leaf-ids.ts @@ -51,7 +51,7 @@ function remapLeafRecord( function collectLeafCounts( node: TerminalPaneLayoutNode, - counts: Map = new Map() + counts = new Map() ): Map { if (node.type === 'leaf') { counts.set(node.leafId, (counts.get(node.leafId) ?? 0) + 1) @@ -73,7 +73,7 @@ function hasLeafPtyBinding( ptyIdsByLeafId: Record | undefined, leafId: string ): boolean { - return ptyIdsByLeafId ? Object.prototype.hasOwnProperty.call(ptyIdsByLeafId, leafId) : false + return ptyIdsByLeafId ? Object.hasOwn(ptyIdsByLeafId, leafId) : false } export function resolveRootlessTerminalLayoutLeafId( diff --git a/src/renderer/src/components/terminal-pane/terminal-layout-pty-ownership.ts b/src/renderer/src/components/terminal-pane/terminal-layout-pty-ownership.ts index f0742328240..4c44c3e6ead 100644 --- a/src/renderer/src/components/terminal-pane/terminal-layout-pty-ownership.ts +++ b/src/renderer/src/components/terminal-pane/terminal-layout-pty-ownership.ts @@ -105,7 +105,7 @@ function coalesceLeafRecord( continue } const retainedLeafId = resolveRetainedLeafId(removedLeafId, retainedLeafIdByRemovedLeafId) - if (!Object.prototype.hasOwnProperty.call(retained, retainedLeafId)) { + if (!Object.hasOwn(retained, retainedLeafId)) { retained[retainedLeafId] = value } } @@ -113,7 +113,7 @@ function coalesceLeafRecord( } function hasLeafRecordValue(source: Record | undefined, leafId: string): boolean { - return Boolean(source && Object.prototype.hasOwnProperty.call(source, leafId)) + return Boolean(source && Object.hasOwn(source, leafId)) } function coalesceScrollbackRecords( @@ -225,7 +225,7 @@ function resolveOwnedActiveLeafId( ptyIdsByLeafId: Record | undefined ): string | null { const hasBinding = (leafId: string): boolean => - Boolean(ptyIdsByLeafId && Object.prototype.hasOwnProperty.call(ptyIdsByLeafId, leafId)) + Boolean(ptyIdsByLeafId && Object.hasOwn(ptyIdsByLeafId, leafId)) if (rootLeafIds.length === 0) { const boundLeafIds = Object.keys(ptyIdsByLeafId ?? {}) if (activeLeafId) { diff --git a/src/renderer/src/components/terminal-pane/terminal-paste-diagnostics.ts b/src/renderer/src/components/terminal-pane/terminal-paste-diagnostics.ts index b32decb98ff..9c89536db50 100644 --- a/src/renderer/src/components/terminal-pane/terminal-paste-diagnostics.ts +++ b/src/renderer/src/components/terminal-pane/terminal-paste-diagnostics.ts @@ -29,7 +29,7 @@ export function createRedactedPasteExecutionDiagnostic({ chunksWritten: number durationMs: number plan: TerminalPastePlan - reason?: TerminalPasteExecutionReason | string + reason?: TerminalPasteExecutionReason | (string & {}) status: TerminalPasteExecutionResult['status'] }): string { return [ diff --git a/src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.ts b/src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.ts index f9a6e544193..9e9a63a94f2 100644 --- a/src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.ts +++ b/src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.ts @@ -168,18 +168,18 @@ export function useTerminalPaneGlobalEffects({ hiddenReasonRef.current = null applyPendingFollowOutputRequests() return - } else { - const hiddenState = hideTerminalVisibility({ - manager, - wasVisible, - wasWorktreeActive, - isWorktreeActive, - hasCompletedVisibleResume: hasCompletedVisibleResumeRef.current, - captureViewportPositions - }) - renderingSuspendedByVisibilityRef.current = hiddenState.renderingSuspended - hiddenReasonRef.current = hiddenState.hiddenReason } + const hiddenState = hideTerminalVisibility({ + manager, + wasVisible, + wasWorktreeActive, + isWorktreeActive, + hasCompletedVisibleResume: hasCompletedVisibleResumeRef.current, + captureViewportPositions + }) + renderingSuspendedByVisibilityRef.current = hiddenState.renderingSuspended + hiddenReasonRef.current = hiddenState.hiddenReason + wasVisibleRef.current = false wasWorktreeActiveRef.current = isWorktreeActive // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.ts b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.ts index 4c686fb28c7..2384e4749b7 100644 --- a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.ts +++ b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.ts @@ -289,7 +289,7 @@ function reportLateWorkspaceCleanupResult( function removeArrayEntry(entries: T[], entry: T): void { const index = entries.indexOf(entry) - if (index >= 0) { + if (index !== -1) { entries.splice(index, 1) } } diff --git a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-post-batch-late-settlement.ts b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-post-batch-late-settlement.ts index 6239da304b2..280aabd6790 100644 --- a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-post-batch-late-settlement.ts +++ b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-post-batch-late-settlement.ts @@ -253,7 +253,7 @@ function releaseSettledPostBatchState(state: PostBatchLateSettlementState): void function removeArrayEntry(entries: T[], entry: T): void { const index = entries.indexOf(entry) - if (index >= 0) { + if (index !== -1) { entries.splice(index, 1) } } diff --git a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-skipped-ancestor-reclassification.ts b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-skipped-ancestor-reclassification.ts index e2bfd7a4c3c..cb301b557e2 100644 --- a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-skipped-ancestor-reclassification.ts +++ b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-skipped-ancestor-reclassification.ts @@ -65,7 +65,7 @@ export function reclassifySkippedWorkspaceCleanupAncestors({ function removeArrayEntry(entries: T[], entry: T): void { const index = entries.indexOf(entry) - if (index >= 0) { + if (index !== -1) { entries.splice(index, 1) } } diff --git a/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-stabilization.ts b/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-stabilization.ts index 9924e55d70b..f20ea1e4a30 100644 --- a/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-stabilization.ts +++ b/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-stabilization.ts @@ -5,7 +5,7 @@ export type DirectSshReconnectTargetState = { authority: DirectSshAuthority installedAt: number dampUntil: number | null - timer: DirectSshReconnectTimer | null + timer: DirectSshReconnectTimer } export function createDirectSshReconnectTargetState( diff --git a/src/renderer/src/lib/agent-hibernation-planner.ts b/src/renderer/src/lib/agent-hibernation-planner.ts index 6920d18ed1e..f5631ffc8ed 100644 --- a/src/renderer/src/lib/agent-hibernation-planner.ts +++ b/src/renderer/src/lib/agent-hibernation-planner.ts @@ -267,10 +267,7 @@ export function planAgentHibernationCandidates( } if ( runtimeLivenessRequiredWorktreeIds.has(worktreeId) && - !Object.prototype.hasOwnProperty.call( - snapshot.runtimeLivePtyIdsByWorktreeId ?? {}, - worktreeId - ) + !Object.hasOwn(snapshot.runtimeLivePtyIdsByWorktreeId ?? {}, worktreeId) ) { continue } diff --git a/src/renderer/src/lib/agent-status.ts b/src/renderer/src/lib/agent-status.ts index e49e3c002fa..11d966cb8bf 100644 --- a/src/renderer/src/lib/agent-status.ts +++ b/src/renderer/src/lib/agent-status.ts @@ -139,9 +139,7 @@ export function agentTypeToIconAgent(agentType: AgentType | null | undefined): T if (!agentType || agentType === 'unknown') { return null } - return Object.prototype.hasOwnProperty.call(ICONABLE_AGENT_TYPES, agentType) - ? (agentType as TuiAgent) - : null + return Object.hasOwn(ICONABLE_AGENT_TYPES, agentType) ? (agentType as TuiAgent) : null } // Why: shared resolver so all send paths stamp identical agent_kind on agent_prompt_sent telemetry. diff --git a/src/renderer/src/lib/crash-diagnostics.ts b/src/renderer/src/lib/crash-diagnostics.ts index 95c9bfe213c..78d1daa76c6 100644 --- a/src/renderer/src/lib/crash-diagnostics.ts +++ b/src/renderer/src/lib/crash-diagnostics.ts @@ -74,7 +74,7 @@ function disposeRendererCrashDiagnostics(): void { rendererSurface = 'main' } -if (typeof import.meta !== 'undefined' && import.meta.hot) { +if (import.meta !== undefined && import.meta.hot) { // Why: Vite can replace this module without a full renderer reload. Remove // global diagnostics hooks so dev sessions do not accumulate listeners. import.meta.hot.dispose(disposeRendererCrashDiagnostics) diff --git a/src/renderer/src/lib/feature-education-telemetry.ts b/src/renderer/src/lib/feature-education-telemetry.ts index 29c07200267..d66fd213722 100644 --- a/src/renderer/src/lib/feature-education-telemetry.ts +++ b/src/renderer/src/lib/feature-education-telemetry.ts @@ -133,7 +133,7 @@ export function trackTerminalPaneSplit(args: { } export function readEmittedSetupGuideStepIds(): Set { - if (typeof globalThis.localStorage === 'undefined') { + if (globalThis.localStorage === undefined) { return new Set() } try { @@ -150,7 +150,7 @@ export function readEmittedSetupGuideStepIds(): Set { } export function persistEmittedSetupGuideStepId(id: FeatureWallSetupStepId): void { - if (typeof globalThis.localStorage === 'undefined') { + if (globalThis.localStorage === undefined) { return } try { @@ -170,7 +170,7 @@ export function reserveTerminalPaneSplitTelemetry( source: TerminalPaneSplitSource, direction: 'vertical' | 'horizontal' ): boolean { - if (typeof globalThis.localStorage === 'undefined') { + if (globalThis.localStorage === undefined) { return true } try { diff --git a/src/renderer/src/lib/markdown-document-templates.ts b/src/renderer/src/lib/markdown-document-templates.ts index 1fadcd2613f..60414d481f0 100644 --- a/src/renderer/src/lib/markdown-document-templates.ts +++ b/src/renderer/src/lib/markdown-document-templates.ts @@ -126,7 +126,7 @@ export function applyMarkdownTemplatePlaceholders( } return content.replace(/\{\{\s*([a-zA-Z][a-zA-Z0-9_-]*)\s*\}\}/g, (match, key: string) => { - return Object.prototype.hasOwnProperty.call(replacements, key) ? replacements[key] : match + return Object.hasOwn(replacements, key) ? replacements[key] : match }) } diff --git a/src/renderer/src/lib/native-chat-telemetry.ts b/src/renderer/src/lib/native-chat-telemetry.ts index 0d6f96584d2..1299204b6db 100644 --- a/src/renderer/src/lib/native-chat-telemetry.ts +++ b/src/renderer/src/lib/native-chat-telemetry.ts @@ -15,7 +15,7 @@ import type { TuiAgent } from '../../../shared/types' * the broader `AgentType` string carried by the chat view. Narrowing to the * closed `agent_kind` enum (with an `'other'` fallback) happens here so call * sites never need an unsound `as TuiAgent` cast. */ -export type NativeChatTelemetryAgent = TuiAgent | string | null | undefined +export type NativeChatTelemetryAgent = TuiAgent | (string & {}) | null | undefined // `launchAgent` is optional on terminal tabs (plain shells, manually-started // agents) and the chat view's `AgentType` may carry a string outside the diff --git a/src/renderer/src/lib/pane-manager/pane-fit.ts b/src/renderer/src/lib/pane-manager/pane-fit.ts index f7165c3aad6..05e71b294dc 100644 --- a/src/renderer/src/lib/pane-manager/pane-fit.ts +++ b/src/renderer/src/lib/pane-manager/pane-fit.ts @@ -141,6 +141,7 @@ function performSafeFit(pane: ManagedPane): boolean { if (shouldRestoreScroll) { try { if (resumePendingFitScrollRestoreAfterFit(pane.terminal)) { + // Resume consumed the pending restore; the fallbacks below must not also run. } else if (pinnedScrollState) { const state: ScrollState = pinnedScrollState pinnedScrollState = null diff --git a/src/renderer/src/lib/pane-manager/pane-identity-registry.ts b/src/renderer/src/lib/pane-manager/pane-identity-registry.ts index 0c1951e979f..709ff30ec5f 100644 --- a/src/renderer/src/lib/pane-manager/pane-identity-registry.ts +++ b/src/renderer/src/lib/pane-manager/pane-identity-registry.ts @@ -3,9 +3,9 @@ import { mintStablePaneId } from './mint-stable-pane-id' import type { ManagedPaneInternal } from './pane-manager-types' export class PaneIdentityRegistry { - private leafIdByNumericId: Map = new Map() - private numericIdByLeafId: Map = new Map() - private publishedPaneIds: Set = new Set() + private leafIdByNumericId = new Map() + private numericIdByLeafId = new Map() + private publishedPaneIds = new Set() claimLeafId(leafIdHint?: string): TerminalLeafId { if (leafIdHint && isTerminalLeafId(leafIdHint) && !this.numericIdByLeafId.has(leafIdHint)) { diff --git a/src/renderer/src/lib/pane-manager/pane-manager.ts b/src/renderer/src/lib/pane-manager/pane-manager.ts index fbdbe1f5afe..f6d59594d01 100644 --- a/src/renderer/src/lib/pane-manager/pane-manager.ts +++ b/src/renderer/src/lib/pane-manager/pane-manager.ts @@ -66,7 +66,7 @@ export type { export class PaneManager { private root: HTMLElement - private panes: Map = new Map() + private panes = new Map() private activePaneId: number | null = null private nextPaneId = FIRST_PANE_ID private options: PaneManagerOptions diff --git a/src/renderer/src/lib/pane-manager/pane-tree-reparent-frame.test.ts b/src/renderer/src/lib/pane-manager/pane-tree-reparent-frame.test.ts index 4dff8e4f964..caf3da45092 100644 --- a/src/renderer/src/lib/pane-manager/pane-tree-reparent-frame.test.ts +++ b/src/renderer/src/lib/pane-manager/pane-tree-reparent-frame.test.ts @@ -38,7 +38,7 @@ function createMockElement(className = ''): TestElement { }, replaceChild: (nextChild: TestElement, oldChild: TestElement): TestElement => { const index = element.children.indexOf(oldChild) - if (index >= 0) { + if (index !== -1) { element.children[index] = nextChild } else { element.children.push(nextChild) diff --git a/src/renderer/src/lib/runtime-pane-title-leaf-id.ts b/src/renderer/src/lib/runtime-pane-title-leaf-id.ts index 0847f1e3de2..c18e752e883 100644 --- a/src/renderer/src/lib/runtime-pane-title-leaf-id.ts +++ b/src/renderer/src/lib/runtime-pane-title-leaf-id.ts @@ -78,7 +78,7 @@ export function resolveRuntimePaneTitleLeafResolution( let hasMultiplePaneTitles = false for (const runtimePaneId in titlesByPaneId) { - if (!Object.prototype.hasOwnProperty.call(titlesByPaneId, runtimePaneId)) { + if (!Object.hasOwn(titlesByPaneId, runtimePaneId)) { continue } diff --git a/src/renderer/src/lib/setup-script-prompt.ts b/src/renderer/src/lib/setup-script-prompt.ts index f25b806d1ef..8bf56d13980 100644 --- a/src/renderer/src/lib/setup-script-prompt.ts +++ b/src/renderer/src/lib/setup-script-prompt.ts @@ -97,7 +97,7 @@ export function filterSetupScriptPromptDismissalsToValidRepos( const unambiguousIdentityByRepoId = new Map() for (const identity of validRepoHostIdentities) { const separatorIndex = identity.indexOf('\0') - const repoId = separatorIndex >= 0 ? identity.slice(separatorIndex + 1) : identity + const repoId = separatorIndex !== -1 ? identity.slice(separatorIndex + 1) : identity unambiguousIdentityByRepoId.set( repoId, unambiguousIdentityByRepoId.has(repoId) ? null : identity diff --git a/src/renderer/src/lib/simulator-pane-shutdown-scheduler.ts b/src/renderer/src/lib/simulator-pane-shutdown-scheduler.ts index 722f322d80a..907ccee5093 100644 --- a/src/renderer/src/lib/simulator-pane-shutdown-scheduler.ts +++ b/src/renderer/src/lib/simulator-pane-shutdown-scheduler.ts @@ -9,7 +9,7 @@ type SimulatorTabReference = { type SimulatorPaneShutdownOptions = { getTabsForWorktree?: (worktreeId: string) => SimulatorTabReference[] - shutdownManagedSimulator?: (worktreeId: string) => Promise | unknown + shutdownManagedSimulator?: (worktreeId: string) => unknown } type ScheduleSimulatorPaneShutdownOptions = SimulatorPaneShutdownOptions & { diff --git a/src/renderer/src/lib/source-control-launch-agent-selection.ts b/src/renderer/src/lib/source-control-launch-agent-selection.ts index e8c00812590..c72098c9481 100644 --- a/src/renderer/src/lib/source-control-launch-agent-selection.ts +++ b/src/renderer/src/lib/source-control-launch-agent-selection.ts @@ -110,7 +110,7 @@ function hasActionOverride( overrides: NormalizedRepoSourceControlAiOverrides['actionOverrides'] | undefined, actionId: SourceControlActionId ): boolean { - return Object.prototype.hasOwnProperty.call(overrides ?? {}, actionId) + return Object.hasOwn(overrides ?? {}, actionId) } function readRecipeOverrideFields( @@ -120,13 +120,13 @@ function readRecipeOverrideFields( | undefined ): SourceControlActionRecipeOverrideField[] { const fields: SourceControlActionRecipeOverrideField[] = [] - if (Object.prototype.hasOwnProperty.call(recipe ?? {}, 'agentId')) { + if (Object.hasOwn(recipe ?? {}, 'agentId')) { fields.push('agent') } - if (Object.prototype.hasOwnProperty.call(recipe ?? {}, 'commandInputTemplate')) { + if (Object.hasOwn(recipe ?? {}, 'commandInputTemplate')) { fields.push('commandTemplate') } - if (Object.prototype.hasOwnProperty.call(recipe ?? {}, 'agentArgs')) { + if (Object.hasOwn(recipe ?? {}, 'agentArgs')) { fields.push('agentArgs') } return fields diff --git a/src/renderer/src/lib/terminal-contrast-correction.test.ts b/src/renderer/src/lib/terminal-contrast-correction.test.ts index 7f5cd76cbfe..2197bdf903c 100644 --- a/src/renderer/src/lib/terminal-contrast-correction.test.ts +++ b/src/renderer/src/lib/terminal-contrast-correction.test.ts @@ -12,7 +12,7 @@ function contrastRatio(a: string, b: string): number { const n = Number.parseInt(hex.replace('#', ''), 16) const toLinear = (channel: number): number => { const c = channel / 255 - return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4) + return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4 } const r = toLinear((n >> 16) & 0xff) const g = toLinear((n >> 8) & 0xff) diff --git a/src/renderer/src/lib/terminal-layout-equality.ts b/src/renderer/src/lib/terminal-layout-equality.ts index ce11907bed1..716482764e8 100644 --- a/src/renderer/src/lib/terminal-layout-equality.ts +++ b/src/renderer/src/lib/terminal-layout-equality.ts @@ -10,9 +10,7 @@ function sameStringRecord( const rightKeys = Object.keys(right) return ( leftKeys.length === rightKeys.length && - leftKeys.every( - (key) => Object.prototype.hasOwnProperty.call(right, key) && left[key] === right[key] - ) + leftKeys.every((key) => Object.hasOwn(right, key) && left[key] === right[key]) ) } diff --git a/src/renderer/src/lib/terminal-quick-command-search.ts b/src/renderer/src/lib/terminal-quick-command-search.ts index 09027cbfd16..8530e915d07 100644 --- a/src/renderer/src/lib/terminal-quick-command-search.ts +++ b/src/renderer/src/lib/terminal-quick-command-search.ts @@ -93,11 +93,11 @@ function scoreCandidate(query: string, rawCandidate: string, baseScore: number): return baseScore + 50 } const wordIndex = candidate.indexOf(` ${query}`) - if (wordIndex >= 0) { + if (wordIndex !== -1) { return baseScore + 100 + wordIndex } const index = candidate.indexOf(query) - if (index >= 0) { + if (index !== -1) { return baseScore + 200 + index } return NO_MATCH diff --git a/src/renderer/src/lib/terminal-themes/shared.ts b/src/renderer/src/lib/terminal-themes/shared.ts index 023213a6605..3d3ee17abba 100644 --- a/src/renderer/src/lib/terminal-themes/shared.ts +++ b/src/renderer/src/lib/terminal-themes/shared.ts @@ -7,7 +7,7 @@ export function mergeTerminalThemeCatalogs( for (const catalog of catalogs) { for (const [name, theme] of Object.entries(catalog)) { - if (Object.prototype.hasOwnProperty.call(merged, name)) { + if (Object.hasOwn(merged, name)) { throw new Error(`Duplicate terminal theme name: ${name}`) } merged[name] = theme diff --git a/src/renderer/src/lib/terminal-title-contrast.ts b/src/renderer/src/lib/terminal-title-contrast.ts index a54a4819092..38afe002ed2 100644 --- a/src/renderer/src/lib/terminal-title-contrast.ts +++ b/src/renderer/src/lib/terminal-title-contrast.ts @@ -183,7 +183,7 @@ function compositeRgb(foreground: RgbaColor, background: RgbaColor, alpha: numbe function relativeLuminance(rgb: Pick): number { const toLinear = (channel: number): number => { const normalized = channel / 255 - return normalized <= 0.03928 ? normalized / 12.92 : Math.pow((normalized + 0.055) / 1.055, 2.4) + return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4 } return 0.2126 * toLinear(rgb.r) + 0.7152 * toLinear(rgb.g) + 0.0722 * toLinear(rgb.b) } diff --git a/src/renderer/src/lib/ui-zoom.ts b/src/renderer/src/lib/ui-zoom.ts index 7ab1a00e19f..eb1749f222c 100644 --- a/src/renderer/src/lib/ui-zoom.ts +++ b/src/renderer/src/lib/ui-zoom.ts @@ -6,7 +6,7 @@ const isMac = navigator.userAgent.includes('Mac') * and repositions the native macOS traffic lights to stay aligned. */ export function applyUIZoom(level: number): void { - const zoomFactor = Math.pow(1.2, level) + const zoomFactor = 1.2 ** level window.api.ui.setZoomLevel(level) document.documentElement.style.setProperty('--ui-zoom-factor', String(zoomFactor)) if (isMac) { @@ -20,7 +20,7 @@ export function applyUIZoom(level: number): void { */ export function syncZoomCSSVar(): void { const level = window.api.ui.getZoomLevel() - const zoomFactor = Math.pow(1.2, level) + const zoomFactor = 1.2 ** level document.documentElement.style.setProperty('--ui-zoom-factor', String(zoomFactor)) if (isMac) { window.api.ui.syncTrafficLights(zoomFactor) diff --git a/src/renderer/src/lib/window-visibility-interval.ts b/src/renderer/src/lib/window-visibility-interval.ts index bdd250d5c16..aa1337fc095 100644 --- a/src/renderer/src/lib/window-visibility-interval.ts +++ b/src/renderer/src/lib/window-visibility-interval.ts @@ -3,7 +3,7 @@ export type WindowVisibilityIntervalTimer = ReturnType export function isWindowVisible(): boolean { return ( typeof document === 'undefined' || - typeof document.visibilityState === 'undefined' || + document.visibilityState === undefined || document.visibilityState === 'visible' ) } diff --git a/src/renderer/src/lib/workspace-emoji-shortcodes.ts b/src/renderer/src/lib/workspace-emoji-shortcodes.ts index 23feeb735e0..29e06dc74c9 100644 --- a/src/renderer/src/lib/workspace-emoji-shortcodes.ts +++ b/src/renderer/src/lib/workspace-emoji-shortcodes.ts @@ -25,7 +25,7 @@ const MATCH_TIER = { exact: 0, prefix: 1, wordStart: 2, substring: 3 } as const function matchTier(shortcode: string, query: string): number | null { const index = shortcode.indexOf(query) - if (index < 0) { + if (index === -1) { return null } if (index === 0) { diff --git a/src/renderer/src/runtime/web-session-tabs-sync.test.ts b/src/renderer/src/runtime/web-session-tabs-sync.test.ts index 526833a5969..cad1147ed1e 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync.test.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync.test.ts @@ -3012,7 +3012,7 @@ describe('applyWebSessionTabsSnapshot', () => { NOW ) as Partial - expect(Object.prototype.hasOwnProperty.call(patch, 'openFiles')).toBe(false) + expect(Object.hasOwn(patch, 'openFiles')).toBe(false) expect(state.openFiles).toEqual([unchanged]) }) diff --git a/src/renderer/src/runtime/web-session-tabs-sync.ts b/src/renderer/src/runtime/web-session-tabs-sync.ts index 2e28113c2d8..107c085db4e 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync.ts @@ -3356,8 +3356,7 @@ export function applyWebSessionTabsStorePatch( let mirroredAgentStatusChanged = false useAppStore.setState((state) => { const patch = buildPatch(state) - mirroredAgentStatusChanged = - patch !== state && Object.prototype.hasOwnProperty.call(patch, 'agentStatusByPaneKey') + mirroredAgentStatusChanged = patch !== state && Object.hasOwn(patch, 'agentStatusByPaneKey') return patch }) // Why: paired-web snapshots bypass setAgentStatus, so arm the stale-boundary timer explicitly like local hook events do. diff --git a/src/renderer/src/store/running-terminal-close-confirm.ts b/src/renderer/src/store/running-terminal-close-confirm.ts index 618d30c98c5..88db03a5d38 100644 --- a/src/renderer/src/store/running-terminal-close-confirm.ts +++ b/src/renderer/src/store/running-terminal-close-confirm.ts @@ -83,7 +83,7 @@ export const useRunningTerminalCloseConfirmStore = create queued.terminalTabId === request.terminalTabId ) - if (queuedIndex >= 0) { + if (queuedIndex !== -1) { queuedRequests[queuedIndex] = mergeRequests(queuedRequests[queuedIndex]!, request) return } diff --git a/src/renderer/src/store/slices/browser.ts b/src/renderer/src/store/slices/browser.ts index 78a7a31b358..3911830c072 100644 --- a/src/renderer/src/store/slices/browser.ts +++ b/src/renderer/src/store/slices/browser.ts @@ -938,7 +938,7 @@ export const createBrowserSlice: StateCreator = if (activePageId) { const restoredPages = get().browserPagesByWorkspace[restored.id] ?? [] const activePageIndex = pages.findIndex((orig) => orig.id === activePageId) - const targetPage = activePageIndex >= 0 ? restoredPages[activePageIndex] : null + const targetPage = activePageIndex !== -1 ? restoredPages[activePageIndex] : null if (targetPage && targetPage.id !== restoredPages[0]?.id) { get().setActiveBrowserPage(restored.id, targetPage.id) } diff --git a/src/renderer/src/store/slices/diffComments.ts b/src/renderer/src/store/slices/diffComments.ts index 1154fd649b5..99b11cb26bb 100644 --- a/src/renderer/src/store/slices/diffComments.ts +++ b/src/renderer/src/store/slices/diffComments.ts @@ -117,7 +117,7 @@ function settingsForWorktreeOwner(state: AppState, worktreeId: string): AppState } // Why: IPC writes aren't ordered, so serialize per worktree to stop an older snapshot from overwriting a newer one on disk. -const persistQueueByWorktree: Map> = new Map() +const persistQueueByWorktree = new Map>() // Why: chain each write onto the prior promise so writes land in call order; both then handlers keep the chain alive past a failure. // Why: queued work reads the latest list at dequeue time, and the returned promise settles for THIS write so callers can roll back. diff --git a/src/renderer/src/store/slices/editor.ts b/src/renderer/src/store/slices/editor.ts index ba0685b54f8..4b2efe9516a 100644 --- a/src/renderer/src/store/slices/editor.ts +++ b/src/renderer/src/store/slices/editor.ts @@ -4163,7 +4163,7 @@ export const createEditorSlice: StateCreator = (s // Why: session-local conflict tracking (Resolved-locally) lives only in the renderer; main returns raw git status, so the renderer owns conflictStatusSource. setGitStatus: (worktreeId, status) => set((s) => { - const hadStatusEntry = Object.prototype.hasOwnProperty.call(s.gitStatusByWorktree, worktreeId) + const hadStatusEntry = Object.hasOwn(s.gitStatusByWorktree, worktreeId) const prevEntries = s.gitStatusByWorktree[worktreeId] ?? [] const prevOperation = s.gitConflictOperationByWorktree[worktreeId] ?? 'unknown' const currentTracked = { ...s.trackedConflictPathsByWorktree[worktreeId] } diff --git a/src/renderer/src/store/slices/repo-host-identity.ts b/src/renderer/src/store/slices/repo-host-identity.ts index be1c2255621..f21da2b153a 100644 --- a/src/renderer/src/store/slices/repo-host-identity.ts +++ b/src/renderer/src/store/slices/repo-host-identity.ts @@ -29,7 +29,7 @@ export function findRepoForHost( repos: readonly T[], repoId: string, options: { - hostId?: ExecutionHostId | string | null + hostId?: ExecutionHostId | (string & {}) | null settings?: Pick | null } = {} ): T | null { diff --git a/src/renderer/src/store/slices/repo-identity-reconcile.ts b/src/renderer/src/store/slices/repo-identity-reconcile.ts index 9212313f0c7..f382ba21e2a 100644 --- a/src/renderer/src/store/slices/repo-identity-reconcile.ts +++ b/src/renderer/src/store/slices/repo-identity-reconcile.ts @@ -41,9 +41,7 @@ function areValuesEqual(a: unknown, b: unknown): boolean { return false } return keys.every( - (key) => - Object.prototype.hasOwnProperty.call(bRecord, key) && - areValuesEqual(aRecord[key], bRecord[key]) + (key) => Object.hasOwn(bRecord, key) && areValuesEqual(aRecord[key], bRecord[key]) ) } @@ -56,7 +54,7 @@ function areReposEqual(a: Repo, b: Repo): boolean { return false } for (const key of keys) { - if (!Object.prototype.hasOwnProperty.call(b, key)) { + if (!Object.hasOwn(b, key)) { return false } if (!areValuesEqual(a[key], b[key])) { diff --git a/src/renderer/src/store/slices/repos.ts b/src/renderer/src/store/slices/repos.ts index 39b9edd20e1..04f395cc13a 100644 --- a/src/renderer/src/store/slices/repos.ts +++ b/src/renderer/src/store/slices/repos.ts @@ -888,9 +888,7 @@ function areCatalogEntriesEqual(a: unknown, b: unknown): boolean { if (keys.length !== Object.keys(b).length) { return false } - return keys.every( - (key) => Object.prototype.hasOwnProperty.call(b, key) && areCatalogEntriesEqual(a[key], b[key]) - ) + return keys.every((key) => Object.hasOwn(b, key) && areCatalogEntriesEqual(a[key], b[key])) } // Why: returning `base` unchanged keeps referential-equality selectors quiet, so a diff --git a/src/renderer/src/store/slices/ssh-target-cleanup.ts b/src/renderer/src/store/slices/ssh-target-cleanup.ts index ba2f5211efc..43707fa86d6 100644 --- a/src/renderer/src/store/slices/ssh-target-cleanup.ts +++ b/src/renderer/src/store/slices/ssh-target-cleanup.ts @@ -200,10 +200,7 @@ export function buildRemovedSshTargetCleanupPatch( const nextTransientClearedConnections = { ...state.transientClearedAgentStatusConnectionIds } - const removedTransientClearBlock = Object.prototype.hasOwnProperty.call( - nextTransientClearedConnections, - targetId - ) + const removedTransientClearBlock = Object.hasOwn(nextTransientClearedConnections, targetId) delete nextTransientClearedConnections[targetId] const nextConnectionStates = new Map(state.sshConnectionStates) const removedConnectionState = nextConnectionStates.delete(targetId) @@ -211,18 +208,9 @@ export function buildRemovedSshTargetCleanupPatch( const removedLabel = nextLabels.delete(targetId) const nextHydrated = new Set(state.remoteWorkspaceHydratedTargetIds) const removedHydrated = nextHydrated.delete(targetId) - const removedSyncStatus = Object.prototype.hasOwnProperty.call( - state.remoteWorkspaceSyncStatusByTargetId, - targetId - ) - const removedPortForwards = Object.prototype.hasOwnProperty.call( - state.portForwardsByConnection, - targetId - ) - const removedDetectedPorts = Object.prototype.hasOwnProperty.call( - state.detectedPortsByConnection, - targetId - ) + const removedSyncStatus = Object.hasOwn(state.remoteWorkspaceSyncStatusByTargetId, targetId) + const removedPortForwards = Object.hasOwn(state.portForwardsByConnection, targetId) + const removedDetectedPorts = Object.hasOwn(state.detectedPortsByConnection, targetId) const nextSyncStatus = { ...state.remoteWorkspaceSyncStatusByTargetId } delete nextSyncStatus[targetId] const nextPortForwards = { ...state.portForwardsByConnection } diff --git a/src/renderer/src/store/slices/worktree-helpers.ts b/src/renderer/src/store/slices/worktree-helpers.ts index ea3ad4bb92f..9675be9a693 100644 --- a/src/renderer/src/store/slices/worktree-helpers.ts +++ b/src/renderer/src/store/slices/worktree-helpers.ts @@ -393,9 +393,7 @@ export function withoutErasedRequiredWorktreeFields( updates: Partial ): Partial { const erased = Object.keys(ERASURE_PROTECTED_KEYS).filter( - (key) => - updates[key as keyof WorktreeMeta] === undefined && - Object.prototype.hasOwnProperty.call(updates, key) + (key) => updates[key as keyof WorktreeMeta] === undefined && Object.hasOwn(updates, key) ) if (erased.length === 0) { return updates diff --git a/src/renderer/src/store/slices/worktree-nav-history.ts b/src/renderer/src/store/slices/worktree-nav-history.ts index 5a72d3d0530..1582e649017 100644 --- a/src/renderer/src/store/slices/worktree-nav-history.ts +++ b/src/renderer/src/store/slices/worktree-nav-history.ts @@ -43,7 +43,7 @@ export type WorktreeNavHistoryTaskDetailEntry = export type WorktreeNavHistoryViewEntry = | WorktreeNavHistorySimpleViewEntry | WorktreeNavHistoryTaskDetailEntry -export type WorktreeNavHistoryEntry = string | WorktreeNavHistoryViewEntry +export type WorktreeNavHistoryEntry = (string & {}) | WorktreeNavHistoryViewEntry export type WorktreeNavHistorySlice = { // Linear history, oldest -> newest. diff --git a/src/renderer/src/store/slices/worktrees.ts b/src/renderer/src/store/slices/worktrees.ts index 706fa02de2f..693eaedacb0 100644 --- a/src/renderer/src/store/slices/worktrees.ts +++ b/src/renderer/src/store/slices/worktrees.ts @@ -1394,8 +1394,7 @@ async function listWorktreeLineageForRuntime( workspaceLineage?: Record } const normalizeLineageResponse = (value: Record | LineageListResponse) => - Object.prototype.hasOwnProperty.call(value, 'lineage') || - Object.prototype.hasOwnProperty.call(value, 'workspaceLineage') + Object.hasOwn(value, 'lineage') || Object.hasOwn(value, 'workspaceLineage') ? { worktreeLineageById: (value as LineageListResponse).lineage ?? {}, workspaceLineageByChildKey: (value as LineageListResponse).workspaceLineage ?? {} @@ -2144,15 +2143,11 @@ function getHostedReviewLinkForMetaRefresh( existingWorktree: Worktree | undefined, key: HostedReviewLinkKey ): number | null { - return Object.prototype.hasOwnProperty.call(updates, key) - ? (updates[key] ?? null) - : (existingWorktree?.[key] ?? null) + return Object.hasOwn(updates, key) ? (updates[key] ?? null) : (existingWorktree?.[key] ?? null) } function hasExplicitPushTargetClear(updates: Partial): boolean { - return ( - Object.prototype.hasOwnProperty.call(updates, 'pushTarget') && updates.pushTarget === undefined - ) + return Object.hasOwn(updates, 'pushTarget') && updates.pushTarget === undefined } type RuntimeWorktreeMetaUpdates = Omit, 'pushTarget'> & { @@ -5557,7 +5552,7 @@ export const createWorktreeSlice: StateCreator set((s) => { for (const [worktreeId, tabs] of Object.entries(s.tabsByWorktree)) { const index = tabs.findIndex((tab) => tab.id === tabId) - if (index < 0) { + if (index === -1) { continue } const tab = tabs[index] diff --git a/src/renderer/src/web/web-clipboard-copy-fallback.test.ts b/src/renderer/src/web/web-clipboard-copy-fallback.test.ts index e6242c11ae4..28e9f1dc4dd 100644 --- a/src/renderer/src/web/web-clipboard-copy-fallback.test.ts +++ b/src/renderer/src/web/web-clipboard-copy-fallback.test.ts @@ -44,7 +44,7 @@ function createFakeDocument(options?: FakeDocOptions) { removeEventListener: vi.fn((type: string, listener: (event: unknown) => void) => { if (type === 'copy') { const index = listeners.indexOf(listener) - if (index >= 0) { + if (index !== -1) { listeners.splice(index, 1) } } diff --git a/src/renderer/src/web/web-preload-api.ts b/src/renderer/src/web/web-preload-api.ts index a1e89aa7135..2baa23a7b28 100644 --- a/src/renderer/src/web/web-preload-api.ts +++ b/src/renderer/src/web/web-preload-api.ts @@ -1081,7 +1081,7 @@ function removeConflictingWebOverrides( const conflictingOverrides = new Set() for (const conflict of conflicts) { for (const actionId of conflict.actionIds) { - if (Object.prototype.hasOwnProperty.call(next, actionId)) { + if (Object.hasOwn(next, actionId)) { conflictingOverrides.add(actionId) } } @@ -1870,8 +1870,7 @@ function createWorktreesApi(): NonNullable['worktrees']> { }), updateMeta: async ({ worktreeId, updates }) => { const rpcUpdates = - Object.prototype.hasOwnProperty.call(updates, 'pushTarget') && - updates.pushTarget === undefined + Object.hasOwn(updates, 'pushTarget') && updates.pushTarget === undefined ? { ...updates, pushTarget: null } : updates const owned = await callRuntimeResultWithOwner<{ worktree: Worktree }>('worktree.set', { diff --git a/src/shared/agent-hook-listener.ts b/src/shared/agent-hook-listener.ts index 44032c03b24..292c2120a44 100644 --- a/src/shared/agent-hook-listener.ts +++ b/src/shared/agent-hook-listener.ts @@ -842,7 +842,7 @@ function readString(record: Record, key: string): string | unde } function hasOwnField(record: Record, key: string): boolean { - return Object.prototype.hasOwnProperty.call(record, key) + return Object.hasOwn(record, key) } function hasAnyOwnField(record: Record, keys: readonly string[]): boolean { diff --git a/src/shared/automation-schedules.ts b/src/shared/automation-schedules.ts index e41ba3ac028..d8a93499287 100644 --- a/src/shared/automation-schedules.ts +++ b/src/shared/automation-schedules.ts @@ -301,7 +301,7 @@ export function parseAutomationRrule(rrule: string): { } const dayCode = rule.byDay[0] const dayOfWeek = DAY_CODES.indexOf(dayCode as (typeof DAY_CODES)[number]) - if (dayOfWeek < 0) { + if (dayOfWeek === -1) { throw new Error('Invalid recurrence day.') } return { diff --git a/src/shared/browser-page-zoom.ts b/src/shared/browser-page-zoom.ts index 28997ebab4a..83a23d90752 100644 --- a/src/shared/browser-page-zoom.ts +++ b/src/shared/browser-page-zoom.ts @@ -23,7 +23,7 @@ export function normalizeBrowserPageZoomLevel(value: unknown): number { export function browserPageZoomLevelToPercent(level: number): number { // Why: Electron zoom levels are exponential; show the same percentage users // expect from Chromium browser zoom controls. - return Math.round(100 * Math.pow(1.2, normalizeBrowserPageZoomLevel(level))) + return Math.round(100 * 1.2 ** normalizeBrowserPageZoomLevel(level)) } export function nextBrowserPageZoomLevel( diff --git a/src/shared/external-worktree-visibility.ts b/src/shared/external-worktree-visibility.ts index be5d24a524c..3907d133a30 100644 --- a/src/shared/external-worktree-visibility.ts +++ b/src/shared/external-worktree-visibility.ts @@ -30,7 +30,7 @@ export function getExternalWorktreeParentPath(worktreePath: string | undefined): return `//${parts.slice(0, -1).join('/')}` } const lastSeparatorIndex = normalized.lastIndexOf('/') - if (lastSeparatorIndex < 0) { + if (lastSeparatorIndex === -1) { return UNKNOWN_EXTERNAL_WORKTREE_PARENT_PATH } if (lastSeparatorIndex === 0) { diff --git a/src/shared/gitlab-types.ts b/src/shared/gitlab-types.ts index 703085fb8a2..c9f2a0fffba 100644 --- a/src/shared/gitlab-types.ts +++ b/src/shared/gitlab-types.ts @@ -215,7 +215,7 @@ export type GitLabTodo = { id: number /** Free-form GitLab action name, e.g. 'assigned', 'mentioned', 'build_failed', 'review_requested'. */ actionName: string - targetType: GitLabTodoTargetType | string + targetType: GitLabTodoTargetType | (string & {}) /** iid for MR/Issue targets; absent for Commit/Note targets where the identifier is a SHA or note ID. */ targetIid: number | null targetTitle: string diff --git a/src/shared/jira-types.ts b/src/shared/jira-types.ts index ad8e84ecbad..6adaa966ead 100644 --- a/src/shared/jira-types.ts +++ b/src/shared/jira-types.ts @@ -19,7 +19,7 @@ export type JiraViewer = { avatarUrl?: string } -export type JiraSiteSelection = string | 'all' +export type JiraSiteSelection = (string & {}) | 'all' export type JiraConnectionStatus = { connected: boolean diff --git a/src/shared/linear-agent-access.ts b/src/shared/linear-agent-access.ts index 2ffa17cb406..69169268767 100644 --- a/src/shared/linear-agent-access.ts +++ b/src/shared/linear-agent-access.ts @@ -115,13 +115,13 @@ export type LinearIssueListRequest = { filter?: LinearIssueListFilter teamInput?: string limit?: number - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' } export type LinearProjectListRequest = { query?: string limit?: number - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' } export type LinearStatusSetRequest = LinearWriteTargetRequest & { diff --git a/src/shared/linear-agent-result-types.ts b/src/shared/linear-agent-result-types.ts index e986460007b..ae04c842d4d 100644 --- a/src/shared/linear-agent-result-types.ts +++ b/src/shared/linear-agent-result-types.ts @@ -144,7 +144,7 @@ export type LinearSearchResult = { issues: LinearSearchIssueSummary[] meta: { query: string - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' limit: number returned: number limitReached: boolean @@ -178,7 +178,7 @@ export type LinearTeamSummary = { export type LinearTeamListResult = { teams: LinearTeamSummary[] meta: { - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' returned: number partial: boolean workspaceErrors: { @@ -211,7 +211,7 @@ export type LinearIssueListResult = { issues: LinearSearchIssueSummary[] meta: { filter: LinearIssueListFilter - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' team?: LinearTeamSummary limit: number returned: number @@ -242,7 +242,7 @@ export type LinearProjectListResult = { projects: LinearAgentProjectSummary[] meta: { query?: string - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' limit: number returned: number hasMore: boolean diff --git a/src/shared/linear-issue-view-resume-state.ts b/src/shared/linear-issue-view-resume-state.ts index 11fbb967570..82c03f87c2e 100644 --- a/src/shared/linear-issue-view-resume-state.ts +++ b/src/shared/linear-issue-view-resume-state.ts @@ -173,9 +173,7 @@ export function selectLinearWorkspaceIssueFilter( if (!workspaceId) { return emptyLinearIssueAttributeFilter() } - const filter = Object.prototype.hasOwnProperty.call(filters, workspaceId) - ? filters[workspaceId] - : undefined + const filter = Object.hasOwn(filters, workspaceId) ? filters[workspaceId] : undefined return filter ? canonicalizeLinearIssueAttributeFilter(filter) : emptyLinearIssueAttributeFilter() } diff --git a/src/shared/linear-links.ts b/src/shared/linear-links.ts index 0f1db592736..097a0f5e1f9 100644 --- a/src/shared/linear-links.ts +++ b/src/shared/linear-links.ts @@ -88,7 +88,7 @@ export function parseLinearIssueInput(input: string): ParsedLinearIssueInput | n const parts = parsed.pathname.split('/').filter(Boolean) const issueIndex = parts.indexOf('issue') const organizationUrlKey = parts[0] - const rawIdentifier = issueIndex >= 0 ? parts[issueIndex + 1] : undefined + const rawIdentifier = issueIndex !== -1 ? parts[issueIndex + 1] : undefined if (!organizationUrlKey || !rawIdentifier) { return null } diff --git a/src/shared/linear-mcp-issue-list.ts b/src/shared/linear-mcp-issue-list.ts index c668c848aef..6955eef16ab 100644 --- a/src/shared/linear-mcp-issue-list.ts +++ b/src/shared/linear-mcp-issue-list.ts @@ -19,7 +19,7 @@ export type LinearMcpIssueListRequest = { createdAt?: string updatedAt?: string includeArchived?: boolean - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' } export type LinearMcpIssueListResult = { @@ -30,7 +30,7 @@ export type LinearMcpIssueListResult = { hasMore: boolean nextCursor?: string orderBy: 'createdAt' | 'updatedAt' - workspaceId?: string | 'all' + workspaceId?: (string & {}) | 'all' partial: boolean workspaceErrors: { workspace: LinearWorkspaceCandidate diff --git a/src/shared/mcp-config.ts b/src/shared/mcp-config.ts index 0cc0ce60d3b..09fbc6d3865 100644 --- a/src/shared/mcp-config.ts +++ b/src/shared/mcp-config.ts @@ -165,7 +165,7 @@ export function inspectMcpConfigContent( function collectMcpServerEntries(rawServers: Record): [string, unknown][] | null { const entries: [string, unknown][] = [] for (const name in rawServers) { - if (!Object.prototype.hasOwnProperty.call(rawServers, name)) { + if (!Object.hasOwn(rawServers, name)) { continue } if ( diff --git a/src/shared/mcp-server-inspection.ts b/src/shared/mcp-server-inspection.ts index ab7a79cf40b..a475ff413e8 100644 --- a/src/shared/mcp-server-inspection.ts +++ b/src/shared/mcp-server-inspection.ts @@ -66,7 +66,7 @@ function inspectMcpEnv(env: unknown): BoundedEnv { const masked: Record = {} let fields = 0 for (const key in env) { - if (!Object.prototype.hasOwnProperty.call(env, key)) { + if (!Object.hasOwn(env, key)) { continue } fields += 1 diff --git a/src/shared/native-chat-image-transcript-markers.ts b/src/shared/native-chat-image-transcript-markers.ts index a55f0c9c272..34c2e9ae32d 100644 --- a/src/shared/native-chat-image-transcript-markers.ts +++ b/src/shared/native-chat-image-transcript-markers.ts @@ -21,7 +21,7 @@ function stripImagePromptMarkersFromFirstText( blocks: readonly NativeChatBlock[] ): NativeChatBlock[] { const textIndex = blocks.findIndex(isTextBlock) - if (textIndex < 0) { + if (textIndex === -1) { return blocks as NativeChatBlock[] } const block = blocks[textIndex] diff --git a/src/shared/native-chat-session-option-commands.ts b/src/shared/native-chat-session-option-commands.ts index 6fb5095be8d..e4efd5efdf9 100644 --- a/src/shared/native-chat-session-option-commands.ts +++ b/src/shared/native-chat-session-option-commands.ts @@ -22,7 +22,7 @@ export function parseBuiltSessionOptionCommand( const marker = '__orca_session_option_value__' const template = build(marker) const markerIndex = template.indexOf(marker) - if (markerIndex < 0) { + if (markerIndex === -1) { return null } const prefix = template.slice(0, markerIndex) diff --git a/src/shared/native-chat-tool-summary.ts b/src/shared/native-chat-tool-summary.ts index 3f71d997e15..a048e16da0e 100644 --- a/src/shared/native-chat-tool-summary.ts +++ b/src/shared/native-chat-tool-summary.ts @@ -283,7 +283,7 @@ function boundedPreviewValue(value: unknown, depth: number, seen: WeakSet = {} let count = 0 for (const key in value) { - if (!Object.prototype.hasOwnProperty.call(value, key)) { + if (!Object.hasOwn(value, key)) { continue } if (count >= MAX_PREVIEW_COLLECTION_ITEMS) { diff --git a/src/shared/persisted-ui-equality.ts b/src/shared/persisted-ui-equality.ts index b82320737c9..701359c487d 100644 --- a/src/shared/persisted-ui-equality.ts +++ b/src/shared/persisted-ui-equality.ts @@ -36,7 +36,7 @@ export function persistedUIValuesEqual(left: unknown, right: unknown): boolean { for (const key of leftKeys) { if ( - !Object.prototype.hasOwnProperty.call(rightRecord, key) || + !Object.hasOwn(rightRecord, key) || !persistedUIValuesEqual(leftRecord[key], rightRecord[key]) ) { return false diff --git a/src/shared/remote-runtime-shared-control-ready.ts b/src/shared/remote-runtime-shared-control-ready.ts index 23461245d0d..9d0a92c500c 100644 --- a/src/shared/remote-runtime-shared-control-ready.ts +++ b/src/shared/remote-runtime-shared-control-ready.ts @@ -37,7 +37,7 @@ export function waitForSharedControlReadyWithTimeout(args: { } settled = true const index = args.readyWaiters.indexOf(waiter) - if (index >= 0) { + if (index !== -1) { args.readyWaiters.splice(index, 1) } reject(remoteRuntimeUnavailableError()) @@ -65,7 +65,7 @@ export function waitForSharedControlReadyWithTimeout(args: { args.open() } catch (error) { const index = args.readyWaiters.indexOf(waiter) - if (index >= 0) { + if (index !== -1) { args.readyWaiters.splice(index, 1) } waiter.reject(error instanceof Error ? error : remoteRuntimeUnavailableError(String(error))) diff --git a/src/shared/setup-script-import-codex-environment.ts b/src/shared/setup-script-import-codex-environment.ts index 77a8e48296d..d684d197497 100644 --- a/src/shared/setup-script-import-codex-environment.ts +++ b/src/shared/setup-script-import-codex-environment.ts @@ -136,7 +136,7 @@ function parseTomlMultilineString( remainder = lines[index] } const closeIndex = remainder.indexOf(delimiter) - if (closeIndex >= 0) { + if (closeIndex !== -1) { if (!oversized && !append(remainder.slice(0, closeIndex))) { oversized = true } diff --git a/src/shared/setup-script-imports.ts b/src/shared/setup-script-imports.ts index 5aa698d13d1..5c37b99dab0 100644 --- a/src/shared/setup-script-imports.ts +++ b/src/shared/setup-script-imports.ts @@ -186,7 +186,7 @@ function resolveSupersetScriptValue( } for (const field in localRecord) { - if (!Object.prototype.hasOwnProperty.call(localRecord, field)) { + if (!Object.hasOwn(localRecord, field)) { continue } if (field !== 'before' && field !== 'after') { @@ -283,7 +283,7 @@ function collectUnsupportedCmuxCommandFields( const supportedFields = new Set(['name', 'title', 'description', 'keywords', 'command']) const unsupportedFields: string[] = [] for (const field in command) { - if (!Object.prototype.hasOwnProperty.call(command, field)) { + if (!Object.hasOwn(command, field)) { continue } if (!supportedFields.has(field)) { diff --git a/src/shared/source-control-ai-actions.ts b/src/shared/source-control-ai-actions.ts index 8b1a8acb636..19dc70aa652 100644 --- a/src/shared/source-control-ai-actions.ts +++ b/src/shared/source-control-ai-actions.ts @@ -195,7 +195,7 @@ export function renderSourceControlActionCommandTemplate( // Why: placeholder names may start with letters or underscores. // Why: only own keys are real variables; inherited Object.prototype names // (e.g. `constructor`) must stay visible instead of rendering their value. - if (!Object.prototype.hasOwnProperty.call(variables, name)) { + if (!Object.hasOwn(variables, name)) { return match } const value = variables[name] diff --git a/src/shared/source-control-ai.ts b/src/shared/source-control-ai.ts index 2ca3541916a..7445ee5b874 100644 --- a/src/shared/source-control-ai.ts +++ b/src/shared/source-control-ai.ts @@ -194,7 +194,7 @@ function normalizeOperationRecord( } const normalized: Partial> = {} for (const operation of SOURCE_CONTROL_AI_OPERATIONS) { - if (!Object.prototype.hasOwnProperty.call(value, operation)) { + if (!Object.hasOwn(value, operation)) { continue } const normalizedValue = normalizeValue(value[operation]) @@ -214,7 +214,7 @@ function normalizeActionRecord( } const normalized: Partial> = {} for (const actionId of SOURCE_CONTROL_ACTION_IDS) { - if (!Object.prototype.hasOwnProperty.call(value, actionId)) { + if (!Object.hasOwn(value, actionId)) { continue } const normalizedValue = normalizeValue(value[actionId]) @@ -388,7 +388,7 @@ function legacyPromptFromCommandTemplate( function hasActionAgentRecipe(recipe: { agentId?: TuiAgent | CustomAgentId | null }): recipe is { agentId: TuiAgent | CustomAgentId | null } { - return Object.prototype.hasOwnProperty.call(recipe, 'agentId') + return Object.hasOwn(recipe, 'agentId') } function legacyCommitMessageCoreChanges( @@ -541,7 +541,7 @@ function mergeLegacyModelSelectionDelta( let changed = false const keys = new Set([...Object.keys(legacy ?? {}), ...Object.keys(projected ?? {})]) for (const key of keys) { - const legacyHasKey = Object.prototype.hasOwnProperty.call(legacy ?? {}, key) + const legacyHasKey = Object.hasOwn(legacy ?? {}, key) const legacyValue = legacy?.[key] if (JSON.stringify(projected?.[key]) === JSON.stringify(legacyValue)) { continue @@ -994,7 +994,7 @@ function hasOwnInstruction( instructions: Partial> | null | undefined, operation: SourceControlAiOperation ): boolean { - return Object.prototype.hasOwnProperty.call(instructions ?? {}, operation) + return Object.hasOwn(instructions ?? {}, operation) } function readRepoInstructionOverride( diff --git a/src/shared/terminal-file-link-conformance.ts b/src/shared/terminal-file-link-conformance.ts index ec2ea0b9c64..d74af87eab2 100644 --- a/src/shared/terminal-file-link-conformance.ts +++ b/src/shared/terminal-file-link-conformance.ts @@ -13,7 +13,7 @@ export type TerminalFileLinkTapConformanceCase = { export function columnForTerminalFileLinkTap(testCase: TerminalFileLinkTapConformanceCase): number { const column = testCase.lineText.indexOf(testCase.tapText) - if (column < 0) { + if (column === -1) { throw new Error(`Tap text "${testCase.tapText}" not found in "${testCase.lineText}"`) } return column diff --git a/src/shared/text-search.ts b/src/shared/text-search.ts index d3d9b40226a..780164372cb 100644 --- a/src/shared/text-search.ts +++ b/src/shared/text-search.ts @@ -393,7 +393,7 @@ export function ingestGitGrepLine( const secondNullIdx = rest.indexOf('\0') let lineNumberText: string let lineContent: string - if (secondNullIdx >= 0) { + if (secondNullIdx !== -1) { lineNumberText = rest.substring(0, secondNullIdx) lineContent = rest.substring(secondNullIdx + 1).replace(/\n$/, '') } else { diff --git a/src/shared/tui-agent-config.ts b/src/shared/tui-agent-config.ts index 06d490342ba..3437e44e560 100644 --- a/src/shared/tui-agent-config.ts +++ b/src/shared/tui-agent-config.ts @@ -331,7 +331,7 @@ export const TUI_AGENT_CONFIG: Record = { } export function isTuiAgent(value: unknown): value is TuiAgent { - return typeof value === 'string' && Object.prototype.hasOwnProperty.call(TUI_AGENT_CONFIG, value) + return typeof value === 'string' && Object.hasOwn(TUI_AGENT_CONFIG, value) } export function getTuiAgentDetectCommands(config: TuiAgentConfig): string[] { diff --git a/src/shared/tui-agent-launch-defaults.ts b/src/shared/tui-agent-launch-defaults.ts index 94ca6e103a1..f0f5d3e8bd1 100644 --- a/src/shared/tui-agent-launch-defaults.ts +++ b/src/shared/tui-agent-launch-defaults.ts @@ -85,7 +85,7 @@ export function resolveTuiAgentLaunchArgs( ): string { if ( configuredArgs && - Object.prototype.hasOwnProperty.call(configuredArgs, agent) && + Object.hasOwn(configuredArgs, agent) && typeof configuredArgs[agent] === 'string' ) { return configuredArgs[agent] ?? '' @@ -97,7 +97,7 @@ export function resolveTuiAgentLaunchEnv( agent: TuiAgent, configuredEnv: Partial>> | null | undefined ): Record { - if (configuredEnv && Object.prototype.hasOwnProperty.call(configuredEnv, agent)) { + if (configuredEnv && Object.hasOwn(configuredEnv, agent)) { return { ...configuredEnv[agent] } } return getTuiAgentDefaultEnv(agent) diff --git a/src/shared/types.ts b/src/shared/types.ts index 0f7a6685f9a..34cc0e43c35 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1497,8 +1497,8 @@ export type PRCheckJob = { export type PRCheckRunDetails = { name: string - status: PRCheckDetail['status'] | string | null - conclusion: PRCheckDetail['conclusion'] | string | null + status: PRCheckDetail['status'] | (string & {}) | null + conclusion: PRCheckDetail['conclusion'] | (string & {}) | null url: string | null detailsUrl: string | null startedAt: string | null @@ -1749,7 +1749,7 @@ export type LinearWorkspace = LinearViewer & { credentialRevision?: number } -export type LinearWorkspaceSelection = string | 'all' +export type LinearWorkspaceSelection = (string & {}) | 'all' export type LinearWorkspaceSelector = LinearWorkspaceSelection | undefined export type LinearConcreteWorkspaceId = string diff --git a/src/shared/workspace-session-schema.sleeping-agent.test.ts b/src/shared/workspace-session-schema.sleeping-agent.test.ts index 9f58d453969..16f499ac7d5 100644 --- a/src/shared/workspace-session-schema.sleeping-agent.test.ts +++ b/src/shared/workspace-session-schema.sleeping-agent.test.ts @@ -219,12 +219,9 @@ describe('parseWorkspaceSession sleeping agents', () => { expect(result.ok).toBe(true) if (result.ok) { - expect( - Object.prototype.hasOwnProperty.call( - result.value.sleepingAgentSessionsByPaneKey ?? {}, - '__proto__' - ) - ).toBe(false) + expect(Object.hasOwn(result.value.sleepingAgentSessionsByPaneKey ?? {}, '__proto__')).toBe( + false + ) const record = result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1'] expect(record?.agent).toBe('codex') expect(record?.launchConfig).toBeUndefined() diff --git a/tests/e2e/chinese-ime-chat-input-repro.spec.ts b/tests/e2e/chinese-ime-chat-input-repro.spec.ts index 8c57b8e6a9f..9c72e8cdd4d 100644 --- a/tests/e2e/chinese-ime-chat-input-repro.spec.ts +++ b/tests/e2e/chinese-ime-chat-input-repro.spec.ts @@ -305,7 +305,7 @@ async function readPromptState(page: Page): Promise .filter((value): value is string => value !== null) const promptIndex = content.lastIndexOf(PROMPT) const liveLine = - promptIndex >= 0 ? (content.slice(promptIndex + PROMPT.length).split(/\r?\n/)[0] ?? '') : '' + promptIndex !== -1 ? (content.slice(promptIndex + PROMPT.length).split(/\r?\n/)[0] ?? '') : '' return { model: liveLine.trimEnd(), cursor: Array.from(liveLine.trimEnd()).length, diff --git a/tests/e2e/codex-composer-echo-latency-probe.ts b/tests/e2e/codex-composer-echo-latency-probe.ts index 9fc9cabf2c7..c49d98d3708 100644 --- a/tests/e2e/codex-composer-echo-latency-probe.ts +++ b/tests/e2e/codex-composer-echo-latency-probe.ts @@ -112,7 +112,7 @@ export async function installCodexEchoLatencyProbe(page: Page, target: string): const observeRender = (): void => { renderEvents += 1 const paintedAt = performance.now() - for (const entry of awaitingRender.splice(0, awaitingRender.length)) { + for (const entry of awaitingRender.splice(0)) { entry.sample.keyToRenderMs = paintedAt - entry.startedAt } } diff --git a/tests/e2e/fixtures/daemon-generation-legacy-close-client.ts b/tests/e2e/fixtures/daemon-generation-legacy-close-client.ts index b9bbb621597..730ebbeffc9 100644 --- a/tests/e2e/fixtures/daemon-generation-legacy-close-client.ts +++ b/tests/e2e/fixtures/daemon-generation-legacy-close-client.ts @@ -62,7 +62,7 @@ const OBSERVER = { function readConfig(): FixtureConfig { const configIndex = process.argv.indexOf('--config') - const configPath = configIndex >= 0 ? process.argv[configIndex + 1] : undefined + const configPath = configIndex !== -1 ? process.argv[configIndex + 1] : undefined if (!configPath) { throw new Error('Legacy close client requires --config ') } diff --git a/tests/e2e/fixtures/daemon-generation-reconnect-client.ts b/tests/e2e/fixtures/daemon-generation-reconnect-client.ts index 28c0a6686a7..6bd163cc474 100644 --- a/tests/e2e/fixtures/daemon-generation-reconnect-client.ts +++ b/tests/e2e/fixtures/daemon-generation-reconnect-client.ts @@ -41,7 +41,7 @@ const MAX_OUTPUT_CHARS = 32_768 function readConfig(): ClientConfig { const configIndex = process.argv.indexOf('--config') - const configPath = configIndex >= 0 ? process.argv[configIndex + 1] : undefined + const configPath = configIndex !== -1 ? process.argv[configIndex + 1] : undefined if (!configPath) { throw new Error('Reconnect client requires --config ') } diff --git a/tests/e2e/fixtures/visible-tui-scroll-fixture.cjs b/tests/e2e/fixtures/visible-tui-scroll-fixture.cjs index 3702ad21a9f..63b7683916a 100644 --- a/tests/e2e/fixtures/visible-tui-scroll-fixture.cjs +++ b/tests/e2e/fixtures/visible-tui-scroll-fixture.cjs @@ -12,7 +12,7 @@ const MOUSE_REPORT_PATTERN = new RegExp(`${ESC}\\[<(64|65);\\d+;\\d+M`, 'g') // at 120 reports/s) instead of minimal rows. const args = process.argv.slice(2) const logIndex = args.indexOf('--log') -const LOG_PATH = logIndex >= 0 ? args[logIndex + 1] : null +const LOG_PATH = logIndex !== -1 ? args[logIndex + 1] : null const HEAVY_FRAMES = args.includes('--heavy') let offset = 0 diff --git a/tests/e2e/helpers/completed-worker-retirement-fixture.ts b/tests/e2e/helpers/completed-worker-retirement-fixture.ts index d1211234258..564f52e2f5f 100644 --- a/tests/e2e/helpers/completed-worker-retirement-fixture.ts +++ b/tests/e2e/helpers/completed-worker-retirement-fixture.ts @@ -86,7 +86,7 @@ export function readCompletedWorkerLedger(): LifecycleEvent[] { } const contents = readFileSync(lifecycleLedgerPath, 'utf8') const lastCompleteLine = contents.lastIndexOf('\n') - if (lastCompleteLine < 0) { + if (lastCompleteLine === -1) { return [] } return contents diff --git a/tests/e2e/korean-ime-terminal-shift-enter-commit.spec.ts b/tests/e2e/korean-ime-terminal-shift-enter-commit.spec.ts index 208e79a8928..1ddfd56adb4 100644 --- a/tests/e2e/korean-ime-terminal-shift-enter-commit.spec.ts +++ b/tests/e2e/korean-ime-terminal-shift-enter-commit.spec.ts @@ -339,7 +339,7 @@ async function dispatchPlainEnter(session: CDPSession): Promise { async function readPromptLine(page: Page): Promise { const content = stripTerminalControls(await getTerminalContent(page, 20_000)) const promptIndex = content.lastIndexOf(PROMPT) - if (promptIndex < 0) { + if (promptIndex === -1) { return '' } return (content.slice(promptIndex + PROMPT.length).split(/\r?\n/)[0] ?? '').trimEnd() diff --git a/tests/e2e/remote-agent-session-focus-authority.spec.ts b/tests/e2e/remote-agent-session-focus-authority.spec.ts index 2b007fddd98..4fb4c3e7ae8 100644 --- a/tests/e2e/remote-agent-session-focus-authority.spec.ts +++ b/tests/e2e/remote-agent-session-focus-authority.spec.ts @@ -140,7 +140,7 @@ async function readRenderedTabOrder(page: Page): Promise { function expectImmediatelyAfter(order: string[], predecessor: string, created: string): void { const predecessorIndex = order.indexOf(predecessor) - if (predecessorIndex < 0) { + if (predecessorIndex === -1) { throw new Error( `placement predecessor ${predecessor} missing before ${created}: ${JSON.stringify(order)}` ) diff --git a/tests/e2e/terminal-link-hover-after-worktree-return.spec.ts b/tests/e2e/terminal-link-hover-after-worktree-return.spec.ts index 70912388cba..ee5ebe81079 100644 --- a/tests/e2e/terminal-link-hover-after-worktree-return.spec.ts +++ b/tests/e2e/terminal-link-hover-after-worktree-return.spec.ts @@ -56,7 +56,7 @@ async function locateHoverProbe(page: Page, needle: string): Promise continue } const idx = line.translateToString(true).indexOf(needle) - if (idx >= 0) { + if (idx !== -1) { hit = { row, col: idx } break } diff --git a/tests/e2e/terminal-paste-ownership.spec.ts b/tests/e2e/terminal-paste-ownership.spec.ts index 5df66cbce04..56c65a139aa 100644 --- a/tests/e2e/terminal-paste-ownership.spec.ts +++ b/tests/e2e/terminal-paste-ownership.spec.ts @@ -114,7 +114,7 @@ async function rightClickActiveTerminalSurface(page: Page): Promise { async function installClipboardReadTerminalBlurRepro(app: ElectronApplication): Promise { await app.evaluate(({ BrowserWindow, ipcMain }) => { - type ClipboardReadHandler = (event: unknown, ...args: unknown[]) => Promise | unknown + type ClipboardReadHandler = (event: unknown, ...args: unknown[]) => unknown const global = globalThis as unknown as { __orcaOriginalClipboardReadTextHandler?: ClipboardReadHandler } @@ -152,7 +152,7 @@ async function installClipboardReadTerminalBlurRepro(app: ElectronApplication): async function restoreClipboardReadTerminalBlurRepro(app: ElectronApplication): Promise { await app.evaluate(({ ipcMain }) => { - type ClipboardReadHandler = (event: unknown, ...args: unknown[]) => Promise | unknown + type ClipboardReadHandler = (event: unknown, ...args: unknown[]) => unknown const global = globalThis as unknown as { __orcaOriginalClipboardReadTextHandler?: ClipboardReadHandler } diff --git a/tests/tools/win-crash-survival-e2e/cli-args.mjs b/tests/tools/win-crash-survival-e2e/cli-args.mjs index 2a289d7893e..e3533342e4d 100644 --- a/tests/tools/win-crash-survival-e2e/cli-args.mjs +++ b/tests/tools/win-crash-survival-e2e/cli-args.mjs @@ -119,7 +119,7 @@ function validateArgShape(argv) { function takeValue(argv, flag) { const idx = argv.indexOf(flag) - if (idx < 0) { + if (idx === -1) { return undefined } const value = argv[idx + 1] diff --git a/tests/tools/win-update-e2e/cli-args.mjs b/tests/tools/win-update-e2e/cli-args.mjs index 510bac5a899..d6310e8d159 100644 --- a/tests/tools/win-update-e2e/cli-args.mjs +++ b/tests/tools/win-update-e2e/cli-args.mjs @@ -190,7 +190,7 @@ function validate(opts, installDirFlagPresent) { function takeValue(argv, flag) { const idx = argv.indexOf(flag) - if (idx < 0) { + if (idx === -1) { return undefined } const value = argv[idx + 1] diff --git a/tests/tools/win-update-e2e/daemon-processes.mjs b/tests/tools/win-update-e2e/daemon-processes.mjs index f7d43cc9b44..d292c0f24b8 100644 --- a/tests/tools/win-update-e2e/daemon-processes.mjs +++ b/tests/tools/win-update-e2e/daemon-processes.mjs @@ -148,7 +148,7 @@ function normalizeArray(raw) { function parseUserDataArg(argv) { const idx = argv.indexOf('--user-data') - return idx >= 0 && argv[idx + 1] ? argv[idx + 1] : defaultUserDataDir() + return idx !== -1 && argv[idx + 1] ? argv[idx + 1] : defaultUserDataDir() } function runStandalone(argv) { @@ -161,7 +161,7 @@ function runStandalone(argv) { console.log(JSON.stringify(pidFiles, null, 2)) const scopeIdx = argv.indexOf('--scope') - const scope = scopeIdx >= 0 && argv[scopeIdx + 1] ? argv[scopeIdx + 1] : '' + const scope = scopeIdx !== -1 && argv[scopeIdx + 1] ? argv[scopeIdx + 1] : '' const processes = findDaemonProcesses(scope) console.log( `[daemon-processes] live daemon processes${scope ? ` scoped to "${scope}"` : ''} (${processes.length}):` From ebcb2b6a60bc59d3cd4099e122584e2259931c92 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:29:01 -0700 Subject: [PATCH 003/100] docs: expand PR template with ELI5, before/after screenshots, and X handle Make the pull request template clearer for contributors: plain-language ELI5, what/why, mandatory before/after UI proof, testing checklist, AI disclosure, and an Author X field. Align CONTRIBUTING with the template. --- .github/CONTRIBUTING.md | 12 +++--- .github/pull_request_template.md | 67 ++++++++++++++++++++++++++------ 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 31fd088d324..189428c47fe 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -56,17 +56,15 @@ CI enforces this for `src/preload/` and `src/shared/` — see `docs/preload-type ## Pull Requests -Each pull request should: +Each pull request should follow [`.github/pull_request_template.md`](./pull_request_template.md). In particular: -- explain the user-visible change -- stay focused on a single topic when possible -- include screenshots or screen recordings for new UI or behavior changes +- open with an ELI5 of the change (plain language paragraph; the PR title is the one-liner) +- explain what changed and why, and stay focused on a single topic when possible +- for any UI or interaction change, attach **before and after** screenshots (or short videos); if there is no visual change, say `No visual change` and why - include high-quality tests when behavior changes or bug fixes warrant them - include a brief code review summary from your AI coding agent that explicitly checks cross-platform compatibility, SSH/remote/local compatibility, supported agent and integration compatibility, performance risk, UI quality when applicable, and basic security risk - mention any platform-specific, remote/SSH-specific, agent-specific, integration-specific, or git-provider-specific behavior and testing notes -- **Include your X (Twitter) handle!** We love giving shoutouts to our contributors when we merge features on [@orca_build](https://x.com/orca_build). - -If there is no visual change, say that explicitly in the PR description. +- **Include your X (Twitter) handle** in the PR template Author section — we shout out contributors when we merge features on [@orca_build](https://x.com/orca_build). ## Release Process diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b69e2dcc233..f8349076c63 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,19 +1,59 @@ -## Summary +## ELI5 -Describe the user-visible change. + -## Screenshots +## What Changed -- Add screenshots or a screen recording for any new or changed UI behavior. -- If there is no visual change, say `No visual change`. + -## Testing +## Why -- [ ] `pnpm lint` -- [ ] `pnpm typecheck` -- [ ] `pnpm test` -- [ ] `pnpm build` -- [ ] Added or updated high-quality tests that would catch regressions, or explained why tests were not needed + + +## Linked Issue + + + +- Fixes # + +## Screenshots (required for UI / behavior changes) + +**Before and after are mandatory** for any user-visible or UI change. Do not open a UI PR with only a description. + +- Attach a **before** screenshot (or short recording of the old behavior). +- Attach an **after** screenshot (or short recording of the new behavior). +- Side-by-side or labeled before/after is preferred. +- For motion, transitions, or interactions, include a short **before and after** video (or one video that clearly shows both). +- If there is truly no visual or interaction change, write exactly: `No visual change` and briefly say why. + +### Before + + + +### After + + + +## How to test + + + +- [ ] I manually tested these changes locally +- [ ] Automated tests added/updated, or explained why not below + +## Checklist + +- [ ] This PR is small and focused +- [ ] I explained what changed and why (including ELI5) +- [ ] Before/after screenshots or videos attached for UI changes, or `No visual change` with reason +- [ ] Self-reviewed for correctness, security, and performance +- [ ] Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A) +- [ ] `pnpm lint`, `pnpm typecheck`, `pnpm test`, and `pnpm build` pass (or CI will cover; local preferred) + +## AI assistance + +- [ ] This PR was created or substantially assisted by an AI coding agent (optional disclosure) +- [ ] I reviewed the full diff, understand every change, and can explain it in review ## AI Review Report @@ -27,3 +67,8 @@ Provide a basic security audit summary from your AI coding agent. Call out any i ## Notes Call out any platform-specific behavior, risks, or follow-up work. + +## Author + +- X / Twitter: @your_handle + From 444638d96b015eae81b83ff63dac5a3b160adb3e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:40:27 -0700 Subject: [PATCH 004/100] Revise pull request template for clarity and updates Updated the pull request template to simplify language and clarify sections. Added new sections for AI disclosure and testing instructions. --- .github/pull_request_template.md | 59 ++++++++++---------------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f8349076c63..a1ba0f2376b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ ## ELI5 - + ## What Changed @@ -12,27 +12,15 @@ ## Linked Issue - + -- Fixes # +Fixes # -## Screenshots (required for UI / behavior changes) - -**Before and after are mandatory** for any user-visible or UI change. Do not open a UI PR with only a description. - -- Attach a **before** screenshot (or short recording of the old behavior). -- Attach an **after** screenshot (or short recording of the new behavior). -- Side-by-side or labeled before/after is preferred. -- For motion, transitions, or interactions, include a short **before and after** video (or one video that clearly shows both). -- If there is truly no visual or interaction change, write exactly: `No visual change` and briefly say why. - -### Before - - - -### After - - +## Visual Proof + + + + ## How to test @@ -41,34 +29,25 @@ - [ ] I manually tested these changes locally - [ ] Automated tests added/updated, or explained why not below + + +## AI Disclosure + + +## Review + +Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance + ## Checklist - [ ] This PR is small and focused - [ ] I explained what changed and why (including ELI5) -- [ ] Before/after screenshots or videos attached for UI changes, or `No visual change` with reason +- [ ] Before/after screenshots or videos attached for UI changes, or `N/A` with reason - [ ] Self-reviewed for correctness, security, and performance - [ ] Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A) - [ ] `pnpm lint`, `pnpm typecheck`, `pnpm test`, and `pnpm build` pass (or CI will cover; local preferred) -## AI assistance - -- [ ] This PR was created or substantially assisted by an AI coding agent (optional disclosure) -- [ ] I reviewed the full diff, understand every change, and can explain it in review - -## AI Review Report - -Summarize the code review you ran with your AI coding agent. Include the main risks it checked, what it flagged, and what you changed or verified as a result. -Confirm that the review explicitly checked cross-platform compatibility for macOS, Linux, and Windows, including shortcuts, labels, paths, shell behavior, and any Electron-specific platform differences touched by this PR. - -## Security Audit - -Provide a basic security audit summary from your AI coding agent. Call out any input handling, command execution, path handling, auth, secrets, dependency, or IPC risks that were reviewed, plus any follow-up needed. - -## Notes - -Call out any platform-specific behavior, risks, or follow-up work. - ## Author - X / Twitter: @your_handle - + From 64aec94cb225eb44f59750f201f18f7958d81046 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:26:53 -0700 Subject: [PATCH 005/100] fix(sidebar): give draft reviews their own glyph instead of a red PR icon (#13919) * fix(sidebar): give draft reviews their own glyph instead of a red PR icon The worktree card review icon used one PR glyph for every state and tinted it by check status, so a draft with failing checks rendered as a red PR icon that read as closed. Shape now carries review state (draft/closed/merged), and check tone applies only to open reviews so the glyph agrees with its tooltip. Same fix for the source-control header icon, where draft and closed differed only by a muted tone. Closes #13088 Co-authored-by: Orca * test(sidebar): pin stateless and GitLab-closed review icon behaviour Documents why a stateless row keeps its check tone (folder cards render one while a linked review loads or its details fail) and covers the closed-MR glyph override, which previously read as already-merged. Co-authored-by: Orca --------- Co-authored-by: Orca --- .../hosted-review-header-chrome.tsx | 22 ++++- .../sidebar/worktree-review-helpers.test.tsx | 81 ++++++++++++++++++ .../sidebar/worktree-review-helpers.tsx | 84 +++++++++++++------ 3 files changed, 160 insertions(+), 27 deletions(-) diff --git a/src/renderer/src/components/right-sidebar/hosted-review-header-chrome.tsx b/src/renderer/src/components/right-sidebar/hosted-review-header-chrome.tsx index 66733779c77..b13a5728d2f 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review-header-chrome.tsx +++ b/src/renderer/src/components/right-sidebar/hosted-review-header-chrome.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { GitMerge } from 'lucide-react' +import { GitMerge, GitPullRequestClosed, GitPullRequestDraft } from 'lucide-react' import type { HostedReviewInfo } from '../../../../shared/hosted-review' import { cn } from '@/lib/utils' import { PullRequestIcon } from './checks-panel-content' @@ -17,6 +17,23 @@ function hostedReviewStateClass(review: HostedReviewInfo): string { return 'text-muted-foreground/50' } +// Why: draft and closed only differed by a near-identical muted tone on the same +// glyph, so a draft review read as closed. +function hostedReviewStateIcon( + review: HostedReviewInfo +): React.ComponentType<{ className?: string }> | null { + if (review.state === 'merged') { + return GitMerge + } + if (review.state === 'closed') { + return GitPullRequestClosed + } + if (review.state === 'draft') { + return GitPullRequestDraft + } + return null +} + export function HostedReviewIcon({ review, className @@ -24,7 +41,8 @@ export function HostedReviewIcon({ review: HostedReviewInfo className?: string }): React.JSX.Element { - const Icon = review.provider === 'gitlab' ? GitMerge : PullRequestIcon + const providerIcon = review.provider === 'gitlab' ? GitMerge : PullRequestIcon + const Icon = hostedReviewStateIcon(review) ?? providerIcon return } diff --git a/src/renderer/src/components/sidebar/worktree-review-helpers.test.tsx b/src/renderer/src/components/sidebar/worktree-review-helpers.test.tsx index 7ca741d4ced..2be58e1bff5 100644 --- a/src/renderer/src/components/sidebar/worktree-review-helpers.test.tsx +++ b/src/renderer/src/components/sidebar/worktree-review-helpers.test.tsx @@ -44,4 +44,85 @@ describe('ReviewIcon', () => { expect(blocked).not.toContain('text-emerald-500/80') expect(blocked).not.toBe(passing) }) + + // Why: #13088 — a draft with failing checks used the same glyph as every other + // review and was painted red, so it was indistinguishable from a closed PR. + it('gives a draft its own glyph and never paints it with a check tone', () => { + const draft = renderToStaticMarkup( + + ) + const closed = renderToStaticMarkup( + + ) + + expect(draft).toContain('lucide-git-pull-request-draft') + expect(draft).not.toContain('text-rose-500/85') + expect(closed).toContain('lucide-git-pull-request-closed') + expect(draft).not.toBe(closed) + }) + + it('keeps check tones on open reviews so failing checks still stand out', () => { + const failing = renderToStaticMarkup( + + ) + + expect(failing).toContain('text-rose-500/85') + }) + + it('renders merged reviews with the merge glyph', () => { + const merged = renderToStaticMarkup( + + ) + + expect(merged).toContain('lucide-git-merge') + }) + + // Why: a closed MR used to render the merge glyph, which read as "already merged". + it('overrides the GitLab provider glyph for closed merge requests', () => { + const closed = renderToStaticMarkup( + + ) + + expect(closed).toContain('lucide-git-pull-request-closed') + expect(closed).not.toContain('lucide-git-merge') + }) + + // Why: folder cards render a stateless row while a linked review loads or its + // details fail. There is no state glyph to contradict, so problems must still be + // visible — but a tone-less row must not be promoted to a passing green. + it('flags problems on a stateless row without ever claiming success', () => { + const stateless = (status: 'failure' | 'pending' | 'success') => + renderToStaticMarkup( + + ) + + expect(stateless('failure')).toContain('text-rose-500/85') + expect(stateless('pending')).toContain('text-amber-500/85') + expect(stateless('success')).not.toContain('text-emerald-500/80') + }) }) diff --git a/src/renderer/src/components/sidebar/worktree-review-helpers.tsx b/src/renderer/src/components/sidebar/worktree-review-helpers.tsx index 7f096a954dc..96e64fb4423 100644 --- a/src/renderer/src/components/sidebar/worktree-review-helpers.tsx +++ b/src/renderer/src/components/sidebar/worktree-review-helpers.tsx @@ -1,4 +1,4 @@ -import { GitMerge } from 'lucide-react' +import { GitMerge, GitPullRequestClosed, GitPullRequestDraft } from 'lucide-react' import { cn } from '@/lib/utils' import { PullRequestIcon } from './WorktreeCardHelpers' import type { WorktreeCardPrDisplay } from './worktree-card-pr-display' @@ -23,6 +23,60 @@ export function getProviderName(review: WorktreeCardPrDisplay): string { return 'GitHub' } +// Why: the glyph must carry review state — tone alone made a draft with failing +// checks render as a red PR icon, which reads as closed. +function getStateIcon( + state: WorktreeCardPrDisplay['state'] +): React.ComponentType<{ className?: string }> | null { + if (state === 'merged') { + return GitMerge + } + if (state === 'closed') { + return GitPullRequestClosed + } + if (state === 'draft') { + return GitPullRequestDraft + } + return null +} + +// Why: checks only gate a review that is actually open; draft/closed/merged keep +// their state tone so the glyph agrees with its tooltip. A stateless row (folder +// cards render one while a linked review is loading or its details failed) has no +// state glyph to contradict, so it still flags problems — but never claims success, +// since emerald would assert an open review we have not confirmed. +function getCheckTone(review: WorktreeCardPrDisplay): string | null { + if (review.state && review.state !== 'open') { + return null + } + if (review.status === 'failure') { + return 'text-rose-500/85' + } + if (review.status === 'pending') { + return 'text-amber-500/85' + } + if (review.state === 'open' && review.status === 'success') { + return 'text-emerald-500/80' + } + return null +} + +function getStateTone(state: WorktreeCardPrDisplay['state']): string { + if (state === 'merged') { + return 'text-purple-600/70 dark:text-purple-400/70' + } + if (state === 'open') { + return 'text-emerald-500/80' + } + if (state === 'closed') { + return 'text-muted-foreground/60' + } + if (state === 'draft') { + return 'text-muted-foreground/50' + } + return 'text-muted-foreground opacity-70' +} + export function ReviewIcon({ review, className, @@ -32,28 +86,8 @@ export function ReviewIcon({ className?: string variant?: 'provider' | 'generic' }): React.JSX.Element { - const Icon = variant === 'provider' && review.provider === 'gitlab' ? GitMerge : PullRequestIcon - const checkTone = - review.state !== 'merged' && review.status === 'failure' - ? 'text-rose-500/85' - : review.state !== 'merged' && review.status === 'pending' - ? 'text-amber-500/85' - : review.state === 'open' && review.status === 'success' - ? 'text-emerald-500/80' - : null - return ( - - ) + const providerIcon = + variant === 'provider' && review.provider === 'gitlab' ? GitMerge : PullRequestIcon + const Icon = getStateIcon(review.state) ?? providerIcon + return } From b51ef400ec74b3dc761809b7cd4a8ef7489e96dd Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:53:22 -0700 Subject: [PATCH 006/100] fix(quick-commands): flatten the settings list and give the command editor room (#13922) --- .../components/settings/QuickCommandsList.tsx | 208 +++++++++++------- .../components/settings/QuickCommandsPane.tsx | 93 +++----- .../settings/QuickCommandsToolbar.tsx | 116 ++++++++++ .../TerminalQuickCommandAdvancedSection.tsx | 57 ++--- .../TerminalQuickCommandAppendEnterSwitch.tsx | 5 +- .../TerminalQuickCommandCollapsibleRow.tsx | 38 ++++ .../TerminalQuickCommandContentSection.tsx | 188 +++++++--------- .../TerminalQuickCommandDialog.tsx | 22 +- .../TerminalQuickCommandDialogFooter.tsx | 10 +- src/renderer/src/components/ui/textarea.tsx | 28 +++ src/renderer/src/i18n/locales/en.json | 6 + 11 files changed, 470 insertions(+), 301 deletions(-) create mode 100644 src/renderer/src/components/settings/QuickCommandsToolbar.tsx create mode 100644 src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandCollapsibleRow.tsx create mode 100644 src/renderer/src/components/ui/textarea.tsx diff --git a/src/renderer/src/components/settings/QuickCommandsList.tsx b/src/renderer/src/components/settings/QuickCommandsList.tsx index ec377677ee5..3a0042b32a8 100644 --- a/src/renderer/src/components/settings/QuickCommandsList.tsx +++ b/src/renderer/src/components/settings/QuickCommandsList.tsx @@ -1,4 +1,4 @@ -import { Check, Copy, Pencil, Trash2 } from 'lucide-react' +import { Check, Copy, Pencil, TerminalSquare, Trash2 } from 'lucide-react' import type { Repo, TerminalQuickCommand, @@ -29,6 +29,15 @@ function getScopeLabel( return repo ? getQuickCommandRepoLabel(repo) : 'Missing project' } +function getRunModeLabel(command: TerminalQuickCommand): string { + if (isTerminalAgentQuickCommand(command)) { + return translate('auto.components.settings.QuickCommandsPane.4ccc63da87', 'Agent') + } + return command.appendEnter + ? translate('auto.components.settings.QuickCommandsPane.9b3e338d62', 'Enter') + : translate('auto.components.settings.QuickCommandsPane.9fcfc29519', 'Insert') +} + function QuickCommandRow({ command, repoById, @@ -43,6 +52,7 @@ function QuickCommandRow({ const scope = getTerminalQuickCommandScope(command) const body = getTerminalQuickCommandBody(command) const { canCopy, copyText, status } = useClipboardTextCopyFeedback(body) + const commandName = command.label || 'quick command' const copyLabel = status === 'copied' @@ -51,19 +61,24 @@ function QuickCommandRow({ ? translate('auto.components.settings.QuickCommandsPane.53b17a4b1b', "Couldn't copy") : canCopy ? translate('auto.components.settings.QuickCommandsPane.a9a564b7e7', 'Copy {{value0}}', { - value0: command.label || 'quick command' + value0: commandName }) : translate('auto.components.settings.QuickCommandsPane.69a1441a21', 'Nothing to copy') + const editLabel = translate( + 'auto.components.settings.QuickCommandsPane.7d90fd5299', + 'Edit {{value0}}', + { value0: commandName } + ) return ( -
+
{command.label || translate('auto.components.settings.QuickCommandsPane.2bb9e38e93', 'Untitled')}
- + {scope.type === 'repo' ? ( <> @@ -74,9 +89,9 @@ function QuickCommandRow({ )}
-
+
{isTerminalAgentQuickCommand(command) ? ( - + ) : null} @@ -91,59 +106,95 @@ function QuickCommandRow({
-
- {isTerminalAgentQuickCommand(command) - ? translate('auto.components.settings.QuickCommandsPane.4ccc63da87', 'Agent') - : command.appendEnter - ? translate('auto.components.settings.QuickCommandsPane.9b3e338d62', 'Enter') - : translate('auto.components.settings.QuickCommandsPane.9fcfc29519', 'Insert')} +
+ {getRunModeLabel(command)} +
+ {/* Why can-hover: touch devices never hover, so the actions must stay visible there. */} +
+ + + +
+
+ ) +} + +function QuickCommandsEmptyState({ + hasCommands, + hasQuery +}: { + hasCommands: boolean + hasQuery: boolean +}): React.JSX.Element { + if (hasCommands) { + return ( +
+ {hasQuery + ? translate( + 'auto.components.settings.QuickCommandsList.noSearchMatches', + 'No commands match this search.' + ) + : translate( + 'auto.components.settings.QuickCommandsPane.3eb9897ab0', + 'No commands in the selected scopes.' + )} +
+ ) + } + // Why no action here: the toolbar's Add Command sits directly above. + return ( +
+ +
+

+ {translate( + 'auto.components.settings.QuickCommandsPane.38d61927e6', + 'No quick commands saved.' + )} +

+

+ {translate( + 'auto.components.settings.QuickCommandsPane.c36912efd5', + 'Run them from the Quick Commands button in the tab bar, or right-click inside any terminal.' + )} +

- - -
) } @@ -151,43 +202,32 @@ function QuickCommandRow({ export function QuickCommandsList({ commands, visibleCommands, + hasQuery, repoById, onEdit, onRemove }: { commands: TerminalQuickCommand[] visibleCommands: TerminalQuickCommand[] + hasQuery: boolean repoById: Map> onEdit: (command: TerminalQuickCommand) => void onRemove: (command: TerminalQuickCommand) => void }): React.JSX.Element { + if (visibleCommands.length === 0) { + return 0} hasQuery={hasQuery} /> + } return ( -
- {visibleCommands.length === 0 ? ( -
- {commands.length === 0 - ? translate( - 'auto.components.settings.QuickCommandsPane.38d61927e6', - 'No quick commands saved.' - ) - : translate( - 'auto.components.settings.QuickCommandsPane.3eb9897ab0', - 'No commands in the selected scopes.' - )} -
- ) : ( -
- {visibleCommands.map((command) => ( - - ))} -
- )} +
+ {visibleCommands.map((command) => ( + + ))}
) } diff --git a/src/renderer/src/components/settings/QuickCommandsPane.tsx b/src/renderer/src/components/settings/QuickCommandsPane.tsx index f7f703e7ac1..a4f1ea85fe4 100644 --- a/src/renderer/src/components/settings/QuickCommandsPane.tsx +++ b/src/renderer/src/components/settings/QuickCommandsPane.tsx @@ -1,26 +1,25 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Plus } from 'lucide-react' import type { GlobalSettings, TerminalQuickCommand } from '../../../../shared/types' import { getTerminalQuickCommandScope } from '../../../../shared/terminal-quick-commands' import { createTerminalQuickCommandDraft, TerminalQuickCommandDialog } from '@/components/terminal-quick-commands/TerminalQuickCommandDialog' +import { searchTerminalQuickCommands } from '@/lib/terminal-quick-command-search' import { useAppStore } from '../../store' import { Button } from '../ui/button' -import { Label } from '../ui/label' import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { getSettingOwnershipSummary } from './setting-ownership' import { translate } from '@/i18n/i18n' import { QuickCommandsList } from './QuickCommandsList' -import { GLOBAL_SCOPE_KEY, QuickCommandsScopeFilter } from './QuickCommandsScopeFilter' +import { QuickCommandsToolbar } from './QuickCommandsToolbar' +import { GLOBAL_SCOPE_KEY } from './QuickCommandsScopeFilter' import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID, parseExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select' import { getTerminalQuickCommandHostOptions, shouldShowTerminalQuickCommandHostOwnership @@ -130,6 +129,7 @@ export function QuickCommandsPane({ // automatically rather than being silently excluded. const [scopeSelection, setScopeSelection] = useState | null>(null) const [scopePopoverOpen, setScopePopoverOpen] = useState(false) + const [query, setQuery] = useState('') const availableHostId = getAvailableQuickCommandHostId(selectedHostId, hostOptions) const editorHostIsCurrent = @@ -164,7 +164,7 @@ export function QuickCommandsPane({ const effectiveSelection: ReadonlySet = scopeSelection ?? allScopeKeys const showAll = scopeSelection === null - const visibleCommands = commands.filter((command) => { + const scopedCommands = commands.filter((command) => { const scope = getTerminalQuickCommandScope(command) if (showAll) { return true @@ -174,6 +174,7 @@ export function QuickCommandsPane({ } return effectiveSelection.has(scope.repoId) }) + const visibleCommands = searchTerminalQuickCommands(scopedCommands, query) const createDraftForCurrentFilter = useCallback((): TerminalQuickCommand => { // Why: when the user has narrowed to a single repo scope, the natural @@ -273,68 +274,32 @@ export function QuickCommandsPane({ void useAppStore.getState().deleteTerminalQuickCommand(selectedHostId, command.id) } - return ( -
-
-
- -

- {shouldShowTerminalQuickCommandHostOwnership(hostOptions) - ? ownership.description - : translate( - 'auto.components.settings.settingOwnership.terminalQuickCommands', - 'Commands are saved on this client, then scoped globally or to a project setup so they run from the selected terminal context.' - )} -

-
- -
+ const openAddDialog = (): void => + setEditor({ + mode: 'add', + command: createDraftForCurrentFilter(), + connectionGeneration: selectedRuntimeConnectionGeneration, + hostId: selectedHostId + }) + return ( +
{shouldShowTerminalQuickCommandHostOwnership(hostOptions) ? ( -
- - -
+

{ownership.description}

) : null} - { + setSelectedHostId(hostId) + setScopeSelection(null) + }} + canAdd={canManageSelectedHost} + onAdd={openAddDialog} repos={hostRepos} effectiveSelection={effectiveSelection} showAll={showAll} @@ -408,6 +373,7 @@ export function QuickCommandsPane({ 0} repoById={repoById} onEdit={(command) => setEditor({ @@ -428,6 +394,7 @@ export function QuickCommandsPane({ mode={editor.mode} command={editor.command} repos={hostRepos} + defaultAdvancedOpen onOpenChange={(open) => !open && setEditor(null)} onSave={saveCommand} /> diff --git a/src/renderer/src/components/settings/QuickCommandsToolbar.tsx b/src/renderer/src/components/settings/QuickCommandsToolbar.tsx new file mode 100644 index 00000000000..65e034eeb3f --- /dev/null +++ b/src/renderer/src/components/settings/QuickCommandsToolbar.tsx @@ -0,0 +1,116 @@ +import type { Dispatch, SetStateAction } from 'react' +import { Plus, Search } from 'lucide-react' +import type { Repo } from '../../../../shared/types' +import type { ExecutionHostId } from '../../../../shared/execution-host' +import { Button } from '../ui/button' +import { Input } from '../ui/input' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select' +import { translate } from '@/i18n/i18n' +import { QuickCommandsScopeFilter } from './QuickCommandsScopeFilter' + +type QuickCommandsToolbarProps = { + query: string + setQuery: (query: string) => void + hostOptions: readonly { id: ExecutionHostId; label: string }[] + showHostSelect: boolean + selectedHostId: ExecutionHostId + onHostChange: (hostId: ExecutionHostId) => void + canAdd: boolean + onAdd: () => void + repos: readonly Repo[] + effectiveSelection: ReadonlySet + showAll: boolean + scopePopoverOpen: boolean + setScopePopoverOpen: Dispatch> + handleSelectAll: () => void + toggleScope: (key: string) => void +} + +export function QuickCommandsToolbar({ + query, + setQuery, + hostOptions, + showHostSelect, + selectedHostId, + onHostChange, + canAdd, + onAdd, + repos, + effectiveSelection, + showAll, + scopePopoverOpen, + setScopePopoverOpen, + handleSelectAll, + toggleScope +}: QuickCommandsToolbarProps): React.JSX.Element { + const searchLabel = translate( + 'auto.components.settings.QuickCommandsToolbar.searchLabel', + 'Search commands' + ) + + return ( +
+
+ + setQuery(event.target.value)} + placeholder={searchLabel} + aria-label={searchLabel} + className="h-8 pl-8 text-xs" + /> +
+ + + + {showHostSelect ? ( + + ) : null} + + +
+ ) +} diff --git a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAdvancedSection.tsx b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAdvancedSection.tsx index d49be9c9616..af196b2c628 100644 --- a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAdvancedSection.tsx +++ b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAdvancedSection.tsx @@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' import { translate } from '@/i18n/i18n' import { TerminalQuickCommandAppendEnterSwitch } from './TerminalQuickCommandAppendEnterSwitch' +import { TerminalQuickCommandCollapsibleRow } from './TerminalQuickCommandCollapsibleRow' import { TerminalQuickCommandScopeField } from './TerminalQuickCommandScopeField' type TerminalQuickCommandAdvancedSectionProps = { @@ -50,42 +51,26 @@ export function TerminalQuickCommandAdvancedSection({ -
-
-
- {!isTerminalAgentQuickCommand(draft) ? ( - - ) : null} - { - lastRepoScopeIdRef.current = repoId - }} - setDraft={setDraft} - /> -
-
-
+ + { + lastRepoScopeIdRef.current = repoId + }} + setDraft={setDraft} + /> + {!isTerminalAgentQuickCommand(draft) ? ( + + ) : null} +
) } diff --git a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAppendEnterSwitch.tsx b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAppendEnterSwitch.tsx index 4450562729e..73f69ebf5d8 100644 --- a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAppendEnterSwitch.tsx +++ b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAppendEnterSwitch.tsx @@ -3,11 +3,13 @@ import { Switch } from '@/components/ui/switch' type TerminalQuickCommandAppendEnterSwitchProps = { appendEnter: boolean onToggle: () => void + disabled?: boolean } export function TerminalQuickCommandAppendEnterSwitch({ appendEnter, - onToggle + onToggle, + disabled = false }: TerminalQuickCommandAppendEnterSwitchProps): React.JSX.Element { return (
@@ -27,6 +29,7 @@ export function TerminalQuickCommandAppendEnterSwitch({
+
+
+ {children} +
+
+
+ ) +} diff --git a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandContentSection.tsx b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandContentSection.tsx index 34035a6233f..19967b0daba 100644 --- a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandContentSection.tsx +++ b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandContentSection.tsx @@ -12,9 +12,11 @@ import { SelectTrigger, SelectValue } from '@/components/ui/select' +import { Textarea } from '@/components/ui/textarea' import { AgentIcon } from '@/lib/agent-catalog' import { cn } from '@/lib/utils' import { translate } from '@/i18n/i18n' +import { TerminalQuickCommandCollapsibleRow } from './TerminalQuickCommandCollapsibleRow' import { getTerminalQuickCommandAgentOptions } from './terminal-quick-command-agent-options' import type { TerminalQuickCommandDialogDraftMemory } from './terminal-quick-command-dialog-draft' @@ -37,85 +39,66 @@ export function TerminalQuickCommandContentSection({ }: TerminalQuickCommandContentSectionProps): React.JSX.Element { return (
- {/* Why: action changes add/remove agent-only fields; animating rows here - keeps the fixed dialog from snapping between content heights. */} -
-
-
-
-
-
+ ) : null} + + + + ) + })} + + +
-