fix: apply the review items for #714 at merge

- Session update no longer resets "How often" while typing: updateGitFetchStatus
  (session-ui.js) leaves a control alone while it is document.activeElement, and
  autoSaveGitFetch writes the clamped interval back itself (Enter saves with the
  field still focused).
- Partial override shows the app values: enabled = session ?? app switch,
  interval = session ?? app interval ?? 300 (session-ui.js updateGitFetchStatus).
- runFetch (git-fetch.ts) arms the spawn with what the core.sshCommand read left
  of timeoutMs, and answers ETIMEDOUT without spawning when nothing is left, so a
  git that never answers costs one timeout, not two.
- fetchEnvFor keeps a GIT_SSH from the server's environment (repoSshCommand ||
  base.GIT_SSH), fileoverview and CLAUDE.md line updated to say so.
- i18n.js: the zh-CN "Git fetch on an interval" description scopes the only
  writing git command to the Git window.
- styles.css: the disabled/busy rules are scoped to .git-status-actions
  .btn-icon-sm (moved beside .git-status-actions), and the busy pulse stops
  under prefers-reduced-motion.
- Comments that called the override per case now say per session: session.ts
  (field, restore and getter docs, setGitFetchSettings), git-fetch-scheduler.ts:2,
  server-timing.ts:78, server.ts (cleanup and restore), app.js _onSessionUpdated,
  session-ui.js (openSessionOptions and updateGitFetchStatus).
- Tests: test/git-fetch-session-options-ui.test.ts (new, JSDOM: typing survives
  an SSE update, the Enter write-back, both partial cases); test/git-fetch.test.ts
  (GIT_SSH kept; one bound for the read and the fetch, stub git on PATH).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-27 01:58:14 +01:00
1 parent 3411d7f35a
commit 6539bc6c82
12 files changed
+299 -68

No files matched your search

+3 -3
View File
@@ -1550,7 +1550,7 @@ export class WebServer extends EventEmitter {
): Promise<void> {
const session = this.sessions.get(sessionId);
this.keepWarmService.remove(sessionId);
// A closed case takes its place in the fetch schedule with it: otherwise a detached-and-forgotten id
// A closed session takes its place in the fetch schedule with it: otherwise a detached-and-forgotten id
// would still count as tracked, and its next run would land on a session that no longer exists.
this.gitFetchScheduler.remove(sessionId);
const pinned = session?.pinned === true || this.store.getSession(sessionId)?.pinned === true;
@@ -3819,9 +3819,9 @@ export class WebServer extends EventEmitter {
// below, a future `expiresAt` re-arms the timer once the pane is attached.
lastRealPromptAt: savedState?.lastRealPromptAt,
keepWarmEnabled: savedState?.keepWarmEnabled,
// The case's own fetch switch and interval, carried over rather than reset to the app default:
// The session's own fetch switch and interval, carried over rather than reset to the app default:
// the choice is the user's and outlives the run that made it. The scheduler reads them fresh
// on its next tick, so a restored case is back on its own schedule without a restart.
// on its next tick, so a restored session is back on its own schedule without a restart.
gitFetchEnabled: savedState?.gitFetchEnabled,
gitFetchIntervalSeconds: savedState?.gitFetchIntervalSeconds,
// Conversations this pane provably owned, oldest first. Its tail is