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.
Upgrade notes
Section titled “Upgrade notes”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.
Stable-only delta after rc.3
Section titled “Stable-only delta after rc.3”- 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 theOissue/PR open menu. The target is the repo’sdocs/tree on GitHub, derived from the crate’srepositoryso a fork resolves to its own docs; rebindable via[tui.keys] open_docsand reachable by name through:open-docs. (#233)
Changed
Section titled “Changed”- Off-thread bootstrap: pressing
bto re-run bootstrap on the selected worktree now runsbootstrap::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 abootstrapping…label andq/Escstay 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 secondbpress while one is in flight coalesces. (#256)
- ✅ Fix a CI-flaky test:
refresh_github_status_auto_detects_pr_for_unlinked_branchrewrote a single fake-ghscript in place between two refreshes while an off-threadpr viewworker could still be executing it (LinuxETXTBSY→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
nameandpaththeme roles, promoting the last structural chrome the #170 audit left on hard-codedColor::White/Color::Gray(#210, follow-up to #170 / #33).name(defaultWhite, bold) colours the worktree name in the table and the sidebar header plus theIssue #N/PR #Nsummary heads;path(defaultGray) colours the table’s path column - a structural mid-grey kept distinct frommuted(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) - andgwm theme showdumps them). The sidebar identity-card path intentionally stays onmuted- moving it topathwould shift its default appearance. The genuinely non-name structuralColor::White(help/step labels, the not-yet-fetched dot) andColor::Reset(unlinked marker) still carry no role. Pinned bytests/tui_theme_audit_tests.rs(worktree_name_style/worktree_path_styleand the summary heads resolve through the new roles, with a default-preservation guard). - 🎨 Add the
staged,modified, anduntrackedtheme roles for the working-tree status panel (#211, follow-up to #170 / #33).working_tree_status_linepreviously 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 (defaultsCyan/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 showdumps them. Badges (PR / issue●) deliberately keep their existing semantic mapping. Pinned bytests/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 everydraw_*/ render helper so role overrides apply everywhere (#170, follow-up to #33 / #168). Before this audit the theme reachedApp.themebut several render sites still painted hard-codedColor::*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 thebranchrole), 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 structuralColor::White(primary text),Color::Gray(table path), andColor::Reset(unlinked marker) carried no semantic role; the White / Gray chrome was subsequently promoted to the dedicatedname/pathroles in the same release (see #210 under Added), leaving onlyColor::Resetand a few non-name labels unthemed. The audit also closes a latent inconsistency it surfaced: a closed issue’s summary-line badge usedColor::Redwhile the sidebar header dot usedColor::Magenta- both now resolve throughissue_badge_colorto thelockedrole so they always agree. Pinned bytests/tui_theme_audit_tests.rs, which drives each site with a synthetic per-role palette and asserts the resolved style.
Dependencies
Section titled “Dependencies”- ⬆️
git20.20→0.21(#169). A breaking source migration, not just a version bump: git2 0.21 reworks the UTF-8 accessorsReference::shorthand/Reference::name/Remote::url/Buf::as_strto returnResult<&str, git2::Error>(wasOption<&str>),Commit::summarytoResult<Option<&str>, _>, andStringArray::iterto yieldResult<Option<&str>, _>;Oid::zerois deprecated in favour of theOid::ZERO_SHA1constant. All call sites now collapse the newErrarm toNonevia.ok()so the observable behaviour (a non-UTF-8 / absent name is treated as missing) is preserved. Side effect: git2 0.21 drops itsurldependency, pruning the wholeidna/icu_*transitive tree fromCargo.lock. Closes Dependabot #157. - ⬆️ Bump the serde-stack group:
serde_yml0.0.12→0.0.13andtoml_edit0.25.11→0.25.12(#208).serde_yml0.0.13 is its final maintenance release and clears RUSTSEC-2025-0068; no source changes were needed on our side.
- 📝 Sync the root
README.mdlanding 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 GitHubgwm new/gwm prworkflow 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 underdocs/.
- TDD throughout; the full suite is green locally and on the
ubuntu-latest/macos-latest/windows-latestmatrix. The theme-role additions are pinned site-by-site intests/tui_theme_audit_tests.rswith default-preservation guards intests/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 -
1focuses the worktrees pane,2opens (if hidden) and focuses the status pane. Both are rebindable asfocus_worktrees/focus_statusunder[tui.keys];Tabkeeps 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 visiblecounter on the worktrees pane, lazygit-style (#217). - ⚡ GitHub issue/PR status (
F) is now fetched off-thread, so the statusbar spinner animates during theghshell-out instead of the event loop blocking (#217).
Changed
Section titled “Changed”- 💄 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 the1/2keys), and the help overlay is titledKeybindingswith 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, alsoh/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 aCreate/Cancelbutton 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 andEnterlinks the highlighted row, whilei/pstay direct picks. The picker state machine and its key handler are extracted into a testableApp::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, butRruns the review launcher - it now resolves the livefetch_githubbinding (Fby default) (#217).
- TDD throughout; the full suite is green locally and on the
ubuntu-latest/macos-latest/windows-latestmatrix. 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/rnow 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 arefreshing worktrees…label andq/Escstay 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
3for 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/3closes it. It recordsghGitHub 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
4for a ~90% fullscreen, scrollable view of the resolved.gwm.toml(the user-level global config deep-merged under the repo file, exactly asgwm config listresolves 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
syncaction - pressSto rungwm syncon 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/Escstay 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 isS(sis the sidebar-mode toggle); rebindable via[tui.keys] syncand reachable by name through:sync(#258).
Changed
Section titled “Changed”- ⚡ 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).
Internal
Section titled “Internal”- ♻️ A refactor / test-net sweep ahead of the async-task work - repo-context prelude and
run_gitpromotion, 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 theubuntu-latest/macos-latest/windows-latestmatrix. 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.