Skip to content

Nehir 0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Jun 23:55

Breaking changes

  • improved Niri layout behavior and monitor-specific layout settings with a simplified configuration model.

    • Breaking: renamed the Niri balanced-width config from maxVisibleColumns / niriMaxVisibleColumns (in [niri] / per-monitor overrides) to balancedColumnCount / niriBalancedColumnCount. Old keys are not migrated or accepted — update them manually, e.g. maxVisibleColumns = 2balancedColumnCount = 2 (and niriMaxVisibleColumnsniriBalancedColumnCount in monitor overrides).
    • Breaking: replaced the old [niri] singleWindowAspectRatio (and per-monitor niriSingleWindowAspectRatio) config with the Lone Window policy. There is no 1:1 aspect-ratio equivalent; express the intent through the new keys:
    • singleWindowAspectRatio = "none" (Fill) → loneWindowPolicy = "fill"
    • Any ratio ("16:9", "4:3", "21:9", "1:1") → loneWindowPolicy = "centered" plus loneWindowMaxWidth = <fraction 0.0–1.0>, where the fraction is now of the monitor working-area width (e.g. loneWindowMaxWidth = 0.5 for half width). The fixed aspect-ratio presets no longer exist.
    • In per-monitor overrides use loneWindowPolicy / loneWindowMaxWidth (the niriSingleWindowAspectRatio key is gone).
    • Added per-monitor spacing overrides in Layout settings. You can now customize Inner Gap and per-edge Screen Margins separately for each display, while leaving individual values set to Use Global when you want them to inherit the global defaults.
    • Added explicit per-monitor Lone Window overrides. Each monitor can now use Use Global, Fill, or Centered with its own centered width, so a display can intentionally force Fill even when the global default is Centered.
    • Fixed lone-window layouts after monitor changes. When a constrained single window is left alone on a smaller or different display, Nehir now keeps it inside the monitor’s visible area instead of letting it leak offscreen.
    • Made lone-window scrolling and snapping more predictable. Fill lone windows remain responsive while scrolling but no longer settle one gap off-center; centered lone windows can still snap left, center, or right; and over-constrained lone windows can be scrolled to reveal their overflowing edges.
    • Corrected inner-gap semantics for stacked and tabbed columns. Inner gap now means spacing between adjacent tiled windows, not extra top/bottom padding at the monitor edge. Use screen margins/outer gaps for edge padding.
    • Preserved proportional column behavior for viewport-fitting layouts such as 50% + 50% and Reveal Partial’s Default mode, so existing proportional Niri workflows continue to fit naturally.

