Skip to content

v1.34.0

Choose a tag to compare

@VictorQuilgars VictorQuilgars released this 06 Sep 20:58
6ff9670

Added

  • A session per repository. The main process kept one git service and replaced it whenever a tab was switched; every request was answered by "the repository of the moment", so an answer that came back after a switch — a log of five hundred commits, a diff of a large file, a comparison — was written into whatever repository was open by then. Each open tab has its own session now: its service, its watchers, its auto-fetch timer, kept for as long as the tab is. Every call names the repository it is about, and is answered by that one or by none — never by another's service standing in. What follows from that, for anyone with more than one repository open: switching tabs is instant — the tab comes back as it was, and what changed while it was hidden arrives with a silent refresh instead of a spinner over an empty graph; a repository in a background tab stays current — a commit made from a terminal, or an auto-fetch, refreshes it where it waits, so its tab shows the present when reopened; and an operation started on one tab finishes on that repository whatever tab is shown when it does. Eight repositories stay open behind the one shown; beyond that, the least recently used lets go of its watchers. And a message about a repository that is not the one shown says which one it is about — alpha · Pushed — while one about the shown repository says nothing more.
  • What the model sees of a diff — per feature, and never a lie about it. Every prompt cut the same way: the first 6,000 characters. On a branch touching seven files that is the first four IN FULL, in git's alphabetical order, and nothing at all of the other three — the model was not shown less detail, it was shown a different change, and it described that one with the confidence of a whole reading. The cut is by file now, and whatever is cut the map stays whole: every prompt names every file that changed and by how much, then shares the budget across their bodies with a give-back, so one 26,000-character file cannot be the only one anybody reads. Each cut lands on a line boundary, says how many lines it dropped and of which file, and a file too small a share to carry a line of the change says it was not read rather than showing a header pretending to be content. Three levels, one control per feature in Settings › AI Assistant — Summary (the map alone, no bodies), Standard (the default, what everyone already had, now shared fairly), Complete (the whole diff, no ceiling) — for the four features that read one: commit message, explanations, pull request, split into commits. At Summary the model is told what it may not claim — it has the map, so it describes the shape of the change and says the commit messages are its only evidence, rather than answering a question about behaviour it has not read. Without that instruction it answered anyway: on a branch whose retry() had just stopped propagating its error, it called the change "safely managing execution failures", having seen src/retry.js +3 −6 and nothing else. Measured on one real branch: at Summary the model invented a retry behaviour the diff contradicts; at Standard it named the actual regression, in a small file the old cut dropped entirely. (#185)
  • Reply length, per feature. Every AI call carries a ceiling — 512 tokens for a commit message, 8192 for a conflict resolution — and until now they were invisible constants nobody could change. They are one control each in Settings › AI Assistant: Standard, Generous, Maximum. (#183)
  • The AI reads more than a commit. A branch, a stash and the uncommitted work can all be explained now — the same reading the commit panel has offered since v1.10, on the three diffs that had nowhere to ask for it. The branch is read against a base worked out rather than assumed: the trunk the remote declares, the remote's copy of it over a stale local one, and its own upstream when the branch asked about is the trunk. Every answer opens in the composers' drawer, breathes while it waits, writes itself in word by word, and takes a focus — only the migration — for another go. (#70)
  • A changelog, from the branch menu — written once, then kept. What a branch adds over its base, grouped under ### Added / ### Changed / ### Fixed — the sections this file uses — with the empty ones forbidden rather than tolerated. It is remembered: reopening the drawer costs nothing and shows what was written, with when. When the branch has moved on it says so — 3 commits since — this changelog does not cover them — and Update extends the text rather than rewriting it, so the bullets you already reviewed stay word for word. Insert into changelog puts it where changelogs live: the repository's own file, under its Unreleased section, merged into the headings already there rather than as a second ### Added, never adding the same bullet twice and never removing a line. No changelog file yet ⇒ one is written. And it decides nothing on your behalf: it shows you what would go in, before anything does — the exact lines, the ones already there word for word that it will skip, what the target sections already say so you can compare, and a warning when the file has uncommitted changes (a changelog is written once and inserted later, into a file that has moved on). It follows your changelog's shape rather than a template's. The section for unreleased work is found under any of the names one goes by — Unreleased, [Unreleased], Next, Upcoming, master, À paraître and a dozen more — and the entry's own headings are re-levelled to sit inside it, so a file whose releases are # gets ## groups rather than ### ones. When a file keeps no such section at all, nothing is invented: it asks where these lines go, offering the file's own headings and the option of a new section — which it then writes at the file's own heading level. Regenerating and inserting again replaces what it wrote rather than doubling it — the model rewords everything on a second pass, so no verbatim check would ever catch that; the lines of the previous insert are remembered and taken back out. One of them edited by hand in the meantime is left exactly where it is, and the preview says so: written once does not mean owned for ever. And it knows what each changelog is about. One in cli/ describes the CLI: if the branch changed nothing under cli/, there is no entry to write there and it says so — no preference can make an empty entry true. If the branch touched both that directory and elsewhere, it asks what the entry should cover, everything the branch carries or only what changed under cli/, and the second generates a different entry from that directory's commits alone. The answer is kept in the repository's own git config beside gitvertex.defaultRemote, and restated in the same question every time with the alternative one click away: a remembered answer that is applied silently is worse than a setting, because a setting can at least be found. It also asks which file, and finds them all: every tracked changelog at any depth, so a repository shipping four products sees its four rather than only the one at the root. All N of them is one of the answers — one change that touches the app and the extension belongs in both, and what was written is remembered per file, so regenerating replaces its own lines in each. It stops on a branch that has been merged or deleted since, where those bullets are presumably already in there. (#70)
  • A commit composer. The uncommitted work, cut into a sequence of atomic commits and reviewed before any of it is history: edit a message, move a file between commits, reorder them, drop one, add one. Whole files, staged or not — and the drawer says so. Every path the model names is checked against the tree: an invented one is dropped and counted, a file it placed nowhere is shown rather than quietly left behind, and a failure halfway says how many commits were made and that the rest is still on disk. (#70)
  • The VS Code panel gets all of it. The readings the desktop shows in a drawer open as editor tabs there — the panel is narrower than the answers' own paragraphs, and this extension already opens the staging editor, the rebase planner and a comparison that way. Same component, same calls, a different frame: explain a branch, a stash or the uncommitted work, write a changelog (insert included, with VS Code's own prompts for the two questions and the preview), and cut the working tree into commits. The activity rail's robot head is now the AI view and holds all of it — what the model has written for this repository, and the agents running in it, which were a second robot head of their own. (#70)
  • A search box above the settings. Nine sections and their long lists, and the way to one control was to walk them. The box matches a section by its name or by any of its text — gpg finds Identity, autolink finds GitHub — and moves to the first match. The theme picker shows the current theme and a handful, with the other built-in ones a click away, rather than thirty-two tiles before the options below them.
  • A view that fails says so in its own pane. A render error in the graph or the details used to take the whole window: a white page, the tabs and the sidebar gone with the pane that failed. Each of the two is fenced now — the one that failed names its error and offers to try again, the rest keeps working.
  • Working changes, as a destination. The way to the staging area was a //WIP row at the top of the graph, there only while the tree was dirty and named for nobody new. A row above the sidebar's lists says Working changes, counts them, is always there, and opens them.
  • Which repository, which branch, and whether that branch is heading for a fight — all three on the toolbar. The repository selector left the top 47px of the left panel (a full-width header above every list, in the one place where height is scarce) for the toolbar's left edge, which was empty. Beside it, a branch picker with a filter, local branches first, remote ones below — picking one goes there, creating the tracking branch if that is what it takes. And beside that, the conflict outlook: a quiet badge that says No conflicts detected against origin/main, or counts the files that would conflict, measured with merge-tree against the same base the AI features read a branch against. It never blocks anything, it fails open — an error means "we do not know", never a warning — and clicking it says what it means: what was simulated, against what, which files would clash, and that nothing was merged.
  • A merged branch is not a lost one. A kept reading — an explanation or a changelog — was struck through the moment its branch stopped resolving, which meant it was struck through on the day the branch merged and was pruned: the ordinary, successful end of a branch, marked as a loss. The row asked whether the name still existed; it asks the commits now. A branch whose work landed says in main and keeps its ink; only a branch force-pushed away — or a stash actually dropped, which the stash list is asked about rather than rev-parse, since a dropped stash's commit still resolves for hours — is struck through. And every row that still has commits to point at wears a chip with their count: click it and the graph highlights exactly what that reading covered, which is the answer to "the branch is gone, so what was this about?". A branch reading now remembers the base it was read against, so the range survives the branch; readings kept before that point at their tip. Forget what is gone sweeps up the lost ones only. (#70)
  • A second stack in the panel: everything the model has written here. A strip above the repository list chooses between List — everything it has always shown — and AI. That stack holds the changelogs written for this repository and every explanation kept: a branch, a stash, the uncommitted work, and the commit explanations the panel has been storing since v1.10 that nothing ever listed. Each row says what it covers and when, wears +3 when its subject has moved on since, and strikes itself through when what it described is gone — a dropped stash, a deleted branch. A row reopens the reading without asking the model again; right-clicking forgets it. Generating anything brings the stack into view with the new entry already in it, rather than leaving it to be discovered. The VS Code panel gets the same view on its rail. (#70)
  • An AI row now looks like one. Every entry that reaches a model wears the mark and is written in the ink this app reserves for what a model proposes, and the submenu the branch menu's AI row opens is outlined in the dashes that go with it. "AI" as a bare word was one more verb in a list of twenty, and nobody found it; the labels drop their now-redundant "(AI)" suffix.
  • Two more features in Settings › AI Assistant — changelog generation and splitting into commits — each with its own model and its own standing instructions, like the seven before them.
  • Unified AI engine that powers branch, stash, working‑tree and commit explanations, guaranteeing consistent model settings and behavior across desktop and extension host.
  • New UI entries to invoke AI: an AI submenu on the branch menu, a row on the stash menu, and a caret button beside the staging pane’s generate button, all opening a single drawer that handles the four AI queries.
  • AI‑generated commit‑message composer that lets you edit the message, move files between commits, reorder or drop commits, and apply them one‑by‑one.
  • Settings now include two separate AI assistants, each with its own model and instructions; the “Explain” command now refers to four diffs instead of one.
  • AI‑generated changelog entries are stored per branch (with source commit SHAs), can be updated, and are automatically inserted into the repository’s CHANGELOG.md under the Unreleased section.
  • Sidebar gains a second AI stack that lists generated changelogs and their associated explanations, shows how many commits have been added since generation, and lets you reopen or forget entries.
  • Menu rows that invoke a model are now visually marked as AI, and the “(AI)” suffix has been removed from other labels.
  • Copy‑to‑clipboard for AI answers no longer waits for the writing animation.

Changed

  • VS Code: commit without enlarging the bottom panel. Short, sufficiently wide panels arrange the graph, changed files and commit form side by side, keeping the extension's existing styling. Both column dividers support dragging and keyboard resizing, and remember compact widths separately from the taller layout. File lists scroll independently, the commit action stays below the message, secondary actions move into menus, and Hide graph gives the files and form more room.
  • The keyboard reaches the menus, the tabs and the rebase. A context menu of eighteen verbs could only be walked with the mouse: arrows move over its enabled rows now, Right opens a submenu on its first entry, Left comes back, Enter acts, Escape leaves, and the menu holds the focus while it is up — so the graph's own arrow keys do not move the selection under it — and gives it back on close. The tab strip is a tab list: arrows switch tabs, Home and End go to the ends, Delete closes one, and only the active tab is a Tab stop. The rebase editor's rows have two arrows each, and Alt+arrows on a focused row, where before there was only drag-and-drop.
  • The rebase editor says what the plan comes to, before it runs. Rewrites 12 commits into 9: 2 folded into another, 1 dropped, 3 reworded — and, when the branch has an upstream and the plan reaches below what it has not yet received, 4 of them are already on the upstream: the branch will need a force push afterwards. Said at the plan, not discovered at the push.
  • The graph says how much history it holds. It loads the first 500 commits and nothing said so: a search that found nothing had searched those and looked like it had searched the repository. The status bar says 500 commits loaded and offers Load 500 more; the count is the scope of the graph and of its search.
  • A window too narrow for three panes gives the details the whole of it. At the minimum window size the sidebar and the details left the graph 50px, and the search field ran 238px past the right edge. When the graph would have no usable width left beside the two side panes — measured from the panes you actually have, so a right pane dragged wide counts as much as a narrow window — selecting a commit now opens its details across the centre, with Back to the graph above them. The toolbar folds Undo, Redo, Branch, Stash, Pop, Gitflow and Terminal into one menu below 1,250px and lets the search field take the room that is left.
  • The guidance field for an explanation is asked for, not offered. The commit pane gave every commit a full row for an optional instruction most explanations never need, above the files — the one place in the pane where height is scarce. Explain stands alone now, with a caret beside it that reveals the field; and the No autolinks found line only appears where a reference could have been, which is a repository with a GitHub remote or an autolink rule, rather than on every commit of every repository.
  • Tokens and API keys are held in the system's protected storage. They sat in settings.json in clear, readable by anything that can read your home folder. They go through the Keychain on macOS, DPAPI on Windows and the keyring on Linux now, and the file holds a ciphertext only this app on this machine opens — a file copied elsewhere yields no token. A file written by an earlier version reads as it is and is sealed at its next write. And the window no longer receives them: the settings page gets a mask where a secret is set, enough to say configured, and a mask sent back means leave it as it is. Every call that uses a token is made outside the window, so nothing there ever needed the value.
  • The window runs in Chromium's sandbox, and the built page does not allow eval. The preload uses the bridge and nothing else, which is exactly what a sandboxed preload may use; the 'unsafe-eval' the dev server needs for hot reload is stripped from the page that ships.
  • A link only opens a browser. Every link the app opens is built from data it did not write — a remote's URL, an issue body, a README — and it handed any of it to the operating system, which opens a file: link as a file and a custom scheme as whatever program registered it. Only http, https and mailto reach the OS now; anything else is refused and says so. The document also stopped declaring itself French to screen readers, which it had done since the interface became English.
  • The last four prompts left a copy per product. Commit message, reword, explain a commit and the pull request description lived in src/main/index.ts and in the extension's aiService.ts, and they had already drifted: one asked about "files/behaviors" with "no bullet lists", the other about "files and behaviours" with "no bullet list", and their refusals spelled analyse both ways. One copy now, in the shared module, with the answer-parsing that was duplicated alongside them — and a test that fails if either product grows a copy again. The pull request prompt also stops claiming the diff it carries is "its beginning": true of a prefix cut, false since the cut became per-file, and contradicted by what the diff itself says inside the same fence. (#185)
  • Base‑branch detection for AI queries now uses the remote’s declared trunk (and its upstream when appropriate) instead of assuming the current checkout, providing more accurate context.
  • AI answer validation now discards nonexistent paths, de‑duplicates files, and ensures each claimed file belongs to exactly one commit, improving reliability of the explanations.
  • The repository selector has been moved from the left‑panel header to the toolbar, freeing vertical space in the main panel.

Fixed

  • A search hit beyond the loaded graph is loaded and selected, or said to be out of reach. The graph holds a page of history and the extended search reads all of it, and the two did not meet: a commit found 800 commits back was a row the graph did not have, and the search looked like it had found nothing. Now the hit is located at its row in the log the graph loads — the same refs, the same order — the page grows to show every hit within the first 5,000 commits, the status bar counts what was loaded, and the nearest hit is selected and scrolled to. A hit further than that is reported with its position rather than dropped, and so is one on a branch the graph hides. (#188)
  • A click on the tab of a repository just opened could close it again. Opening a repository converted the home tab into the repository's tab only after the recents and the GitHub detection had been read; until then the tab showing the repository was still a home tab, and a click on it in that window cleared the repository. A few milliseconds for a hand, every fourth run for the end-to-end suite, which is how it was found. The tab is converted before anything is awaited.
  • Enter on Cancel no longer confirms. A confirmation listened for Enter on the whole document and answered yes whatever had the focus — Tab to Cancel, Enter, and the branch was deleted anyway. Enter now activates the button that has the focus and nothing else. A destructive question opens with the focus on Cancel, an ordinary one on Confirm, so the reflex keystroke still says yes where yes is cheap and does nothing where it is not. Tab stays inside the box, Escape closes it, and closing gives the focus back to what opened it. The box is announced as a dialog, named after its question.
  • A commit message survives a look at the history. The message lived in the form, and the form was unmounted the moment you selected a commit — a paragraph written, one click to check something, gone. A draft is kept per repository now, saved as you type, restored when you come back and after a restart, and cleared only by a commit that succeeded; a rejected one keeps it. The amend message is kept apart from the ordinary one, so ticking and unticking Amend no longer overwrites what you had written — and an amend armed for a commit that is no longer HEAD, after a restart or a commit from a terminal, disarms itself rather than rewriting the wrong commit with an old message.
  • Two comparison tabs no longer show the same comparison. Open A against the working tree, then B, then click A's tab: the tab switched, the comparison stayed B's, because one view served every tab and only read its references once. Every tab has its own view now, keeps the references and the axis you changed in it, and the picker shows a commit hash as a real choice — it used to read choose a reference over a comparison it had already made. Hashes are shown short wherever they are named.
  • One auto-fetch, not two, and one that reports. The interval in Settings armed a timer in the main process and a second one in the window, so every interval fetched twice — and the first, the one that survived a reload, swallowed its result. The main process owns the timer now and tells the window what it did: the status bar's fetched N min ago and the graph follow it, a slow remote cannot stack a second fetch on the first, and a failure is shown once per distinct message rather than never.
  • A diff git could not read no longer reads as "No differences". Every diff method answered { diff: '' } to a failure as well as to an empty change, and the views showed the same empty state for both — a bad object, a refused pathspec, a stash git could not show, all rendered as nothing changed. The reason travels now, the file diff and the comparison say it, and each has a Retry. Same contract in the VS Code panel.
  • Full context in a working-tree diff reaches git. The button asked for the whole file and the bridge dropped the number on the way: 3 lines of context and 100,000 produced the same 14-line diff. It is passed through now, staged or not.
  • A request the model refuses for size now says which control fixes it. It used to arrive as the provider's own words — Request too large for model x in organization org_01ksn… service tier on_demand on tokens per minute (TPM): Limit 8000, Requested 10013 — which names an organization id, a service tier and a unit nobody chose, and not the one thing that caused it. Worse, the retry loop then sent the same oversized request twice more, half a second apart: three certain failures where one was already certain. It is read now, across all five providers' wordings, and answered with the numbers in figures and one control — set What the model sees to Standard for this feature — the value below whatever it was sent at, or a plainer this needs a model with more room when it is already at the bottom and there is nothing left to send less of. A feature carrying no diff is never sent to a diff setting. The provider's own words are kept, minus its billing upsell. An ordinary rate limit is not read as this and keeps its retries — waiting is what fixes that one. (#185)
  • A model that thinks before it writes no longer fails silently. A reasoning model spends its budget reasoning, so a ceiling calibrated for one model can cut another off before it emits a single character — and the app called that "the model returned an empty response after 3 attempts", indistinguishable from a bad key. All three provider dialects say when they ran out of room (finish_reason: length, stop_reason: max_tokens, finishReason: MAX_TOKENS) and all three were thrown away. Now a truncated answer is retried with more room rather than with the same, what worked is kept in that feature's own control — a correction made on your behalf is one you can see and undo — and an answer that still will not fit says exactly that, naming the model and the number. (#183)
  • The ceilings lived in two copies, one per product, free to drift as the prompts once did. One table now, read by both. (#183)
  • Previously duplicated AI implementations could disagree on ref resolution and wording; they are now unified, eliminating inconsistent explanations.
  • The AI submenu label no longer shows the generic “(AI)” suffix, reducing confusion.
  • An end-to-end suite drives the built app. npm run e2e launches the build on a throwaway profile, connects over the Chrome DevTools Protocol and walks the journeys the audit reproduced by hand — the draft that survives a look at the history, two comparison tabs, Enter on Cancel, the window at 900 × 600 and 1,300 × 800 — the session ones: a call bound to a hidden repository, a commit made while hidden shown at once on return — and the daily one: a file staged and committed, the draft kept by a commit a hook refused and cleared by the one that succeeded. Five more walk what the audit's lot shipped without a journey: a history six hundred commits deep paged by Load more, the settings opened by Ctrl+, and narrowed by their search, the tab strip and a commit's menu walked from the keyboard into the rebase editor and its summary, an amend disarmed by a commit made from a terminal, and a draft that is still there after the app is closed and opened again — the runner can now restart the app on the same profile. Screenshots are compared with a reference per platform; on the CI runner it runs under xvfb, and the release gate runs it before a desktop release. No display needed, no new dependency beyond ws for Node 20.
  • The bridge is held to one signature. The renderer's declaration of window.gitAPI, the preload that forwards each call and the extension's service that answers it are three copies of every signature, and only the first was checked by the compiler — which is how a third parameter could be declared, passed and dropped. A test now fails when the declaration and the preload disagree on a parameter count, when the preload exposes something the renderer is not told about, and when the extension's service accepts fewer arguments than the preload sends.
  • The conflict-prediction tests say what they skipped. They need git merge-tree --merge-base, which is git 2.40, and on a machine whose PATH resolves to Apple's 2.39 they failed as though the code were wrong. They are skipped there, under a name that says which git was found.