Merge remote-tracking branch 'origin/main' into nwparker/guard-add-repo-nested-import-state

# Conflicts:
#	src/renderer/src/components/sidebar/AddRepoDialog.tsx
This commit is contained in:
Jinwoo-H
2026-05-30 06:45:06 -07:00
4 changed files with 67 additions and 14 deletions
@@ -213,6 +213,18 @@ function Settings(): React.JSX.Element {
const repoHooksRuntimeIdentityRef = useRef<string>('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<boolean> => {
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 (
<div className="flex flex-1 items-center justify-center text-muted-foreground">
Loading settings...
<div
ref={setSettingsRootNode}
className="settings-view-shell flex min-h-0 flex-1 overflow-hidden bg-background"
>
<div className="flex flex-1 items-center justify-center text-muted-foreground">
Loading settings...
</div>
</div>
)
}
@@ -721,7 +729,10 @@ function Settings(): React.JSX.Element {
activeSectionId === 'shortcuts' && settingsSearchQuery.trim() === ''
return (
<div className="settings-view-shell flex min-h-0 flex-1 overflow-hidden bg-background">
<div
ref={setSettingsRootNode}
className="settings-view-shell flex min-h-0 flex-1 overflow-hidden bg-background"
>
<SettingsSidebar
activeSectionId={activeSectionId}
generalGroups={generalNavGroups}
@@ -118,6 +118,9 @@ const AddRepoDialog = React.memo(function AddRepoDialog() {
// Why: nested group import can create many repos; resetState must prevent
// stale import completions from reopening setup UI after Back/close.
const nestedImportGenRef = 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<string | null>(null)
@@ -252,6 +255,7 @@ const AddRepoDialog = React.memo(function AddRepoDialog() {
localAddGenRef.current++
serverAddGenRef.current++
nestedImportGenRef.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()
@@ -736,13 +740,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()
}, [
@@ -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(
<CommentMarkdown
@@ -265,7 +265,11 @@ function transformGitHubReferenceChildren(
const nextChildren: MarkdownNode[] = []
for (const child of node.children) {
if (child.type === 'text' && child.value !== undefined) {
nextChildren.push(...splitGitHubReferenceText(child.value, defaultRepo))
// Why: generated agent comments can contain thousands of issue refs;
// appending iteratively avoids V8's argument-list limit.
for (const part of splitGitHubReferenceText(child.value, defaultRepo)) {
nextChildren.push(part)
}
} else {
transformGitHubReferenceChildren(child, defaultRepo)
nextChildren.push(child)