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

@@ -116,14 +116,35 @@ export function FolderTitleBar() {
if (!folderPath) return
let cancelled = false
const clearPoll = () => {
if (intervalRef.current !== undefined) {
clearInterval(intervalRef.current)
intervalRef.current = undefined
}
}
const armPoll = () => {
if (intervalRef.current !== undefined) return
intervalRef.current = setInterval(() => {
void doFetch()
}, 10_000)
}
async function doFetch() {
if (document.visibilityState !== "visible") return
try {
const b = await getGitBranch(folderPath)
if (!cancelled) setBranch(b)
if (cancelled) return
setBranch(b)
if (b === null) {
clearPoll()
} else {
armPoll()
}
} catch {
if (!cancelled) setBranch(null)
clearPoll()
}
}
@@ -134,14 +155,11 @@ export function FolderTitleBar() {
}
void doFetch()
intervalRef.current = setInterval(() => {
void doFetch()
}, 10_000)
document.addEventListener("visibilitychange", handleVisibilityChange)
return () => {
cancelled = true
clearInterval(intervalRef.current)
clearPoll()
document.removeEventListener("visibilitychange", handleVisibilityChange)
}
}, [folderPath])