From 1a22e2d05c5ea6119c448674680b66dfca52cc9a Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Wed, 10 Jun 2026 19:53:03 -0700 Subject: [PATCH] UX/copy tweaks (#5142) * UX/copy tweaks * UX/copy tweaks --- src/main/star-nag/service.test.ts | 58 ++-- src/main/star-nag/service.ts | 39 +-- src/main/startup/hydrate-shell-path.test.ts | 7 +- src/preload/api-types.ts | 3 +- src/preload/index.ts | 8 +- src/renderer/src/components/Landing.tsx | 41 ++- src/renderer/src/components/StarNagCard.tsx | 61 +++-- .../feature-wall/ConnectIntegrationsList.tsx | 10 +- .../settings/GeneralSupportSection.tsx | 56 ++-- .../src/components/sidebar/SidebarNav.tsx | 246 +---------------- .../sidebar/SidebarTaskNavButton.tsx | 256 ++++++++++++++++++ src/renderer/src/i18n/locales/en.json | 20 +- src/renderer/src/i18n/locales/es.json | 20 +- src/renderer/src/i18n/locales/ja.json | 20 +- src/renderer/src/i18n/locales/ko.json | 20 +- src/renderer/src/i18n/locales/zh.json | 20 +- src/shared/types.ts | 2 +- 17 files changed, 520 insertions(+), 367 deletions(-) create mode 100644 src/renderer/src/components/sidebar/SidebarTaskNavButton.tsx diff --git a/src/main/star-nag/service.test.ts b/src/main/star-nag/service.test.ts index f46cf64c6..5028667d2 100644 --- a/src/main/star-nag/service.test.ts +++ b/src/main/star-nag/service.test.ts @@ -150,7 +150,7 @@ describe('StarNagService', () => { emitAgentStarted(46) expect(window.webContents.send).toHaveBeenCalledTimes(1) - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show') + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'gh' }) expect(consoleInfoMock).toHaveBeenCalledTimes(1) expect(consoleInfoMock).toHaveBeenCalledWith({ event: 'star_nag_shown', @@ -161,22 +161,39 @@ describe('StarNagService', () => { }) }) - it.each([null, true])( - 'does not log a threshold exposure when checkOrcaStarred returns %s', - async (result) => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - checkOrcaStarredMock.mockResolvedValue(result) - const { service, emitAgentStarted } = createHarness() + it('shows the browser fallback when checkOrcaStarred cannot determine star state', async () => { + const window = createWindow() + browserWindowMock.getAllWindows.mockReturnValue([window]) + checkOrcaStarredMock.mockResolvedValue(null) + const { service, emitAgentStarted } = createHarness() - service.start() - emitAgentStarted(45) - await flushAsyncWork() + service.start() + emitAgentStarted(45) + await flushAsyncWork() - expect(window.webContents.send).not.toHaveBeenCalled() - expect(consoleInfoMock).not.toHaveBeenCalled() - } - ) + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'web' }) + expect(consoleInfoMock).toHaveBeenCalledWith({ + event: 'star_nag_shown', + app_version: '1.2.3', + threshold: STAR_NAG_INITIAL_THRESHOLD, + agents_since_baseline: 35, + source: 'threshold' + }) + }) + + it('does not log a threshold exposure when checkOrcaStarred returns true', async () => { + const window = createWindow() + browserWindowMock.getAllWindows.mockReturnValue([window]) + checkOrcaStarredMock.mockResolvedValue(true) + const { service, emitAgentStarted } = createHarness() + + service.start() + emitAgentStarted(45) + await flushAsyncWork() + + expect(window.webContents.send).not.toHaveBeenCalled() + expect(consoleInfoMock).not.toHaveBeenCalled() + }) it('does not block a later real prompt after crossing the threshold with no window', async () => { const { service, emitAgentStarted } = createHarness() @@ -192,7 +209,7 @@ describe('StarNagService', () => { emitAgentStarted(46) await flushAsyncWork() - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show') + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'gh' }) expect(consoleInfoMock).toHaveBeenCalledWith({ event: 'star_nag_shown', app_version: '1.2.3', @@ -265,7 +282,7 @@ describe('StarNagService', () => { browserWindowMock.getAllWindows.mockReturnValue([window]) forceShow() - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show') + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'gh' }) expect(consoleInfoMock).toHaveBeenCalledWith({ event: 'star_nag_shown', app_version: '1.2.3', @@ -320,7 +337,7 @@ describe('StarNagService', () => { await flushAsyncWork() getIpcHandler('star-nag:dismiss')() - emitAgentStarted(115) + emitAgentStarted(114) await flushAsyncWork() expect(window.webContents.send).toHaveBeenCalledTimes(1) @@ -368,7 +385,7 @@ describe('StarNagService', () => { expect(consoleInfoMock).not.toHaveBeenCalled() }) - it('replays force_show after an in-flight threshold evaluation exits without showing', async () => { + it('keeps threshold source when an in-flight star check falls back to the browser', async () => { const window = createWindow() browserWindowMock.getAllWindows.mockReturnValue([window]) const deferredStarCheck = createDeferred() @@ -388,12 +405,13 @@ describe('StarNagService', () => { expect(window.webContents.send).toHaveBeenCalledTimes(1) expect(consoleInfoMock).toHaveBeenCalledTimes(1) + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'web' }) expect(consoleInfoMock).toHaveBeenCalledWith({ event: 'star_nag_shown', app_version: '1.2.3', threshold: STAR_NAG_INITIAL_THRESHOLD, agents_since_baseline: 35, - source: 'force_show' + source: 'threshold' }) }) diff --git a/src/main/star-nag/service.ts b/src/main/star-nag/service.ts index ca16b6565..76afffd8d 100644 --- a/src/main/star-nag/service.ts +++ b/src/main/star-nag/service.ts @@ -5,6 +5,7 @@ import type { Store } from '../persistence' import type { StatsCollector } from '../stats/collector' type StarNagPromptSource = 'threshold' | 'force_show' +type StarNagPromptMode = 'gh' | 'web' type StarNagPromptSession = { source: StarNagPromptSource @@ -13,7 +14,7 @@ type StarNagPromptSession = { /** * Service that decides when to prompt the user with the "star Orca on GitHub" * notification. Counts agents spawned since the current app version was first - * seen; crosses a doubling threshold (default 50 → 100 → 200 …) to fire the + * seen; crosses a doubling threshold (default 35 → 70 → 140 …) to fire the * renderer notification via 'star-nag:show'. * * State lives in PersistedUIState so it survives restarts alongside the rest @@ -27,7 +28,7 @@ export class StarNagService { // dismisses or stars. Without this in-memory guard, every subsequent // agent_start past the threshold would re-enter maybeShow() and spawn a new // `gh api` subprocess on each spawn — cheap individually, but a power user - // at 55 agents with threshold 50 would fork gh on every spawn until they + // at 40 agents with threshold 35 would fork gh on every spawn until they // act on the card. private promptVisible = false // Why: prevent concurrent gh invocations if agents spawn rapidly during the @@ -64,6 +65,7 @@ export class StarNagService { registerIpcHandlers(): void { ipcMain.handle('star-nag:dismiss', () => this.dismiss()) ipcMain.handle('star-nag:complete', () => this.markCompleted()) + ipcMain.handle('star-nag:disable', () => this.markCompleted()) ipcMain.handle('star-nag:forceShow', () => this.forceShow()) } @@ -118,14 +120,17 @@ export class StarNagService { } this.evaluating = true try { - // Why: the notification is only useful for users whose gh CLI can - // actually perform the star. Calling checkOrcaStarred both gates on gh - // availability and skips users who already starred outside the app. - // Errors (network, gh missing) map to null — skip silently and leave - // state unchanged so we retry on the next spawn without racing forward - // to the next threshold. + // Why: checkOrcaStarred lets us skip users who already starred outside + // the app. When gh cannot tell us, keep the prompt available but route + // the renderer to the browser fallback instead of a dead direct-star + // button. const starred = await checkOrcaStarred() + if (this.store.getUI().starNagCompleted) { + this.pendingForceShow = false + return + } if (starred === null) { + this.broadcastShow(source, 'web') return } if (starred) { @@ -134,14 +139,10 @@ export class StarNagService { this.markCompleted() return } - if (this.store.getUI().starNagCompleted) { - this.pendingForceShow = false - return - } if (this.promptVisible) { return } - this.broadcastShow(source) + this.broadcastShow(source, 'gh') } finally { this.evaluating = false this.flushPendingForceShow() @@ -156,17 +157,17 @@ export class StarNagService { if (this.promptVisible) { return } - this.broadcastShow('force_show') + this.broadcastShow('force_show', 'gh') } - private broadcastShow(source: StarNagPromptSource): boolean { + private broadcastShow(source: StarNagPromptSource, mode: StarNagPromptMode): boolean { const win = BrowserWindow.getAllWindows().find((w) => !w.isDestroyed()) if (!win) { this.promptVisible = false this.promptSession = null return false } - win.webContents.send('star-nag:show') + win.webContents.send('star-nag:show', { mode }) this.promptVisible = true this.promptSession = { source } this.logConsoleEvent('star_nag_shown', source) @@ -198,7 +199,7 @@ export class StarNagService { * User closed the notification without starring → double the threshold and * rebase the baseline so the next fire is "threshold more agents since this * dismissal" (not "threshold total since install"). This matches the - * product intent of exponential back-off: 50 more, then 100 more, then 200 + * product intent of exponential back-off: 35 more, then 70 more, then 140 * more, etc. */ private dismiss(): void { @@ -219,7 +220,7 @@ export class StarNagService { this.promptSession = null } - /** User successfully starred → never nag again. */ + /** User successfully starred or opted out → never nag again. */ private markCompleted(): void { this.store.updateUI({ starNagCompleted: true }) this.promptVisible = false @@ -236,6 +237,6 @@ export class StarNagService { this.pendingForceShow = true return } - this.broadcastShow('force_show') + this.broadcastShow('force_show', 'gh') } } diff --git a/src/main/startup/hydrate-shell-path.test.ts b/src/main/startup/hydrate-shell-path.test.ts index 5496fa93a..4e1a05c14 100644 --- a/src/main/startup/hydrate-shell-path.test.ts +++ b/src/main/startup/hydrate-shell-path.test.ts @@ -198,12 +198,7 @@ describe('mergePathSegments', () => { }) it('moves user-local shell paths ahead of packaged Homebrew fallbacks', () => { - process.env.PATH = joinPath( - '/opt/homebrew/bin', - '/Users/tester/.local/bin', - '/usr/bin', - '/bin' - ) + process.env.PATH = joinPath('/opt/homebrew/bin', '/Users/tester/.local/bin', '/usr/bin', '/bin') const added = mergePathSegments(['/Users/tester/.local/bin', '/opt/homebrew/bin']) diff --git a/src/preload/api-types.ts b/src/preload/api-types.ts index 7736aed6a..05077b524 100644 --- a/src/preload/api-types.ts +++ b/src/preload/api-types.ts @@ -1576,9 +1576,10 @@ export type PreloadApi = { listTransitions: (args: { key: string; siteId?: string }) => Promise } starNag: { - onShow: (callback: () => void) => () => void + onShow: (callback: (payload?: { mode?: 'gh' | 'web' }) => void) => () => void dismiss: () => Promise complete: () => Promise + disable: () => Promise forceShow: () => Promise } /** Fire-and-forget track. Loose typing at the IPC boundary on purpose — diff --git a/src/preload/index.ts b/src/preload/index.ts index dd4fea259..93173a4df 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1455,13 +1455,17 @@ const api = { }, starNag: { - onShow: (callback: () => void): (() => void) => { - const listener = (_event: Electron.IpcRendererEvent): void => callback() + onShow: (callback: (payload?: { mode?: 'gh' | 'web' }) => void): (() => void) => { + const listener = ( + _event: Electron.IpcRendererEvent, + payload?: { mode?: 'gh' | 'web' } + ): void => callback(payload) ipcRenderer.on('star-nag:show', listener) return () => ipcRenderer.removeListener('star-nag:show', listener) }, dismiss: (): Promise => ipcRenderer.invoke('star-nag:dismiss'), complete: (): Promise => ipcRenderer.invoke('star-nag:complete'), + disable: (): Promise => ipcRenderer.invoke('star-nag:disable'), forceShow: (): Promise => ipcRenderer.invoke('star-nag:forceShow') }, diff --git a/src/renderer/src/components/Landing.tsx b/src/renderer/src/components/Landing.tsx index 23adac351..74fd63814 100644 --- a/src/renderer/src/components/Landing.tsx +++ b/src/renderer/src/components/Landing.tsx @@ -69,7 +69,9 @@ function getPreflightIssues(status: { return issues } -type StarState = 'loading' | 'starred' | 'not-starred' | 'hidden' +const ORCA_STARGAZERS_URL = 'https://github.com/stablyai/orca/stargazers' + +type StarState = 'loading' | 'starred' | 'not-starred' | 'web-fallback' | 'hidden' function GitHubStarButton({ hasRepos }: { hasRepos: boolean }): React.JSX.Element | null { const [state, setState] = useState('loading') @@ -84,7 +86,7 @@ function GitHubStarButton({ hasRepos }: { hasRepos: boolean }): React.JSX.Elemen return } if (result === null) { - setState('hidden') + setState('web-fallback') } else { setState(result ? 'starred' : 'not-starred') } @@ -112,6 +114,11 @@ function GitHubStarButton({ hasRepos }: { hasRepos: boolean }): React.JSX.Elemen setMenuOpen((v) => !v) return } + if (state === 'web-fallback') { + await window.api.shell.openUrl(ORCA_STARGAZERS_URL) + await window.api.starNag.complete() + return + } if (state !== 'not-starred') { return } @@ -119,7 +126,7 @@ function GitHubStarButton({ hasRepos }: { hasRepos: boolean }): React.JSX.Elemen const ok = await window.api.gh.starOrca('landing') if (!ok) { if (mountedRef.current) { - setState('not-starred') + setState('web-fallback') } return } @@ -129,7 +136,7 @@ function GitHubStarButton({ hasRepos }: { hasRepos: boolean }): React.JSX.Elemen await window.api.starNag.complete() } - // Hide if gh CLI is unavailable, or if the user has already starred and added a repo + // Hide once the user has already starred and added a repo. if (state === 'hidden' || (state === 'starred' && hasRepos)) { return null } @@ -140,7 +147,7 @@ function GitHubStarButton({ hasRepos }: { hasRepos: boolean }): React.JSX.Elemen className={cn( 'inline-flex items-center gap-2 rounded-full border px-4 py-1.5 text-[13px] font-medium transition-all duration-300', state === 'loading' && 'pointer-events-none opacity-0', - state === 'not-starred' && + state !== 'starred' && 'cursor-pointer border-amber-500/60 text-amber-700 hover:border-amber-500/80 hover:bg-amber-400/10 dark:border-amber-400/30 dark:text-amber-300/90 dark:hover:border-amber-400/50 dark:hover:bg-amber-400/[0.08]', state === 'starred' && 'cursor-pointer border-amber-500/50 bg-amber-400/10 text-amber-700 dark:border-amber-400/25 dark:bg-amber-400/[0.06] dark:text-amber-400/60' @@ -148,17 +155,23 @@ function GitHubStarButton({ hasRepos }: { hasRepos: boolean }): React.JSX.Elemen onClick={handleClick} disabled={state === 'loading'} > - + {state === 'web-fallback' ? ( + + ) : ( + + )} {state === 'starred' ? translate('auto.components.Landing.ec43b38ba7', 'Starred on GitHub') - : translate('auto.components.Landing.0d0ace8861', 'Star on GitHub')} + : state === 'web-fallback' + ? translate('auto.components.Landing.157bb5ecbb', 'Open GitHub') + : translate('auto.components.Landing.0d0ace8861', 'Star on GitHub')} {state === 'starred' && menuOpen && (
diff --git a/src/renderer/src/components/StarNagCard.tsx b/src/renderer/src/components/StarNagCard.tsx index f96326982..4f6a4f9ea 100644 --- a/src/renderer/src/components/StarNagCard.tsx +++ b/src/renderer/src/components/StarNagCard.tsx @@ -1,11 +1,14 @@ import { useEffect, useState } from 'react' -import { Star, X } from 'lucide-react' +import { ExternalLink, Star, X } from 'lucide-react' import { Card } from './ui/card' import { Button } from './ui/button' import { useAppStore } from '../store' import { useMountedRef } from '@/hooks/useMountedRef' import { translate } from '@/i18n/i18n' +const ORCA_STARGAZERS_URL = 'https://github.com/stablyai/orca/stargazers' +type StarNagMode = 'gh' | 'web' + /** * Persistent "star Orca on GitHub" notification card. * @@ -20,7 +23,7 @@ import { translate } from '@/i18n/i18n' export function StarNagCard(): React.JSX.Element | null { const [visible, setVisible] = useState(false) const [busy, setBusy] = useState(false) - const [error, setError] = useState(false) + const [mode, setMode] = useState('gh') const mountedRef = useMountedRef() // Why: UpdateCard lives at the same bottom-right slot. When it is visible // (any non-idle / non-not-available state), stack the star-nag card above @@ -30,8 +33,8 @@ export function StarNagCard(): React.JSX.Element | null { const updateCardVisible = updateStatus.state !== 'idle' && updateStatus.state !== 'not-available' useEffect(() => { - return window.api.starNag.onShow(() => { - setError(false) + return window.api.starNag.onShow((payload) => { + setMode(payload?.mode === 'web' ? 'web' : 'gh') setVisible(true) }) }, []) @@ -44,6 +47,11 @@ export function StarNagCard(): React.JSX.Element | null { void window.api.starNag.dismiss() } + const handleDisable = (): void => { + setVisible(false) + void window.api.starNag.disable() + } + useEffect(() => { if (!visible) { return @@ -67,15 +75,24 @@ export function StarNagCard(): React.JSX.Element | null { if (busy) { return } + if (mode === 'web') { + setBusy(true) + await window.api.shell.openUrl(ORCA_STARGAZERS_URL) + await window.api.starNag.disable() + if (mountedRef.current) { + setBusy(false) + setVisible(false) + } + return + } setBusy(true) - setError(false) const ok = await window.api.gh.starOrca('star_nag') if (mountedRef.current) { setBusy(false) } if (!ok) { if (mountedRef.current) { - setError(true) + setMode('web') } return } @@ -122,20 +139,6 @@ export function StarNagCard(): React.JSX.Element | null { )}

- {error ? ( -

- {translate( - 'auto.components.StarNagCard.cf82170065', - 'Could not star the repo. Make sure' - )} - {translate('auto.components.StarNagCard.cd8c34aac1', 'gh')}{' '} - {translate( - 'auto.components.StarNagCard.92b0f9d921', - 'is authenticated and try again.' - )} -

- ) : null} - +
+ + +
diff --git a/src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx b/src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx index 2a51c2c47..cd378eb3f 100644 --- a/src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx +++ b/src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx @@ -28,8 +28,14 @@ function TaskSourceNameList(props: { names: readonly string[] }): React.JSX.Elem {index > 0 ? index === props.names.length - 1 ? props.names.length > 2 - ? translate('auto.components.feature.wall.ConnectIntegrationsList.list_end', ', and ') - : translate('auto.components.feature.wall.ConnectIntegrationsList.list_pair', ' and ') + ? translate( + 'auto.components.feature.wall.ConnectIntegrationsList.list_end', + ', and ' + ) + : translate( + 'auto.components.feature.wall.ConnectIntegrationsList.list_pair', + ' and ' + ) : translate('auto.components.feature.wall.ConnectIntegrationsList.list_mid', ', ') : null} {name} diff --git a/src/renderer/src/components/settings/GeneralSupportSection.tsx b/src/renderer/src/components/settings/GeneralSupportSection.tsx index 944c6e08f..053960f71 100644 --- a/src/renderer/src/components/settings/GeneralSupportSection.tsx +++ b/src/renderer/src/components/settings/GeneralSupportSection.tsx @@ -1,6 +1,6 @@ import type React from 'react' import { useEffect, useState } from 'react' -import { Loader2, Star } from 'lucide-react' +import { ExternalLink, Loader2, Star } from 'lucide-react' import { useMountedRef } from '@/hooks/useMountedRef' import { Button } from '../ui/button' import { Label } from '../ui/label' @@ -9,7 +9,16 @@ import { SearchableSetting } from './SearchableSetting' import { SettingsSubsectionHeader } from './SettingsFormControls' import { translate } from '@/i18n/i18n' -type SupportState = 'loading' | 'not-starred' | 'starring' | 'starred' | 'hidden' | 'error' +const ORCA_STARGAZERS_URL = 'https://github.com/stablyai/orca/stargazers' + +type SupportState = + | 'loading' + | 'not-starred' + | 'web-fallback' + | 'opening-github' + | 'starring' + | 'starred' + | 'hidden' type GeneralSupportSectionProps = { hasPrecedingSections: boolean @@ -20,9 +29,8 @@ export function GeneralSupportSection({ }: GeneralSupportSectionProps): React.JSX.Element { const mountedRef = useMountedRef() // Why: the star state is derived from gh, not from settings, so it does not - // live in the global settings store. 'hidden' covers the gh-unavailable and - // already-starred-on-a-previous-session cases so the section drops out for - // users who can't or don't need to act. + // live in the global settings store. 'hidden' covers already-starred users + // so the section drops out for people who don't need to act. // // We start in 'loading' and render a placeholder at the exact same // dimensions as the resolved section. When gh resolves to 'hidden', the @@ -37,7 +45,7 @@ export function GeneralSupportSection({ return } if (result === null) { - setStarState('hidden') + setStarState('web-fallback') } else { setStarState(result ? 'starred' : 'not-starred') } @@ -48,14 +56,23 @@ export function GeneralSupportSection({ }, []) const handleStarClick = async (): Promise => { - if (starState !== 'not-starred' && starState !== 'error') { + if (starState === 'web-fallback') { + setStarState('opening-github') + await window.api.shell.openUrl(ORCA_STARGAZERS_URL) + await window.api.starNag.complete() + if (mountedRef.current) { + setStarState('web-fallback') + } + return + } + if (starState !== 'not-starred') { return } setStarState('starring') const ok = await window.api.gh.starOrca('settings') if (!ok) { if (mountedRef.current) { - setStarState('error') + setStarState('web-fallback') } return } @@ -133,7 +150,7 @@ function SupportRow({ state, onStarClick }: { - state: 'not-starred' | 'starring' | 'starred' | 'error' + state: 'not-starred' | 'web-fallback' | 'opening-github' | 'starring' | 'starred' onStarClick: () => void | Promise }): React.JSX.Element { // Why: the left-hand label is the setting's identity and must not change @@ -147,7 +164,7 @@ function SupportRow({ )} description={translate( 'auto.components.settings.GeneralSupportSection.511782265b', - 'Support the project with a GitHub star via the gh CLI.' + 'Support the project with a GitHub star.' )} keywords={['star', 'github', 'support', 'feedback', 'like']} className="flex items-center justify-between gap-4 py-2" @@ -165,19 +182,26 @@ function SupportRow({ variant="default" size="sm" onClick={() => void onStarClick()} - disabled={state === 'starring'} + disabled={state === 'starring' || state === 'opening-github'} className="shrink-0 gap-1.5" > - {state === 'starring' ? ( + {state === 'starring' || state === 'opening-github' ? ( + ) : state === 'web-fallback' ? ( + ) : ( - + )} {state === 'starring' ? translate('auto.components.settings.GeneralSupportSection.397719bee5', 'Starring...') - : state === 'error' - ? translate('auto.components.settings.GeneralSupportSection.73b327e793', 'Try Again') - : translate('auto.components.settings.GeneralSupportSection.964acc6bb4', 'Star')} + : state === 'opening-github' + ? translate('auto.components.settings.GeneralSupportSection.cb65c75b11', 'Opening...') + : state === 'web-fallback' + ? translate( + 'auto.components.settings.GeneralSupportSection.f2d4f877b2', + 'Open GitHub' + ) + : translate('auto.components.settings.GeneralSupportSection.964acc6bb4', 'Star')} )} diff --git a/src/renderer/src/components/sidebar/SidebarNav.tsx b/src/renderer/src/components/sidebar/SidebarNav.tsx index d6da20298..1ef46a2f4 100644 --- a/src/renderer/src/components/sidebar/SidebarNav.tsx +++ b/src/renderer/src/components/sidebar/SidebarNav.tsx @@ -1,21 +1,10 @@ import React from 'react' -import { Bell, CalendarClock, EyeOff, Github, Gitlab, List, Search, Smartphone } from 'lucide-react' +import { Bell, CalendarClock, EyeOff, Search, Smartphone } from 'lucide-react' import { useAppStore } from '@/store' -import { useRepoMap } from '@/store/selectors' import { cn } from '@/lib/utils' -import { isGitRepoKind } from '../../../../shared/repo-kind' import type { GlobalSettings } from '../../../../shared/types' -import { getTaskPresetQuery, PER_REPO_FETCH_LIMIT } from '@/lib/new-workspace' -import { LinearIcon } from '@/components/icons/LinearIcon' -import { JiraIcon } from '@/components/icons/JiraIcon' -import { - normalizeVisibleTaskProviders, - restoreAvailableDefaultTaskProvider, - resolveVisibleTaskProvider -} from '../../../../shared/task-providers' import { useActivityUnreadCount } from '@/components/activity/useActivityUnreadCount' import { useShortcutLabel } from '@/hooks/useShortcutLabel' -import { getLocalPreflightContext, localPreflightContextKey } from '@/lib/local-preflight-context' import { useMobileSidebarOnboardingBadge } from './mobile-sidebar-onboarding-badge' import { ContextMenu, @@ -24,6 +13,7 @@ import { ContextMenuTrigger } from '@/components/ui/context-menu' import { SetupGuideSidebarEntry } from './SetupGuideSidebarEntry' +import { SidebarTaskNavButton } from './SidebarTaskNavButton' import { translate } from '@/i18n/i18n' export { getSetupGuideSidebarEntryReady, shouldShowSetupGuideEntry } from './SetupGuideSidebarEntry' @@ -57,157 +47,23 @@ function HideSidebarMenu({ onHide }: { onHide: () => void }): React.JSX.Element ) } -function TaskProviderShortcut({ - canBrowseTasks, - label, - onOpen, - children -}: { - canBrowseTasks: boolean - label: string - onOpen: () => void - children: React.ReactNode -}): React.JSX.Element { - return ( - { - e.stopPropagation() - if (!canBrowseTasks) { - return - } - onOpen() - }} - className={cn( - 'rounded p-0.5 text-muted-foreground/70', - canBrowseTasks ? 'transition-colors hover:text-foreground' : 'cursor-default' - )} - aria-label={canBrowseTasks ? label : undefined} - aria-hidden={canBrowseTasks ? undefined : true} - > - {children} - - ) -} - const SidebarNav = React.memo(function SidebarNav() { const worktreePaletteShortcut = useShortcutLabel('worktree.palette') - const openTaskPage = useAppStore((s) => s.openTaskPage) const openAutomationsPage = useAppStore((s) => s.openAutomationsPage) const openActivityPage = useAppStore((s) => s.openActivityPage) const openMobilePage = useAppStore((s) => s.openMobilePage) const openModal = useAppStore((s) => s.openModal) const updateSettings = useAppStore((s) => s.updateSettings) const activeView = useAppStore((s) => s.activeView) - const repos = useAppStore((s) => s.repos) - const repoMap = useRepoMap() - const canBrowseTasks = repos.some((repo) => isGitRepoKind(repo)) - // Why: the setting is opt-out (default true). `!== false` keeps the button - // visible for users whose persisted settings predate this field. - const showTasksButton = useAppStore((s) => s.settings?.showTasksButton !== false) - const rawVisibleTaskProviders = useAppStore((s) => s.settings?.visibleTaskProviders) - const defaultTaskSource = useAppStore((s) => s.settings?.defaultTaskSource ?? 'github') - const preflightStatus = useAppStore((s) => s.preflightStatus) - const preflightStatusChecked = useAppStore((s) => s.preflightStatusChecked) - const preflightStatusContextKey = useAppStore((s) => s.preflightStatusContextKey) - const refreshPreflightStatus = useAppStore((s) => s.refreshPreflightStatus) - const expectedPreflightContextKey = useAppStore((s) => - localPreflightContextKey(getLocalPreflightContext(s)) - ) - const linearStatus = useAppStore((s) => s.linearStatus) - const linearStatusChecked = useAppStore((s) => s.linearStatusChecked) - const checkLinearConnection = useAppStore((s) => s.checkLinearConnection) const showAgentsButton = useAppStore((s) => shouldShowAgentsButton(s.settings)) const showAutomationsButton = useAppStore((s) => shouldShowAutomationsButton(s.settings)) const showMobileButton = useAppStore((s) => shouldShowMobileButton(s.settings)) - const preferredVisibleTaskProviders = React.useMemo( - () => normalizeVisibleTaskProviders(rawVisibleTaskProviders), - [rawVisibleTaskProviders] - ) - const preflightStatusCurrent = preflightStatusContextKey === expectedPreflightContextKey - const visibleTaskProviders = React.useMemo( - () => - restoreAvailableDefaultTaskProvider( - preferredVisibleTaskProviders, - { - gitlabInstalled: preflightStatusCurrent && preflightStatus?.glab?.installed === true, - linearConnected: linearStatus.connected === true - }, - defaultTaskSource - ), - [ - defaultTaskSource, - linearStatus.connected, - preferredVisibleTaskProviders, - preflightStatusCurrent, - preflightStatus?.glab?.installed - ] - ) - const resolvedDefaultTaskSource = React.useMemo( - () => resolveVisibleTaskProvider(defaultTaskSource, visibleTaskProviders), - [defaultTaskSource, visibleTaskProviders] - ) - React.useEffect(() => { - if (!preflightStatusChecked || !preflightStatusCurrent) { - void refreshPreflightStatus() - } - if (!linearStatusChecked) { - void checkLinearConnection() - } - }, [ - checkLinearConnection, - linearStatusChecked, - preflightStatusChecked, - preflightStatusCurrent, - refreshPreflightStatus - ]) - - // Why: warm the GitHub work-item cache on hover/focus so by the time the - // user's click finishes the round-trip has either completed or is already - // in-flight. Shaves ~200–600ms off perceived page-load latency. - const prefetchWorkItems = useAppStore((s) => s.prefetchWorkItems) - const activeRepoId = useAppStore((s) => s.activeRepoId) - const defaultTaskViewPreset = useAppStore((s) => s.settings?.defaultTaskViewPreset ?? 'all') - const handlePrefetch = React.useCallback(() => { - if (!canBrowseTasks || resolvedDefaultTaskSource !== 'github') { - return - } - const activeRepo = activeRepoId ? (repoMap.get(activeRepoId) ?? null) : null - const activeGitRepo = activeRepo && isGitRepoKind(activeRepo) ? activeRepo : null - const firstGitRepo = activeGitRepo ?? repos.find((r) => isGitRepoKind(r)) - if (firstGitRepo?.path) { - // Why: warm the exact cache key the page will read on mount — must - // match TaskPage's `initialTaskQuery` derived from the same default - // preset, otherwise the prefetch lands in a key the page never reads - // and we pay the full round-trip after click. - prefetchWorkItems( - firstGitRepo.id, - firstGitRepo.path, - PER_REPO_FETCH_LIMIT, - getTaskPresetQuery(defaultTaskViewPreset) - ) - } - }, [ - activeRepoId, - canBrowseTasks, - defaultTaskViewPreset, - prefetchWorkItems, - repoMap, - repos, - resolvedDefaultTaskSource - ]) - - const tasksActive = activeView === 'tasks' const automationsActive = activeView === 'automations' const activityActive = activeView === 'activity' const mobileActive = activeView === 'mobile' const activityUnreadCount = useActivityUnreadCount(showAgentsButton, 'sidebar-badge') const mobileOnboardingBadge = useMobileSidebarOnboardingBadge(showMobileButton) - const hideTasksButton = React.useCallback(() => { - void updateSettings({ showTasksButton: false }) - }, [updateSettings]) const hideAutomationsButton = React.useCallback(() => { void updateSettings({ showAutomationsButton: false }) }, [updateSettings]) @@ -221,103 +77,7 @@ const SidebarNav = React.memo(function SidebarNav() { data-contextual-tour-target="sidebar-navigation" > - {showTasksButton ? ( - - - - - - - ) : null} + {showAutomationsButton ? ( diff --git a/src/renderer/src/components/sidebar/SidebarTaskNavButton.tsx b/src/renderer/src/components/sidebar/SidebarTaskNavButton.tsx new file mode 100644 index 000000000..77b6b73c7 --- /dev/null +++ b/src/renderer/src/components/sidebar/SidebarTaskNavButton.tsx @@ -0,0 +1,256 @@ +import React from 'react' +import { EyeOff, Github, Gitlab, List } from 'lucide-react' +import { JiraIcon } from '@/components/icons/JiraIcon' +import { LinearIcon } from '@/components/icons/LinearIcon' +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger +} from '@/components/ui/context-menu' +import { getTaskPresetQuery, PER_REPO_FETCH_LIMIT } from '@/lib/new-workspace' +import { getLocalPreflightContext, localPreflightContextKey } from '@/lib/local-preflight-context' +import { cn } from '@/lib/utils' +import { useAppStore } from '@/store' +import { useRepoMap } from '@/store/selectors' +import { translate } from '@/i18n/i18n' +import { isGitRepoKind } from '../../../../shared/repo-kind' +import { + normalizeVisibleTaskProviders, + restoreAvailableDefaultTaskProvider, + resolveVisibleTaskProvider +} from '../../../../shared/task-providers' + +function HideTaskSidebarMenu({ onHide }: { onHide: () => void }): React.JSX.Element { + return ( + + + + {translate('auto.components.sidebar.SidebarNav.d599269755', 'Hide from sidebar')} + + + ) +} + +function TaskProviderShortcut({ + canBrowseTasks, + label, + onOpen, + children +}: { + canBrowseTasks: boolean + label: string + onOpen: () => void + children: React.ReactNode +}): React.JSX.Element { + return ( + { + e.stopPropagation() + if (!canBrowseTasks) { + return + } + onOpen() + }} + className={cn( + 'rounded p-0.5 text-muted-foreground/70', + canBrowseTasks ? 'transition-colors hover:text-foreground' : 'cursor-default' + )} + aria-label={canBrowseTasks ? label : undefined} + aria-hidden={canBrowseTasks ? undefined : true} + > + {children} + + ) +} + +export function SidebarTaskNavButton(): React.JSX.Element | null { + const openTaskPage = useAppStore((s) => s.openTaskPage) + const updateSettings = useAppStore((s) => s.updateSettings) + const activeView = useAppStore((s) => s.activeView) + const repos = useAppStore((s) => s.repos) + const repoMap = useRepoMap() + const canBrowseTasks = repos.some((repo) => isGitRepoKind(repo)) + const showTasksButton = useAppStore((s) => s.settings?.showTasksButton !== false) + const rawVisibleTaskProviders = useAppStore((s) => s.settings?.visibleTaskProviders) + const defaultTaskSource = useAppStore((s) => s.settings?.defaultTaskSource ?? 'github') + const preflightStatus = useAppStore((s) => s.preflightStatus) + const preflightStatusChecked = useAppStore((s) => s.preflightStatusChecked) + const preflightStatusContextKey = useAppStore((s) => s.preflightStatusContextKey) + const refreshPreflightStatus = useAppStore((s) => s.refreshPreflightStatus) + const expectedPreflightContextKey = useAppStore((s) => + localPreflightContextKey(getLocalPreflightContext(s)) + ) + const linearStatus = useAppStore((s) => s.linearStatus) + const linearStatusChecked = useAppStore((s) => s.linearStatusChecked) + const checkLinearConnection = useAppStore((s) => s.checkLinearConnection) + const prefetchWorkItems = useAppStore((s) => s.prefetchWorkItems) + const activeRepoId = useAppStore((s) => s.activeRepoId) + const defaultTaskViewPreset = useAppStore((s) => s.settings?.defaultTaskViewPreset ?? 'all') + const preferredVisibleTaskProviders = React.useMemo( + () => normalizeVisibleTaskProviders(rawVisibleTaskProviders), + [rawVisibleTaskProviders] + ) + const preflightStatusCurrent = preflightStatusContextKey === expectedPreflightContextKey + const visibleTaskProviders = React.useMemo( + () => + restoreAvailableDefaultTaskProvider( + preferredVisibleTaskProviders, + { + gitlabInstalled: preflightStatusCurrent && preflightStatus?.glab?.installed === true, + linearConnected: linearStatus.connected === true + }, + defaultTaskSource + ), + [ + defaultTaskSource, + linearStatus.connected, + preferredVisibleTaskProviders, + preflightStatusCurrent, + preflightStatus?.glab?.installed + ] + ) + const resolvedDefaultTaskSource = React.useMemo( + () => resolveVisibleTaskProvider(defaultTaskSource, visibleTaskProviders), + [defaultTaskSource, visibleTaskProviders] + ) + + React.useEffect(() => { + if (!preflightStatusChecked || !preflightStatusCurrent) { + void refreshPreflightStatus() + } + if (!linearStatusChecked) { + void checkLinearConnection() + } + }, [ + checkLinearConnection, + linearStatusChecked, + preflightStatusChecked, + preflightStatusCurrent, + refreshPreflightStatus + ]) + + const handlePrefetch = React.useCallback(() => { + if (!canBrowseTasks || resolvedDefaultTaskSource !== 'github') { + return + } + const activeRepo = activeRepoId ? (repoMap.get(activeRepoId) ?? null) : null + const activeGitRepo = activeRepo && isGitRepoKind(activeRepo) ? activeRepo : null + const firstGitRepo = activeGitRepo ?? repos.find((r) => isGitRepoKind(r)) + if (firstGitRepo?.path) { + prefetchWorkItems( + firstGitRepo.id, + firstGitRepo.path, + PER_REPO_FETCH_LIMIT, + getTaskPresetQuery(defaultTaskViewPreset) + ) + } + }, [ + activeRepoId, + canBrowseTasks, + defaultTaskViewPreset, + prefetchWorkItems, + repoMap, + repos, + resolvedDefaultTaskSource + ]) + + const hideTasksButton = React.useCallback(() => { + void updateSettings({ showTasksButton: false }) + }, [updateSettings]) + + if (!showTasksButton) { + return null + } + + const tasksActive = activeView === 'tasks' + + return ( + + + + + + + ) +} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index bee532238..151784947 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -869,7 +869,8 @@ "9c00bd4adf": "Select a workspace from the sidebar to begin.", "16e9e3df89": "starred", "0d0ace8861": "Star on GitHub", - "ec43b38ba7": "Starred on GitHub" + "ec43b38ba7": "Starred on GitHub", + "157bb5ecbb": "Open GitHub" }, "LinearIssueMarkdownDescriptionEditor": { "d9c47069ef": "Markdown", @@ -1201,7 +1202,13 @@ "b5e685e4d9": "Dismiss", "5f6df21046": "Enjoying Orca?", "2d67b6c849": "Star on GitHub", - "af3c9bbb37": "Starring…" + "af3c9bbb37": "Starring…", + "68a41bc3aa": "Could not star with", + "996bf76e46": "Open GitHub to finish in your browser.", + "d32015fec7": "Opening...", + "157bb5ecbb": "Open GitHub", + "8c967b4d15": "Not now", + "73dfd4eb8d": "Don't ask again" }, "TaskPage": { "513cddfa7a": "Verifying…", @@ -3344,7 +3351,10 @@ "2991a0106c": "Help", "4e8f5710d3": "Couldn't restart Orca.", "5161eef55d": "Restarting Orca…", - "d396773ef0": "checking" + "d396773ef0": "checking", + "f8a2c91d4e": "Milestones", + "b7e4d2a19c": "Onboarding", + "c4f8e1b72a": "X" }, "SidebarToolbar": { "19e32d0e5f": "Open folder picker to add a project", @@ -4369,7 +4379,9 @@ "1e29570462": "starring", "9d181300e3": "starred", "5c49f02662": "hidden", - "b3f0584f5d": "loading" + "b3f0584f5d": "loading", + "cb65c75b11": "Opening...", + "f2d4f877b2": "Open GitHub" }, "GeneralUpdateSettingsSection": { "8a52ca1d02": "Release notes", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index c6acb98a1..7a274d1a7 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -869,7 +869,8 @@ "9c00bd4adf": "Seleccione un espacio de trabajo de la barra lateral para comenzar.", "16e9e3df89": "sembrado de estrellas", "0d0ace8861": "Estrella en GitHub", - "ec43b38ba7": "Destacado en GitHub" + "ec43b38ba7": "Destacado en GitHub", + "157bb5ecbb": "Open GitHub" }, "LinearIssueMarkdownDescriptionEditor": { "d9c47069ef": "Markdown", @@ -1201,7 +1202,13 @@ "b5e685e4d9": "Despedir", "5f6df21046": "¿Disfrutando de Orca?", "2d67b6c849": "Estrella en GitHub", - "af3c9bbb37": "Protagonizada…" + "af3c9bbb37": "Protagonizada…", + "68a41bc3aa": "Could not star with", + "996bf76e46": "Open GitHub to finish in your browser.", + "d32015fec7": "Opening...", + "157bb5ecbb": "Open GitHub", + "8c967b4d15": "Not now", + "73dfd4eb8d": "Don't ask again" }, "TaskPage": { "513cddfa7a": "Verificando…", @@ -3344,7 +3351,10 @@ "2991a0106c": "Ayuda", "4e8f5710d3": "No se pudo reiniciar Orca.", "5161eef55d": "Reiniciando Orca...", - "d396773ef0": "de cheques" + "d396773ef0": "de cheques", + "f8a2c91d4e": "Milestones", + "b7e4d2a19c": "Onboarding", + "c4f8e1b72a": "X" }, "SidebarToolbar": { "19e32d0e5f": "Abra el selector de carpetas para agregar un proyecto", @@ -4369,7 +4379,9 @@ "1e29570462": "protagonizada", "9d181300e3": "sembrado de estrellas", "5c49f02662": "oculto", - "b3f0584f5d": "cargando" + "b3f0584f5d": "cargando", + "cb65c75b11": "Opening...", + "f2d4f877b2": "Open GitHub" }, "GeneralUpdateSettingsSection": { "8a52ca1d02": "Notas de la versión", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index cf8e9779a..e2b11e4ed 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -869,7 +869,8 @@ "9c00bd4adf": "開始するには、サイドバーからワークスペースを選択します。", "16e9e3df89": "星付き", "0d0ace8861": "GitHub でスターを付ける", - "ec43b38ba7": "GitHub でスターを獲得" + "ec43b38ba7": "GitHub でスターを獲得", + "157bb5ecbb": "Open GitHub" }, "LinearIssueMarkdownDescriptionEditor": { "d9c47069ef": "Markdown", @@ -1201,7 +1202,13 @@ "b5e685e4d9": "閉じる", "5f6df21046": "Orcaを楽しんでいますか?", "2d67b6c849": "GitHub でスターを付ける", - "af3c9bbb37": "主演…" + "af3c9bbb37": "主演…", + "68a41bc3aa": "Could not star with", + "996bf76e46": "Open GitHub to finish in your browser.", + "d32015fec7": "Opening...", + "157bb5ecbb": "Open GitHub", + "8c967b4d15": "Not now", + "73dfd4eb8d": "Don't ask again" }, "TaskPage": { "513cddfa7a": "確認中…", @@ -3325,7 +3332,10 @@ "2991a0106c": "ヘルプ", "4e8f5710d3": "Orca を再起動できませんでした。", "5161eef55d": "Orca を再起動しています…", - "d396773ef0": "チェック中" + "d396773ef0": "チェック中", + "f8a2c91d4e": "Milestones", + "b7e4d2a19c": "Onboarding", + "c4f8e1b72a": "X" }, "SidebarToolbar": { "19e32d0e5f": "フォルダーピッカーを開いてプロジェクトを追加します", @@ -4354,7 +4364,9 @@ "1e29570462": "主演", "9d181300e3": "星付き", "5c49f02662": "隠れた", - "b3f0584f5d": "読み込み中" + "b3f0584f5d": "読み込み中", + "cb65c75b11": "Opening...", + "f2d4f877b2": "Open GitHub" }, "GeneralUpdateSettingsSection": { "8a52ca1d02": "リリースノート", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index abeb320ff..94cfc2364 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -869,7 +869,8 @@ "9c00bd4adf": "시작하려면 사이드바에서 워크스페이스를 선택하세요.", "16e9e3df89": "별표가 붙은", "0d0ace8861": "GitHub의 스타", - "ec43b38ba7": "GitHub에 별표 표시됨" + "ec43b38ba7": "GitHub에 별표 표시됨", + "157bb5ecbb": "Open GitHub" }, "LinearIssueMarkdownDescriptionEditor": { "d9c47069ef": "Markdown", @@ -1201,7 +1202,13 @@ "b5e685e4d9": "닫기", "5f6df21046": "Orca를 즐기고 있나요?", "2d67b6c849": "GitHub의 스타", - "af3c9bbb37": "스타 표시 중…" + "af3c9bbb37": "스타 표시 중…", + "68a41bc3aa": "Could not star with", + "996bf76e46": "Open GitHub to finish in your browser.", + "d32015fec7": "Opening...", + "157bb5ecbb": "Open GitHub", + "8c967b4d15": "Not now", + "73dfd4eb8d": "Don't ask again" }, "TaskPage": { "513cddfa7a": "확인 중…", @@ -3325,7 +3332,10 @@ "2991a0106c": "돕다", "4e8f5710d3": "Orca를 다시 시작할 수 없습니다.", "5161eef55d": "Orca를 다시 시작하는 중…", - "d396773ef0": "확인 중" + "d396773ef0": "확인 중", + "f8a2c91d4e": "Milestones", + "b7e4d2a19c": "Onboarding", + "c4f8e1b72a": "X" }, "SidebarToolbar": { "19e32d0e5f": "폴더 선택기를 열어 프로젝트를 추가하세요.", @@ -4354,7 +4364,9 @@ "1e29570462": "스타 표시", "9d181300e3": "별표가 붙은", "5c49f02662": "숨겨진", - "b3f0584f5d": "로드 중" + "b3f0584f5d": "로드 중", + "cb65c75b11": "Opening...", + "f2d4f877b2": "Open GitHub" }, "GeneralUpdateSettingsSection": { "8a52ca1d02": "릴리스 노트", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index c721ad323..4fbb5d903 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -869,7 +869,8 @@ "9c00bd4adf": "从侧边栏中选择一个工作区开始。", "16e9e3df89": "已加星标", "0d0ace8861": "在 GitHub 上加星标", - "ec43b38ba7": "在 GitHub 上加星标" + "ec43b38ba7": "在 GitHub 上加星标", + "157bb5ecbb": "Open GitHub" }, "LinearIssueMarkdownDescriptionEditor": { "d9c47069ef": "Markdown", @@ -1201,7 +1202,13 @@ "b5e685e4d9": "关闭", "5f6df21046": "喜欢 Orca 吗?", "2d67b6c849": "在 GitHub 上加星标", - "af3c9bbb37": "主演…" + "af3c9bbb37": "主演…", + "68a41bc3aa": "Could not star with", + "996bf76e46": "Open GitHub to finish in your browser.", + "d32015fec7": "Opening...", + "157bb5ecbb": "Open GitHub", + "8c967b4d15": "Not now", + "73dfd4eb8d": "Don't ask again" }, "TaskPage": { "513cddfa7a": "正在验证...", @@ -3325,7 +3332,10 @@ "2991a0106c": "帮助", "4e8f5710d3": "无法重新启动 Orca。", "5161eef55d": "正在重启 Orca…", - "d396773ef0": "检查中" + "d396773ef0": "检查中", + "f8a2c91d4e": "Milestones", + "b7e4d2a19c": "Onboarding", + "c4f8e1b72a": "X" }, "SidebarToolbar": { "19e32d0e5f": "打开文件夹选择器以添加项目", @@ -4354,7 +4364,9 @@ "1e29570462": "主演", "9d181300e3": "已加星标", "5c49f02662": "隐", - "b3f0584f5d": "加载中" + "b3f0584f5d": "加载中", + "cb65c75b11": "Opening...", + "f2d4f877b2": "Open GitHub" }, "GeneralUpdateSettingsSection": { "8a52ca1d02": "发行说明", diff --git a/src/shared/types.ts b/src/shared/types.ts index 74eb5c5e8..e28c11df3 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -2737,7 +2737,7 @@ export type PersistedUIState = { * spawn — effectively restarting the nag countdown after each update. */ starNagAppVersion?: string | null /** Next threshold (agents spawned since baseline) at which the star-nag - * notification should fire. Starts at 50 and doubles each time the user + * notification should fire. Starts at 35 and doubles each time the user * dismisses the notification without starring. */ starNagNextThreshold?: number /** Once the user has starred Orca (from any entry point) we permanently