From 4268c0439ab35f561dac7d92e7e10413ec53e904 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 06:33:17 -0700 Subject: [PATCH 1/3] fix: guard add repo setup state (#3698) --- src/renderer/src/components/sidebar/AddRepoDialog.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/renderer/src/components/sidebar/AddRepoDialog.tsx b/src/renderer/src/components/sidebar/AddRepoDialog.tsx index dc621f19d13..04b0c756019 100644 --- a/src/renderer/src/components/sidebar/AddRepoDialog.tsx +++ b/src/renderer/src/components/sidebar/AddRepoDialog.tsx @@ -115,6 +115,9 @@ const AddRepoDialog = React.memo(function AddRepoDialog() { // Why: server path adds share the same dialog but run against a runtime // server; resetState cancels their stale scan/add/fetch continuations. const serverAddGenRef = useRef(0) + // Why: setup actions can await settings/worktree refreshes; resetState + // cancels stale continuations when the setup step is dismissed. + const setupActionGenRef = useRef(0) // Why: a dropped path is modal data, so ordinary state updates must not // re-run the import while the Add Project dialog advances through steps. const droppedLocalPathHandledRef = useRef(null) @@ -248,6 +251,7 @@ const AddRepoDialog = React.memo(function AddRepoDialog() { cloneGenRef.current++ localAddGenRef.current++ serverAddGenRef.current++ + setupActionGenRef.current++ // Why: kill the git clone process if one is running, so backing out // or closing the dialog doesn't leave a clone running on disk. void window.api.repos.cloneAbort() @@ -722,13 +726,20 @@ const AddRepoDialog = React.memo(function AddRepoDialog() { if (!projectId) { return } + const gen = ++setupActionGenRef.current trackSetupAction('open_existing') if (!otherWorktreesVisible) { const updated = await updateRepo(projectId, { externalWorktreeVisibility: 'show' }) + if (gen !== setupActionGenRef.current) { + return + } if (updated && addedRepo) { setAddedRepo({ ...addedRepo, externalWorktreeVisibility: 'show' }) } await fetchWorktrees(projectId) + if (gen !== setupActionGenRef.current) { + return + } } await finishImportedRepoWithoutOpening() }, [ From c5e69a7415849469977b5820f6caaed41b89a3b2 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 06:37:38 -0700 Subject: [PATCH 2/3] fix: handle large comment markdown references (#3699) --- .../sidebar/CommentMarkdown.test.tsx | 29 ++++++++++++++++++- .../components/sidebar/CommentMarkdown.tsx | 6 +++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/components/sidebar/CommentMarkdown.test.tsx b/src/renderer/src/components/sidebar/CommentMarkdown.test.tsx index 0c632615285..e39f528552a 100644 --- a/src/renderer/src/components/sidebar/CommentMarkdown.test.tsx +++ b/src/renderer/src/components/sidebar/CommentMarkdown.test.tsx @@ -1,6 +1,6 @@ import { renderToStaticMarkup } from 'react-dom/server' import { describe, expect, it } from 'vitest' -import CommentMarkdown from './CommentMarkdown' +import CommentMarkdown, { remarkGitHubReferences } from './CommentMarkdown' describe('CommentMarkdown', () => { it('autolinks same-repo GitHub issue references when repo context is provided', () => { @@ -42,6 +42,33 @@ describe('CommentMarkdown', () => { expect(markup).not.toContain('href="https://github.com/stablyai/orca/issues/2317"') }) + it('autolinks very large generated GitHub reference comments', () => { + const referenceCount = 130_000 + const tree = { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'text', + value: Array.from({ length: referenceCount }, (_, index) => `#${index + 1}`).join(' ') + } + ] + } + ] + } + + const transform = remarkGitHubReferences({ owner: 'stablyai', repo: 'orca' })() + + expect(() => transform(tree)).not.toThrow() + expect(tree.children[0]?.children).toHaveLength(referenceCount * 2 - 1) + expect(tree.children[0]?.children[0]).toMatchObject({ + type: 'link', + url: 'https://github.com/stablyai/orca/issues/1' + }) + }) + it('contains long PR body markdown inside its available width', () => { const markup = renderToStaticMarkup( Date: Sat, 30 May 2026 06:37:58 -0700 Subject: [PATCH 3/3] perf: clean settings search reset from effect (#3700) --- .../src/components/settings/Settings.tsx | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/renderer/src/components/settings/Settings.tsx b/src/renderer/src/components/settings/Settings.tsx index eeffdeedea7..9678967f2c6 100644 --- a/src/renderer/src/components/settings/Settings.tsx +++ b/src/renderer/src/components/settings/Settings.tsx @@ -213,6 +213,18 @@ function Settings(): React.JSX.Element { const repoHooksRuntimeIdentityRef = useRef('local') const shortcutsEscapeConfirmUntilRef = useRef(0) + const setSettingsRootNode = useCallback( + (node: HTMLDivElement | null): void => { + if (node) { + return + } + // Why: the settings search is a transient in-page filter. Leaving it behind makes the next + // visit look partially broken because whole sections stay hidden before the user types again. + setSettingsSearchQuery('') + }, + [setSettingsSearchQuery] + ) + const confirmDiscardCommitPromptChanges = useCallback(async (): Promise => { if (!hasUnsavedCommitPromptChanges) { return true @@ -339,15 +351,6 @@ function Settings(): React.JSX.Element { return () => document.removeEventListener('keydown', handleFindShortcut) }, [keybindings]) - useEffect( - () => () => { - // Why: the settings search is a transient in-page filter. Leaving it behind makes the next - // visit look partially broken because whole sections stay hidden before the user types again. - setSettingsSearchQuery('') - }, - [setSettingsSearchQuery] - ) - useEffect(() => { if (!settings || !settingsNavigationTarget) { return @@ -694,8 +697,13 @@ function Settings(): React.JSX.Element { if (!settings) { return ( -
- Loading settings... +
+
+ Loading settings... +
) } @@ -721,7 +729,10 @@ function Settings(): React.JSX.Element { activeSectionId === 'shortcuts' && settingsSearchQuery.trim() === '' return ( -
+