fix(workspace-state): stop resync loop on non-git folders and allow retry for degraded watcher

Gate git refresh on .git presence so file churn in non-git workspaces no longer produces endless resync_hint events, and silently log tree/git refresh errors during watch flushing instead of flagging requires_resync, which turned transient failures into self-reinforcing loops.

Degrade gracefully when the filesystem watcher fails to attach (e.g. permission denied, inotify quota): keep the initial snapshot, surface a degraded flag, and expose a store-level restart that the banner uses to retry attachment after the root cause is fixed.

Propagate is_git_repo through the snapshot so the git log and changes tabs render a dedicated "Not a Git repository" empty state instead of raw git stderr with a useless retry button.

Stop polling get_git_branch from the title bar once it returns null and re-arm on visibility change.

Add translations for the new banner, empty-state, and retry keys across all ten locales.
This commit is contained in:
xintaofei
2026-04-18 17:18:11 +08:00
parent c5c2bdd331
commit 7ef8d84d44
19 changed files with 380 additions and 74 deletions

View File

@@ -8,7 +8,7 @@ import {
useRef,
useState,
} from "react"
import { ChevronsDownUp, ChevronsUpDown } from "lucide-react"
import { ChevronsDownUp, ChevronsUpDown, GitBranch } from "lucide-react"
import { useTranslations } from "next-intl"
import { toast } from "sonner"
import {
@@ -38,6 +38,7 @@ import { useFolderContext } from "@/contexts/folder-context"
import { useTabContext } from "@/contexts/tab-context"
import { useWorkspaceContext } from "@/contexts/workspace-context"
import { useWorkspaceStateStore } from "@/hooks/use-workspace-state-store"
import { WorkspaceDegradedBanner } from "@/components/layout/workspace-degraded-banner"
import {
deleteFileTreeEntry,
gitAddFiles,
@@ -1185,14 +1186,30 @@ export function GitChangesTab() {
}
return (
<>
<ScrollArea className="h-full min-h-0" x="scroll">
<div className="flex flex-col h-full min-h-0">
{workspaceState.degraded && (
<WorkspaceDegradedBanner onRetry={workspaceState.restart} />
)}
<ScrollArea className="flex-1 min-h-0" x="scroll">
{trackedChanges.length === 0 && untrackedChanges.length === 0 ? (
<div className="flex items-center justify-center h-full p-4">
<p className="text-xs text-muted-foreground text-center">
{t("noChanges")}
</p>
</div>
!workspaceState.isGitRepo ? (
<div className="flex flex-col items-center justify-center h-full gap-1 p-6 text-center">
<GitBranch
className="size-5 text-muted-foreground/60"
aria-hidden
/>
<p className="text-sm font-medium">{t("notAGitRepoTitle")}</p>
<p className="text-xs text-muted-foreground">
{t("notAGitRepoHint")}
</p>
</div>
) : (
<div className="flex items-center justify-center h-full p-4">
<p className="text-xs text-muted-foreground text-center">
{t("noChanges")}
</p>
</div>
)
) : (
<div className="space-y-2 pb-2">
{trackedChanges.length > 0 && (
@@ -1644,6 +1661,6 @@ export function GitChangesTab() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
</div>
)
}