feat(git): resolve nested repositories in the other git surfaces

Extract GitView's resolution flow into hooks/useNestedGitDirectory (root
probe, discovery, auto-select, stale-selection recovery) so the flow no
longer depends on SessionSidebar probing the root first, and reuse it in
the pull-request view, walkthrough view, and mobile changes surface —
all three now operate on the selected nested repository instead of
dead-ending on a non-repo root. Shared pending/failed/unsupported/empty
states live in git/NestedRepoResolutionStates; desktop changes inherits
the behavior through GitView. Selection stays shared per root, so the
picker's pick carries across surfaces.
This commit is contained in:
jaygupta17
2026-08-25 19:15:53 +05:30
parent 5e3f9d1ba2
commit cda273d69f
7 changed files with 315 additions and 130 deletions
@@ -0,0 +1,87 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/icon/Icon';
import { useI18n } from '@/lib/i18n';
import type { NestedRepoDiscovery } from '@/stores/useGitStore';
type NestedRepoResolutionStatesProps = {
/** Probe of the project root: `false` means nested resolution applies. */
rootIsGitRepo: boolean | null;
/** Discovery outcome for the root (`undefined` = not run yet). */
nestedRepos: NestedRepoDiscovery | undefined;
onRetryDiscovery: () => void;
/** Optional extra line under the not-a-repository description. */
emptyStateFooter?: React.ReactNode;
};
/**
* Shared empty/loading states for git surfaces while nested-repository
* resolution is pending, failed, or impossible. Renders null once
* repositories are resolved so the consumer can proceed into its own content.
*
* A runtime without the discovery route (VS Code) reports "unsupported": the
* honest state there is the plain not-a-repository empty state, without a
* retry that can never succeed.
*/
export const NestedRepoResolutionStates: React.FC<NestedRepoResolutionStatesProps> = ({
rootIsGitRepo,
nestedRepos,
onRetryDiscovery,
emptyStateFooter,
}) => {
const { t } = useI18n();
if (rootIsGitRepo !== false) return null;
if (nestedRepos === undefined || nestedRepos === null) {
return (
<div className="flex h-full flex-col items-center justify-center px-4 text-center">
<Icon name="loader-4" className="mb-3 size-6 animate-spin text-muted-foreground" />
<p className="typography-ui-label font-semibold text-foreground">
{nestedRepos === null
? t('gitView.empty.discoverFailed')
: t('gitView.empty.discoveringRepositories')}
</p>
{nestedRepos === null ? (
<Button
type="button"
variant="outline"
size="sm"
className="mt-3 gap-1.5"
onClick={onRetryDiscovery}
>
<Icon name="refresh" className="size-4" />
{t('gitView.empty.retryDiscovery')}
</Button>
) : null}
</div>
);
}
if (nestedRepos === 'unsupported' || nestedRepos.length === 0) {
return (
<div className="flex h-full flex-col items-center justify-center px-4 text-center">
<Icon name="git-branch" className="mb-3 size-6 text-muted-foreground" />
<p className="typography-ui-label font-semibold text-foreground">
{t('gitView.empty.notGitRepository')}
</p>
<p className="typography-meta mt-1 text-muted-foreground">
{t('gitView.empty.notGitRepositoryDescription')}
</p>
{emptyStateFooter}
</div>
);
}
// Repositories were found and one is about to be auto-selected (or the
// selected repository is still probing) — hold a brief loading state.
return (
<div className="flex h-full flex-col items-center justify-center px-4 text-center">
<Icon name="loader-4" className="mb-3 size-6 animate-spin text-muted-foreground" />
<p className="typography-ui-label font-semibold text-foreground">
{t('gitView.loading.checkingRepository')}
</p>
</div>
);
};