Skip to content
gwmgwmgwmv1.10.0

v0.9.0

Stable promotion of the full 0.9.0 release train. This consolidates every pre-release delta from rc.1, rc.2, and rc.3 into the stable notes. The cycle is a TUI depth pass on a new async foundation: rc.1 finished the theme-role coverage the 0.8.0 series started (dedicated name / path and staged / modified / untracked roles, every draw_* site threaded) and landed the git2 0.20 → 0.21 source migration; rc.2 reworked the TUI chrome (contextual statusbar, direct pane-focus keys 1 / 2, off-thread GitHub fetch, a full modal-readability pass, stacked-by-default sidebar); and rc.3 introduced the generic async-task spine (coalescing + late-result drop) and built three in-TUI panels on top - a Command Logs overlay (3), a Configuration panel (4) with per-row source attribution, and a sync action (S) - plus moving the GitHub fetch onto the spine (fixing a stale-data race) and a fuzzy-filter / command-palette polish. The stable delta after rc.3 adds off-thread bootstrap (b) and an open-docs key (.), and fixes a CI-flaky test.

Install with cargo install gwm --version 0.9.0, cargo binstall gwm, or from the release archives. MSRV is 1.82.

No .gwm.toml migration is required - 0.9.0 adds no config-schema change. Every new TUI key is rebindable under [tui.keys] and reachable by name through the command palette (:): 1 / 2 (pane focus), 3 (command logs), 4 (config panel), S (sync), b (bootstrap, now off-thread), and . (open docs). All defaults are preserved, so an existing keymap and an omitted [theme] see no change.

  • Open docs in the browser: press . in the TUI to open the gwm documentation in your default browser, reusing the same OS-opener path as the O issue/PR open menu. The target is the repo’s docs/ tree on GitHub, derived from the crate’s repository so a fork resolves to its own docs; rebindable via [tui.keys] open_docs and reachable by name through :open-docs. (#233)
  • Off-thread bootstrap: pressing b to re-run bootstrap on the selected worktree now runs bootstrap::run (file copies, guards, command hooks) on the shared async-task spine instead of blocking the event loop, so a slow bootstrap no longer freezes the TUI. The statusbar spinner animates with a bootstrapping… label and q / Esc stay responsive; the Report view opens when it finishes. The TOFU trust gate still runs synchronously on the main thread before the worker spawns, and a second b press while one is in flight coalesces. (#256)
  • ✅ Fix a CI-flaky test: refresh_github_status_auto_detects_pr_for_unlinked_branch rewrote a single fake-gh script in place between two refreshes while an off-thread pr view worker could still be executing it (Linux ETXTBSY → None). Now writes two distinct write-once scripts. No production change. (#248)

Historical delta: [0.9.0-rc.1] - 2026-06-02

Section titled “Historical delta: [0.9.0-rc.1] - 2026-06-02”

Delta against v0.8.0 (the previous stable). Merged PRs: #204, #207, #209, #212, #213, #215, #208.

Opens the 0.9.0 cycle by finishing the theme-role coverage the 0.8.0 series started: the #170 audit threaded the resolved [theme] through every draw_* site, and this RC promotes the last structural Color::White / Color::Gray chrome into dedicated roles (name, path) and decouples the working-tree git-status families into their own staged / modified / untracked roles - so a preset or override now recolours the whole TUI with no hard-coded literals left on the named surfaces. Underneath, the git2 0.20 → 0.21 source migration lands, a serde-stack bump clears RUSTSEC-2025-0068, and the README landing page is synced to the v0.8.0 surface. Every default is preserved, so users who omit [theme] see no visual change; the new roles are pinned site-by-site by tests/tui_theme_audit_tests.rs.

  • 🎨 Add the name and path theme roles, promoting the last structural chrome the #170 audit left on hard-coded Color::White / Color::Gray (#210, follow-up to #170 / #33). name (default White, bold) colours the worktree name in the table and the sidebar header plus the Issue #N / PR #N summary heads; path (default Gray) colours the table’s path column - a structural mid-grey kept distinct from muted (DarkGray). Each default equals the previous literal, so users who omit [theme] see no change, and both roles are overridable / preset-driven like any other (all four built-in presets now define them - the claude-dark chrome maps to the palette’s --text / --text-dim (#214) - and gwm theme show dumps them). The sidebar identity-card path intentionally stays on muted - moving it to path would shift its default appearance. The genuinely non-name structural Color::White (help/step labels, the not-yet-fetched dot) and Color::Reset (unlinked marker) still carry no role. Pinned by tests/tui_theme_audit_tests.rs (worktree_name_style / worktree_path_style and the summary heads resolve through the new roles, with a default-preservation guard).
  • 🎨 Add the staged, modified, and untracked theme roles for the working-tree status panel (#211, follow-up to #170 / #33). working_tree_status_line previously borrowed the nearest semantic role - staged → accent, worktree-modified → dirty, untracked → clean - so recolouring a focus highlight (accent) silently recoloured staged files, and a branch-divergence warning (dirty) recoloured modified files. The three families now read their own roles (defaults Cyan / Yellow / Green, equal to the colours they used to borrow), decoupling the signals. All four built-in presets seed the new roles from their previous accent/dirty/clean so a preset’s working-tree panel is unchanged; gwm theme show dumps them. Badges (PR / issue ●) deliberately keep their existing semantic mapping. Pinned by tests/tui_theme_audit_tests.rs, whose status-family fixture now asserts the roles are distinct from accent/dirty/clean (so a regression to the borrow fails) plus a default-preservation guard.
  • 🎨 Thread the resolved [theme] through every draw_* / render helper so role overrides apply everywhere (#170, follow-up to #33 / #168). Before this audit the theme reached App.theme but several render sites still painted hard-coded Color::* literals, so overriding a role (branch, dirty, locked, …) left them unchanged: the worktree table (marker, branch/status cells, age, selection background, column header), the sidebar identity card (branch line, badges, created/freshness, path), the working-tree status lines, the commit-graph nodes/connectors (now the branch role), recent-commits hash/initials, stash list, the / filter prompt, the header picker chip + path, the footer hint labels + status, and the issue/PR badge colours. Each literal maps to the role whose default equals it, so users who omit [theme] see no change. At audit time the structural Color::White (primary text), Color::Gray (table path), and Color::Reset (unlinked marker) carried no semantic role; the White / Gray chrome was subsequently promoted to the dedicated name / path roles in the same release (see #210 under Added), leaving only Color::Reset and a few non-name labels unthemed. The audit also closes a latent inconsistency it surfaced: a closed issue’s summary-line badge used Color::Red while the sidebar header dot used Color::Magenta - both now resolve through issue_badge_color to the locked role so they always agree. Pinned by tests/tui_theme_audit_tests.rs, which drives each site with a synthetic per-role palette and asserts the resolved style.
  • ⬆️ git2 0.20 → 0.21 (#169). A breaking source migration, not just a version bump: git2 0.21 reworks the UTF-8 accessors Reference::shorthand / Reference::name / Remote::url / Buf::as_str to return Result<&str, git2::Error> (was Option<&str>), Commit::summary to Result<Option<&str>, _>, and StringArray::iter to yield Result<Option<&str>, _>; Oid::zero is deprecated in favour of the Oid::ZERO_SHA1 constant. All call sites now collapse the new Err arm to None via .ok() so the observable behaviour (a non-UTF-8 / absent name is treated as missing) is preserved. Side effect: git2 0.21 drops its url dependency, pruning the whole idna / icu_* transitive tree from Cargo.lock. Closes Dependabot #157.
  • ⬆️ Bump the serde-stack group: serde_yml 0.0.12 → 0.0.13 and toml_edit 0.25.11 → 0.25.12 (#208). serde_yml 0.0.13 is its final maintenance release and clears RUSTSEC-2025-0068; no source changes were needed on our side.
  • 📝 Sync the root README.md landing page to the v0.8.0 surface and fix the MSRV badge (1.80+ → 1.82+) (#203). The “what gwm does” list now covers the config CLI + user-level global config, lifecycle hooks, CLI aliases + Gitmoji, the GitHub gwm new / gwm pr workflow with PR auto-detection, safety-daily (--dry-run, gwm undo / gwm history), gwm sync, and TUI personalisation (themes / remappable keymap / command palette / stashes). Follow-up to the #199 docs refresh - landing page only, every feature keeps its dedicated section under docs/.
  • TDD throughout; the full suite is green locally and on the ubuntu-latest / macos-latest / windows-latest matrix. The theme-role additions are pinned site-by-site in tests/tui_theme_audit_tests.rs with default-preservation guards in tests/theme_tests.rs.

Historical delta: [0.9.0-rc.2] - 2026-06-06

Section titled “Historical delta: [0.9.0-rc.2] - 2026-06-06”

Delta against v0.9.0-rc.1. Merged PRs: #218, #221, #223, #225.

A TUI polish pass that lands the #217 statusbar / layout / modal rework and its three follow-up rounds (#220, #222, #224). The list view gains a contextual statusbar (context chip + animated GitHub-fetch spinner + contextual hints + the action log pinned right) and direct pane-focus keys (1 / 2, rebindable). Every modal is reworked for readability - the create-worktree form, the link prompt, the confirm-delete and Issue/PR overlays now lift their title into the frame, gain interior padding, and speak the statusbar’s badge language. The sidebar defaults to the stacked layout with per-axis split ratios, and the GitHub F fetch moves off-thread so the spinner animates instead of the event loop blocking. No CLI or config-schema change; the new pane-focus actions are the only addition to the documented key surface.

  • ✨ Direct pane-focus keys - 1 focuses the worktrees pane, 2 opens (if hidden) and focuses the status pane. Both are rebindable as focus_worktrees / focus_status under [tui.keys]; Tab keeps its focus toggle (#217).
  • ✨ Contextual statusbar - a context chip (worktrees / status / switch) on the left, an animated loading spinner while a GitHub fetch is inflight, contextual hints in the middle, and the action log pinned right with absolute priority (#217).
  • ✨ A bottom-right selected of visible counter on the worktrees pane, lazygit-style (#217).
  • ⚡ GitHub issue/PR status (F) is now fetched off-thread, so the statusbar spinner animates during the gh shell-out instead of the event loop blocking (#217).
  • 💄 The sidebar now defaults to the stacked layout (status pane under the worktrees table), and the split ratios are tuned per axis - 42% / 58% stacked, 55% / 45% side-by-side. The orientation cycle (V) is unchanged (#217).
  • 💄 Panes are labelled [1] Worktrees (N) and [2] Status (the focus mnemonics for the 1 / 2 keys), and the help overlay is titled Keybindings with a centred context subtitle in a distinct colour. Its section headers are Title-Cased (Global, List View, Issue / PR, Create Form, Confirm Delete), and the overlay now scrolls (j/k, g/G) when it outgrows the modal (#217).
  • 💄 Modal overlays lift their title off the border into the frame as a centred line and gain interior padding on every side, so no content hugs the edge; the confirm-delete buttons render as flat coloured chips (Confirm / Cancel) instead of [ Confirm ] / [ Cancel ] (#217).
  • 💄 The create-worktree modal is reworked - the branch type is a horizontal ‹ name › selector (Left/Right or Up/Down, also h / l) whose focused value reads as a highlighted chip, the issue and description fields are single-row inputs with a background surface (was 3-row bordered boxes) with vertical gaps between rows, the live branch/dir preview sits above the inputs, the modal width is capped so it doesn’t span a wide terminal, and it grows a Create / Cancel button row plus a control hint. It opens focused on the Issue field (the branch type keeps its default and stays reachable via Shift-Tab), and the issue/description fields are length-capped so the resolved branch name stays within git’s ref limit (#217).
  • 💄 The link prompt’s target step is reworked into a titled (Link) vertical selectable list - j/k (or arrows) move the highlight and Enter links the highlighted row, while i/p stay direct picks. The picker state machine and its key handler are extracted into a testable App::handle_link_prompt_key (mirroring the create modal) (#217).
  • 🐛 Polish pane/modal follow-ups - Bootstrap Report logs now sit in a nested pane, sidebar titles surface their live bindings (Issue / PR [F], Working Tree [R], Recent Commits [l]), GitHub loading uses an animated loader, Link/Open modals are wider without a Refresh button, modal hints use statusbar-style badges, and Delete Worktree honours the delete-branch toggle binding while open (#224).
  • 🐛 Polish remaining modal follow-ups - Delete Worktree casing and toggle badges, shared Link / Open Issue/PR layouts, and horizontal Keybindings scrolling with clearer section spacing (#222).
  • 🐛 Polish modal follow-ups after the statusbar pass - compact Link prompt with chip selection, clearer create-Issue feedback, distinct Keybindings section colour, and aligned Confirm Delete details (#220).
  • 🐛 The sidebar Issue/PR block prompted press R to fetch status, but R runs the review launcher - it now resolves the live fetch_github binding (F by default) (#217).
  • TDD throughout; the full suite is green locally and on the ubuntu-latest / macos-latest / windows-latest matrix. The statusbar, pane-focus, and modal reworks are pinned by pure state-machine / helper tests (tests/tui_app_tests.rs, tests/tui_footer_tests.rs, tests/tui_header_tests.rs, tests/tui_ui_helpers_tests.rs, tests/tui_theme_audit_tests.rs) - no ratatui backend needed.

Historical delta: [0.9.0-rc.3] - 2026-06-07

Section titled “Historical delta: [0.9.0-rc.3] - 2026-06-07”

Delta against v0.9.0-rc.2. Merged PRs: #254, #259, #260, #261, #263, #264, #249.

The async-task release. A new generic off-thread spine (coalescing + late-result drop) lands as the foundation, and the worktree list refresh (f / r) is the first consumer - a large repo no longer freezes the TUI mid-refresh, the statusbar spinner animates instead. Built on it: the GitHub issue/PR fetch (F) moves onto the same spine (one off-thread mechanism, not two) and its per-key generation counter fixes a stale-data race. Three new in-TUI views complete the 1/2/3/4 pane-key family - a Command Logs overlay (3), a Configuration panel (4) with per-row source attribution, and a sync action (S) that rebases the selected worktree off-thread. The fuzzy filter and command palette get a polish pass, and an internal refactor / perf sweep (#235–#244) trims duplication and drops a per-frame sidebar clone. No CLI or config-schema change; S, 3, and 4 are the only additions to the documented key surface, all rebindable.

  • ✨ Off-thread worktree list refresh - pressing f / r now re-lists on a background worker instead of blocking the event loop, so a large repo or slow filesystem no longer freezes the TUI mid-refresh. The statusbar spinner animates with a refreshing worktrees… label and q / Esc stay responsive; a failed re-list surfaces on the status bar instead of tearing down the session. Built on a new generic async-task spine (coalescing + late-result drop) that the GitHub fetch, bootstrap, and future panels can adopt (#231).
  • ✨ Command Logs overlay - press 3 for a lazygit-style, scrollable transcript of the external commands gwm ran (resolved command line, duration, exit status, captured output), newest-first over a ~90% fullscreen modal. Scrolls like the help overlay (j/k, g/G, h/l); Esc / q / 3 closes it. It records gh GitHub calls, bootstrap shell steps, and lifecycle hooks; read-only sidebar previews are excluded to keep it signal-rich. Reachable via :command-logs, rebindable through [tui.keys] command_logs (#226).
  • ✨ Configuration panel - press 4 for a ~90% fullscreen, scrollable view of the resolved .gwm.toml (the user-level global config deep-merged under the repo file, exactly as gwm config list resolves it). Rows are grouped by [section] and each carries a colour-coded source column (repo / user / default) marking which layer won - provenance the CLI never exposed. Read-first: inline editing stays a follow-up. Reachable via :config-panel, rebindable through [tui.keys] config_panel (#232).
  • ✨ TUI sync action - press S to run gwm sync on the selected worktree (fetch + rebase its branch onto the configured upstream) without leaving the TUI. Runs off-thread on the shared async-task spine (statusbar spinner + syncing… label, q / Esc stay responsive), then refreshes the list so the new ahead/behind state shows and reports the outcome (up-to-date / rebased N commits / dirty-refused / conflict). Default key is S (s is the sidebar-mode toggle); rebindable via [tui.keys] sync and reachable by name through :sync (#258).
  • ⚡ The sidebar warm-cache draw path no longer deep-clones the cached sections on every frame - it renders the cached lines by reference. On a 300-commit sidebar this is ~19% faster per frame, with no perceptible change otherwise (#238).
  • ♻️ The off-thread GitHub issue / PR fetch (F) now runs on the same shared async-task spine as the worktree refresh instead of its own channel - one off-thread mechanism, not two. Behaviour-preserving for the happy path; the spine’s per-key generation counter is what fixes the race below (#255).
  • 💄 The fuzzy filter (/) now renders inside the worktrees pane title instead of a separate row below the table: the /query, cursor, and (visible/total) ratio read in the pane border, attached to the list they narrow. The standalone filter bar is gone, giving the table one more row (#262).
  • 💄 The command palette (:) input moved to the top of the modal (above the matches) and adopts the New Worktree modal’s background-filled input style, so the palette reads input-then-results like the create form (#262).
  • 🐛 GitHub status refresh no longer shows stale issue / PR data after a quick re-fetch. Pressing F (or navigating away and back) while a fetch was still in flight could let the older worker’s result win the race and overwrite the fresh one, because the previous dedupe had no per-fetch generation. The fetch now rides the async-task spine, so a superseded worker’s late result is dropped regardless of arrival order (#255).
  • ♻️ A refactor / test-net sweep ahead of the async-task work - repo-context prelude and run_git promotion, summary-line / centered-abs dedup, PR-base resolution to the worktree, dead-helper removal, and a modal-render test net (#235–#244). No observable behaviour change.
  • TDD throughout; the full suite is green locally (env-minimal PATH) and on the ubuntu-latest / macos-latest / windows-latest matrix. The async-task spine, the three in-TUI panels, and the sync action are pinned by pure state-machine / helper tests (tests/tui_state_async_task_tests.rs, tests/tui_app_tests.rs, tests/sync_tests.rs, tests/keymap_tests.rs, tests/tui_ui_helpers_tests.rs) - no ratatui backend needed.