diff --git a/config/max-lines-baseline.txt b/config/max-lines-baseline.txt index e732ac529ba..bf35d6db0f5 100644 --- a/config/max-lines-baseline.txt +++ b/config/max-lines-baseline.txt @@ -79,7 +79,6 @@ inline src/renderer/src/components/LinearItemDrawer.tsx inline src/renderer/src/components/NewWorkspaceComposerCard.tsx inline src/renderer/src/components/TaskPage.tsx inline src/renderer/src/components/Terminal.tsx -inline src/renderer/src/components/UpdateCard.tsx inline src/renderer/src/components/WorktreeJumpPalette.tsx inline src/renderer/src/components/activity/ActivityPrototypePage.tsx inline src/renderer/src/components/automations/AutomationsPage.tsx @@ -99,8 +98,6 @@ inline src/renderer/src/components/right-sidebar/ChecksPanel.tsx inline src/renderer/src/components/right-sidebar/PortsPanel.tsx inline src/renderer/src/components/right-sidebar/checks-panel-content.tsx inline src/renderer/src/components/settings/AccountsPane.tsx -inline src/renderer/src/components/settings/AgentsPane.tsx -inline src/renderer/src/components/settings/RepositoryHooksSection.tsx inline src/renderer/src/components/settings/RuntimeEnvironmentsPane.tsx inline src/renderer/src/components/settings/Settings.tsx inline src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx diff --git a/src/renderer/src/components/UpdateCard.test.ts b/src/renderer/src/components/UpdateCard.test.ts index d5103ed30fc..a12146e9754 100644 --- a/src/renderer/src/components/UpdateCard.test.ts +++ b/src/renderer/src/components/UpdateCard.test.ts @@ -5,6 +5,10 @@ import type { ChangelogData, UpdateStatus } from '../../../shared/update-status- import { createUISlice } from '../store/slices/ui' import type { AppState } from '../store/types' import { isHttp2ProtocolError } from './UpdateCard' +import { + getUpdateCardAriaLabel, + isUpdateCardVisible +} from './maintenance/update-card/update-card-visibility' // ── Helpers ────────────────────────────────────────────────────────── @@ -319,35 +323,13 @@ type VisibilityInput = { type VisibilityResult = 'hidden' | 'visible' -/** Mirrors the visibility gates in UpdateCard's render path. */ function computeVisibility(input: VisibilityInput): VisibilityResult { - const { status, dismissedVersion, cachedVersion, hasStartedDownload } = input - const isUserInitiated = 'userInitiated' in status && status.userInitiated - const updateUserInitiatedCycle = input.updateUserInitiatedCycle ?? false - const shouldShowDetailedErrorCard = - status.state === 'error' && (hasStartedDownload || cachedVersion !== null) - - if (status.state === 'checking' && !isUserInitiated) { - return 'hidden' - } - if (status.state === 'not-available' && !isUserInitiated) { - return 'hidden' - } - if (status.state === 'idle') { - return 'hidden' - } - if (status.state === 'error' && !shouldShowDetailedErrorCard && !isUserInitiated) { - return 'hidden' - } - - const effectiveVersion = 'version' in status ? status.version : cachedVersion - if (effectiveVersion && dismissedVersion === effectiveVersion && !updateUserInitiatedCycle) { - if (status.state !== 'downloading' && status.state !== 'error') { - return 'hidden' - } - } - - return 'visible' + return isUpdateCardVisible({ + ...input, + updateUserInitiatedCycle: input.updateUserInitiatedCycle ?? false + }) + ? 'visible' + : 'hidden' } describe('UpdateCard visibility gates', () => { @@ -362,6 +344,10 @@ describe('UpdateCard visibility gates', () => { ).toBe('hidden') }) + it('uses the generic accessible label on idle', () => { + expect(getUpdateCardAriaLabel({ state: 'idle' })).toBe('Update status') + }) + it('hides background checking (not user-initiated)', () => { expect( computeVisibility({ diff --git a/src/renderer/src/components/UpdateCard.tsx b/src/renderer/src/components/UpdateCard.tsx index bb2b2d194c3..4bbf47fd492 100644 --- a/src/renderer/src/components/UpdateCard.tsx +++ b/src/renderer/src/components/UpdateCard.tsx @@ -1,94 +1,31 @@ -/* eslint-disable max-lines -- Why: keeps the updater state machine and its presentation variants in one file. */ import { useCallback, useEffect, useRef, useState } from 'react' +import { X } from 'lucide-react' +import type { ChangelogData } from '../../../shared/update-status-types' import { usePrefersReducedMotion } from '@/hooks/usePrefersReducedMotion' import { useAppStore } from '../store' import { Card } from './ui/card' import { Button } from './ui/button' -import { Progress } from './ui/progress' -import { AlertCircle, Check, Loader2, Minus, X } from 'lucide-react' -import type { ChangelogData } from '../../../shared/update-status-types' -import { UpdateErrorCardContent, type UpdateErrorCardModel } from './UpdateErrorCardContent' -import { LinuxPackageInstallRecoveryCard } from './LinuxPackageInstallRecoveryCard' -import { - isWindowsSignatureCheckUnavailableFailure, - isWindowsSignatureMismatchFailure -} from '../../../shared/updater-windows-signature-check' -import { getReleaseNotesUrlForVersion } from '../../../shared/release-channel' import { translate } from '@/i18n/i18n' +import { UpdateCardStateContent } from './maintenance/update-card/UpdateCardStateContent' +import { buildUpdateCardErrorModel } from './maintenance/update-card/update-card-error-model' +import { + getUpdateCardAriaLabel, + isHttp2ProtocolError, + isUpdateCardVisible +} from './maintenance/update-card/update-card-visibility' -// ── Helpers ────────────────────────────────────────────────────────── +export { isHttp2ProtocolError } -function isAnimatedGif(url: string | undefined): boolean { - return typeof url === 'string' && url.toLowerCase().endsWith('.gif') -} - -export function isHttp2ProtocolError(message: string): boolean { - const normalized = message.toLowerCase() - return ( - normalized.includes('err_http2_protocol_error') || - normalized.includes('http2_protocol_error') || - (normalized.includes('http/2') && normalized.includes('protocol')) - ) -} - -// ── Compact card (transient check feedback) ───────────────────────── - -function CompactCardContent({ - icon, - text, - onClose, - action -}: { - icon: 'spinner' | 'check' | 'error' - text: string - onClose?: () => void - action?: { label: string; url: string } -}) { - return ( -
{text}
- {action && ( - - )} -- {translate('auto.components.UpdateCard.09a55c39b5', 'Installing...')} -
-- {release.description} - {releasesBehind !== null && releasesBehind > 1 && ( - <> - {' '} - - > - )} -
- - - - -- {translate('auto.components.UpdateCard.05ad78a6d1', 'Orca v{{value0}} is ready.', { - value0: version - })} -
- -- {translate('auto.components.UpdateCard.fdd4a364fa', "Sessions won't be interrupted.")} -
- - {releaseUrl && ( - - )} - - -- {release - ? release.description - : translate('auto.components.UpdateCard.93794ea932', 'Orca v{{value0}} is downloading.', { - value0: version - })} -
- - {showReleaseNotes && ( - - )} - -- {translate('auto.components.UpdateCard.6e45bfa2e0', 'Downloading...')} {percent}% -
-- {translate( - 'auto.components.UpdateCard.6714206e5a', - "Orca v{{value0}} is downloaded. Restart when you're ready.", - { value0: version } - )} -
- - -+ {release.description} + {releasesBehind !== null && releasesBehind > 1 && ( + <> + {' '} + + > + )} +
+ + ++ {translate('auto.components.UpdateCard.05ad78a6d1', 'Orca v{{value0}} is ready.', { + value0: version + })} +
++ {translate('auto.components.UpdateCard.fdd4a364fa', "Sessions won't be interrupted.")} +
+ {releaseUrl && ( + + )} + ++ {translate('auto.components.UpdateCard.09a55c39b5', 'Installing...')} +
+{text}
+ {action && ( + + )} ++ {release + ? release.description + : translate('auto.components.UpdateCard.93794ea932', 'Orca v{{value0}} is downloading.', { + value0: version + })} +
+ {showReleaseNotes && ( + + )} ++ {translate('auto.components.UpdateCard.6e45bfa2e0', 'Downloading...')} {percent}% +
++ {translate( + 'auto.components.UpdateCard.6714206e5a', + "Orca v{{value0}} is downloaded. Restart when you're ready.", + { value0: version } + )} +
+ ++ {translate( + 'auto.components.settings.AgentsPane.f9f127d664', + 'Override the binary path or name, and edit the default launch arguments or environment for this agent.' + )} +
++ {translate( + 'auto.components.settings.AgentsPane.3f1bdf3cb4', + 'Environment text is too large to parse safely.' + )} +
+ )} +- {translate( - 'auto.components.settings.AgentsPane.3f1bdf3cb4', - 'Environment text is too large to parse safely.' - )} -
- )} -- {translate( - 'auto.components.settings.AgentsPane.f9f127d664', - 'Override the binary path or name, and edit the default launch arguments or environment for this agent.' - )} -
-+ {translate( + 'auto.components.settings.RepositoryHooksSection.21fb607a87', + 'Default behavior when a new worktree is created.' + )} +
++ {translate( + 'auto.components.settings.RepositoryHooksSection.waitForSetupBeforeAgentHelp', + 'Turn this on when setup installs dependencies, MCP servers, or config files the agent needs during startup.' + )} +
++ {translate( + 'auto.components.settings.RepositoryHooksSection.32fec28f5b', + 'Command Source' + )} +
+
+ {translate('auto.components.settings.RepositoryHooksSection.ac9038d2cc', 'When both')}{' '}
+
+ {translate(
+ 'auto.components.settings.RepositoryHooksSection.39da2ae12f',
+ 'orca.yaml'
+ )}
+ {' '}
+ {translate(
+ 'auto.components.settings.RepositoryHooksSection.3397879bee',
+ 'and local commands exist, choose which run.'
+ )}
+
+ {translate( + 'auto.components.settings.RepositoryHooksSection.b2b06c7ce8', + 'Available environment variables (hover for details):' + )} +
+
+ {name}
+
+ {field.description}
+
+ {translate(
+ 'auto.components.settings.RepositoryHooksSection.39da2ae12f',
+ 'orca.yaml'
+ )}
+ {' '}
+ {translate(
+ 'auto.components.settings.RepositoryHooksSection.7e4427b4a2',
+ 'to change.'
+ )}
+
+
+ {sharedScript ?? ''}
+
+ + {translate( + 'auto.components.settings.RepositoryHooksSection.5426ecbdcb', + 'Local scripts will not run' + )} +
++ {notice.kind === 'checking' + ? translate( + 'auto.components.settings.RepositoryHooksSection.7f78e5eea6', + 'Local scripts are saved. Orca is still checking orca.yaml before it can recommend which script source to use.' + ) + : translate( + 'auto.components.settings.RepositoryHooksSection.0ce113fd7b', + 'Local scripts are saved, but Script Source is set to orca.yaml only.' + )} +
+ = { policy: P; label: string; description: string }
-const LOCAL_HOOK_NAMES = ['setup', 'archive'] as const
-type LocalHookName = (typeof LOCAL_HOOK_NAMES)[number]
-type HookSettingsPolicyDraft = Partial<
- Pick ({
- options,
- selected,
- onSelect,
- columns
-}: {
- options: PolicyOption []
- selected: P
- onSelect: (p: P) => void
- columns: string
-}): React.JSX.Element {
- return (
- ({
- options,
- selected,
- onSelect
-}: {
- options: PolicyOption []
- selected: P
- onSelect: (p: P) => void
-}): React.JSX.Element {
- return (
-
- {translate('auto.components.settings.RepositoryHooksSection.175daba180', 'Example')}{' '}
-
- {translate(
- 'auto.components.settings.RepositoryHooksSection.b2b06c7ce8',
- 'Available environment variables (hover for details):'
- )}
-
- {translate(
- 'auto.components.settings.RepositoryHooksSection.5426ecbdcb',
- 'Local scripts will not run'
- )}
-
- {isChecking
- ? translate(
- 'auto.components.settings.RepositoryHooksSection.7f78e5eea6',
- 'Local scripts are saved. Orca is still checking orca.yaml before it can recommend which script source to use.'
- )
- : translate(
- 'auto.components.settings.RepositoryHooksSection.0ce113fd7b',
- 'Local scripts are saved, but Script Source is set to orca.yaml only.'
- )}
- {field.description}
- {translate('auto.components.settings.RepositoryHooksSection.39da2ae12f', 'orca.yaml')}
- {' '}
- {translate('auto.components.settings.RepositoryHooksSection.95a0411b3e', 'template')}
-
- {EXAMPLE_TEMPLATE}
-
-
- {content}
-
- )
-}
-
-function EnvVarChips(): React.JSX.Element {
- const envVars = getEnvVars()
-
- return (
-
- {name}
-
- {field.label}
-
- {translate(
- 'auto.components.settings.RepositoryHooksSection.39da2ae12f',
- 'orca.yaml'
- )}
- {' '}
- {translate(
- 'auto.components.settings.RepositoryHooksSection.7e4427b4a2',
- 'to change.'
- )}
-
-
- {translate( - 'auto.components.settings.RepositoryHooksSection.21fb607a87', - 'Default behavior when a new worktree is created.' - )} -
-- {translate( - 'auto.components.settings.RepositoryHooksSection.waitForSetupBeforeAgentHelp', - 'Turn this on when setup installs dependencies, MCP servers, or config files the agent needs during startup.' - )} -
-
- {translate(
- 'auto.components.settings.RepositoryHooksSection.b997331366',
- 'Optional override. Use'
- )}{' '}
-
- {translate(
- 'auto.components.settings.RepositoryHooksSection.c85c2c88a2',
- '{{artifact_url}}',
- { artifact_url: ARTIFACT_URL_TEMPLATE_TOKEN }
- )}
- {' '}
- {translate(
- 'auto.components.settings.RepositoryHooksSection.70ad20f883',
- 'for the linked issue or PR URL.'
- )}
-
- {translate( - 'auto.components.settings.RepositoryHooksSection.32fec28f5b', - 'Command Source' - )} -
-
- {translate(
- 'auto.components.settings.RepositoryHooksSection.ac9038d2cc',
- 'When both'
- )}{' '}
-
- {translate(
- 'auto.components.settings.RepositoryHooksSection.39da2ae12f',
- 'orca.yaml'
- )}
- {' '}
- {translate(
- 'auto.components.settings.RepositoryHooksSection.3397879bee',
- 'and local commands exist, choose which run.'
- )}
-
- {yamlStateCopy.heading} -
-{yamlStateCopy.description}
-- {translate( - 'auto.components.settings.RepositoryHooksSection.af49e2a19e', - 'The file is present, but Orca could not find valid `scripts` or `issueCommand` definitions.' - )} -
-
+ {translate('auto.components.settings.RepositoryHooksSection.175daba180', 'Example')}{' '}
+
+ {translate('auto.components.settings.RepositoryHooksSection.39da2ae12f', 'orca.yaml')}
+ {' '}
+ {translate('auto.components.settings.RepositoryHooksSection.95a0411b3e', 'template')}
+
+ {EXAMPLE_TEMPLATE}
+
+ + {copy.heading} +
+{copy.description}
+
+ {renderYamlScriptPreview(yamlHooks)}
+
+ ) : yamlState === 'invalid' ? (
+ + {translate( + 'auto.components.settings.RepositoryHooksSection.af49e2a19e', + 'The file is present, but Orca could not find valid `scripts` or `issueCommand` definitions.' + )} +
+
+ {translate(
+ 'auto.components.settings.RepositoryHooksSection.b997331366',
+ 'Optional override. Use'
+ )}{' '}
+
+ {translate(
+ 'auto.components.settings.RepositoryHooksSection.c85c2c88a2',
+ '{{artifact_url}}',
+ { artifact_url: ARTIFACT_URL_TEMPLATE_TOKEN }
+ )}
+ {' '}
+ {translate(
+ 'auto.components.settings.RepositoryHooksSection.70ad20f883',
+ 'for the linked issue or PR URL.'
+ )}
+