Novedades

Todas las versiones, de la más reciente a la más antigua. Cada entrada cuenta qué estaba mal y por qué, no solo qué se ha movido: es la versión útil, y además escribirlo es lo que evita que el mismo fallo vuelva. La numeración es año.mes.release, así que 26.9.1 es la segunda versión de septiembre de 2026.

Se mantiene solo en inglés para que no se desincronice del CHANGELOG.md del repositorio.

26.9.2 2026-09-10

Stars on the branches you use, and submodules all the way down

Añadido

  • Star the branches you actually work on and they sort to the top. Hover a branch row and a hollow star appears in its own column; click it to star the branch, click it again to unstar. The context menu offers the same thing for anyone who looks there first. A starred row keeps its star lit and rises to the top of the level it sits on. Hoisting is per level, which keeps the folder tree honest — a starred feat/website-refresh rises to the top of the feat folder rather than escaping it, and the folder itself stays where it is. A star belongs to the branch, not to the ref: develop and origin/develop are one branch as far as you are concerned, so starring either lights up both, and unstarring either clears both. The stars live with the repository, in <repo>/.beardgit/favorites.json next to the requests collections and the AI reports, so they survive a relaunch and a project that does not gitignore .beardgit/ can commit them and share them with the team. They are read when the branch panel mounts rather than on every repository mutation: the file changes when you click a star, not when you commit.
  • Submodules of submodules are listed, and can be operated on. The panel only ever showed the first level, so a repository that nests them — the common shape for vendored dependency trees — hid everything below the top. The listing now recurses (five levels deep, past which it stops rather than trusting a symlinked working tree not to loop), depth-first with each parent followed by its children, and the panel indents a nested row under the submodule it belongs to. Its actions work there too: a nested submodule is registered in its parent's .gitmodules, not the repository's, so initialize, update, deinitialize and remove now run inside that parent — addressed from the root, git rejects the path as one it does not know. A submodule with no working tree has nothing below it and is not descended into; one that is checked out but whose repository will not open is listed without its children instead of failing the whole panel.
  • Clone can bring the submodules with it. A checkbox in the clone dialog, off by default: on, the clone is --recurse-submodules and every submodule is checked out as part of it. Left off, submodules arrive registered but empty, exactly as before, and the Submodules panel fills them in on demand. It is a choice rather than the new default because a recursive clone fetches every submodule at full depth, which for a repo that vendors large dependencies is a different order of download than the one the user asked for.

Corregido

  • A submodule whose checkout has moved reads as "outdated" instead of "dirty". The status of every submodule came out of three libgit2 flags that were OR-ed together as if they meant the same thing. One of them, WD_MODIFIED, means "the submodule's HEAD is not the commit the superproject records" — which is the definition of outdated — so a submodule sitting at the wrong commit was reported as having uncommitted changes, and the check that would have called it outdated could never run: the panel had a status it was incapable of showing. Dirty is now what its name says, the submodule's own index and working tree, and keeps precedence when both hold.
  • Opening a submodule in a tab cannot land outside the repository. The path was joined onto the repository root and accepted if it existed, and the result is handed to the project-opening command, so a .. in it would have opened a tab on an arbitrary directory. Both sides are resolved now and a path outside the repository is refused.

Cambiado

  • The submodule fixture in the test suite creates a submodule. It was named for one and never added it: it built a superproject and a loose repository that no .gitmodules mentioned. The single test using it asserted the submodule list came back empty, which it did, for the wrong reason — so the status computation, the only real logic in the module, had no coverage at all. Writing the first honest test found the bug above. Fifteen tests now cover the four statuses, nested listings, the containment check, and the argv that puts a nested operation in the right repository. Repository::add_submodule went with the pass: it duplicated git submodule add synchronously and nothing had called it since the command moved to the task runner, which is where it has to be — the operation clones, and on the main thread that froze the window.
26.9.1 2026-09-07

Two switches, a terminal that draws what the shell drew, and the CLIs you actually ship

Añadido

  • A switch to turn the GitHub / GitLab integration off, and BeardGit becomes a git-only client. Settings → General → Integrations. Without a connected account the forge surfaces already hid themselves, but the app still went to the network on its own: at every launch it validated each saved token over HTTPS to reconnect, and on every repository open it resolved the origin remote against the forge API. Off, neither happens, the Integrations page shows only a pointer back to the switch so the gh / glab status checks it runs on mount never fire, and the sidebar group, status-bar pill, forge views, graph badges and init-repo's "create remote" follow the existing no-provider gating. The saved accounts and tokens are kept, not deleted; turning it back on reconnects them as a launch would. With it off, the only network traffic the app initiates on its own is the update check.
  • A switch to turn the AI integration off. Same place. Without an AI CLI installed the app already hid the commit-box actions and the toolbar menu, but the rest stayed on regardless: the AI Config and AI Sessions entries in the sidebar, the AI slot in the status bar, Cmd+Shift+A opening a background-run dialog that could only fail, a probe for the three CLIs at every launch and every visit to Settings → AI, transcript listeners re-armed on each project switch, and a thread asking every three seconds which process the terminal was running, purely to spot an AI agent. Off, none of that happens: the surfaces go, the views reroute to the graph like the forge views do when a provider disconnects, and the backend refuses new AI work even if something asks. Runs already in progress finish, history stays readable, and the preferred provider and background-run settings are kept for when it comes back on. Default on.

Corregido

  • The terminal draws what the shell drew. Opening the app from Finder or a desktop launcher gave the shell no TERM, COLORTERM or LANG — launchd hands a GUI app none of them — so zsh, fish and fzf fell back to a dumb terminal and the line editor mangled every multibyte glyph. Ctrl+R history search, fzf, vim, anything that draws in raw mode, left garbage in the buffer. It never reproduced in development because the developer's own terminal leaked its values in. The PTY now declares itself xterm-256color with truecolor and a UTF-8 locale when none is set, and on macOS starts a login shell like every native terminal does, so path_helper and ~/.zprofile run and your tools are on PATH. Four more faults in the same chain went with it: the interactive terminal converted a bare \n into \r\n, which raw-mode programs use to mean "down one line, same column"; keyboard input was base64-encoded from UTF-16 code units, so ñ reached the shell as one invalid byte and or an emoji threw and was dropped; closing a terminal tab with another one behind it left the survivor mute, because the tab layer matched terminals by position and unregistered the wrong session's output; and the pool that recycled xterm instances handed out terminals that could never attach to a new container, so the fourth terminal you opened after closing one of three came up blank. The pool is gone, one instance per tab.
  • "Log in" with gh / glab works for the user who has neither installed. The button typed a bare gh auth login into a terminal, which the shell resolved through its own PATH — "command not found" for exactly the user the bundled copies exist for. The command now names the binary the app itself will use.

Cambiado

  • BeardGit drives its own gh and glab. The bundled copies were only ever a fallback: whatever gh or glab you had on PATH won, so the same build ran a different CLI version on every machine, and the bundled ones had quietly sat at gh 2.62.0 and glab 1.46.1 for close to two years — that glab could no longer read the config a current glab writes, and failed every GitLab call for anyone who also had a modern one installed. The bundled binaries now come first, with PATH as the fallback. Your login is not affected: both CLIs keep credentials in your per-user config and keyring, keyed by host, not by which binary ran auth login. The pins move to gh 2.100.0 and glab 1.116.0, and a check in the gate and in CI fails once they trail the latest release by more than 90 days.
26.9.0 2026-09-03

Sections that remember, an editor that keeps its place, and a graph that draws what git did

Añadido

  • Every section remembers where you left it. Leaving a view and coming back used to reset it: the filter you had typed in Branches, the folders you had collapsed, the split you had dragged, the comment you were halfway through on a merge request, the message in the commit box. Each section is one component that unmounts when you leave, so anything it held in local state was simply gone. That state now outlives the section for the length of the session — filters and scroll positions in every list, panel widths in the nine split views, the collapsed groups and folders of the branch tree, the search chips in Graph / MRs / Issues / Pipelines, the drafts (commit message and amend toggle, stash message, new submodule, bisect good/bad refs, MR and issue comments keyed by item, repo-config labels and protection rules), the Report/Transcript sub-tab and split of AI sessions, and the job-log pane of Pipelines. Repo-specific state is kept per project tab and dropped when the tab closes; layout is kept once for the app. Nothing is persisted across a relaunch. The commit draft moved into the per-repo state along the way, so switching project tabs no longer swaps one repository's half-written message under another.
  • The editor keeps its tree and tabs across views and project tabs. Switching to another section and back collapsed every folder in the file tree and re-read every open tab from disk — dropping unsaved edits — because the panel remembered which project it had loaded in a variable that died with it. The editor store now owns that, with a per-project session cache: a remount for the same project is a no-op, a different project swaps its parked tree, expanded folders and tabs back in, corrects the listings and clean buffers against the disk without blanking anything, and leaves dirty buffers exactly as they were. A file opened "in editor" from the Changes view before the panel has ever mounted is adopted by the project that loads next rather than filed under the previous one.
  • Reveal the active file in the tree. New editor preference, on by default: when the active tab changes, the tree expands the file's ancestors — listing the ones it had not visited yet — and scrolls the row into view. Off while the tree filter is active, since the flat result list has nothing to expand.

