(panel): always offer Changes, and say what the directory is - #310
Conversation
A control that vanishes explains nothing: its absence is indistinguishable from a bug, and it gives the user nowhere to ask why. The Changes button is now unconditional, and the panel it opens reports whichever of four answers is true — the file list, "No changes", "This directory is not a git repository.", or git's own bounded message when there is a repository it refuses to open. Telling those last two apart is kept exactly as it was: exit 128 is git's generic fatal code, so "no repository" is still established by a filesystem walk for a .git entry (gitEntryAtOrAbove), never by a message match or an exit status, and status() still returns reason: 'not-a-repo' only on that positive evidence. Only what the renderer does with the answer changed. With the button unconditional there is nothing left to decide before a switch, so the eager availability probe goes with it: the git-changes-available IPC, its preload binding, the three-way memo, the in-flight dedupe, the stale-reply guard and the withdrawal path through toggleChangesTab. status() already asks the same question at the moment the tab needs the answer, and isWorkTree() stays on the runner for it.
…failure With a file open, the notice above the editor borrowed both the wording and the colour of a refresh that failed — the same sentence the list below deliberately renders as a neutral note. A directory that has no repository did not fail at anything, so the notice now states the fact itself and takes the neutral colour, while every genuine failure keeps "The file list could not be refreshed" and the error colour. The dirty-buffer case runs over the three ways the tree can stop answering under an open editor — the repository removed, the directory gone, and a repository git refuses — rather than only the first.
devsuitup
left a comment
There was a problem hiding this comment.
Re-reviewed at 3940b67.
The delta since 871ca31 is scoped to the notice bar: notARepo no longer counts as alarming, the no-repository case gets its own text instead of "The file list could not be refreshed", and .ai/contexts/changes-view.md now describes the same neutral treatment for the list and the notice bar. The three tree-lost payloads (repo removed, directory gone, git refuses) keep the dirty buffer without a prompt; a genuine refresh failure still lands in the red notice. test/dom-file-panel-changes.test.js passes on this head (94/94, replayed locally), and all CI jobs are green on 3940b67.
Nothing else moved: main.js, preload.js, git-changes-runner.js, docs/changes-view.md and test/panel-terminal.test.js are identical to the previous head, so the earlier findings on those files stand as already noted.
No blocking finding. Approving.
The behaviour
Every local session offers the Changes control. Opening it shows, in plain language, whichever of these is true:
N files changed +A −BNo changesThis directory is not a git repository.— a note, not an error colourA repository deleted under an open tab is reported into that tab on the next refresh; a
git initunder a session that had none is listed on the next refresh too. Nothing polls.Remote sessions are unchanged: with no local filesystem to corroborate against, a remote 128 always reports git's message rather than the plain line.
Kept
The distinction between no repository and a repository git refuses is untouched. Exit 128 is git's generic fatal code — dubious ownership, unreadable
.gitpermissions, an unsupportedcore.repositoryformatversion, a.gitfile whose gitdir is gone — so "no repository" is still established bygitEntryAtOrAbove, anfs.lstatwalk for a.gitentry up to the root, never by an exit status and never by a message match.status()still returnsreason: 'not-a-repo'only on that positive evidence, andisWorkTree()keeps its full table. All oftest/git-changes-runner-real-git.test.js's fixtures for the refused-repository cases are unchanged and still pin that none of them producesnot-a-repo.What changed is only what the renderer does with that answer: it picks the wording instead of deciding whether a button exists.
Removed, and the evidence it was dead
The eager availability probe existed for one purpose — to decide whether to render the button before the user could click it. With the button unconditional that purpose is gone, and
status()already asks the same question at the moment the tab needs it, on the failure path it was going to take anyway.Removed, in full:
git-changes-available(main handler + preload binding) — the only caller wasrefreshChangesAvailability; nothing else in the repo referenced the channel orgitChangesAvailable.refreshChangesAvailability,updateChangesToggle,noteChangesUnavailable,CHANGES_UNANSWERED,changesAvailabilityInFlight, and thechangesAvailablefield onfilePanelState— the memo, the in-flight dedupe, the three-way memo state, the stale-reply guard and the withdrawal path throughtoggleChangesTab. All of it existed to make a per-activation probe affordable and to undo a button that had already been drawn.isWorkTree()stays on the runner's interface:status()calls it throughcwdHasNoWorkTree(), and it is what the real-git tests assert the 128-corroboration table against.Nothing is half-deleted:
switchPanelno longer touches the Changes control at all, and the only remaining decision in this area is the one branch inrefreshChangesthat picks the wording.Tests
The tests that pinned the hiding behaviour are deleted — the probe memo/dedupe/stale-reply suite, the withdrawal suite, and the dirty-buffer withdrawal cases. New tests cover: the control present with no work tree; the control present with a clean work tree; each of the four states' text reaching the DOM; a refused repository showing git's message and not the no-repository text; a repository disappearing under an open tab reporting rather than closing; a repository disappearing under unsaved edits asking nothing and keeping the buffer; and a git failure after a no-repository answer rendering as a failure.
Mutations run against the guards added, each caught:
reason === 'not-a-repo'checknotAReporeset on the git-failure branchnpx eslint .— 0 errors, 333 warnings (unchanged fromc939748).npm test— batch 1: 1882 tests / 1879 pass / 1 fail / 2 skipped; batch 2: 120 tests / 119 pass / 0 fail / 1 skipped. The single failure is the pre-existing environmental one,test/ipc-path-validator.test.js"allows files under ~/.claude/". The pre-commit hook was bypassed for that reason; the numbers above are from a manual full run.