Repository navigation
v1.31.0
Added
- The pull request composer grew into what the click promised. It is a drawer out of the left panel now, like the filter editor — the graph stays readable beside it — rather than a sheet over the window. The title and description can be written for you: one call reads the branch — every commit subject, the diffstat, and as much of the real diff as fits, with the prompt told when it was cut rather than left to guess — and fills both fields together, so they cannot disagree; you edit them, and it never submits. Both ends are choosable: source repository and branch, target repository and branch — the selectors offer the repositories this one is connected to, its git remotes and its fork parent, not everything the account can see; the parent is looked up and offered as a target, and a cross-repository head spells itself
owner:branch, which is all a fork needs. Reviewers, assignees and labels are chosen behind the composer's More options fold — most requests need none of them — with a filter in each picker, and a label the repository does not have yet can be created from the picker itself, in the target repository, with a colour derived from its name; the picks are applied to the request the moment it exists — the create endpoint takes none of them, so they ride just after, and a refusal there is reported rather than mistaken for a failure to create. A checkbox opens the request as a draft, at last. The rules that decide which request a branch row offers still decide that; the composer starts from their answer and lets you override it. And the PULL REQUESTS header carries a+that opens the composer — prefilled with the checked-out branch's proposal when the rules have one, with where you stand when they do not: the four ends are choosable in there, so the door stays open even where a single pair would not — while GITHUB ISSUES'+opens the issue composer. - Describe a saved filter in words and get the query. The completion helps someone who knows the vocabulary is there; this is for someone who knows what they want and not how GitHub spells it. Say the ones waiting on my review that nobody has touched in a fortnight and the drawer writes the query — into the field, where you read and edit it before anything is saved, and it names the filter after your sentence if you have not named it yourself. The section is part of the question, so each one is given its own vocabulary rather than left to guess. The model is told the things that make the difference between a query that is valid and one that is right — that terms combine with and and there is no or, that
base:andhead:match a branch name by prefix, and that@meis available — because without them it writes queries that parse and match nothing. And the answer is checked before it lands: a query is the rare thing a model can be asked for that the app can verify, so one using a qualifier this app refuses is named and never written, exactly as a typed one would be. - An issue composer, in the same drawer. GITHUB ISSUES'
+opens the PR composer's sibling: a title, a description, the repository's labels and assignees in the same filtering pickers — one POST carries all four, which is the thing an issue's create endpoint does that a pull request's does not, and without push access GitHub quietly ignores the staffing rather than refusing. The AI writes from what is there: type a few words — or a full rough draft — into the title or the description and click; the model rewrites the fields in place, told to state only what your note supports and never to invent reproduction steps or numbers it does not contain. The wait breathes on the fields the answer will land in, the answer writes itself in word by word (instant under reduced motion), and what it replaced is one click away — a replacement without a way back is not a proposal. The PR composer's generation gained the same breath, the same reveal and the same restore line. A checkbox starts the branch the issue suggests the moment it exists — the same call the row's menu and the detail make, prompt, suggested name and linking included. - One hover card, everywhere a reference is. The
#123in a commit message used to open a two-line state tooltip while the sidebar rows opened the full preview — two renderings of one thing, kept apart only by where you hovered. The reference now opens the same card: the description as rendered markdown, the labels, the assignees, the reporter. And the card learned what only a reference can teach it — the sidebar lists are open items by construction, a#123resolves anything — so it says closed and merged now: a merged pull request reads Merged, a closed pull request failed where a closed issue completed, each in its own colour. - The filter query completes itself. Type a few letters and the qualifiers that match are offered, colon included; type past the colon and the values are, where a qualifier has a closed set of them —
status:offers success, pending and failure. Arrows move the choice, Enter or Tab takes it, Escape dismisses the list before it closes anything else. A qualifier whose value is a user name, a branch or a date offers nothing, deliberately: a list of names the app has never fetched would be worse than no list. The query is still checked as it is typed, so a half-writtenstatus:names itself as the offending token and cannot be saved. - Everything the GitHub surface shows keeps itself current. The lists, an open request's mergeability, its checks, its comments and every saved filter all re-ask on their own, so a refresh button is something you reach for when you are impatient rather than because the app cannot tell. What makes it affordable is that every one of those reads now carries an
ETag: GitHub answers 304 Not Modified when nothing has changed, and a 304 costs no rate limit at all. An unchanged answer is not written back either, so nothing on screen moves, re-orders or loses its place because a poll happened. The lists ask once a minute — sixty seconds is GitHub's own published interval, not a number we picked — an open request every few seconds while it is still being decided and every twenty once it is not, because a decided request still gets comments, reviews and re-run checks. A poll that finds nothing new writes nothing at all, so a list never reorders under an open hover card and a failed poll leaves the screen exactly as it is; a window nobody is looking at stops asking, and asks once on the way back. - The auto-fetch interval in Settings drives the loop it is named for. The renderer's five-minute fetch was gated on a
localStoragekey nothing in the app ever wrote, so it could not be turned off, and the Auto-Fetch Interval setting reached only the main process's own timer. The setting is read as state now, honoured at the interval it names, and 0 stops the loop as it says it does. - Every section in the left panel carries its own mark. LOCAL, REMOTE, REMOTES, TAGS, STASH, WORKTREES, SUBMODULES, REFLOG, PULL REQUESTS, GITHUB ISSUES and AGENTS each show an icon before their name. Eleven headers set in the same small capitals are told apart by reading them; a mark is what the eye lands on when it is not reading, which is what a panel you glance at needs. They come from the set the app already draws — the machine for LOCAL, a cloud for the branches the remote holds, and each of the others its own. GITHUB ISSUES wears GitHub's own mark, which is a different kind of thing: the icon set is ours to reweight, a trademark is only ever displayed, so it comes from the brand module and through a prop of its own rather than being mistaken for a drawing we could restyle.
- The sidebar draws a branch name as the path it is.
feat/views-in-tabsandfix/ano longer sit as two long strings in a flat list: the segments before the last are folders, each saying how many branches are under it, and a leaf reads as its last segment because the folders above already spell the rest. REMOTE groups by remote first, sooriginis a level rather than a prefix on every row — which is also what now tells twomains apart, and why the prefixing that used to do it is gone. The tree starts level with the section's own title and never left of it, and a folder carries no disclosure triangle — clicking the row is what folds it. A folder you close stays closed, per repository; everything else opens. A filter flattens the tree for as long as it is set: a tree that stays folded while you type reads as an empty section, and every row keeps its full name while filtering. Nothing a row carries changes — the menu, the#Nchip, ahead/behind, favourite, solo, hide and drag-and-drop are the same rows, differently arranged. - The pull request and issue sections can be refreshed. They only ever reloaded when a repository was opened, or after a write made from the detail pane — so a request opened, merged or commented on anywhere else stayed wrong on screen until you reopened the repository. Each section header now carries its own refresh: they are two lists from two calls, and refreshing both because one looks stale spends two requests to answer one question. A refresh also forces the saved filters past their cache — those re-query through GitHub's search, which is held for 20 seconds, so without that the one click you make because the list looks wrong would hand you back the same list. The button is out of action while its own section is in flight, since each saved filter is one more request against a search API capped at thirty a minute.
- The commit pane says more per row. The header is one line now — hash, parent, the branch, and what the commit cost as
+A ✎N −D— where it used to take four; the author's line reads as 7 hours ago with the full date a tooltip away. Each file row carries its own+N −Mat the right, puts the name before the folder, and offers its actions on hover — history, open on remote, copy path — instead of only in a menu. Compare against the working tree is a button beside the author, and the AI action is an icon with a menu rather than a full-width bar: a proposal the model makes is not the purpose of the pane. - The commit pane's remaining reference rows. The file list has a real bar now — FILES CHANGED with its count, Path/Tree as icons, one click to copy the file list — and the filter is a field that is always there, the staging pane's rule applied to the commit's list. Explain takes guidance: a line under the message where you say what to focus on; a guided answer is computed fresh and never cached, because it answers your question, not the commit. The message has a copy control for subject and body; a message that references no issue says No autolinks found rather than showing nothing; and a commit of yours is signed You, your name a tooltip away.
- The pull requests and the issues are in the left panel, as two sections among the branches and the tags, with their counts. Each row says what the forge said: a state icon (a draft is greyed), the number and the title, then the author, the age and the comment count. Right-click an issue to start its branch — the same gesture, and the same single row component, as the GitHub tab, so the two lists can no longer drift apart. Rest on a row and a hover card opens over the graph: the description rendered as markdown (headings, task lists, code), the status, the full labels, assignees and reporter — clamped to the window and never scrolling: the card is a preview, what does not fit fades out, and clicking the card opens the issue itself. Clicking a pull request now opens its own detail in the centre, the issue detail's sibling: the cost line (N commits · M files · +A −D),
head → base, mergeability read and reported — the checks of the head ref (passed / failed / pending), GitHub's own conflict verdict, including still computing when that is the truth, and whether you may merge at all, your permission on the repository stated with the rest: a read-only account is told so rather than offered a button that would answer 403 — the description and the comments editable through the same endpoints an issue uses. And when both hold — checks green, no conflicts — a merge button is there (merge, squash or rebase, your pick), the pane flipping to Merged on GitHub's yes and showing GitHub's own message on its no. The button always says the method you chose. When rules block the request, the pane speaks github.com's language: a warning block says Merging is blocked and why — at least one approving review is awaited, changes have been requested — and where you stand against that rule — You can bypass this rule and merge anyway when the account is one of its bypass actors, You cannot bypass this rule when it is not. That is asked of the rulesets protecting the base branch, each of which carries GitHub's own verdict for the signed-in account, rather than guessed from a permission level: a ruleset that grants the bypass to a team, an app or a single role is read correctly, and the common shape — a broad ruleset nobody bypasses beside a narrow one you do — answers yes. Your standing is said before the click rather than discovered by it. When you can, the method button's click reveals a separate danger button, Bypass Rules and Merge: consent is its own act, on its own button, gone on a click elsewhere. Without the rights, the method button is disabled and there is no bypass button at all — the reviews are what is awaited. The merge goes through GraphQL, where a bypass actor's rights actually apply — the REST merge refuses them. Once merged (or closed), the pane offers Delete Work Branches — the remote head branch and the local one, in one action, each outcome reported where the click happened. Deleting the branch you are standing on — the commonest case, you just merged its request — steps onto the base branch first, the reference clients' answer; any other refusal stays git's own message. When nothing holds, there is no button at all: a disabled button explaining itself is still a button that cannot be pressed. And on the graph itself, a branch that is an open request's head carries a #N chip beside its branch chip — a lookup into the already-loaded PR list, never a search per row — and clicking it opens that detail. The sections start folded: the graph is the point of the window, and the counts on their headers already say what is behind. They are also the only door now: the desktop's GitHub tab and the icon rail that opened it are gone — the sections and the issue detail are the integration. Inside, each section is named groups, not one flat list: My Pull Requests, Assigned To Me, Awaiting My Review and All Pull Requests for the first — the three account groups exist only when someone is signed in, and an empty one still shows its 0, which is what says the query ran — and All Open Issues for the second. The asymmetry is the point: a pull request is something you are personally on the hook for, an issue is not. Above the groups, each section carries a search box: a display lens like the staging filter, narrowing what is already shown without re-querying, the counts still counting everything — the lists themselves are always the open repository's. Beside it, saved filters: the section header's ⧩ opens an editor — a name, a query in that section's vocabulary, live validation that names the offending token, the syntax beside the field and the full reference a link away — and each saved filter becomes one more named group that re-queries through GitHub's search, its count the search's own total with a +N more on GitHub tail when the page is shorter than the count. A refused query costs that filter, never the section, and filters are kept per repository. In the VS Code panel they are two more views on the activity rail, which is how that rail already works. A repository with no GitHub — or nothing to authenticate with — shows no section at all, rather than two empty ones. - Clicking an issue opens it in the app — a third layout, defined precisely: the toolbar and the left panel stay (the list is what is being navigated, which is why this is not a tab), the graph is replaced by the issue, and the commit panel is not shown, because there is no commit in this context. The detail carries the issue's own side column — status, assignees, labels, and Create a Branch for This Issue, the same call the row's context menu makes. The body renders as markdown, the comments are there with a box to add one, and everything edits in place: click the title to rename it, the pencil beside a field for the rest — close and reopen, assignees from the people who can be assigned, labels from the repository's own. Every write goes through one PATCH and applies only after GitHub said yes; a refusal is written where the edit happened. Closing the detail restores the graph and the commit panel. The browser is still one click away, from the detail's own header.
- Git Vertex talks to a GitHub Enterprise Server instance. Settings › GitHub takes the instance's host and a token for it, and every call about a repository on that host goes there instead of github.com — pull requests, issues,
#123cards, creating a pull request, sharing a patch. It is the same API on your own domain, so nothing else changes.⚠️ A host is only treated as GitHub once you have named it: nothing in a hostname says whether a self-hosted forge is GitHub or something else. And a token is only ever sent to the host it was entered for — your github.com credential does not go to your company's server, nor the other way round. - A file's diff and the settings open in a tab too. A file's diff took the middle of the repo view with the sidebar and the commit panel still around it; the settings took the whole window without being a tab, so nothing could be kept open beside them. Each is a tab now, full width, and asking for the same one again returns to it. A file staged and the same file unstaged are two tabs, because reading one against the other is the point. Settings also stays on the section you were reading: leaving the tab is a click elsewhere, not a decision to close it, and coming back to the top of the list every time was the tab forgetting what you were doing.
- Comparisons, a file's history and a stash's contents open in a tab, not in a window over the graph. The tab is the whole window — no sidebar, no commit panel, no toolbar around it, because none of them acts on what you are looking at. They have a name in the tab bar, they stay put when you click elsewhere, you can keep several open, and opening the same one twice takes you back to the tab you already have. The tab is named after what it shows, and follows it: change the two refs and the name changes with them.
- A commit in a comparison opens on its own. The two lists were inert — you could read the hashes and nothing else. Clicking one shows that commit's diff in the pane beside it, and a click back returns to the whole comparison.
- The list of changed files is as tall as you want it, everywhere a diff is shown. It was 120 pixels whatever it held — three rows, the rest behind a scrollbar inside a box that could not be resized, which is no way to choose what to read out of twenty files. It opens at 200 now, drags to 640, and remembers. The comparison's own split between the commit lists and the diff drags too.
- A comparison says which way round it is reading. The two selectors carry their roles — from and what — and a line under them spells the comparison out: "what this branch has done since it left main; main itself is not described, it is the starting point". Two anonymous dropdowns with a
…between them left the direction to be guessed, and the direction is the whole thing: the commit lists show both sides, the diff only ever describes one. - A comparison with nothing in it says why. "Since they diverged" reports what the target did, so comparing a branch that is ahead against a
mainthat has not moved is legitimately empty — while the list beside it shows commits, which reads as a failure. It now names the side that has done nothing, says where the commits actually are, and offers to compare the other way round. - The comparison view is the full one now — the same one the VS Code panel has: which question it answers (since they diverged / end to end), the working tree as a target, and the comparisons you have already looked at. The app had three smaller windows instead, each answering a part of it.
- A file's history is in the app at last. The button in a commit's file list opened a modal list of commits with no diff; it now opens the history view, with the diff and the blame beside it.
- Hide anything from the graph, not just a branch. A tag, a whole remote and the stash can now be taken out of the view the way a branch already could — right-click the row and Hide from Graph. Each section also hides in one go: LOCAL, REMOTE, TAGS, REMOTES and STASH carry Hide All from Graph and Show All in Graph on their header, and hiding a whole family keeps hiding what arrives later, so a branch pushed afterwards does not reappear on its own.
- A section says how much of it the graph is not showing. Its header carries a count of what is hidden, and clicking that count brings everything in the section back. Hiding used to leave nothing on screen to say the graph was filtered.
- Restore a file to the version in a commit. Right-click it in a commit's file list. It asks first, then leaves the change unstaged — so what you brought back is a diff you can read before you keep it, rather than something already staged. A file deleted since comes back on disk.
- Start a branch from an issue. Right-click an issue in the sidebar's GITHUB ISSUES section (or use its ⋮ menu): it suggests
123-the-issue-title, you edit it or accept it, and the branch is created, checked out and linked to that issue — so the branch shows its issue number from then on. Linking a branch to an issue already existed; this is the direction you actually reach for.
Changed
- A section header shows its count, or its controls — never both. The
+and the refresh appeared on hover but sat beside the count, so the corner carried two things competing for the same glance. They swap now, in place: the two share one slot, stacked, so nothing changes width and nothing slides — the count simply fades as the controls arrive, and back when the pointer leaves. Both stay in the tree throughout, so Tab still reaches the buttons, and a refresh already in flight holds its place whatever the pointer is doing. On a touch screen, where there is no hover to wait for, everything is simply shown. - The drawer is pulled open, and pushed shut the same way. Its left edge stays pinned to the panel's edge and the box grows out to the right, then folds back the same way — a drawer whose back stays put while its front travels, rather than a panel flying in from off-screen — and the closing animation finishes before it goes, which means it has to be started by something. That something is its own close button, or Escape. A click outside no longer closes it, and the control that opened it no longer closes it either: the list behind a drawer is meant to be read while the query is written, and a stray click there used to take a half-written query with it.
- The drawer is wider, and it arrives. It was as wide as the form's minimum; it now takes the room a query and its reference need to be read rather than wrapped, still clamped to what is left of the window.
- The saved-filter editor is a drawer out of the left panel. It used to open inline, inside the section, pushing the groups below it down — a form with a name, a query, live validation and its syntax help, wedged into a 280-pixel column where the help is the first thing squeezed out. It comes out of the panel's right edge now, full height, with the list it is about still visible beside it. It reads as a real form: a header naming what it will create, labels above their fields, the query's verdict inside the field it is about, and one primary action. Below it, the section's vocabulary is documented rather than linked away — one line per qualifier, the name a person would use and the syntax with its value spelled out (
author:user_name,review:approved or review:changes_requested), each one a button that writes itself into the query. Listing the keys alone said a token exists without saying what may follow the colon, which is the half that makes a query writable. Escape and a click outside close it, and neither loses your work: a half-written query is kept and restored the next time you open it, until you create the filter or press Cancel. - A folded GitHub section is one row. The search box and the saved-filter editor were controls of the list kept outside the list: the search sat between the header and the groups, and the ⧩ was a button on the header itself. Both are inside the part that folds now, the filter opening from beside the field it belongs with. Folding a section also drops whatever was typed in its search — a filter still applied but no longer visible is how a section comes back looking empty while its count says otherwise.
- The
+and the refresh appear on hover. Two buttons drawn at rest on every header is a row of controls competing with the count, which is the one thing there that has to read at a glance. They stay focusable and appear on keyboard focus, they keep their space reserved so no title reflows as the pointer crosses, they are always visible on a touch screen — and a refresh already in flight stays visible, since its disabled state is the only feedback it has. - The signature shield marks only what cannot be vouched for. It used to sit on every signed commit, which on a repository that signs is a mark on every row saying the same thing — no information, and the only strongly coloured thing in a row nobody scans for signatures. Nothing is drawn now for a good signature, one of unknown validity, or an unsigned commit; a badge stays for one that is bad, unverifiable, expired or revoked. The commit still carries its signature either way: this changed what the list draws, not what the app knows.
- Watching a request costs less the longer it takes. A check suite that runs for twenty minutes was asked about every five seconds for all of it. The wait now stretches by half each time nothing has changed, up to the same twenty seconds everything else uses — so the first minute, the one somebody actually watches, still answers in seconds, and a long build settles into the background. Anything moving puts it straight back to five. The request's checks and its comments are also fetched together rather than one after the other, which is one round trip of waiting saved per look.
- The groups inside the GitHub sections start folded. My Pull Requests, Assigned To Me, Awaiting My Review, All Pull Requests, All Open Issues and every saved filter all opened at once, so unfolding a section buried the branches it was meant to be read beside. They arrive closed now, like the sections themselves — the count on each header is what makes a folded group informative rather than hidden.
- The git icons read at the size they are actually drawn. A branch, a merge, a rebase, a pull request, a worktree — the nodes in each were open rings, and an open ring of that radius has a 0.12-pixel hole at the eleven pixels the sidebar draws them at. The ring closed anyway, so it was never a ring; it was a soft blob with a fuzzy edge, which is why the marks looked thin and hard to tell apart. The nodes are filled discs now, slightly larger, and the nine marks that carry one are floored at 13 pixels wherever they are drawn — a 2.4-unit disc is 2.2 device pixels at eleven, which is under the three a disc needs to read as a shape rather than a smudge. The floor is in the icon component, beside the one that already exists for the two most detailed marks, so it holds wherever one is drawn next and cannot catch the chevrons that are deliberately eight. The set loses one distinction — an arrival point used to be filled where a source was open — but that distinction was invisible below roughly 24 pixels, which is where none of these are drawn.
- The graph's branch scope moved to the left panel, out of the toolbar. Show all branches was a button in the top bar — the only part of the system that decides what the graph draws to live anywhere but the panel, where hiding a ref, hiding a whole family and soloing a branch have always been. It is now All Branches in the Graph on the LOCAL header's menu, ticked when it is on, ruled off from the hide entries above it because it is not one of them: hiding takes refs away from what the graph collects, this decides whether it collects them at all.
- No more emoji in the interface. The 🗑, 🔀, 🔗, ⛙, ✏, ⚡ and the arrows that led a menu row are gone, and so is the ✓ that every success message carried — the chip that reports an action draws its own icon now, so that was a second tick beside the first. A convention test already forbade this and had been passing for a year on half the catalogue: it read only the entries whose value is a plain string, and every label taking an argument is a function. It reads both now, and caught five more the moment it could see them.
- Opening a pull request is a right-click action, not a toolbar button. The PR button in the top bar is gone. It could only ever propose the one request the checked-out branch would open, while the right-click row on any branch says which way the request runs and against what — and is the only one of the two that can say a request is already open. One place, one answer.
- The typecheck gate covers the two places it did not.
npm run typecheckread the renderer and nothing else, sosrc/mainandsrc/preloadwere checked only by whichever files a test happened to import — and the panel's own entry files were excluded from every program, because esbuild bundles them and esbuild does not resolve names. An identifier that does not exist bundled cleanly and threw at mount, which is howI is not definedreached a shipped panel and took three versions to surface. Both are gated now, and both had a backlog to clear first: 4 findings in the main process, 130 in the panel. The panel's bridge is the reason for most of them — it had been declaredany, which is a hole the compiler cannot see through, and is now the shared renderer's own interface plus the methods only the extension host answers. - The chip that confirms an action was redesigned, and moved. It used to appear at the bottom right — exactly where the Commit button sits, so the confirmation of what you just did covered the control you reach for next. The stack is at the bottom centre now, which also fits a panel far narrower than a window. It is a chip rather than a tinted card: one line on the ordinary surface with a real icon in the colour of the outcome, instead of a card washed in that colour, which made every success shout as loudly as every failure. An error no longer expires — a success is over once read and goes on a timer, but a failure waits to be dismissed and carries git's own message. Repeats collapse: the same result ten times running is one chip and a count, and the stack is capped, so a loop over many files cannot bury the window it is reporting on. It is also announced to screen readers for the first time — the stack is a live region, errors interrupt, the rest waits its turn. And the staging area speaks at last: staging, unstaging and discarding a file each confirm, where all three used to say nothing — discarding worst of all, being destructive and silent. The rule behind those calls is written down once now: a mutating action confirms, navigation does not.
- The issue a branch is working on no longer has to be a GitHub number. Associate issue still lists this repository's GitHub issues, but the box under them now takes any reference you type —
PROJ-421,ABC-9, a closed issue the list does not carry. If it matches one of the reference patterns from Settings › GitHub, the link opens where that pattern points; if it matches nothing, the reference is still kept and shown on the branch. Branch names follow: a branch created fromPROJ-421is suggested asPROJ-421-the-title, with the key's case left alone because a key is a name, not prose.
Fixed
-
A merge commit no longer wears a warning because you have not imported GitHub's key. The signature badge was narrowed to the states where something is wrong, and cannot be checked was counted among them — but that state says nothing about the commit: it means the local keyring has no public key for whoever signed it. Every merge GitHub makes is signed with its own key, which almost nobody imports, so the badge came back on every merge commit in the graph. It is now drawn only for a signature that is bad, expired, or made with an expired or revoked key.
-
A filter that already exists says so. Nothing stopped the same filter being saved twice, so a section could end up with two rows called closed, both showing the same count, told apart by nothing. A name already used in that section is now named and refuses to save; a query already run under another name is pointed out without refusing — two views on the same search can be deliberate. Editing a filter does not clash with itself.
-
A merged pull request stops offering what is already done. Its mergeability block is gone — the question can this be merged has an answer, and the header states it — and its checks are no longer fetched for a block nobody will see. The branch cleanup is offered only while there is something to clean: the pane looks before it offers, names which side is left (Delete Local Branch, Delete Remote Branch) when only one is, and touches only that side. A request closed without merging keeps its mergeability, deliberately: it can be reopened, and its checks are still the truth about its head. And git's push refusal for a branch that is simply not there now reads as not found rather than two lines of error.
-
A request you are watching keeps being watched. 2 of 4 checks pending could sit there unchanged for ever: the pane asked once and then stopped, because its timer was re-armed by its own writes — and a poll that finds nothing new writes nothing, which is precisely what a request still running its checks answers. The loop owns its timer now, so nothing about a render can end it.
-
The checks refresh with the rest of the pane. Checks: unknown could stick on a request whose checks were green and everything else around it updating. Two causes, both ours: the pane cancelled its own checks request every time it updated the request itself, and it threw the answer away whenever GitHub said not modified — which is the routine answer for something the pane has never seen, because the cache that says it outlives the pane.
-
A request that fails to open now retries instead of dying. A transient network failure at the moment you opened a pull request replaced the whole pane with a bare fetch failed, and nothing ever cleared it — the only way back was to close it and open it again. A pane with nothing loaded is treated as still waiting, so it asks again within seconds and the error goes the moment an attempt succeeds. A refresh that fails while a request is already on screen changes nothing at all.
-
Checks belong to the commit they ran on. Push to a request you have open and its previous head's result stayed on screen — 4 checks passed, over a commit those checks had never seen — with the merge button offered on it. GitHub then refuses the merge, because the checks it requires have not run on what you just pushed. The moment the head moves, the checks read as unknown and the button goes, until the new commit has an answer of its own.
-
A pull request you are watching finishes deciding in front of you. Open one just after a push and GitHub answers mergeable: null — it computes the merge asynchronously — while its checks are still running. The pane asked once, so it stayed on Mergeability still computing… for as long as it was left open, and the merge button, which needs a settled answer, never appeared however green the rows above it went. It asks again every few seconds while anything is still undecided, and stops the moment nothing is — so waiting in front of it is now the thing it is good at. It never asks while you are editing the title or the description, because a refresh there would replace the text under your cursor.
-
A pull request you open from Git Vertex appears in the sidebar straight away. The composer told the app about the push it had just made and never about the request it had just created, so the one write the app is certain about was the one it had to be informed of by a refresh button. It now reloads the list that should contain it, the moment GitHub says the request exists.
-
The left panel has one indent scale. The groups inside PULL REQUESTS and GITHUB ISSUES — My Pull Requests, All Open Issues, every saved filter — sat 16 pixels left of the branch tree beside them and stood five to seven pixels shorter, because the two families had been indented on two different systems: the tree carried its numbers in the component, the groups took theirs from a rule nobody had compared to it. A group now lands exactly where a top-level folder does, its rows where a branch one level in does, and every row in the column — folder, branch, group, header — is the same 26 pixels tall. The section's search box shares that left edge too, which it did only by accident before.
-
Tidying up after a merge no longer leaves you standing on a stale trunk. Delete Work Branches steps onto the base branch when the branch it is deleting is the one you are on — and it did nothing else, so the tidy-up ended with you on a
mainthat did not contain the merge you had just made, with nothing on screen saying so until you noticed the ahead/behind counter. The base is now fast-forwarded — fetch, then a merge that names the upstream ref and refuses to write a commit — and the report says whether that worked. Naming the ref rather than reaching forpull --ff-onlyis the point: a repository that fetches several refs makes pull refuse before it reaches the fast-forward, which would have been reported here as a diverged base when nothing had diverged. A base that has diverged — your own commits, a rebase — is left exactly as it is and reported as still behind: reconciling someone's trunk unasked is worse than the stale state this fixes. The action also names what else is standing on the merged commit — another local branch at the same tip, which it never knew about because it only ever deletes the request's own ref by name. It names them and leaves them: a cleanup that removes branches nobody named would be a worse bug than the one it fixes. -
The pull request row says which way the request runs, and promises a push only when there is one to make. It now reads Start a pull request to origin/main from origin/your-branch when the remote already has the branch's tip, and Push your-branch and start a pull request to origin/main when it does not — whether that is because the branch has never been pushed or because the tip is still only local. Both ends are named on your own branch's row too, where the base used to stay implicit: the row you use most said the least about what it was going to do. The head reads as its remote ref only when nothing is left to push, because what a push moves is the local branch.
-
A branch already on the remote no longer offers to push it again. The right-click row read Push "x" and start a Pull Request whether or not the branch had ever left the machine — so on one you had just pushed it promised work that would not happen, and read as though the push had not registered. It says Start a Pull Request from "x" in that case. Which of the two you get was already decided when the row was built; only the label was not asking.
-
Three panel commands that did nothing now work, and an avatar lookup that never ran does too. All four were found the same way: by type-checking the two places nothing type-checked. Add Worktree in the panel's sidebar, and the Browse buttons of Clone and Init, each read
.pathoff the folder picker's answer — but the extension host returned the bare path string instead of the{ path }the desktop returns, so the check was always false and the three did nothing at all. Checkout on a tag in the panel's sidebar was never handed its handler, so it wasundefinedat the click. And on the desktop, the avatar lookup that resolves your own commits — including unpushed ones, and private e-mail addresses — read a name that does not exist where it stood: it threw on every call, into acatchthat swallowed it whole and cleared the memo so the next call could fail the same way. It had never once worked; avatars quietly fell through to a slower path. -
A branch whose pull request is already open no longer offers to start one. Right-clicking it still read Push and start a Pull Request, on a branch whose request was open two panels away — the same list that puts the
#Nchip beside it. The row is gone in that case, on both surfaces: the same head into the same base is the one thing GitHub refuses outright, so the menu was offering an action the API would have rejected. The same head into a different base is still offered, because a stacked request is a different request. With no GitHub remote — or in the moment before the list has arrived — the row behaves exactly as it did. -
The sort button over a commit's files is gone, because it never sorted. It had shipped with no handler at all — a control that looked like a feature and did nothing on any surface. The files bar now holds only controls that do something, and a test keeps it that way.
-
The settings open with no repository. The gear, the profile chip and
⌘,did nothing at all until a repository was open — no error, no message, just a click that went nowhere. Making the settings a tab had tied them to a repository, which every other tab genuinely is about and the settings are not. -
GitHub features find your repository when its name has a dot in it — or when the remote is an SSH URL with a port. Everything that talks to GitHub starts by reading
owner/repooff the remote, and the pattern doing that stopped at the first dot, somy.appwas read asmy; a remote likessh://git@github.com:22/owner/repo.githanded over the port as the owner. Git Vertex then asked GitHub about a repository that does not exist: the pull request and issue lists came back empty with nothing to say why,#123references never resolved, and the Launchpad could not name the repository a row belonged to. It now reads the remote with the same parser that builds the links, in the one place all four used to do it by hand — and a remote that is not on github.com is reported as such instead of being guessed at. -
Hiding a branch no longer takes your stashes with it. Hiding handed git the list of branches that remained, and an explicit list of refs replaces
--all— so every commit that only a tag or the stash reached vanished along with the branch you hid. What is hidden is now named and excluded, git keeps deciding what is reachable, and a commit something visible still reaches keeps its place. -
The stash hides as a group, and says so. The entries of
git stash listare the reflog of a single ref, so git can take all of them out of the graph or none — the action lives on the section rather than promising, per row, something git cannot do. -
A change to what the graph shows now always reaches the graph. A reload asked for while another one was running was dropped and never retried — invisible for a background refresh, since the next file-watcher event covers it, but not for hiding a branch: there is no next event, so the graph kept showing what you had just hidden until something else in the repository changed. Found by driving the real app; no test saw it.
-
The app stops piling up file-watcher listeners. Subscribing to repository changes handed the callback straight to Electron's bridge, and unsubscribing handed it back — but the bridge builds a new proxy for the same function every time it crosses, so the removal never matched what the subscription had registered. Every hide, solo or branch switch added a listener and removed none, and the ones that fired were an accumulation of stale ones: git was run several times for a single change, with filters you had already moved on from. Subscribing now hands back its own unsubscribe, which is the pattern every other event in the app already used.