Corregido

  • A merged branch's lane no longer runs to the bottom of the graph. When two lanes were both waiting for the same parent commit, the one that did not get to draw it was never released: its segment stayed open and painted a vertical line with no commits on it down to the last row, or until the lane cap forced a reclaim. Under every merged branch. The lane now closes at the row above the parent and is free for reuse, and cached layouts are rebuilt once.
  • Merge curves and the lines they hand off to agree on where they meet. Three artefacts from one disagreement. The segment starting under a merge was clipped with an arrival point computed one way while the curve was drawn another, leaving a stub on the parent's lane that nothing connected to. A merge pulling a parent into a fresh lane now bends at the top, into that lane, in that lane's colour and group — it used to bend at the bottom in the child's colour and change hue where the segment took over, and hovering or dimming the branch left its first bend at full strength — while a branch rejoining an existing line bends at the bottom, and its segment stops where the curve begins to bend instead of poking out below it as a spike. Which curve opened a lane is now decided by the layout, not guessed by the renderer from "a segment starts here": a merge whose two parents landed on the same lane matched that guess twice, and the first-parent edge was painted over the other lane's line, leaving the merge's own line — master's, in the report — ending in mid-air.
  • Ref badges are the same colour in the graph and in the commit detail, and the theme's ref colours are honoured. Badges were coloured by a hash of the ref name — the canvas over the lane palette, the detail pane over five accent tokens with a different modulus — so one branch had two colours, and a branch could share its colour with an unrelated lane. The theme's ref_branch / ref_remote / ref_tag fields were loaded and never read. Both now colour by kind: local branch, remote, tag, HEAD, from the theme, which also publishes them as --graph-ref-* tokens for DOM badges. A badge whose theme colour was rgb() or short hex also stopped vanishing on the canvas: its alpha was produced by appending two hex digits to the string.
  • The editor's file tree follows symlinks. A symlinked directory or file was skipped outright. It now lists as what it points at — a linked directory expands to the target's children — and only dangling links are dropped. The tree filter's walk does not descend into linked directories, so a link back up the tree cannot send it in circles.

Cambiado

  • The AI actions send their prompt over stdin, and say less. The five specialised actions — commit message, review, and the rest — handed Claude Code their whole prompt as a single command-line argument, staged diff included, which the provider crate itself documented as truncating past the platform's argv limit; a large diff came back reviewed in part. Providers that read stdin now get the prompt piped. The prompts themselves lost the pressure language written for older models ("Output ONLY", "Be thorough") and the review prompts state the output shape, since the reply is saved as a Markdown file. Codex and OpenCode keep the argument form.
  • Typing in the editor is lighter, and the completion popup opens sooner. Every keystroke wrote the buffer into the tab store from inside CodeMirror's update listener, before the character painted, re-rendering the tab strip and toolbar per key and rebuilding the whole extension array because a derived value watched the tab object rather than its path. The store now catches up 100 ms after the last keystroke, or immediately before anything reads the buffer (save, tab switch, close); the extensions rebuild only when the file changes. The autocomplete popup's typing delay drops from 100 ms to 30 ms.
26.8.0 2026-08-28

Themes you can read, three data-loss fixes, and an updater that finishes

Añadido

  • 11 new themes, and every bundled theme now passes a contrast audit. Rosé Pine (Moon / Dawn), Everforest (Dark / Light), Kanagawa, Ayu (Dark / Mirage / Light), Material, Zenburn and Oxocarbon take the built-ins from 20 to 31. They arrived with a WCAG contrast check that the theme derivation had always claimed to run and never did — measuring the existing themes found 25 tokens below the readable floor across 13 of the 20, and the worst of them were not merely quiet but unreadable: gruvbox-dark at 1.67:1, nord at 1.69:1, one-dark at 1.90:1. All three text rungs now clear 4.5:1 on every bundled theme, pinned by a test. A theme you wrote yourself is reported, never rewritten: the picker lists which tokens fall short and by how much, and applies your theme regardless.
  • Collapse a hunk, expand the whole file, open it in the editor. The Changes view had no collapsed state at all, so a file with a dozen hunks was a dozen hunks of scrolling to reach the one you cared about — each hunk header now has a chevron, and the toolbar a collapse-all that flips to expand-all. Seeing the rest of the file needed the backend: libgit2 keeps three lines of context around each change and nothing had ever asked it for more, so the surrounding code simply was not in the payload. The toolbar's expand control re-fetches the file as one hunk, and the context survives a background refresh. Each row also gets a pencil that opens it in the editor, and Mod+E opens whatever the diff pane is holding.
  • Log level is now a setting, and the log has something in it. The log file had one line in it: 165 #[instrument] attributes created spans that the formatter was never told to print, and the level was fixed at startup with no way to change it. Settings → Advanced now switches between error, info and debug live, persisted across restarts, and the events that make a bug report answerable are there — project open/close/switch, watcher start, mutation fan-out, task start/finish with exit code, and every IPC failure from a single chokepoint. Prose, URLs that can carry a credential, and shell input stay out of the file by construction.

Corregido

  • Per-line staging staged the lines you picked, not the ones at those indices. A line selection is positional — hunk 2, lines 5–7 of what the pane rendered — and the backend threw that diff away and re-derived its own at libgit2's fixed three lines of context. The two agreed right up until "show whole file" let the pane ask for the file as one hunk; from then on the same indices named different lines on each side. Tick a line, press Stage, get a success toast, and a different line was staged. Discard destroyed the wrong lines, or failed with a corrupt-patch error, depending on the shape. Two clicks from the Changes view, and it survived navigation. All three staging paths now re-derive the diff at the context the selection was built against.
  • Discarding or unstaging a single line works again. Both failed every time: git apply rejected the patch, the target was left untouched, and you got an error toast. The rule for what to do with a non-selected changed line was written for a forward patch, and these two paths apply with --reverse, where the polarity is the other way round — so the patch described a state the file was not in. Safe, but the action was unusable.
  • Installing an update dead-ended on macOS and Windows. The install flow set a re-auth notice and returned early, and the dialog meant to carry that notice was never mounted by any route — so the toast disappeared and nothing was ever installed. Linux was the only platform where updating worked. The gate is gone: the download starts immediately, and the unsigned-build notice moved into the surfaces you see before installing, since the Windows installer kills the app mid-install and nothing shown after the download would ever be read.
  • Discarding an untracked symlink no longer deletes what it points at. Discarding resolved the path before deciding what to remove, and resolving a symlink follows it — so discarding a link that pointed at a tracked directory ran a recursive delete on that directory, wiping committed files, while the link itself stayed put and the app reported success. A link pointing outside the repository took the opposite path: the containment check skipped it, so nothing was deleted and you were told it had been. Discard now classifies the entry without following it, so a symlink is removed as a link whatever it points at, and a delete that is attempted and fails comes back as an error naming the paths still on disk instead of reporting a clean working tree.
  • An interactive rebase left open while HEAD moves no longer drops the new commits. The rebase plan is a snapshot taken when the editor opens, and nothing rechecked it before running. git rebase -i applies exactly the todo list it is given and silently discards every commit missing from it, so if anything moved HEAD while the dialog was open — a commit from a terminal, a pull, an AI background run — submitting the plan erased those commits from the branch. The plan is now re-checked against the current range and refused if it no longer covers it, with a message telling you to reopen the editor. Marking a commit drop is unaffected. Planning a rebase over more than 1,000 commits is now refused up front rather than truncated, for the same reason: a shortened list would be a shortened todo file.
  • Background work now shows up in the tasks popover — and a failed tag push finally says so. Eight operations that already ran as background tasks never appeared anywhere: pushing a tag, pushing all tags, updating a submodule (and updating all of them), checking out a merge request or pull request, publishing a tag + release, uploading a release asset, and an automated bisect run. They spawned as untyped tasks, which the drawer deliberately filters out, so there was no row and no spinner on the status-bar icon. Worst of them was the tag push, which was silent twice over: it also never surfaced a failure, so a push rejected for lack of permission, an existing remote tag, or no network produced nothing at all, anywhere. All eight now produce a cancellable row, and a rejected tag push shows an error toast.
  • Bisect now works inside a linked worktree or a submodule, and external commits there refresh the view. Both assumed .git is a directory. In a linked worktree it is a file pointing elsewhere, so the bisect state file was looked for at a path that never exists and the bisect UI simply never appeared with a session running. The filesystem watcher had the same blind spot from the other side: a worktree keeps its HEAD and index in one out-of-tree directory and its refs in another, and a commit touches neither the working tree nor anything the watcher was looking at — so committing from a terminal in a worktree refreshed nothing. Both now resolve the real git directory, and the watcher watches it.
  • A CI pipeline with more than 100 jobs now shows all of them. The job list for a workflow run (GitHub) or pipeline (GitLab) asked for one page of 100 and never looked further, so a wide build matrix showed the first hundred jobs and said nothing about the rest. Both now follow pagination to the end, and log it if they ever stop at the safety bound.
  • A GitLab issue whose comments can't be loaded no longer looks like an issue with no comments. The detail pane derived the comment count from the notes it fetched and fell back to an empty list when that fetch failed — a token without the right scope is the usual cause — which replaced the real count with zero. The issue list beside it still showed the true count, so the two disagreed: "5 comments" in the list, no comments section at all in the detail. The count now survives a failed fetch, and the pane says the comments could not be loaded instead of hiding the section.
  • The editor's file tree lists every folder, and stops going stale. "I can't open the main folder, it isn't listed." The tree asked for the entire working directory capped at 10,000 entries, and the cap was applied to a depth-first walk that stopped dead wherever it happened to be — so it kept whatever the walk reached first and dropped the rest, directories included, and the folders that did survive arrived without their children and expanded to nothing. There is no tree walk left to budget: the tree now lists one level at a time and fetches a folder's children when you first open it. A refresh also drops its cached listings, closing the gap where a file created inside a collapsed folder was invisible until you switched projects, and a rename left the tree offering the old name.
  • Every theme's editor colours now actually reach the app. The 15 [editor] tokens of all 20 bundled themes were inert: the Rust struct serialized them as added-bg / syntax-keyword / gutter-fg while the frontend read added_bg / syntax_keyword / gutter_fg, so every read came back undefined. The two diff-background tokens are the only ones with no derived fallback, which is why a light theme painted its added and removed rows in the dark defaults — the exact pixels in the reported screenshot. From the same pass: the scrollbar thumb and spinner track were hardcoded white at 15% opacity, which in every light theme is a white thumb on a white page.