Changes

  • Internal refactor: introduce a central projection invalidation pipeline. State owners (WorkspaceManager) now emit structured invalidations (workspace, focus, settings projections) through callbacks routed to workspace bar, status bar, and IPC events via coalesced scheduling. Reduces risk of missed refreshes from scattered manual calls.

  • Redesigned Settings, status bar menu, and toggle commands.

    Settings sidebar now has ten sections organized into four groups — General, Behavior, Layout, Monitors, Workspaces, Workspace Bar, Borders, App Rules, Hotkeys, and Diagnostics.

    • General: Theme, Status Bar display, Prevent Display Sleep, Developer Mode, and nehirctl CLI install
    • Behavior: Focus Follows Mouse, Follow Window to Monitor, Move Cursor to Focused, Scroll Gestures, and Mouse Resize — all in one place
    • Layout: Inner Gaps, Outer Margins, and Column Layout settings with per-monitor scope
    • Hotkeys: search field at the top filters bindings live; Reset to Defaults moved to the bottom
    • App Rules: inline add/edit panel replaces the modal sheet; +/− footer buttons; Escape to dismiss
    • Diagnostics: Accessibility permission status added alongside display environment checks

    Settings placement was reorganized across sections so related options land together — focus behavior, scroll gestures, and mouse resize now share the Behavior tab; gap and column layout controls live in Layout. Captions and inline descriptions were added to clarify what each setting does. Controls use consistent styling (grouped form sections, uniform slider rows, caption text) instead of the previous mixed layouts.

    Status bar menu is now minimal: three quick toggles (Focus Follows Mouse, Window Borders, Workspace Bar), Open Settings, Config Files, and Quit. CLI install and IPC enable have moved to Settings → General.

    Six new toggle commands: toggle-focus-follows-mouse, toggle-focus-follows-window-to-monitor, toggle-move-mouse-to-focused, toggle-borders, toggle-prevent-sleep, toggle-ipc.

    Debug commands (dump/reset/restart state, trace) are hidden from the command palette and hotkey settings unless Developer Mode is on. The Hotkeys section shows a "Dev" badge next to the debugging group when developer mode is active; experimental features show an "Experimental" badge inline.

  • Fixed cursor warping to window center when focus returns after dismissing the command palette or other overlays with Move Cursor to Focused Window enabled.

  • Release pipeline now signs and notarizes prereleases; RC suffix is auto-calculated.

  • Fixed status bar menu showing stale permission warnings after accessibility access is granted.

  • Fixed focus-follows-mouse getting stuck on the wrong window during fast pointer movement

  • Redesigned Niri viewport navigation around snap points instead of explicit centering and legacy reveal controls.

    Breaking changes — the following config keys are removed and no longer accepted. Migrate them to the new [niri] revealPartial setting:

    Removed key (was in [niri]) Migrate to
    centerFocusedColumn = "never" remove it, or set revealPartial = "off"
    centerFocusedColumn = "onOverflow" revealPartial = "default" (closest equivalent)
    centerFocusedColumn = "always" revealPartial = "snapCenter"
    alwaysCenterSingleColumn = true revealPartial = "snapCenter"
    alwaysCenterSingleColumn = false revealPartial = "default" (or remove it)
    Removed key (was in [gestures]) Migrate to
    scrollSnap = true remove it — snap is now always driven by the snap grid. To temporarily bypass snap during a scroll, hold the Mouse Modifier key.
    scrollSnap = false remove it — there is no longer a global disable toggle; use the Mouse Modifier to bypass snap per-gesture, and revealPartial to control reveal-on-focus.

    The new [niri] revealPartial accepts: default, off, snapClosest, snapCenter.

    • Removed explicit center commands from hotkeys, IPC, command handling, and the action catalog. Centering is now produced by the snap grid when a center snap point is the selected target.
    • Removed per-monitor overrides for the removed viewport navigation settings (niriCenterFocusedColumn, niriAlwaysCenterSingleColumn, and the gesture scrollSnap). There is no per-monitor equivalent for these keys.

    Added Reveal Partial in Gestures & Focus → Navigation. It controls what happens when focus moves to a partially visible column:

    • Default: choose the closest snap when the resulting viewport is a natural fit; otherwise center the target column.
    • Off: do not reveal partially visible columns.
    • Snap Closest: snap to the nearest target-column edge or center candidate.
    • Snap Center: center the target column.

    Improved default reveal behavior for proportional columns. Column groups such as 50% + 50% and 25% + 35% + 40% are treated as viewport-fitting without users having to compensate for gaps. Oversized combinations such as 65% + 50% are not treated as a fit.

    Added viewport movement commands for snapping left and right through the snap grid.

    Renamed the resize modifier setting to Mouse Modifier. The same modifier is used for right-mouse resize and for temporarily bypassing trackpad snap during scroll gestures.

    Fixed focus-follows-mouse so FFM focus changes do not reveal, relayout, or scroll the viewport, including duplicate AX focus confirmations for the same FFM target.

    Fixed trackpad and keyboard/AX reveal paths to use the same snap-grid geometry, reducing divergence between gesture release, command navigation, and focus reveal.

  • Added Reset Runtime State and Restart Clearing State developer menu items to the status bar menu (visible when Developer Mode is enabled). Both actions require confirmation when triggered from the status bar menu or command palette. The restart dialog includes an Enable Tracing checkbox that starts runtime trace capture as early as possible during app bootstrap.

    Moved Reveal Config Folder and Edit settings.toml from the status bar menu into Settings → General.

  • Fix floating window drag when cursor warp fires during mouse click. Skip moveMouseToFocusedWindow and moveMouseToMonitor while any mouse button is held. Add detailed tracing for mouse down/drag/up, cursor warp decisions, and focus-follows-mouse skip reasons.

  • Fix hidden workspace window reveals after monitor and workspace transitions

  • Fix workspace bar positioning during external monitor attach/detach when the external display is the main display

  • Added an openSettings command for opening the Nehir settings window.

    The new command is surfaced everywhere other commands live:

    • Command Palette — searchable under the Commands tab (try "settings" or "preferences")
    • Hotkeys — assignable in Settings → Hotkeys (unassigned by default; persisted as ui.settings in the config)
    • CLI / IPCnehirctl command open-settings (IPC name open-settings)

    It behaves like the other "open surface" commands (open-command-palette, open-menu-anywhere): it activates the Settings window via the shared SettingsWindowController, and is reachable through the same hotkey, command palette, and IPC paths. The status bar menu's existing Settings item is unchanged.

  • Fixed workspaces staying assigned to monitors after they are disconnected, which could cause stale workspace bar positioning or window reveal issues following an external display detach. Workspace-to-monitor assignments are now revalidated against the current monitor set whenever displays change.

  • Hardened window visibility tracking to prevent windows getting stuck hidden or shown in invalid states.

  • Internal refactor: centralize the RefreshReason to refresh-route mapping. A single RefreshReason routing table now drives route (fullRescan / relayout / immediateRelayout / visibilityRefresh / windowRemoval) and scheduling policy, exposed via a new reason-driven LayoutRefreshController.requestRefresh entry point. Adding a reason or changing refresh policy is now a one-place edit instead of scattered call sites; behavior is unchanged.

  • Fixed multi-monitor mouse warping when Nehir is inactive by using an active pass-through event tap for warp detection. Thanks @flschulz and @dagrlx.

  • Fixed false resize-minimum pin on terminal windows that snap to cell rows.

    • Terminal/grid apps such as Ghostty round window geometry to whole cell rows. When a requested fill height fell between grid lines, the app snapped the window up by a few pixels and Nehir misread that small overshoot as a hard app-enforced minimum.
    • Nehir then permanently recorded an inferred resize minimum on just that window, over-constrained the Niri solver against its siblings, and pinned the window to the snapped (taller) height. This is why one Ghostty window on a multi-column workspace could end up taller than the others.
    • Small verificationMismatch overshoots (within ~one cell) are now treated as bidirectional cell quantization rather than a one-sided minimum: the observed snapped frame is accepted and confirmed, but no inferred minimum is recorded and the solver is no longer force-pinned. Genuine app minimums are unaffected because they are reported via AXMinSize and respected independently.
  • Onboarding, What's New, and config-mismatch migration.

    • First-run onboarding — new installs are greeted by a setup wizard that teaches Nehir's core model before the tiling engine activates. Includes an interactive demo of Niri move semantics, a live workspace-bar preview, and an experimental-features step. The layout engine, hotkeys, and workspace bar stay suppressed until you finish so onboarding never moves real windows. Re-run it any time from Settings → General.
    • What's New on upgrade — a curated summary appears the first time you launch a new release (once per version), and is always reachable from the status-bar menu.
    • Window Borders are now off by default for new setups. Existing values are unaffected.
  • Fixed focus-follows-mouse and swipe gestures affecting windows behind unmanaged overlays such as quick terminals, while avoiding hot-path WindowServer polling during mouse drags and gesture frames.

  • Polished the Workspaces settings tab. Editing the display name now coalesces config syncs instead of re-applying workspace settings and refreshing the layout on every keystroke. Workspaces with an empty-string display name now show "Workspace <id>" in the sidebar instead of a blank label, and the context-menu Delete item is disabled (rather than prompting a confirmation that then silently does nothing) for workspaces that can't be deleted.

  • Fixed the Status Bar "Show Workspace" toggle not clearing the workspace text when disabled — the title persisted until the app was restarted. Settings-projection refreshes now reach the status bar even when the feature is being turned off, so it clears its title immediately.

  • Fixed several cases where Nehir could jump to the wrong workspace or place new windows in the wrong location.

    • New windows now open on the workspace/monitor you are actively using, even when macOS reports AX focus before the window-create event or when an older confirmed focus belongs to another workspace.
    • Closing or hiding a quick-terminal/dropdown window no longer pulls you back to an older window or column on another workspace.
    • Layout refreshes no longer auto-activate newly rediscovered windows on inactive workspaces.
    • Workspace Bar clicks now target the exact workspace entry instead of resolving through the displayed label, so clicking later workspaces remains reliable.

    Also improved runtime traces for diagnosing placement and focus recovery decisions.

  • Added an About page to Settings with GitHub links, GPL-2.0-only license and OmniWM attribution details, and a sponsor showcase placeholder inviting support.

  • Added keyed-table workspaces.toml support with legacy [[workspace]] decoding, a Diagnostics migration entry with Migrate/Postpone Warning actions, release-scoped warning suppression state, and a documented introduced/deprecated/enforced migration policy.

  • Unified settings config diagnostics — unknown keys are now preserved and surfaced instead of stripped.

    Previously, any key in settings.toml that the current Nehir schema didn't recognize was treated as a blocking error: on launch Nehir backed up the file, rewrote it without those keys, and showed a Config Update Required screen. That silently destroyed valid config written by a newer Nehir version or by hand whenever the app loaded and saved.

    This change introduces one policy with a clean blocking/non-blocking split:

    • Unknown keys round-trip. Settings the schema doesn't model are now captured during decode and re-emitted on save, so a load → edit → save cycle no longer drops them. Missing known values still fall back to defaults.
    • Unknown keys are non-blocking Diagnostics. Instead of blocking startup, unrecognized keys appear in Settings → Diagnostics (and count toward the sidebar badge) as a warning offering Copy AI Prompt, Postpone Warning (hidden until the next Nehir release), and Remove Unknown Keys (writes a timestamped backup, then drops only the unknown keys).
    • Startup recovery only for invalid config. The launch-time strip is gone. Nehir only blocks pre-startup when settings.toml can't be safely loaded at all (TOML parse failure, a known key with the wrong type, or an enforced legacy format). That screen is now Couldn't load settings.toml, offers a Copy AI Prompt, and never rewrites the file automatically.
    • Warnings surface everywhere. Non-postponed settings warnings now appear in the status-bar menu's "Issues detected" section and on the What's New screen, each with a direct path to Diagnostics.

    Invalid values for known keys still throw and route to the blocking recovery path — the capture mechanism did not weaken existing validation.

  • Removed bundled per-app minimum-size rules; window resize floors are now inferred at runtime when an app refuses a size write, so existing and new apps are handled accurately without hardcoded defaults

  • Fixed proportional column pairs being left-aligned with the slack gap on one side.

    • Multi-column Niri layouts like a 50% + 50% pair were sized correctly but misplaced: the column group was anchored to the left edge, leaving the entire proportional slack gap on the right side instead of splitting it evenly. A 50/50 pair now centers its slack — on a 2056px display it sits at 16 / 1036 instead of 0 / 1020.
    • Applies consistently across the second-window insertion, startup/restore, and native-fullscreen restore paths so the viewport settles to the same position regardless of how it was reached.
    • Column widths, heights, column order, and window identity are preserved; only the horizontal origin of the filling group shifts by at most the viewport-fill tolerance.
  • Settings sliders no longer lag while dragging.

    • Dragging the Scroll Sensitivity slider (and other settings sliders such as Border Width, Workspace Bar Height/Opacity, and global gaps) used to trigger a synchronous settings save plus controller side-effects on every drag tick. The save's debounce was a single task yield, so it effectively fired once per frame, blocking the main actor with a TOML encode and disk write on each pixel of motion.
    • SettingsSliderRow now buffers the value in a local draft while dragging and commits it exactly once when the drag ends, so the didSet save and any .onChange controller work run a single time at release. The numeric value next to the slider still updates live while dragging.
  • Reset the Niri viewport when closing back to a single fill-width window.

    • Closing the right column of a proportional pair could leave the surviving lone window with the old two-column viewport target (for example targetViewStart=-516 after a 50% + 50% pair collapsed to one full-width Ghostty window). A later focus/click relayout then applied that stale viewport and shifted the window hundreds of pixels off its fill position.
    • Lone-window viewport preparation now runs on window-removal relayouts even when the removal created an in-flight scroll/column animation, so the surviving fill-width window settles back to the fill viewport immediately.

    Fixes #56. Thanks @dagrlx.

  • Moved Developer Mode from Settings → General into Settings → Diagnostics and expanded Diagnostics into a developer troubleshooting surface while Developer Mode is on.

    • Runtime State section lists each debug command (Dump Runtime State, Trace Capture, Reset Runtime State, Restart Clearing State) with its currently assigned shortcut and a button to run it directly, replacing a separate read-only hotkey list.
    • Restart Clearing State reuses the same confirmation dialog with an Enable Tracing checkbox as the status bar menu.
    • Recent Traces section lists the last ten exported trace captures with Copy Path (plain path string) and Copy File (Finder-style file pasteboard object) actions, plus Reveal Traces Folder.
    • Added a Show Trace Capture Button toggle (duplicated from Workspace Bar settings) for one-place configuration.
    • Assign in Hotkeys deep-links into the Hotkeys tab and filters it to the debug commands.
  • Added a "Spread the word" button next to the Sponsor call-to-action on the About page. It opens a compact modal listing places to share Nehir (X, Bluesky, Reddit, Hacker News, LinkedIn, Telegram, Email), with pre-filled share text where it makes sense and plain links elsewhere. The About page now scrolls when content exceeds the available height.

Notes

Nehir is distributed as a signed and notarized app. If macOS still shows a Gatekeeper warning, right-click the app and choose Open.

Nehir requires Accessibility permission to manage windows.