diff --git a/.oxlintrc.json b/.oxlintrc.json index f414c9066..87cfc8862 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -22,9 +22,11 @@ "react/jsx-filename-extension": ["error", { "extensions": [".tsx", ".jsx"] }], "react/jsx-fragments": "error", "react/jsx-key": "error", + "react/jsx-no-constructed-context-values": "error", "react/jsx-no-target-blank": "error", "react/jsx-no-useless-fragment": ["error", { "allowExpressions": true }], "react/jsx-pascal-case": "error", + "react/no-object-type-as-default-prop": "error", "react/self-closing-comp": "error", "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", diff --git a/src/renderer/src/components/editor/EditorContent.tsx b/src/renderer/src/components/editor/EditorContent.tsx index 8a4a5d8ba..45def20fc 100644 --- a/src/renderer/src/components/editor/EditorContent.tsx +++ b/src/renderer/src/components/editor/EditorContent.tsx @@ -55,6 +55,8 @@ type FileContent = { loadError?: string } +const noopCloseMarkdownTableOfContents = (): void => {} + function matchesPendingEditorReveal( reveal: PendingEditorReveal | null, file: Pick @@ -106,7 +108,7 @@ export function EditorContent({ isChangesMode, sideBySide, showMarkdownTableOfContents = false, - onCloseMarkdownTableOfContents = () => {}, + onCloseMarkdownTableOfContents = noopCloseMarkdownTableOfContents, markdownAnnotationsEnabled = true, pendingEditorReveal, handleContentChange, diff --git a/src/renderer/src/components/editor/MarkdownPreview.tsx b/src/renderer/src/components/editor/MarkdownPreview.tsx index 3b47beab0..761274e7d 100644 --- a/src/renderer/src/components/editor/MarkdownPreview.tsx +++ b/src/renderer/src/components/editor/MarkdownPreview.tsx @@ -89,6 +89,8 @@ import { findWorktreeById } from '@/store/slices/worktree-helpers' import { dirname } from '@/lib/path' import { relativePathInsideRoot } from '../../../../shared/cross-platform-path' +const EMPTY_MARKDOWN_DOCUMENTS: MarkdownDocument[] = [] + type MarkdownPreviewProps = { content: string filePath: string @@ -434,7 +436,7 @@ export default function MarkdownPreview({ initialAnchor = null, showTableOfContents = false, onCloseTableOfContents, - markdownDocuments = [], + markdownDocuments = EMPTY_MARKDOWN_DOCUMENTS, onOpenDocument, markdownAnnotationsEnabled = false }: MarkdownPreviewProps): React.JSX.Element { diff --git a/src/renderer/src/components/settings/AccountsPane.tsx b/src/renderer/src/components/settings/AccountsPane.tsx index 6a904e488..403666895 100644 --- a/src/renderer/src/components/settings/AccountsPane.tsx +++ b/src/renderer/src/components/settings/AccountsPane.tsx @@ -43,6 +43,8 @@ import { getCodexAccountAuthWarning } from './codex-account-auth-warning' export { ACCOUNTS_PANE_SEARCH_ENTRIES } +const EMPTY_WSL_DISTROS: string[] = [] + type AccountsPaneProps = { settings: GlobalSettings updateSettings: (updates: Partial) => void @@ -234,7 +236,7 @@ export function AccountsPane({ updateSettings, wslSupportedPlatform = false, wslAvailable = false, - wslDistros = [], + wslDistros = EMPTY_WSL_DISTROS, wslCapabilitiesLoading = false }: AccountsPaneProps): React.JSX.Element { const searchQuery = useAppStore((s) => s.settingsSearchQuery) diff --git a/src/renderer/src/components/settings/AgentLocationSetting.tsx b/src/renderer/src/components/settings/AgentLocationSetting.tsx index a427d0806..a7ac65052 100644 --- a/src/renderer/src/components/settings/AgentLocationSetting.tsx +++ b/src/renderer/src/components/settings/AgentLocationSetting.tsx @@ -2,6 +2,8 @@ import type { GlobalSettings } from '../../../../shared/types' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select' import { SettingsRow, SettingsSegmentedControl } from './SettingsFormControls' +const EMPTY_WSL_DISTROS: string[] = [] + type AgentDetectionRuntime = { runtime: 'host' | 'wsl' wslDistro?: string | null @@ -56,7 +58,7 @@ export function AgentLocationSetting({ refresh, wslSupportedPlatform = false, wslAvailable = false, - wslDistros = [], + wslDistros = EMPTY_WSL_DISTROS, wslCapabilitiesLoading = false }: AgentLocationSettingProps): React.JSX.Element | null { const agentRuntime = getSelectedAgentRuntime( diff --git a/src/renderer/src/components/settings/AgentsPane.tsx b/src/renderer/src/components/settings/AgentsPane.tsx index 34ab8cd90..9800ac895 100644 --- a/src/renderer/src/components/settings/AgentsPane.tsx +++ b/src/renderer/src/components/settings/AgentsPane.tsx @@ -30,6 +30,8 @@ import { export { AGENTS_PANE_SEARCH_ENTRIES } from './agents-search' +const EMPTY_WSL_DISTROS: string[] = [] + type AgentsPaneProps = { settings: GlobalSettings updateSettings: (updates: Partial) => void | Promise @@ -360,7 +362,7 @@ export function AgentsPane({ updateSettings, wslSupportedPlatform = false, wslAvailable = false, - wslDistros = [], + wslDistros = EMPTY_WSL_DISTROS, wslCapabilitiesLoading = false }: AgentsPaneProps): React.JSX.Element { const { detectedIds: detectedList, isRefreshing, refresh } = useDetectedAgents() diff --git a/src/renderer/src/components/settings/TerminalPane.tsx b/src/renderer/src/components/settings/TerminalPane.tsx index 94ebd1243..88ca8e51f 100644 --- a/src/renderer/src/components/settings/TerminalPane.tsx +++ b/src/renderer/src/components/settings/TerminalPane.tsx @@ -36,6 +36,8 @@ import { ManageSessionsSection } from './ManageSessionsSection' import { OSC52_CLIPBOARD_SETTING_ID } from '../terminal-pane/osc52-clipboard-setting-anchor' import { WINDOWS_GIT_BASH_SHELL } from '../../../../shared/windows-terminal-shell' +const EMPTY_WSL_DISTROS: string[] = [] + type TerminalPaneProps = { settings: GlobalSettings updateSettings: (updates: Partial) => void @@ -59,7 +61,7 @@ export function TerminalPane({ scrollbackMode, setScrollbackMode, wslAvailable, - wslDistros = [], + wslDistros = EMPTY_WSL_DISTROS, wslCapabilitiesLoading = false, pwshAvailable, gitBashAvailable = false diff --git a/src/renderer/src/components/sidebar/WorktreeContextMenu.tsx b/src/renderer/src/components/sidebar/WorktreeContextMenu.tsx index f4dc1aa75..d1c850534 100644 --- a/src/renderer/src/components/sidebar/WorktreeContextMenu.tsx +++ b/src/renderer/src/components/sidebar/WorktreeContextMenu.tsx @@ -203,10 +203,12 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({ worktree, children, contentClassName, - selectedWorktrees = [worktree], + selectedWorktrees, onContextMenuSelect, onOpenChange }: Props) { + const defaultSelectedWorktrees = useMemo(() => [worktree], [worktree]) + const effectiveSelectedWorktrees = selectedWorktrees ?? defaultSelectedWorktrees const updateWorktreeMeta = useAppStore((s) => s.updateWorktreeMeta) const workspaceStatuses = useAppStore((s) => s.workspaceStatuses) const openModal = useAppStore((s) => s.openModal) @@ -217,7 +219,8 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({ const deleteState = useAppStore((s) => s.deleteStateByWorktreeId[worktree.id]) const [menuOpen, setMenuOpen] = useState(false) const [menuPoint, setMenuPoint] = useState({ x: 0, y: 0 }) - const [contextWorktrees, setContextWorktrees] = useState(selectedWorktrees) + const [contextWorktrees, setContextWorktrees] = + useState(effectiveSelectedWorktrees) const [createGroupDialogOpen, setCreateGroupDialogOpen] = useState(false) const isDeleting = deleteState?.isDeleting ?? false const repoMap = useRepoMap() @@ -230,7 +233,7 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({ const deleteStateByWorktreeId = useAppStore((s) => s.deleteStateByWorktreeId) const scopeRef = useRef(null) const contextMenuOpenedAtRef = useRef(null) - const activeContextWorktrees = menuOpen ? contextWorktrees : selectedWorktrees + const activeContextWorktrees = menuOpen ? contextWorktrees : effectiveSelectedWorktrees const isMultiContext = activeContextWorktrees.length > 1 const sleepableWorktrees = useMemo( () => @@ -469,7 +472,7 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({ event.preventDefault() contextMenuOpenedAtRef.current = Date.now() window.dispatchEvent(new Event(CLOSE_ALL_CONTEXT_MENUS_EVENT)) - setContextWorktrees(onContextMenuSelect?.(event) ?? selectedWorktrees) + setContextWorktrees(onContextMenuSelect?.(event) ?? effectiveSelectedWorktrees) const bounds = event.currentTarget.getBoundingClientRect() setMenuPoint({ x: event.clientX - bounds.left, y: event.clientY - bounds.top }) setMenuOpenState(true) diff --git a/src/renderer/src/components/sidebar/WorktreeList.tsx b/src/renderer/src/components/sidebar/WorktreeList.tsx index aee34d649..58b0b4542 100644 --- a/src/renderer/src/components/sidebar/WorktreeList.tsx +++ b/src/renderer/src/components/sidebar/WorktreeList.tsx @@ -722,7 +722,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp reorderRepos, prCache, workspaceStatuses, - projectGroups = [], + projectGroups = EMPTY_PROJECT_GROUPS, onMoveWorktreeToStatus, onMoveWorktreesToStatus, onPinWorktree, diff --git a/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx b/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx index 1ecaf5547..a868cc985 100644 --- a/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx +++ b/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx @@ -16,6 +16,8 @@ import { } from './tab-agent-launch-options' import type { TuiAgent } from '../../../../shared/types' +const EMPTY_AGENT_OPTIONS: readonly TabAgentLaunchOption[] = [] + type TabBarCreateEntryProps = { agentOptions?: readonly TabAgentLaunchOption[] groupId: string @@ -28,7 +30,7 @@ type TabBarCreateEntryProps = { } export default function TabBarCreateEntry({ - agentOptions = [], + agentOptions = EMPTY_AGENT_OPTIONS, groupId, menuOpen, onDidOpenEntry, diff --git a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.tsx b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.tsx index 50a4d276f..9e7b035e7 100644 --- a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.tsx +++ b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.tsx @@ -50,6 +50,8 @@ type TerminalQuickCommandDialogProps = { onSave: (command: TerminalQuickCommand) => void } +const EMPTY_REPOS: Pick[] = [] + export function createTerminalQuickCommandDraft( scope: TerminalQuickCommandScope = { type: 'global' } ): TerminalQuickCommand { @@ -66,7 +68,7 @@ export function TerminalQuickCommandDialog({ open, mode, command, - repos = [], + repos = EMPTY_REPOS, onOpenChange, onSave }: TerminalQuickCommandDialogProps): React.JSX.Element { diff --git a/src/renderer/src/components/ui/toggle-group.tsx b/src/renderer/src/components/ui/toggle-group.tsx index 18f1a69ff..61a76ae0d 100644 --- a/src/renderer/src/components/ui/toggle-group.tsx +++ b/src/renderer/src/components/ui/toggle-group.tsx @@ -28,6 +28,8 @@ function ToggleGroup({ VariantProps & { spacing?: number }) { + const contextValue = React.useMemo(() => ({ variant, size, spacing }), [variant, size, spacing]) + return ( - + {children}