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.
Stars on the branches you use, and submodules all the way down
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..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.--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.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... in it would have opened a tab on an arbitrary directory. Both sides are resolved now and a path outside the repository is refused..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.Two switches, a terminal that draws what the shell drew, and the CLIs you actually ship
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.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.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.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.Sections that remember, an editor that keeps its place, and a graph that draws what git did
master's, in the report — ending in mid-air.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.Themes you can read, three data-loss fixes, and an updater that finishes
Mod+E opens whatever the diff pane is holding.#[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.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.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..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.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.[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.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.Off the UI thread, per-tab state isolation, and broader error codes
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.){ 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.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..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.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.)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.)Instant on huge repos, commit signing, a compare view, and branch cleanup
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.[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.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.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.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.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.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.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..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.(branch tip, upstream tip) and recomputed only for branches whose tips actually moved..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.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.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.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.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.Fix phantom rebase toolbar
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.)Rebase recovery, live change detection, and a Windows-safe version scheme
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.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.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.Clearer hierarchy, a grouped sidebar, and a friendlier first run
Design identity overhaul: own themes, merged title bar, coherent diffs
color-scheme was never set, now synced per theme). Markdown task-lists in issue/PR/release bodies re-skin to the same checkbox visual.--font-size-* scale, and the graph canvas uses deliberate type: ref badges, SHA and date columns in Fira Code, prose in the system sans.Staging diffs that stay fresh + sidebar reorder that works everywhere
Audit hardening pass + a resizable, selectable diff panel
Per-theme accents, command palette, security & performance pass
In-app mini editor + per-file discard
Requests panel + brand mark refresh
AI tasks in the drawer + persisted reviews, landing & community polish
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).Init repo on folder open + consolidated post-0.1.8 work
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."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.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.ProviderIcon picks the right one off $activeTheme.meta.mode so the logo stays legible on both dark and light themes.<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.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.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.feat(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.refactor(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.fix(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.fix(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.ci: 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.)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.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/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/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 session list --format json and maps directory → cwd, updated → last_activity_at, title verbatim.ai_list_conversations and ai_resume_conversation. The old ai_list_sessions / ai_resume_session / aiListSessions / aiResumeSession are deleted.AiSessionList.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.~/.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/).ai_sessions_* keys in en-US + es-ES (peninsular tuteo). ConversationRow is keyboard-reachable (role="button" + Enter/Space).mutation-events Rust crate — Snapshot::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..git/** change → MutationKind::External so CLI edits outside BeardGit still refresh the UI.MutationKind::Ai { source } on completion / failure / cancellation.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.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.projectProvider derived store. repoConfig wins; otherwise inferred from origin URL (github.com, gitlab.*). Renders 0 or 1 pill, never both.ready latch tied to the open transition.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.ProviderIcon; generic nerd-font glyphs retired.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.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.#[serde(default)] didn't accept explicit JSON null. New null_as_default deserializer handles null body / assets from gh/glab payloads.LookAndFeelSection.svelte extracted from GeneralSettings. General now owns a single Look & Feel card (no duplicate blocks).appearance deep-links redirect to general.editor deep-links redirect to general.CATEGORY_IDS reduced to general / git / ai / integrations / advanced.ProviderIcon.ConnectionHowTo — reworked as a compact top-level dropdown (OAuth / PAT / CLI modes) rendered above the card, not inside.ConnectionRow primitive dispatching on kind (github / gitlab / gh / glab). CliAuthSection.svelte and ProviderSetup.svelte deleted.e2e/ directory (specs, fixtures, page objects, Dockerfile, run scripts).e2e-tests job from .github/workflows/ci.yml.@wdio/* devDependencies + the npm run e2e* scripts from package.json.window.__E2E__ surface + VITE_BEARDGIT_E2E gate from src/routes/+layout.svelte.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_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.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.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.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.--workspace --all-targets -- -D warnings.Phases 6–10: Bisect, CLI Auth, AI Stack, Forge Integration, Bundled CLIs, Refactor, E2E, Performance
ai-provider — AiBackgroundRunInput + 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-runner — TaskKind::AiBackground variant + spawn_with_options with stdin piping (backwards compatible — spawn() unchanged).app-core — AiBackgroundCoordinator 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-engine — create_worktree_at helper used by the coordinator.storage — AppConfig gains ai_worktree_root, ai_background_concurrency_cap, ai_prompt_auto_accept fields with serde defaults.CreateBackgroundRunDialog 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.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).which::which() results per provider kind on AppState — repeated provider detection no longer hits the filesystem.TaskManager::wait_for_terminal backed by tokio::sync::Notify — no more spin-wait on long-running tasks.Arc<dyn ForgeProvider> keyed on (provider_index, project_path) — repeated forge lookups skip the construction cost. CorrectnessMrPr.labels with Issue.labels on Vec<Label>; PillRow now renders the real label colour everywhere.refreshIssueList calls on label / assignee / milestone mutations — the optimistic update already covers it.resolve_startup_theme through src/lib/api/tauri.ts for consistency with every other IPC call.#each blocks over MR/PR diff files + comment lists for stable Svelte reconciliation. Code qualityai-provider (alongside provider and forge-provider).build_gh_upload_args / build_glab_upload_args across crates instead of duplicating the argv shape.TaskManager::get_status and a frontend taskById derived map for O(1) status lookup.shell_escape into helpers.rs with unit tests.MrPrComment to ForgeComment in TypeScript (deprecated alias kept for one release).MrPrFilter with author / label / text fields, matching IssueFilter.render:text perf measure to render:badges-and-text to match what it actually measures.projects/: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.merge-requests so routing is unchanged; only the label swaps. New sidebar_pull_requests i18n key.refreshMrPrList() 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..msi, .deb, and .rpm removed from the bundle list. The .dmg, .AppImage, and .exe remain as the supported install paths per platform..claude/, .codex/, etc.) untracked from the repo and added to .gitignore.claude, codex, or opencode and updates the tab label plus brand icon dynamically.git worktree lock / unlock wired through git-engine with context menu controls.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.claude --resume <sessionId> in a new PTY terminal.storage::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.#[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(...).#[instrument(name = "cmd::…")] spans across 19 command modules. Hierarchical names make log grepping trivial.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.xterm.js instance pool recycles terminals across tab open/close. Faster tab spawn, lower GC pressure.Map<string, Extension> short-circuits repeated dynamic imports per file extension. Second-and-subsequent opens of a language are instant.npm audit --audit-level=moderate clean. Override added for serialize-javascript (wdio transitive dep) that upstream hadn't patched yet.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. ---git bisect run and reports the culpritgit-engine bisect module with full lifecycle (start, good, bad, skip, reset, log)gh auth status and glab auth status detection — shows CLI login state in Settingsgh auth login / glab auth login in a PTY tabcli-provider auth module with status parsing and terminal login commands AI Config Editorai_get_config_content, ai_save_config_content, ai_create_config_file AI Sessions~/.claude/sessions/ with auto-refresh on changesEnrichedWorktree type combining git worktree data with AI provider statuscodex crate: full AiProvider implementation with binary detection, command building, and config discoveryopencode crate: full AiProvider implementation with binary detection, command building, and config discoveryapp-core provider factory with automatic detectiontracing with tracing-appender daily rotation~/.local/share/com.beardgit.app/logs/ (platform-appropriate data dir)ErrorDialog component with copy-error-to-clipboard and open-log-file actionscommands.rs (3,267 LOC) into 24 feature-based modules under commands/dialog.css (93 lines) replacing duplicated dialog styles across 7 componentsfetchIntoStore utility for consistent store-loading patterns E2E Test Infrastructuretauri-driver configuration for end-to-end testinge2e/fixtures/setup.sh) for reproducible test environmentssidebar.page.ts, graph.page.tsapp-launch.spec.ts, navigation.spec.ts Bug Fixes & PolishfetchIntoStore reducing boilerplate across stores