Cambiado

  • Cloning a repository and adding a submodule no longer freeze the window, and can be cancelled. Both ran the clone inline on the UI thread, so the app was unresponsive for as long as the remote took, with no progress and no way to stop it — the dialog's own spinner couldn't even paint, because the thread that would paint it was the blocked one. Both now run as background tasks with a cancellable row in the tasks popover, and the clone opens its new tab when the task lands. Cancelling a clone leaves the partial checkout in place; retrying reports that the folder already exists rather than deleting anything for you.
  • Assorted performance work. Two of the bigger ones, if you hit them: the Changes list and the commit-detail file list now render only the rows in view above 500 files, so a working tree with tens of thousands of untracked files (an unignored node_modules, say) no longer mounts a DOM node per file; and the working-tree file search — which runs on every keystroke in the file-editor search — moved off the UI thread along with four other measured-expensive commands, so typing no longer costs a frame per character. The rest were smaller and are not worth listing individually.
26.7.1 2026-07-03

Off the UI thread, per-tab state isolation, and broader error codes

Cambiado

  • Opening diffs and refreshing the Changes view no longer compete with the UI thread. The diff and file-content read commands — the working-tree and staged diffs (whole-tree and per-file), the cheap per-file change stats that back the Changes list, and reading a file's contents from a commit, the working tree, or the index — ran their git2 work synchronously on the webview's main thread. On a large repo, opening a big diff or letting the Changes list refresh could stutter or briefly freeze the window. They now run on a background thread (spawn_blocking), keeping the UI responsive; results are unchanged. (get_diff_workdir/get_diff_index/get_diff_file, get_diff_stats_workdir/get_diff_stats_index, get_file_at_commit/get_file_workdir/get_file_index.)
  • More git actions now fail with a stable error code instead of raw text, and the "not a git repository" code is unified. The typed { code, message } error envelope — which lets the app react to why a command failed rather than parsing its message — now covers the common working-tree mutations: branch delete (single and batch), checkout, the stash family (push/pop/apply/apply-file/drop), merge, rebase, revert, cherry-pick, reset, branch rename, and tag create/delete. Three situations you act on differently now carry their own code and get a clearer toast: switching branches with uncommitted edits that would be overwritten ("Checkout would overwrite uncommitted changes — commit or stash first"), a safe delete of a branch that still has unmerged commits ("Branch has unmerged commits — delete with force to discard them"), and renaming a branch onto a name that's already taken ("A branch with that name already exists — choose a different name"). Opening a non-repository also reports one consistent code everywhere: open_project already used not_a_repo, but open_repo emitted a different repo_not_found for the identical situation and the frontend only recognised one of them, so that path fell back to raw text — both now use not_a_repo (the old code is still accepted as an alias). No behaviour changes for successful commands; only how failures are labelled.

Corregido

  • The commit graph no longer jumps back to the top when a background change lands or when you switch tabs. The graph reset its scroll to the very first row whenever the active repo's info was re-fetched — which happens on every background mutation (a commit, fetch, or checkout picked up by the watcher) and on every tab switch — so scrolling deep into history and then having anything refresh (or flipping to another tab and back) yanked you straight back to the newest commit, undoing the per-repo scroll position the graph now keeps. The reset now fires only when you actually switch to a different repository that has no cached view yet: a background refresh keeps your exact scroll position (reconciling if new commits landed above), and returning to a tab restores the offset and selection you left it at.
  • Checking out a commit (detached HEAD) over uncommitted changes now warns you instead of failing with a raw error. Switching branches over a dirty tree already surfaced the actionable "Checkout would overwrite uncommitted changes — commit or stash first" toast, but checking out a specific commit shared none of that: it returned a generic git error code, so the UI couldn't tell you to commit or stash first. Detached checkout now maps the same libgit2 conflict to the would_lose_changes code, so both paths give you the same clear prompt.
  • The commit graph no longer forgets your selected commit when you switch repo tabs — and switching back is instant. The graph's viewport, scroll offset, and selected commit lived in module-level stores shared by every open repo tab, kept in sync only by a hand-rolled per-path cache that was cleared on every switch. So opening another repo wiped the first repo's graph selection (its commit detail panel emptied), and coming back re-fetched and re-laid-out the graph from scratch. This state now lives per-repo in the RepoState container, so each tab keeps its own graph view and commit selection: switching tabs is a pointer swap — the cached graph paints immediately with no spinner or rebuild, and the commit you had selected is still selected. A commit-detail load that resolves after you've already switched tabs now lands in the repo it was started for instead of the tab you're looking at.
  • The Compare view no longer shows one repo's comparison in another repo's tab. Starting a compare (or opening a per-file diff in it) in repo A and switching to repo B before the backend responded could paint A's merge-base, commit list, and changed files into B's Compare view — the stale-response guard was a module-level counter that nothing bumped on a tab switch, while the writes always landed in whatever repo was active when the response arrived. Each compare now captures the repo it was started for and writes its results back into that repo's own state, so a late response lands where it belongs and is invisible in the tab you switched to; the last-wins guard is now per-repo, so a newer compare in the same repo still cancels an older one. The same fix now covers the Compare view's "Load more" pagination: clicking it in repo A and switching to a repo B that happened to have the same base/compare refs could append A's next page of commits into B's list — it now appends into the repo it was started for.
  • The Requests panel no longer leaks its selection across repository tabs — and can't Send or Save against the wrong repo. The selected .http file, its parsed request, the active environment, the run state, and the last response were held in module-level stores shared by every open repo tab. Because the panel doesn't remount on a tab switch, selecting a project-local request in repo A and then switching to repo B left A's file path selected while the Send/Save buttons used B's project path — so hitting Send wrote or executed the file in the wrong repository, and only a 10-second background poll eventually cleared it (and it missed same-named files entirely). This selection state now lives per-repo in the RepoState container, so switching tabs instantly shows that repo's own selection and every request is always sent and saved against the repo it belongs to.
  • The MR/PR and Issues views now keep their state per repository tab. The pull-request and issue lists, their filter tabs, the selected item and its detail, the open per-file PR diff, and the label/milestone picker caches lived in module-level stores shared by every open repo tab — so opening repo B wiped repo A's list and selection, and switching back re-fetched everything from scratch (and a slow list or detail load that resolved after a tab switch could paint one repo's pull requests into another repo's view). Each of these now lives per-repo in the RepoState container: switching tabs is a pointer swap that instantly restores that repo's own MR/PR and Issues view, and a list, detail, or PR file-diff load that resolves after you've switched tabs lands in the repo it was started for instead of the tab you're looking at.
  • Each tab's at-a-glance status badges are now backed by that repo's own state, closing the last cross-repo cache. The compact status pills on inactive tabs (↑ ahead, ↓ behind, ! modified, + staged, ? untracked, ⚑ stashes) and the cached commit-graph viewport that lets a tab repaint instantly on switch were read from a single app-wide Map<projectPath, ProjectSnapshot> — the exact "wrote active-repo data under an inactive repo's key" hazard that this cache had already been bitten by twice. That in-memory cache now lives per-repo in the RepoState container, reached only by a repo's own path, so a tab's badges and instant-paint graph can never be sourced from another repo's data. This completes the per-repo state migration (spec 08): with the graph, branches, changes, compare, requests, MR/PR, and Issues views already moved, no feature store keeps its own private per-path cache for tab survival anymore. (The unified tasks popover and AI background-runs list stay intentionally app-global — they aggregate work across every repo and carry no per-repo routing key.)
  • Staging, checkout, and branch cleanup/delete no longer freeze the window. These commands ran their real work — index writes, git add -A working-tree walks, patch application, checkouts that rewrite the working tree, and the git branch -d/-D subprocess spawns (including the batch delete, which forks one process per branch) — synchronously on the webview's main thread, so staging a large change, switching branches on a big repo, or bulk-deleting dozens of branches would lock the UI until they finished. They now run on a background thread (spawn_blocking), keeping the window responsive; the mutation-driven refresh is unchanged. Same fix for opening the "Clean up branches…" dialog, whose candidate list is an uncached O(branches × divergence) graph walk that used to block the thread every time it opened. (stage_files/unstage_files/stage_all/unstage_all/stage_hunks/unstage_hunks/discard_files/discard_hunks, checkout_branch/checkout_detached, delete_branch/delete_branches, list_branch_cleanup_candidates.)
26.7.0 2026-07-02

Instant on huge repos, commit signing, a compare view, and branch cleanup

