fix: resolve an untracked selection into Unstaged once the lists are merged - #32
Conversation
…merged Section presence describes lists ON SCREEN, not file states — the rule the SectionPresence doc already spells out. The component derived the three fields inline and they disagreed about it: `unstaged` came from the merged display list, `untracked` came from the raw file state. So selecting an untracked file and then turning on "Merge Untracked into Unstaged" reported presence in BOTH. resolveSection kept the recorded "untracked", the pane resolved to a section with no rows in that mode, nothing highlighted, and the Unstaged header offered "Stage all" instead of "Stage". The rule has now been violated once per field, so it moves out of the component into `renderedPresence` in workingSection.ts, beside the type it builds and the resolver that consumes it, with the merged case under test. `untracked` was also answering two different questions through one field. It now answers only "is the Untracked SECTION showing this row", which is false while merging. Whether the FILE is untracked — which selects `git diff --no-index` and disables hunk staging — reads from the file list directly and is unaffected by the mode, which is what it always needed to be. The Untracked header that also consults it renders only when NOT merging, so the two readings coincide there. Reported by Codex review on the v0.4.0 promotion (#30). npm run check 530 files 0 errors; npm test 393 passed; cargo test 232 + 7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
💡 Codex Reviewgit-it/src/lib/components/Sidebar.svelte Line 120 in 49cf239 If the user leaves this confirmation open and switches repositories through the newly added native Open Repository… menu, ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
`refActionsBlocked()` covers the instant of the click. Every ref action that asks first then waits on a prompt with no timeout, and `gitActions` targets `appState.repo` — not whatever was on screen when the dialog opened. Switch repositories mid-prompt and confirming runs the OLD repo's branch or tag name against the NEW repository; where both hold that name, "Delete" force-deletes the wrong one. The same time-of-check/time-of-use shape as the stale-diff guard on discard, which is the precedent for fixing it the same way. Codex flagged branch delete. Six more sites have the identical gap, three of them destructive, so all seven are guarded rather than the one reported: confirmBranchDelete -> deleteBranch (reported) prompt -> renameBranch prompt -> createTag prompt -> createBranch (from ref) confirm -> deleteRemoteBranch confirm -> deleteTag prompt -> createBranch (detached HEAD) `sameRepoAfterPrompt()` captures the repo before the await and returns a predicate checked after it, so each call site keeps its own control flow instead of being wrapped in a callback. Refusing states plainly that nothing was done, rather than failing silently or acting on a guess. Reported by Codex review on #32. npm run check 530 files 0 errors; npm test 393 passed; cargo test 232 + 7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Confirmed and fixed in Extended past the reported site. Six other call sites in
I audited every Your "capture the repository before opening the dialog" is what I implemented; I did not take the "or retarget" alternative. Retargeting would silently apply an action the user authorised against a different repository's state — the branch they confirmed deleting is not the branch they would be deleting.
|
Reported by Codex review on the v0.4.0 promotion (#30).
nextis protected by a ruleset requiring a PR, so the fix lands here and #30 picks it up.The bug
SectionPresencealready documents the rule — presence describes lists on screen, not file states, and explicitly says the untracked case must report"unstaged"while merging. The component derived the three fields inline, and they disagreed about what "rendered" meant:So: select an untracked file with the sections separate, then enable Merge Untracked into Unstaged. Presence reports the path in both,
resolveSectionkeeps the recorded"untracked", and the pane resolves to a section that renders no rows in that mode — nothing highlights, and the Unstaged header offers "Stage all" instead of "Stage".The fix
Two parts, and the second is the one that makes the first safe.
1. The rule moves out of the component into
renderedPresenceinworkingSection.ts— beside the type it builds and the resolver that consumes it. This rule has now been violated once per field (theunstagedhalf was the same bug, caught in #26 review), which is the argument for it being one tested function rather than three inline expressions that can drift.2.
untrackedstops answering two questions through one field. It now answers only "is the Untracked section showing this row", which is false while merging. Whether the file is untracked — which selectsgit diff --no-indexand disables hunk staging — now reads from the file list directly and is unaffected by the mode, which is what that call always needed. Had I only changed presence,selectedIsUntrackedwould have flipped to false for merged untracked files and fetched the wrong diff.The Untracked section header also consults it, but that section renders only when NOT merging, so the two readings coincide there.
Tests
Five new cases on
renderedPresence, including the merged one and an end-to-end pairing withresolveSection:Watched failing first — the type gate reported
has no exported member 'renderedPresence'before the function existed.npm run check530 files / 0 errors ·npm test393 passed (up from 388) ·cargo test232 + 7.🤖 Generated with Claude Code