Añadido

  • Push and Pull buttons show what's out of sync. The top-bar Push and Pull buttons now carry a small count badge (↑N on Push, ↓N on Pull) in the corner and pick up the accent tint whenever the current branch is ahead of / behind its upstream — so you can see at a glance that there's something to push or pull without opening the status bar. Counts come from the same branch-vs-upstream data the status bar already tracks (refreshed through the mutation pipeline, no polling), the badge caps at "99+", and it's an absolutely-positioned overlay so the buttons never resize or shift. When there's no upstream, HEAD is detached, or you're in sync, the badge and tint stay off. The tooltip spells the count out ("Push — 3 commits ahead").
  • Commit signing (SSH/GPG/X.509) is now honored and surfaced. If your git config has commit.gpgsign=true, commits created in BeardGit are now signed — creation routes through the git CLI (which drives your ssh/gpg/x509 backend and agent natively) instead of the libgit2 path that silently produced unsigned commits, so signature-enforcing branch protection no longer rejects your pushes. Amend, merge, revert, cherry-pick, and interactive-rebase squash/reword already went through the CLI and so sign automatically under the same config; AI-agent commits are made by the agent's own git, so they sign too. Signing runs non-interactively (GIT_TERMINAL_PROMPT=0) so a locked key fails fast with the real gpg/ssh error rather than hanging — agent-based setups (ssh-agent / gpg-agent) are the supported mode. When signing is not configured, nothing changes (the byte-identical libgit2 path is kept). The commit box shows a subtle "Will be signed (SSH/GPG)" chip, Git settings gains a Commit signing section with a Test signing button that signs a throwaway commit with your real config and reports success or the exact stderr, and the commit-detail pane shows a signature chip (present → lazily verified via git verify-commit on the open commit, cached per commit). Annotated tags are signed when tag.gpgSign is set (honored via config). Signature verification renders git's verdict only; no trust-store management.
  • Branch cleanup: [gone] detection + bulk delete. Local branches whose upstream was deleted on the remote (and pruned) now show a red [gone] chip in the branches list, alongside the ahead/behind pips. A new "Clean up branches…" action in the branches-view header opens a dialog with two grouped sections — Gone (pre-checked) and Merged into <default> (unchecked, since squash-merge workflows make "merged" incomplete) — each row showing the last-commit date and how many commits it holds beyond the target. Selecting and confirming deletes the whole batch in one command (one refresh, not N), with per-branch failures reported rather than aborting the run. Gone branches that aren't fully merged surface a "not fully merged" warning and require an explicit force acknowledgment before they can be deleted; everything else uses the safe git branch -d. Deleted branches remain recoverable from the Reflog view. Current, default, and worktree-checked-out branches are never listed as candidates.
  • Compare any ref against any ref. A new Compare view answers "what does my branch add over main?" without leaving the app: pick a base and a compare ref (branch, tag, or SHA, with autocomplete), and see the commits the compare side adds (with an ahead/behind summary), the changed-file list, and a per-file diff — the same diff pane, binary/too-large handling, and status badges used elsewhere. Two range modes: 3-dot (merge-base(A,B)..B — "what B adds", PR semantics; the default) and 2-dot (direct A..B tree comparison), plus a swap button. The commit list windows with a "Load more" so a huge divergence doesn't flood the UI. It opens contextually — from the graph node menu ("Compare with HEAD…", "Compare with…"), the branches list menu ("Compare with current branch"), or the command palette ("Compare refs…") — and deliberately adds no sidebar item. All read-only.
  • Reorder tabs by dragging. Drag any tab to a new spot in the tab strip: the neighbouring tabs slide aside to open a gap and the dragged tab eases into place on drop. A composite tab (a repo plus its linked terminals/worktrees) moves as one whole pill — the sections inside it keep their order. The new order is persisted across restarts. (Implemented with pointer dragging rather than HTML5 drag-and-drop, which drops silently in the desktop webview.)
  • Keyboard navigation and range selection in the Changes lists. Arrow keys move a focus cursor within a list, Space toggles the focused file's checkbox, and Shift+Arrow or Shift+click select a range of files. Focus and ranges stay within a single list (Staged / Unstaged).
  • Stash selected files from the right-click menu. Right-click in Changes → Stash Selected (N) stashes just the checked files (or the file under the cursor), untracked files included, leaving the rest of the working tree untouched.
  • Batch actions on a multi-file selection in the Changes right-click menu. When two or more files are checked and you right-click one of them, the context menu grows a dedicated section that applies the git action to the whole selection at once: Stage Selected (N) / Discard Selected (N) on the Unstaged list, Unstage Selected (N) on the Staged list, plus Stash Selected (N) and Copy Paths (N) on both. Discarding confirms once for the whole batch ("Discard changes in N files?") and runs as a single operation (one refresh, tracked files reset and untracked files deleted together) instead of file-by-file. The single-file actions are unchanged when nothing is multi-selected.
  • CI now runs the frontend test suite and the Rust tests on all three platforms. A dedicated frontend-tests job runs Vitest on every push/PR (a broken frontend test now fails CI), and the Rust cargo test job runs on a Linux + Windows + macOS matrix for pushes to beta/main (Linux-only for feature/** pushes and PRs, keeping iteration cheap). fmt/clippy/the trait-purity guard stay Linux-only.
  • Weekly performance benchmarks (perf.yml). A scheduled (and manually dispatchable) workflow runs Criterion benchmarks for commit-walk (git-engine) and graph layout/viewport (graph-builder) over a generated synthetic repository — no external fixture required — and uploads the results as an artifact for trend tracking.
  • The IPC contract between the Rust backend and the frontend is now enforced. An ESLint rule forbids importing invoke (@tauri-apps/api/core) anywhere outside src/lib/api/, so every backend call must go through a typed wrapper; and a new CI step (scripts/check-ipc-contract.mjs) fails the build if a Rust #[tauri::command] has no tauri.ts wrapper (or a wrapper points at a command that no longer exists). This closes the "silently drifted" gap that let the whole Requests feature bypass the contract.
  • Structured error codes across the IPC boundary. New/high-value commands (clone_repo, open_project, open_repo, push/pull/fetch) now reject with a typed { code, message } envelope instead of a bare string, so the frontend can branch on a stable code (not_a_repo, auth_required, not_fast_forward, destination_exists, …) rather than parsing error text. errors.ts parses both the new and legacy string shapes.

Cambiado

  • Internal: backend crate decomposition (dev-facing, no behaviour change). app-core shed three chunks of business logic to the library crates where they belong, keeping only thin #[tauri::command] glue; the IPC contract, command names, and payloads are unchanged throughout. (1) The remote-repository-settings logic (GitHub/GitLab gh/glab calls, JSON parsing, config diffing) moved behind a ForgeRepoConfig seam in cli-provider, with the shared data types (Visibility, RemoteRepoConfig, RemoteRepoConfigPatch, BranchProtection, diff_config) now living in the dependency-light forge-provider contract crate; commands/repo_config.rs shrank from 3,627 lines to ~310, and its ~55 forge-config tests now run under cargo test -p cli-provider without Tauri. (2) The AI background-run coordinator (queue, concurrency cap, worktree lifecycle, pending_finishes/apply_task_finish race handling, session registry) moved to a new tauri-free ai-runner crate, so its 19 tests run without Tauri; app-core keeps the Tauri event-sink bridge and command glue. (3) The structurally-identical worktree-discovery/cleanup, version parsing, and commit-attribution helpers duplicated between the codex and opencode provider crates collapsed into a new ai-provider-common crate parameterized by a ProviderSpec, so a fix lands once instead of twice (a worktree-cleanup bug previously had to be fixed in both); claude-code keeps its genuinely-different git-porcelain worktrees and richer attribution, sharing only the identical version-token scanner. Storage hardening from the same audit: the per-project snapshot cache key switched from DefaultHasher (unstable across Rust releases) to a stable SHA-256, and the SQLite commit cache gained a schema-version tag so an encoding change invalidates stale rows instead of misreading them.
  • Committing on a huge repo is now instant — the whole graph no longer re-lays-out on every change. A plain commit, amend, checkout, or fast-forward moves one branch forward by a commit or two, yet every such action (plus every commit the filesystem watcher notices) used to re-walk up to 20,000 commits and rebuild the entire lane layout from scratch. That work is now incremental: when exactly one branch advanced on top of the current graph tip, the new rows are spliced onto the cached layout in place (O(new commits)) instead of rebuilding it. Anything that isn't a simple advance — merges, rebases, force-pushes, resets — still does a correct full rebuild, and the fast path is cross-checked against a full rebuild in debug builds so it can never drift.
  • Opening a repository and switching tabs are faster. The graph layout cache used to walk up to 20,000 commits just to compute its cache key — on every open, even a cache hit. The key now derives from cheap O(refs) material (ref tips + HEAD + the HEAD tree + a .git/shallow marker), so a warm repo opens without that walk. Opening also no longer scans the working tree twice, and persisting the layout no longer clones the entire graph before writing it.
  • Per-branch ahead/behind counts are cached. Listing branches recomputed the ahead/behind divergence for every tracking branch on every refresh; the counts are now cached per (branch tip, upstream tip) and recomputed only for branches whose tips actually moved.
  • Scrolling deep into a huge history no longer slows down progressively. Two linear scans on the scroll path are gone. (1) Streaming the next page of commits used to re-walk the graph from the tips and throw away everything above the scroll position, so each page got slower the deeper you went; a single-branch (first-parent) view now continues the walk from the last loaded commit instead, so a page 80,000 commits down costs the same as the first page (measured ~0.85 ms vs ~120 ms at depth 4,000 on a synthetic repo). Full multi-branch views keep the exact previous results. (2) Drawing each viewport used to rescan every lane line and merge curve in the whole graph on every scroll step; a row-range index (rebuilt transparently from the cache, no on-disk format change) now returns only the lines and curves in the visible window, so the per-scroll cost stays flat as graphs grow into the tens of thousands of merges.
  • Changes keeps your checkbox selection but clears the open file on exit. Leaving and re-entering Changes now preserves which files you had checked, while resetting the diff panel so you come back to a clean view instead of the last file you had open. Each open repository remembers its own selection.
  • The Changes view no longer fetches every file's full diff on each refresh. A regenerated lockfile, a minified bundle, or any large/generated file in the working tree used to freeze the Changes view and balloon memory, because every save streamed the full hunks of every changed file over IPC. The file lists now render from cheap per-file stats (name/status + add/del counts, shown inline as +N/-N), and a file's hunks are fetched only when you open it — so mutation refreshes stay tiny and instant regardless of working-tree size. The currently open file re-fetches automatically after a mutation.
  • Builds and installs no longer cause background CPU churn. The filesystem watcher now understands .gitignore: a cargo build, npm install, or bundler writing thousands of files under target/ / node_modules/ used to wake a full repo scan (refs + stashes + worktrees + status walk) every half-second for the duration of the build. Those ignored paths are now dropped from each batch before any scan runs — if a batch touches only ignored files, nothing happens at all. Live refresh is unchanged for everything that matters: editing a tracked file, adding a new (non-ignored) file, external commits/branch switches, and edits to .gitignore / .git/info/exclude itself all still refresh instantly, including files re-included by a negation rule (!keep.me) and files matched only by a nested .gitignore.
  • The Requests panel now goes through the typed IPC layer and gains toasts. All 22 requests_* calls (previously raw, untyped invoke()s spread across 12 components) now run through typed wrappers in tauri.ts; mutating actions — create/save, rename, delete, duplicate, and environment save/delete/secret-set — route through runMutation, so they show the same success/failure toasts (with a "See details" affordance) as the rest of the app instead of failing silently. Behaviour is otherwise unchanged; the panel's own tree-refresh flow is preserved.

Corregido

  • The "Parent folder" field in the New File / New Folder dialogs is now editable. It previously looked like a text field but was read-only, so you were stuck creating files under whatever folder the tree selection implied. You can now type a relative directory (e.g. src/utils), with autocomplete against the repo's existing folders, and leave it blank for the repo root. Typing a directory that doesn't exist yet creates the intermediate folders on the way. Input is validated inline — .. segments, absolute paths, and illegal characters are rejected before submit — with the backend still enforcing the same rules. The Rename dialog is unchanged.
  • Right-click menus opened near a window edge no longer get clipped. Context menus now measure themselves on open and flip/clamp to stay fully inside the window (with a small margin), so items near the right or bottom edge are always reachable — across every menu (graph, branches, changes, file tree, worktrees, submodules, requests, reflog, pipelines) and their submenus.
  • Your Changes selection is no longer lost when you work in another repo tab. With several repositories open, checking files in one repo's Changes list, switching to another tab (or a background change landing in one), and switching back used to return an empty selection — the checkbox state was a single global store that got wiped on every tab switch. Per-repo view state (Changes selection, branch list, open staging diff, commit-message draft) now lives in one per-repository state container, so switching tabs is a pointer swap that restores exactly what each repo had. This is the first migration onto that container (branches + changes), which makes the whole "state leaked across tabs" class of bug structurally impossible for the migrated stores — a guarded two-repo isolation test locks the behaviour in. (Graph, MR/PR, issues, and the rest migrate in follow-up steps.)
  • Oversized and binary diffs no longer freeze or bloat the app. Every diff/file-content path is now capped: a single file's diff stops collecting past 5 MB or 10,000 lines and is tagged truncated, binary files short-circuit before any line collection, and the workdir/index file-content endpoints enforce the same 5 MB + binary guard the commit path already had. The Changes and diff panes render a clear "diff too large to display" / "binary file — no preview" notice instead of hanging on a multi-megabyte payload. A whole-response budget (20 MB) further bounds the working-tree and staged diff list endpoints.
  • The UI no longer freezes during bisect. Every bisect action (start, good/bad/skip, reset, state/log queries) shells out to git off the app's async runtime, so the window stays responsive — you can scroll the graph and use other commands while bisect works, even on large repos.
  • Automated bisect is now cancellable. git bisect run executes as a managed background task that streams its output and can be stopped from the UI, so a slow or runaway test command no longer locks up the app until it finishes.
  • An AI background run can no longer get stuck showing "running" forever. If the provider process finished in the instant before its task registration completed, the terminal event was silently dropped and the session never left the running state. The finish event is now buffered and applied as soon as the registration lands (mirroring the existing output-line buffering).
  • CI reliability on Windows and fresh runners. The Rust test repos pin core.autocrlf=false so content assertions aren't broken by the runners' global CRLF conversion; the clone-pipeline test builds a well-formed file:/// URL from Windows paths; and the Vitest CI job runs svelte-kit sync first so a fresh runner has the generated tsconfig. The Rust dependency audit ignores (with justification) two quick-xml advisories that have no upstream fix path yet — it fails again on anything new.
26.6.3 2026-06-30

Fix phantom rebase toolbar

Corregido

  • A finished rebase showed a phantom Abort/Continue toolbar. After a rebase completed, git removes its rebase-merge/ / rebase-apply/ state directories but leaves the .git/REBASE_HEAD ref behind. Conflict detection treated that leftover ref as an in-progress rebase, so the toolbar appeared and clicking Abort/Continue ran git rebase --abort/--continue, which failed with "fatal: no rebase in progress". Detection now keys only off the rebase state directories (what git itself uses), matching git's actual state. (Surfaced once interactive rebases started completing instead of hanging, in 26.6.2.)
26.6.2 2026-06-30

Rebase recovery, live change detection, and a Windows-safe version scheme

Cambiado

  • Version scheme migrated to 2-digit-year CalVer (YY.M.patch, e.g. 26.6.2) instead of 4-digit (2026.6.x). Windows installer version fields cap the major component at 255, so a 2026 major silently broke the Windows NSIS auto-updater: the new build downloaded but the install never "took", leaving the client re-detecting and re-downloading in a loop (other platforms were unaffected). The release workflow now hard-fails any tag whose version is not strict semver with major ≤ 255, minor ≤ 255, patch ≤ 65535, so an un-updatable Windows artifact can never ship again, and it force-marks the published release as GitHub "latest" (GitHub resolves "latest" by semver, and this tag is numerically lower than the stranded 2026.6.x tags). - One-time manual update required. Because no Windows-legal version can ever be numerically greater than 2026.x, existing users on any prior build (0.x or 2026.6.x) are asked to download 26.6.2 manually once. Auto-update resumes normally from 26.x onward.

Corregido

  • Interactive rebase could hang with no way to abort. A rebase plan containing squash, fixup, or reword made git rebase -i open the commit-message editor; with no controlling terminal it blocked forever, so the command never returned, no refresh event fired, and the Abort/Continue toolbar never appeared. The rebase now runs with a non-interactive GIT_EDITOR, so it always completes (or surfaces its conflict) and stays recoverable.
  • Conflict state and submodules no longer refreshed in real time. After the filesystem watcher migrated to the project-mutated event, conflict status (which drives the Abort/Continue toolbar) and the submodule list were still wired only to the legacy repo-changed event and stopped updating live. Both are now driven by the mutation dispatcher, so an in-progress rebase/merge surfaces its toolbar immediately and submodules track external changes.
  • Silent filesystem-watcher start failures are now logged. If a repo's watcher fails to start (e.g. an OS watch-descriptor limit on a large tree), real-time refresh was disabled for that repo with no diagnostic. The failure is now logged so "changes don't appear live" reports are traceable.
2026.6.1 2026-06-18

Clearer hierarchy, a grouped sidebar, and a friendlier first run

More depth between panels

Consistent, colour-coded file status

One consistent set of buttons

A clearer commit box

A grouped sidebar

A welcome screen that shows what BeardGit does

Up-to-date dependencies

2026.6.0 2026-06-13

Design identity overhaul: own themes, merged title bar, coherent diffs

Own theme families — BeardGit (copper) is the new default

Editors and diffs now follow the theme — 100%, by construction

The tab bar is the title bar now

A single design language for primitives and states

  • Checkbox & Switch primitives — all 46 native checkboxes across 19 component files replaced: custom-drawn Checkbox for selection (staging lists, dialogs, pickers) and a role="switch" toggle for boolean settings, both keeping a real hidden input so keyboard/AT semantics are untouched. Native controls also stopped rendering light-mode on dark themes (color-scheme was never set, now synced per theme). Markdown task-lists in issue/PR/release bodies re-skin to the same checkbox visual.
  • Skeleton loaders — list panels and every detail pane's first load sketch their incoming content (shimmer rows / heading+paragraph bars) instead of a centered spinner; spinners remain for point operations only. The status-bar tasks slot swaps its "sync" glyph (which read as a refresh button) for a checklist icon at rest and a real spinner ring while work runs.
  • EmptyState everywhere — every "Select a X to view details" italic one-liner became the shared icon + title block (with CTAs where they help, e.g. "New Worktree"), and elevation got a system: two shadow tokens replace 27 ad-hoc box-shadows, master list panes sit one surface step above detail panes.
  • Typography — all 641 hardcoded font sizes now reference the --font-size-* scale, and the graph canvas uses deliberate type: ref badges, SHA and date columns in Fira Code, prose in the system sans.

Graph: first-parent view, branch scope, smoother curves, faster canvas

Copper branding, end to end

Interaction & ergonomics

Fixes along the way

0.2.1 2026-06-10

Staging diffs that stay fresh + sidebar reorder that works everywhere

Unstaged files show their diff again

The selected file is highlighted in the changes lists

Sidebar (and rebase) reorder no longer relies on HTML5 drag & drop

0.2.0 2026-06-08

Audit hardening pass + a resizable, selectable diff panel

The bottom diff panel is now resizable on both axes — and selectable

Audit — correctness & data-integrity

Audit — security hardening

Audit — robustness, resources & UTF-8

Dependencies

0.1.13 2026-05-12

Per-theme accents, command palette, security & performance pass

Themes finally have an identity

Command Palette (Cmd+Shift+P)

Six performance wins on the libgit2 hot path

Nine security audit fixes

UX polish — ten quick wins + the bigger ergonomic plays

Plumbing

Visual regression suite

GitHub PR review comments — inline threads finally show up

Post-merge polish

Landing page redesign

0.1.12 2026-05-05

In-app mini editor + per-file discard

Editor — edit repo files without leaving BeardGit

Discard unstaged changes per file

Patches generated by BeardGit are now valid for users with a custom `diff.external`

0.1.11 2026-05-05

Requests panel + brand mark refresh

Requests panel — `.http` API testing inside the repo

Brand mark refresh

Tab + welcome polish

Landing site

0.1.10 2026-04-28

AI tasks in the drawer + persisted reviews, landing & community polish

AI code review surfaces in the tasks drawer + persists to disk

Code review + Commit-message buttons gated on staged changes

Tasks drawer polish

Diff: show whitespace toggle

Tauri Build workflow → manual-only

README — AI and Observability sections rewritten

Landing page — SEO/social meta, AVIF screenshots, Keyboard + FAQ sections

Community health — Code of Conduct, security policy, issue/PR templates

Internal

  • crates/cli-provider/src/auth.rs: mock_cli switched from fs::write + set_permissions to OpenOptions::new().mode(0o755) + sync_all + drop, plus a wait_for_exec_ready probe loop that retries the script's exec on ETXTBSY for up to ~1.5 s before returning. Targets the intermittent "Text file busy" failure on the GitHub Actions ubuntu runners. (Still flaky on main — tracked separately.)
  • crates/terminal/src/manager.rs: Session::last_fg_process and its constructor assignment are gated behind #[cfg(unix)] to match the master_fd field, silencing the dead-code warning Windows builds were emitting.
  • crates/git-engine/src/patch.rs: removed an unused create_review_patch helper that briefly backed a HEAD-vs-worktree review patch (replaced by direct createWorkingTreePatch(true) from the FE; staged-only is the correct semantic for review).
0.1.9 2026-04-27

Init repo on folder open + consolidated post-0.1.8 work

Init repo on folder open

Init repo — use existing remote URL + tooltips on every element

Sidebar edit-menu fixes

Fixed — drag-reorder of Navigation items did nothing

Added — "Show more…" expander when items are hidden

Fixed — customize-sidebar pencil button rendered a hover rectangle

Post-IconButton polish

Fixed — `gh pr view` regression on `headRepositoryUrl`

Fixed — AI toolbar dropdown click-outside swallowed by xterm/CodeMirror

Fixed — AI dropdown tooltip described only one of its two actions

Fixed — `+` glyph and "↗ Graph" nav in the Branches view

Fixed — primary / danger / ghost button system: tonal-rest, solid-hover

  • primary: translucent accent-blue tint at rest (color-mix(accent-blue 18%, transparent)) with accent-blue text → solid var(--accent-blue) with text-primary on hover.
  • danger: same pattern in red. Solid red at rest read as alarming for buttons (Disconnect, Clear cache, Delete asset) that don't fire instantly.
  • ghost: dropped the background: var(--overlay-hover) rectangle on hover; only the text colour brightens, matching the IconButton rule. The AiSessionDetail Resume / Focus buttons get the same pattern via local CSS overrides.

Fixed — "Check for updates" raw error + missing diagnostics

  • The Tauri updater plugin returns implementation-detail strings ("could not fetch json", "the network has temporary issue", etc.) verbatim. The Settings → Advanced → Check for updates row now maps recognisable "endpoint unreachable" shapes to a localized hint (update_server_unreachable) and only shows the raw text for unexpected failures.
  • A new diagnostics block under the row exposes Last checked: <relative time>, Endpoint: <url> (the latest.json URL the plugin tries), and on error a monospace Detail: <raw> line. UpdateState.lastCheckedAt is set in the store on every terminal resolution. Useful for distinguishing "endpoint 404'd" from "DNS hiccup" without leaving the app. Note: the underlying 404 (releases/latest/download/latest.json is missing because every release is currently flagged prerelease=true and GitHub's /releases/latest/ redirect skips prereleases) is a release-pipeline concern, not addressed here.

Icon-only buttons + brand logos

Added — `IconButton` component + `Button.description` prop

Changed — every icon-only button migrated, dead per-component CSS dropped

Changed — official brand logos for Codex / OpenCode

  • Codex — OpenAI monoblossom mark, shipped in black + white variants. ProviderIcon picks the right one off $activeTheme.meta.mode so the logo stays legible on both dark and light themes.
  • OpenCode — official two-tone wordmark, shipped in light + dark variants and switched the same way. <img> cannot resolve currentColor from the parent document, so a single asset per brand would either flatten the two-tone OpenCode mark or paint OpenAI's monoblossom in only one mode — hence the two-asset approach.

Repo settings multi-instance fixes

Fixed — auth probe scoped to the repo's host

Fixed — `glab repo view` payload with both `topics` and `tag_list`

Theme color audit groundwork

Theme — six new `--overlay-accent-*` tokens

Brand allowlist

Playwright visual baseline

Lint — color literals blocked

PR diff view

PR / MR diff view

Data model

Fix

Branches UI feature-complete

Branches — new-branch entry points + rename + force-push + shortcut

Sidebar customization

Sidebar — reorder and hide Navigation items

AI sessions list trim

AI sessions — one-line rows, detail-pane actions

Toolbar — AI dropdown, plain terminal button

  • refactor(toolbar): AI becomes a dropdown, terminal becomes a plain button. The toolbar's terminal split-button is now a single button (its old dropdown only surfaced the project-root fallback that the button itself already does). The "AI" / "IA" button is now an always-dropdown listing every installed AI CLI provider plus a "Launch session in background…" entry, which is where the per-provider launchers used to live under the terminal chevron. Escape / outside-click close the menu; aria-haspopup / aria-expanded / role=menu land for screen-reader parity.

Pipelines + Issues lists v2 (was drafted as 0.1.11-beta)

Wider list pane, shared row primitive, richer meta

Repo settings in the sidebar (was drafted as 0.1.10-beta)

Repo settings — sidebar view replaces the modal

Auto-update, viewport-windowed graph, lean statusbar, landing page, quick wins, reactivity foundation, AI sessions UX, forge data fixes, settings IA polish, log rename, E2E retirement, AI sessions transcript-first rewrite (originally drafted as the first 0.1.9 cut)

In-app auto-update

Graph — viewport-windowed commit walking (MT-1)

Lean statusbar + unified tasks drawer

Persistent graph layout cache

Repo config — configure remote repo via gh/glab CLI

AI — Codex + OpenCode provider parity

Landing page (docs/)

  • site(landing): wire real screenshots with theme-aware swap and lightbox — replaces the placeholder art with actual app screenshots; each shot has dark + light variants that swap based on the visitor's prefers-color-scheme, and clicking opens a lightbox.
  • fix(landing): responsive breakpoints for mobile + tablet — layout no longer breaks under 768 px.

Forge — pre-releases + closed PRs on GitHub

AI sessions polish (pre-Wave A)

Quick Wins bundle

  • Shared empty-state partialfeat(frontend): shared empty-state partial + descriptions for issues/pipelines/releases. Empty states across Issues, Pipelines, and Releases now render from one primitive with per-view descriptive copy, instead of three near-duplicate empty blocks.
  • Picker consolidationrefactor(mr-pr): migrate MrPrDetail to common/LabelPicker, chore(mr-pr): drop duplicate LabelPicker component, refactor(issues): adopt shared dialog chrome in AssigneePicker, refactor(mr-pr): adopt shared dialog chrome in ReviewerPicker. The MR-PR view stops shipping its own LabelPicker copy; AssigneePicker and ReviewerPicker drop their hand-rolled dialog styles in favour of the shared chrome.
  • .btn-icon refactor — nerd-font glyph-only buttons across ShortcutOverlay, CommitDetail, BlameView, StagingDiffEditor, TriggerWorkflowDialog, PipelineView, TaskPopover, TaskPanel, and the dialog close buttons in RebaseEditor / CreateReleaseDialog / CreateIssueDialog / TriggerWorkflowDialog now share one .btn-icon class on dialog.css. refactor(frontend): resolve .btn-icon naming collisions cleans up the one place two different components had collided on the name.
  • Graph refresh on branch-from-context-menufix(graph): reload graph after creating a branch from context menu. The graph didn't pick up branches created from the context menu until the next manual refresh; it now reacts immediately.
  • Console-noise cleanupfix(frontend): route theme/branch errors through toast, drop console noise. Theme-load and branch-switch failures used to log to the console and vanish; they now surface in the toast system where the user actually sees them.
  • E2E on feature branchesci: run E2E suite on all branches, not just main/beta. Catches regressions before the beta merge, not after. (Later disabled temporarily while the 2026-04-21 spec set lands, then removed entirely with the Spec-6 E2E retirement below.)

Tests — app-core command coverage

Wave two — the 2026-04-21 / 2026-04-22 spec cycle

AI sessions — transcript-first rewrite (Spec 7, 2026-04-22)

  • New AiConversation type (Rust ai_provider::AiConversation, TS mirror) — id, provider, cwd, created/last_activity unix-ms timestamps, title, optional parent_id 8-char prefix when the transcript was forked.
  • New AiProvider::list_conversations trait method. list_sessions and is_session_active deleted from the trait; AiSession the type stays (background runs still use it).
  • Claude Code reads ~/.claude/projects/{cwd-slug}/*.jsonl. Title walks for the first non-meta type: "user" record with extractable text (supports both string content and {type, text} arrays; skips <command-name> / <local-command-caveat> envelopes). Fork detection from the first record's parentUuid.
  • Codex walks ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl, parses the first-line session_meta payload for id/cwd/timestamp. Title is MVP-empty (follow-up extracts the first user prompt from later lines).
  • OpenCode shells out to opencode session list --format json and maps directory → cwd, updated → last_activity_at, title verbatim.
  • Two new Tauri commandsai_list_conversations and ai_resume_conversation. The old ai_list_sessions / ai_resume_session / aiListSessions / aiResumeSession are deleted.
  • Two-section UIAiSessionList.svelte renders Active (BeardGit-owned terminal tabs + segments + running/queued bg-runs) above Conversations (on-disk transcripts for the current repo). Mutually-exclusive selection between conversation and bg-run in the detail pane. Row action for Conversations is labelled "Resume in new terminal" with a tooltip naming the forking semantics explicitly.
  • Dropped the ~/.claude/sessions/{pid}.json scanner, is_claude_process, process_alive, is_file_active, AiSessionWatcher pointing at the dead PID dir (repointed to ~/.claude/projects/ + ~/.codex/sessions/).
  • i18n — 10 new ai_sessions_* keys in en-US + es-ES (peninsular tuteo). ConversationRow is keyboard-reachable (role="button" + Enter/Space).
  • Testing: Rust workspace 1 045 tests (−31 for deleted legacy tests), Vitest 653 tests across 91 files (−25 for deleted stores), clippy clean, svelte-check 0/0.

Reactivity & feedback foundation (Spec 1)

  • New mutation-events Rust crateSnapshot::capture + diff, MutationGuard RAII wrapper, MutationKind enum (commit / push / stash / worktree / staging_change / ai / external / …), MutationFlags struct, emit_mutation helper. Status fingerprint tracks per-file index/worktree bitflags so staging transitions flip status_changed even when the overall dirty boolean doesn't move.
  • app-core commands wrapped — every mutating Tauri command (commit / amend / branch / tag / stash / staging / worktree / remote / cherry-pick / revert / rebase / reset / merge / conflict / clean / patch / submodule / mr_pr / releases) fires the guard on success.
  • watcher crate — debounced .git/** change → MutationKind::External so CLI edits outside BeardGit still refresh the UI.
  • AI background runs — coordinator captures pre/post worktree snapshots and emits MutationKind::Ai { source } on completion / failure / cancellation.
  • TS mutations.ts store — single project-mutated listener coalesces events per rAF tick, buffers flags per project path, flushes on tab switch, dispatches the minimal refresh set to graph / changes / stashes / worktrees / repoConfig.
  • runMutation wrapper — caller-side toast + task-record seam. Silent-set (stage / unstage / discard) suppresses success toast. Failures are sticky with a See details action that opens the Tasks popover at the failing task's detail panel.
  • Graph cache-first paint — per-project GraphViewportCache slice persisted in project-cache.ts; synchronous hydration on cold start; faint skeleton stripes while first paint resolves; HEAD-OID reconciliation preserves scroll anchor when new commits land above the cached top.
  • Statusbar provider filter — new projectProvider derived store. repoConfig wins; otherwise inferred from origin URL (github.com, gitlab.*). Renders 0 or 1 pill, never both.
  • Tasks popover regression fixed — click-bubble race where the opening click hit the outside-click handler on the same frame. Rising-edge ready latch tied to the open transition.

AI sessions UX pass (Spec 2)

  • ProviderIcon shared component + brand SVG assets for Claude Code, Codex, OpenCode, and a generic fallback. No enclosing background square — brand logos render at native transparency.
  • AiSessionList — shell paints immediately, refresh fires fire-and-forget in onMount. Row layout: 8 px padding, vertically-centered 20 px icon slot, External badge when worktree_path is missing or unreachable.
  • AiSessionDetail — populates on selectedBackgroundSessionId change. Header uses ProviderIcon. Open-in-terminal / Cancel / Discard routed through runMutation with sticky-failure toasts.
  • AI Settings, TabBar, AiSlot, TaskEntryRow — migrated to the shared ProviderIcon; generic nerd-font glyphs retired.
  • i18n — en-US + es-ES keys for the new toast labels.

Forge data fixes (Spec 3)

  • ForgeDetailShell — shared loading / error / empty / content state primitive used by both MrPrDetail and ReleaseDetail.
  • withTimeout helper + TimeoutError — 15 s bound on detail fetches; errors surface via a new per-detail error store (mrPrDetailError / releaseDetailError) + sticky toast with a Retry action.
  • PR #18 infinite loading — root cause traced to unbounded gh api --paginate for ~3 400-file diffs. Fix: 50 MB payload cap + 20 s subprocess timeout in cli-provider via wait-timeout. Frontend's 15 s withTimeout is the outer guard.
  • Release-blank#[serde(default)] didn't accept explicit JSON null. New null_as_default deserializer handles null body / assets from gh/glab payloads.
  • Empty-state copy — "No changes in this pull request." / "No release notes or assets published for {tag}."

Settings IA polish (Spec 4)

  • LookAndFeelSection.svelte extracted from GeneralSettings. General now owns a single Look & Feel card (no duplicate blocks).
  • Appearance tab removed — collapsed into General; legacy appearance deep-links redirect to general.
  • Editor/Diff tab removed — the placeholder page wasn't implemented; legacy editor deep-links redirect to general.
  • CATEGORY_IDS reduced to general / git / ai / integrations / advanced.
  • AI Settings — stray broken-glyph refresh button deleted; provider icons verified wired to ProviderIcon.
  • ConnectionHowTo — reworked as a compact top-level dropdown (OAuth / PAT / CLI modes) rendered above the card, not inside.
  • Integrations Connections — unified single Card with a new ConnectionRow primitive dispatching on kind (github / gitlab / gh / glab). CliAuthSection.svelte and ProviderSetup.svelte deleted.

Log filename convention (Spec 5)

E2E infrastructure retired

  • Deleted e2e/ directory (specs, fixtures, page objects, Dockerfile, run scripts).
  • Dropped the e2e-tests job from .github/workflows/ci.yml.
  • Removed @wdio/* devDependencies + the npm run e2e* scripts from package.json.
  • Dropped the window.__E2E__ surface + VITE_BEARDGIT_E2E gate from src/routes/+layout.svelte.

Auth & Integrations

  • Connecting a forge PAT now also logs the matching gh/glab CLI in automatically via a fire-and-forget background task; disconnecting logs it out. Users get both API and CLI auth in one action, under the same identity.
  • ConnectionHowTo PAT guidance rewritten: defaults to the PAT mode, explicit warning against fine-grained tokens (they break gh/glab via missing GraphQL support — see cli/cli#6680), SSO callout for GitHub orgs, and a collapsed "manual login command" reference block with <YOUR_PAT> placeholders and a no-history read -rs variant.
  • CLI row in the Connections card now labels itself "Connected · via {Provider} PAT" when its authenticated username matches a connected forge provider — surfacing that both halves share one identity.
  • Removed orphan programmatic OAuth device-code flow (cli_login Tauri command, start_cli_login, OAuthLoginProcess, OAuthLoginInfo, oauth-device-code event emit). Superseded by the terminal-hosted gh auth login flow shipped with xterm.js in v0.1.5 and the new PAT-pipe flow above.

Wave A polish — buttons, markdown, AI sessions

  • Button primitive. ui/Button gains a subtle variant (theme-token tonal fill + accent-blue hover border) that sits between primary and ghost for actions that need to read as actionable without going loud. secondary's baseline moved from --overlay-hover (a hover state, never a fill) to --bg-secondary — fixes the task-row action buttons rendering as near-white on the default dark theme. ConnectionRow.svelte fully migrated: every button routes through the primitive, 39 lines of shadowed local .btn-* CSS gone. Manage button no longer reads as disabled.
  • Markdown renderer. Release / Issue / MR-PR detail bodies now render full GitHub-flavored markdown (fenced code blocks, tables, task lists, autolinks, strikethrough) via marked. Replaces the minimal snarkdown renderer that dropped or garbled those elements. Sanitiser extended to allow <input type="checkbox"> for task lists and to rewrite href="javascript:…" to href="#". Scoped .body styles added per detail component using theme tokens.
  • AI Sessions interactions. Fixed four bugs that made the view feel broken: clicking an external (provider-reported) session now populates the detail pane (was always empty — the derived selection only looked in the background-run map); the detail pane gains real Focus / Open-Terminal / Dismiss actions instead of a placeholder "external terminal" label; the Focus button on a composite-tab-hosted terminal now sets both activeTabIndex and the composite's activeSegmentIndex so the user actually sees the terminal; the merged-sessions list dedupes by (provider, cwd) for active-interactive rows so a single Claude process doesn't show as two entries with different IDs. Tier/focus/resume logic extracted to a shared aiSessionActions.ts so list and detail share one implementation.

Testing

  • Rust: 1 034 tests across the workspace, clippy clean on --workspace --all-targets -- -D warnings.
  • Frontend: 639 Vitest tests across 90 files.
  • svelte-check: 0 errors / 0 warnings across 2 989 files.
0.1.8 esta versión

Phases 6–10: Bisect, CLI Auth, AI Stack, Forge Integration, Bundled CLIs, Refactor, E2E, Performance

AI Background Worktree Runs (Phase 10)

  • ai-providerAiBackgroundRunInput + AiBackgroundRunStatus + AiTokenUsage types; launch_background trait method with NotSupported default; MockProvider override for tests.
  • claude-code / codex / opencode — headless command builders with provider-specific flags (Claude: --print --output-format stream-json --verbose; Codex/OpenCode: prompt concatenation fallback where skill/prompt flags aren't native).
  • task-runnerTaskKind::AiBackground variant + spawn_with_options with stdin piping (backwards compatible — spawn() unchanged).
  • app-coreAiBackgroundCoordinator with full lifecycle (Queued → Running → Completed / Failed / Cancelled), concurrency cap enforcement, worktree creation via git-engine and cleanup on discard. 6 Tauri commands + 2 settings commands.
  • git-enginecreate_worktree_at helper used by the coordinator.
  • storageAppConfig gains ai_worktree_root, ai_background_concurrency_cap, ai_prompt_auto_accept fields with serde defaults.
  • FrontendCreateBackgroundRunDialog with Free / Saved / Skill tabs, BackgroundRunStatusBadge, BackgroundRunTranscript with ANSI stripping, session detail + list integration, settings card, ~50 i18n keys per locale. aiBackground.ts store wires ai-background-output / ai-background-status events via requestAnimationFrame batching (matches the tasks.ts pattern); merges live runs into aiSessions for unified sidebar display.
  • Testing — 13 tests in app-core::ai_background (coordinator lifecycle + cap + cancel + discard), 5 in ai-provider, 3 per provider for argv builders, 7 vitest tests for the store. Known follow-ups for a later release: "View changes" button (deferred — merge-editor expects a conflict state, current release ships "Switch to worktree tab" as the review path), toast notification event wiring (i18n keys present), and an end-to-end spec exercising the full dialog (placeholder at e2e/specs/regression/ai-background.spec.ts).

Beta Audit — Performance & Code Quality

  • Cache which::which() results per provider kind on AppState — repeated provider detection no longer hits the filesystem.
  • Replace task polling loops with TaskManager::wait_for_terminal backed by tokio::sync::Notify — no more spin-wait on long-running tasks.
  • Memoise Arc<dyn ForgeProvider> keyed on (provider_index, project_path) — repeated forge lookups skip the construction cost. Correctness
  • Populate the GitLab label cache so issue labels render with their real colour.
  • Unify MrPr.labels with Issue.labels on Vec<Label>; PillRow now renders the real label colour everywhere.
  • Drop redundant refreshIssueList calls on label / assignee / milestone mutations — the optimistic update already covers it.
  • Route resolve_startup_theme through src/lib/api/tauri.ts for consistency with every other IPC call.
  • Key #each blocks over MR/PR diff files + comment lists for stable Svelte reconciliation. Code quality
  • Extend the trait-crate purity CI guard to include ai-provider (alongside provider and forge-provider).
  • Share build_gh_upload_args / build_glab_upload_args across crates instead of duplicating the argv shape.
  • Add TaskManager::get_status and a frontend taskById derived map for O(1) status lookup.
  • Move shell_escape into helpers.rs with unit tests.
  • Rename MrPrComment to ForgeComment in TypeScript (deprecated alias kept for one release).
  • Extend MrPrFilter with author / label / text fields, matching IssueFilter.
  • Rename the render:text perf measure to render:badges-and-text to match what it actually measures.

GitLab Provider Polish

  • Per-file +/- countsprojects/:id/merge_requests/{n}/diffs returns the raw patch but no additions/deletions counts. We were hardcoding 0/0, which showed as "+0 -0" beside every file in the MR detail panel. New count_patch_changes parser counts + / - content lines while skipping +++ / --- file headers and @@ hunk headers. 4 unit tests.
  • glab mr list boolean state flags — glab (both 1.46.1 and 1.92.1) does not accept --state <value>; passing --state opened made glab reply "Unknown flag" and our list returned empty. Switched to the boolean form glab actually supports: default → opened, --closed, --merged, --all. Dropped the unused state_to_glab_str helper.
  • Provider-aware sidebar label — sidebar "Merge Requests" now reads "Pull Requests" when the active provider is GitHub. View id stays merge-requests so routing is unchanged; only the label swaps. New sidebar_pull_requests i18n key.
  • MR/PR list errors are surfacedrefreshMrPrList() no longer swallows failures. Errors go to a new mrPrListError store and MrPrList renders them inline with a Retry button instead of an empty list with no explanation.

Settings — Connection Guide

  • "How to connect" guide — collapsible help block in Settings → Connection covering standard gitlab.com / github.com setup (PAT + CLI flows), self-hosted GitLab with OAuth and token fallback, plus troubleshooting for the multi-config warning and the 404-when-self-hosted-points-at-gitlab.com trap.

Distribution

  • macOS x64 dropped from the release matrix — Apple Silicon runners are now the only macOS target. Reduces CI matrix time and avoids the dual-bundle confusion at install time.
  • Bundle formats trimmed.msi, .deb, and .rpm removed from the bundle list. The .dmg, .AppImage, and .exe remain as the supported install paths per platform.
  • First-launch documentation — README now explains the unsigned-build workaround for macOS until code signing lands (Gatekeeper right-click → Open). E2E fixture path no longer pins to a hardcoded location; derived from the working directory at runtime so contributors can run the suite from anywhere.
  • Repository hygiene — AI assistant artifacts (.claude/, .codex/, etc.) untracked from the repo and added to .gitignore.

Forge Integration (Phase 8)

Bundled CLI Binaries (Phase 7.2)

Terminal Enhancements (Phase 7.1)

  • OSC 7 cwd auto-detection — terminals emit their current working directory on every prompt; when the cwd matches an open project path, the terminal tab auto-links to that project's composite tab.
  • AI provider auto-detection — a lightweight polling loop detects when a terminal launches claude, codex, or opencode and updates the tab label plus brand icon dynamically.

UI Polish & Bug Fixes (Phase 7.6)

  • Bisect graph integration — good/bad/current/skipped commits get colored overlays in the canvas graph; right-click a commit for "Mark as good / bad / skip".
  • Worktree lock / unlock — full git worktree lock / unlock wired through git-engine with context menu controls.
  • Worktree "Open in graph" — navigates the graph view to the worktree's branch.
  • AI Config Editor live reload — new watcher::ai_config module picks up external edits to settings.json, agents/*.md, skills/*/SKILL.md, and CLAUDE.md files; the editor refreshes without losing in-progress changes.
  • AI Sessions "Focus" — focuses the linked terminal tab if the session has one; otherwise launches claude --resume <sessionId> in a new PTY terminal.

Infrastructure (Phase 7.5)

  • Log rotationstorage::logging::purge_old_logs() auto-removes beardgit.*.log files older than 7 days on startup (async, non-blocking). Legacy beardgit.log.* files from pre-rename installs are also purged by age.
  • Tracing on git writes — 41 #[instrument] spans on git-engine write operations (bisect / operations / conflict / reset / clean / remote / worktree / submodule / interactive_rebase). Sensitive fields (commit bodies, PR descriptions, PAT tokens) excluded via skip(...).
  • Tracing on Tauri commands — 80 #[instrument(name = "cmd::…")] spans across 19 command modules. Hierarchical names make log grepping trivial.

Performance (Phase 7.7)

  • Graph render profiler — six performance.mark pairs around the render loop plus a dev-only FPS overlay toggled with Ctrl+Shift+P. Measurement infrastructure for future optimisations without runtime overhead in production bundles.
  • Interactive terminal pool — 3-deep xterm.js instance pool recycles terminals across tab open/close. Faster tab spawn, lower GC pressure.
  • CodeMirror language cache — module-level Map<string, Extension> short-circuits repeated dynamic imports per file extension. Second-and-subsequent opens of a language are instant.

Code Quality (Phase 7.3)

E2E Testing (Phase 7.4 + follow-up)

Provider Architecture Cleanup (Phase 9)

Security

  • npm audit --audit-level=moderate clean. Override added for serialize-javascript (wdio transitive dep) that upstream hadn't patched yet.

Tooling

  • npm run e2e:docker (plus :rebuild and :shell) — one-command local E2E.
  • e2e/README.md documents the happy-path authoring pattern so test authors have a template. ---

Phase 6 — Bisect, CLI Auth, AI Views, Multi-Provider, Code Quality

  • Visual bisect workflow with good/bad/skip controls and progress indicator
  • Auto-bisect mode: provide a test command, BeardGit runs git bisect run and reports the culprit
  • New git-engine bisect module with full lifecycle (start, good, bad, skip, reset, log)
  • 8 new Tauri commands, dedicated store, 2 Svelte components (BisectWorkflow, AutoBisectDialog) CLI Auth (gh/glab)
  • gh auth status and glab auth status detection — shows CLI login state in Settings
  • Terminal-based login flow: "Login with CLI" opens interactive gh auth login / glab auth login in a PTY tab
  • Unified Authentication settings page combining Token Auth and CLI Auth sections
  • New cli-provider auth module with status parsing and terminal login commands AI Config Editor
  • Dual file tree (project-scoped + user-scoped) showing all AI config files
  • Editable CodeMirror pane for settings.json, agents, skills, and CLAUDE.md files
  • Create Config dialog for adding new agent/skill/settings files
  • 3 new Tauri commands: ai_get_config_content, ai_save_config_content, ai_create_config_file AI Sessions
  • Project-scoped session list showing active and recent Claude Code sessions
  • File watcher on ~/.claude/sessions/ with auto-refresh on changes
  • Session metadata: model, start time, duration, token usage, status (active/completed) AI Worktree Enrichment
  • EnrichedWorktree type combining git worktree data with AI provider status
  • AI badges on worktrees created by Claude Code / Codex / OpenCode
  • Context menu with cleanup action for orphaned AI worktrees Codex & OpenCode Providers
  • New codex crate: full AiProvider implementation with binary detection, command building, and config discovery
  • New opencode crate: full AiProvider implementation with binary detection, command building, and config discovery
  • Both wired into app-core provider factory with automatic detection
  • Dynamic terminal dropdown: only shows providers detected on the system
  • Codex brand color corrected (#10a37f → #ffffff) Structured Error Logging
  • Structured file logging via tracing with tracing-appender daily rotation
  • Logs written to ~/.local/share/com.beardgit.app/logs/ (platform-appropriate data dir)
  • New ErrorDialog component with copy-error-to-clipboard and open-log-file actions
  • All dialogs (Confirm, Clean, CreateMrPr, PatchPreview, TagCreate, CreateWorktree) upgraded with error display Composite Tab Upgrade
  • Multi-segment tabs: N terminals + worktrees per project in a single composite tab
  • Fixed segment ordering: Project → Worktrees → AI Terminals → Terminals
  • Terminal button always adds to the active project's composite tab instead of creating standalone tabs Code Quality — commands.rs Split
  • Split monolithic commands.rs (3,267 LOC) into 24 feature-based modules under commands/
  • Modules: advanced, bisect, branch, ci, clean, cli_auth, commit, config, conflict, diff, gitignore, graph, helpers, logging, mod, mr_pr, patch, project, provider_auth, reflog, remote, repository, settings, staging, stash, submodule, tag, theme, worktree
  • Extracted shared dialog.css (93 lines) replacing duplicated dialog styles across 7 components
  • New fetchIntoStore utility for consistent store-loading patterns E2E Test Infrastructure
  • WebdriverIO + tauri-driver configuration for end-to-end testing
  • Fixture repo setup script (e2e/fixtures/setup.sh) for reproducible test environments
  • Page objects: sidebar.page.ts, graph.page.ts
  • Initial specs: app-launch.spec.ts, navigation.spec.ts Bug Fixes & Polish
  • AI Config file tree correctly distinguishes project vs user scope
  • AI Sessions auto-cleanup on component destroy (watcher unsubscribe)
  • CreateConfigDialog validates file paths and prevents duplicates
  • Store helpers centralized with fetchIntoStore reducing boilerplate across stores