Documentation

Everything Panewright does, with an example for everything. Your config lives at ~/.config/panewright/panewright.toml — every key below is optional, and saving the file applies it live.

Install & permissions

brew trust nitschw/tap                # Homebrew requires this for third-party casks
brew install nitschw/tap/panewright   # Panewright + the tiling engine, borders, status bar

That's the whole install: the tiling engine is built into Panewright (a patched build of AeroSpace that runs under Panewright's own permissions), and the cask pulls in focus borders and the status bar. Launch Panewright.app and the setup checklist (menu bar icon → Setup…) walks through the permissions below.

Already running AeroSpace, any flavor? Uninstall it first — two engines fight over the same windows.

WhoPermissionWhy
PanewrightAccessibility + Input MonitoringWindow management (the built-in tiling engine runs under Panewright's own grant), the drag-to-tile event tap, and focus-follows-mouse. Quit and reopen Panewright after granting.

That's the only permissions row — the tiling engine is a child process of Panewright, so macOS attributes its window control to Panewright and it never appears in System Settings separately.

How it works

Panewright reads your one config file and drives everything from it. The tiling engine — Panewright's own build of AeroSpace, patches in the open — runs inside the app as a child process under Panewright's permission grant; the focus borders (JankyBorders) and status bar (SketchyBar) are supervised external tools whose configs Panewright generates. Save your config and everything downstream reloads within a second.

It also keeps the system alive. If the bar dies it comes back on its own. If the engine dies or its Accessibility connection stalls under load — the honest weakness of every macOS tiler — Panewright respawns it, and because it continuously snapshots which window lives on which workspace, the restart puts everything back where it was instead of dumping your layout onto one workspace. It only bothers you (to re-grant the permission) if a restart doesn't take.

Files Panewright generates — ~/.config/aerospace/aerospace.toml, ~/.config/sketchybar/*, ~/.config/panewright/scripts/* — are overwritten on every apply. Edit panewright.toml, not them.

The mod key

Every binding hangs off $mod. Pick a style:

ValuePressNeeds
"hyper"Caps LockKarabiner-Elements — see below. The closest thing to a real i3 $mod: no collisions with anything.
"ctrl"Control (or Caps Lock, remapped)Nothing — macOS remaps Caps Lock → Control natively. Watch for terminal bindings (Ctrl+C, Ctrl+L).
"alt"Option (or Caps Lock, remapped)Nothing — same native remap, fewer collisions than Control.
"ctrl-alt", "ctrl-cmd", "cmd"That chordNothing.
"leader"A prefix, then a bare keyNothing — tmux-style, no chord at all.

Caps Lock without extra software

System Settings → Keyboard → Keyboard Shortcuts…Modifier Keys → set Caps Lock to Control or Option, then use the matching modifier value. Caps Lock becomes your mod key with nothing installed.

Caps Lock as a true hyper key

For a mod key that collides with nothing, install Karabiner-Elements and let it map Caps Lock to Cmd+Opt+Ctrl:

brew install --cask karabiner-elements

Launch it once and approve the driver extension and Input Monitoring when macOS asks. Panewright writes the remap rule to ~/.config/karabiner/karabiner.json, so with modifier = "hyper" Caps Lock immediately becomes $mod (and a solo tap still sends Escape).

modifier = "ctrl-alt"     # Ctrl+Option held — no typing collisions
# or: "hyper"    — Caps Lock via Karabiner-Elements (Cmd+Opt+Ctrl)
# or: "alt", "cmd", "ctrl-cmd"
# or: "leader"   — no chord at all; a tmux-style prefix:
modifier = "leader"
leader-key = "ctrl-semicolon"   # press it, release, then tap a bare key

Leader style is one-shot: each command returns the keyboard to normal; Esc cancels a pending prefix.

Write the leader-key however feels natural — + or - separators, and punctuation glyphs like cmd+` or cmd+~, are normalized to AeroSpace's key syntax (cmd-backtick, cmd-shift-backtick) for you. Prefer a modifier chord (like cmd+`) over a bare function key: a chord is fully swallowed, whereas an F-key can leak its escape sequence into the focused terminal.

Keybindings

Each binding is a key plus an i3-flavored action. Defining any binding replaces the default set, so list everything you want (or start from the defaults below and edit).

[[binding]]
key = "1"
action = "workspace 1"

[[binding]]
key = "shift-h"                 # shift- is the only key prefix
action = "move left"

[[binding]]
key = "t"
action = "workspace 4; layout accordion"   # chains run in order

Key names: letters a–z, digits 0–9, enter, esc, space, tab, minus, equal, comma, period, slash, semicolon, quote, and arrows left down up right — each optionally prefixed shift-.

Action reference

ActionDoes
workspace 3Switch to workspace 3 — i3-style summon: if it's showing on another monitor, it's pulled to the focused one instead of yanking focus away
move to workspace 3Send the focused window there (focus stays)
workspace back_and_forthBounce to the previous workspace
focus leftMove focus (left/down/up/right)
move rightMove the window: toward a window swaps, toward a stack enters it, toward the screen edge pops it out as its own column/row
layout tilesSplit layout (i3's default)
layout accordionStacked full-screen layers (i3's stacking/tabbed)
fullscreenToggle fullscreen for the focused window
floating togglePop the window out of the tiling tree / back in
join leftNest the focused window with its neighbor into an opposite-orientation container
flattenUn-nest the whole workspace back to flat columns — the panic button
resize width -50Resize by pixels (width/height, ±N)
mode resizeEnter a named mode (see below); mode main exits
focus monitor nextFocus another monitor (left/down/up/right/next/prev)
move to monitor leftSend the window to that monitor
move workspace to monitor nextMove the whole focused workspace to another monitor (left/down/up/right/next/prev)
balanceEqualize every window's size in the workspace
fullscreen nativeThe real macOS green-button fullscreen (vs. virtual fullscreen)
minimizeMinimize the focused window to the Dock
close othersClose every window on the workspace except the focused one
focus back_and_forthJump to the previously focused window
scratchpad showSummon the stashed window (see Scratchpad)
move scratchpadStash the focused window
closeClose the focused window (i3's kill)
workspace back_and_forthBounce to the previous workspace
todo addOpen the to-do editor for a new task
pill windowPark the focused window in the bar
helpOpen the cheat-sheet window (default: $mod+?)
exec open -a GhosttyRun any shell command

Modes

Modal keybindings, i3-style: enter a mode and bare keys take on new meanings until you exit. The status bar shows a badge (like [RESIZE]) while any mode is active.

[[binding]]
key = "r"
action = "mode resize"

[[mode]]
name = "resize"

[[mode.binding]]
key = "h"                       # bare keys inside modes — release your mod
action = "resize width -50"

[[mode.binding]]
key = "esc"
action = "mode main"
Defining any [[mode]] replaces the default modes (resize and join), so include the ones you want to keep.

Default keymap

KeysAction
$mod 1–9 0 / +⇧Switch workspace / send window there. On several monitors the workspace activates on its own home monitor, i3-style — it never migrates. An empty, hidden workspace opens on the monitor you're on. Focus crossing monitors brings the pointer along (mouse-follows-focus)
float-secondary-windowsNew windows of already-open apps arrive floating — auth prompts and OAuth popups are usually an app's second window. Re-tile keepers with $mod ⇧ Space. Off by default
$mod ⇥Previous workspace (back & forth)
$mod ⇧ ⇥Summon mode — the next number key pulls that workspace to the focused monitor (its new home)
$mod DCommand palette — windows, apps, commands
$mod OWorkspace overview
$mod `Dropdown terminal
$mod ⇧ PPills mode — number keys summon numbered pills
$mod H J K L or $mod ←↓↑→ / +⇧Focus / move window — vim keys and arrows, both i3-style
$mod E / $mod STiles / accordion layout
$mod FFullscreen
$mod ⇧ SpaceFloating toggle
$mod RResize mode — then bare H J K L, exit with Esc
$mod GJoin mode — then bare H J K L toward the neighbor to stack with
$mod ⇧ GFlatten the workspace
$mod − / $mod ⇧ −Summon / stash scratchpad
$mod ⏎Open a terminal
$mod TCapture a to-do
$mod PPark the focused window as a bar pill
$mod , $mod . / +⇧Focus / move window to previous / next monitor
$mod ?Cheat sheet — every binding in your actual config, plus mouse and bar interactions (also in the menu bar dropdown)

Workspaces

Ten virtual workspaces, ordered 1–9 0 like the keyboard row. They're not macOS Spaces: switching is instant and animation-free. On the bar you can click a number to switch — or drag a window onto a number to send it there.

Empty workspaces hide. i3-style, a number only appears on the bar once it holds a window (or is the one you're looking at). Move a window to workspace 4 and its pill springs into existence; empty it and the pill vanishes again.

Multi-monitor

Every display gets its own status bar, and each bar shows only the workspaces living on that monitor — highlighting the one currently visible there and leading with an M1/M2/M3 badge naming the monitor (the primary is always M1; externals count left-to-right). Monitors are independent: switching a workspace on one display leaves the others exactly where they were, just like i3.

Summon semantics. Pressing $mod+N while the workspace is showing on another monitor pulls it over to the one you're focused on — i3's model — instead of moving your focus across the room. The vacated monitor falls back to a free workspace on its own.

Workspaces auto-distribute across your displays. On launch — and whenever you plug in or unplug a monitor — Panewright hands one workspace to each additional display and keeps the rest on your primary, migrating windows off any throwaway workspace macOS's tiler would otherwise strand. Unplug a display and its workspaces return to a surviving one.

The bar-to-monitor mapping is derived from real display geometry, so each monitor's pill labels always match the workspace actually on it — even though macOS, AeroSpace, and SketchyBar each number displays differently.

macOS Spaces

Keep one macOS Space per display and let Panewright workspaces do the rest — they are the "multiple desktops" layer, with two things Spaces can't offer: instant animation-free switching and full keyboard/scripting control.

Why not build on Spaces directly? macOS has no public API for them — nothing can enumerate Spaces, move a window between them, or switch without the swipe animation. Tools that do manipulate Spaces rely on private calls and disabling SIP, which Panewright will never require. So extra Spaces are invisible to the tiler: windows parked on a non-current Space can't be tiled, dragged, or found until that Space comes back.

If you use extra Spaces today: System Settings → Desktop & Dock → turn off "Automatically rearrange Spaces based on most recent use", and prune down to one Space per display. Mission Control's "Displays have separate Spaces" should stay on — that's what gives each monitor its own bar and full-screen behavior.

Pinning to monitors

Want a workspace to always live on a specific display instead of following the auto-distribution? Pin it:

[workspace-monitors]
1 = "main"          # patterns: main, secondary, a 1-based index,
9 = "secondary"     # or a display-name regex

A workspace never appears on two monitors at once. Switching to a workspace pinned elsewhere moves your focus to that monitor.

Drag-to-tile

Grab a tiled window by its title bar and pull — the window doesn't move. A red ghost marks the cell it came from; a blue ghost previews the drop:

All of it works across monitors: carry a window to another display and drop it on a window there to swap, split, or stack into that monitor's layout. Always on (it's how Panewright works); needs Panewright's two permissions from the install table.

Palette, overview & dropdown

The command palette ($mod D) is dmenu for your desktop: type to fuzzy-filter across every open window (jump to it), every installed app (launch it), and Panewright's own commands — workspaces, layouts, settings. Arrows and Return; Esc dismisses and focus goes back where it was.

The workspace overview ($mod O) is the Mission Control that virtual workspaces otherwise take away: one card per occupied workspace with its windows named and iconed. Click a window to focus it, a card to switch there. (Names and icons rather than thumbnails — screenshots would cost you a Screen Recording permission prompt.)

The dropdown terminal ($mod `) summons one terminal to the top strip of the screen and dismisses it with the same key, quake-style. It picks the first terminal you have installed (iTerm2, Ghostty, WezTerm, Alacritty, kitty, Warp, Terminal) — or any app at all, if you name it. Resize it and it holds that size for the rest of the session. Want two terminals in it? Your terminal's own splits are the right tool.

[dropdown]
enabled = true
app = "com.mitchellh.ghostty"   # optional; auto-detects your terminal
height = 0.35                   # fraction of the screen it covers

Scratchpad

A hidden shelf for one-or-more windows — classically a terminal you summon anywhere.

Summoning with nothing stashed posts a notification instead of silently doing nothing.

Per-app rules

Always float

floating-apps = [
  "com.apple.systempreferences",
  "com.apple.calculator",
]

Always open on a workspace (i3's assign)

[workspace-apps]
"com.spotify.client" = 5
"com.apple.mail" = 4

Find any app's bundle ID:

osascript -e 'id of app "Spotify"'

Overlap rescue

[fitting]
enabled = true
overflow = true        # allow moving a window out as the true last resort
minimum-usable = 360   # don't shrink anything narrower than this (points)

macOS apps have minimum sizes and will refuse to shrink past them — the engine divides the space evenly, the app says no, and windows quietly render on top of each other. No error, no log; tiling just stops being tiling. Panewright watches the real frames, and when windows overlap it escalates in order:

Corrections pause while your mouse is down and for a few seconds after a wake, so it never fights your hand or a half-restored display. Set overflow = false if you'd rather live with overlap than have anything moved.

Focus follows mouse

focus-follows-mouse = true    # default false

Hover moves focus — no click. Pairs well with the focus border. Automatically pauses during drags.

Gaps & borders

[gaps]
inner = 8         # between windows
outer = 8         # screen edges; the bar reserves its own strip on top

[border]
enabled = true
width = 4
active-color = "#D6295F"      # #RRGGBB or #RRGGBBAA
inactive-color = "#00000000"

The border accent also drives the bar's workspace highlight — one color, one system. To break them apart, give the bar its own accent:

[bar]
accent-color = "#30D158"      # workspace highlight only; omit to follow the border

macOS Spaces

Panewright's workspaces replace macOS Spaces — the model assumes one Space per display, and windows on another Space are invisible to tiling until you return (macOS offers no way to see them). Keep one Space per display and let $mod 1–9 do that job; Space switches are detected and tiling politely stands down during them, but a multi-Space layout will always feel half-managed.

Status bar

[bar]
enabled = true
theme = "native"     # vibrancy + SF Pro — or "technical": square, monospace
accent-color = "#30D158"   # optional; defaults to the focus border's active color
position = "bottom"  # or "top" (SketchyBar has no vertical bars)
thickness = 30       # bar height in points (18–60); tiles reserve space to match
font-size = 13       # label size (9–24)
opacity = 0.17       # background alpha 0–1; turn up for busy wallpaper
show-in-fullscreen = false
auto-hide = false        # reveal on mouseover at the bar's edge…
auto-hide-delay = 5      # …and hide this many seconds after the pointer leaves

On several monitors, each display can have its own bar personality. With no configuration, the automatic policy applies: the main display carries the widget chips, and every other display gets a clean workspace strip — nothing duplicates. Override per display with [[bar.monitor]] blocks, matched in order (first match wins):

[[bar.monitor]]
match = "portrait"       # a name fragment ("LC32G7") or a class: builtin, external, portrait, landscape, main, *
widgets = ["battery", "network"]  # the [modules] names, plus "todo" and "integrations"; omit for all, [] for none

[[bar.monitor]]
match = "*"
hidden = true            # no bar at all on everything else

Profiles re-apply automatically when displays are plugged, unplugged, or rearranged. (Bar thickness stays uniform across displays — the bar is one surface; the widgets are per-display.)

Each monitor's bar leads with its M1/M2/M3 badge, then that monitor's workspace numbers (clickable, drag targets, accent highlight on the active one), a mode badge while resize/join is active, and the frontmost app. Disabling the bar returns everything to the native macOS menu bar. Quick toggles for the bar and borders live in the menu bar icon's dropdown. The bar stays clear of the Dock on its own — lifted above a bottom Dock, inset from a side one — and follows if you move the Dock while it's running. With auto-hide on, the bar stays out of sight until your pointer touches its screen edge, and — the real point — windows reclaim the strip it normally reserves.

Bar widgets

Optional modules beyond the window manager. They ship on — the bar arrives looking like the screenshot below — and every one is a single toggle to remove: open Widgets… from the menu bar icon to browse them with a live example of each, toggle them (changes apply instantly — no bar reload), and drag to set their order. Right-click any widget in the bar to turn it off.

The Panewright bar with every widget enabled

Everything at once: workspaces and monitor badge on the left; work trackers, to-dos, and system widgets on the right.

System

System widgets in the bar
WidgetShowsSource
system-monitorCPU and memory percentages; click to unfurl a mini-htop panel with load averages, uptime, memory breakdown, and the top five processes by CPU and by memorytop, vm_stat, ps
system-graphsLive sparklines beside the CPU/memory readout, so you can see a spike building rather than just its current value. Travels with the chip; turn it off to keep the numbers without the graphicssame
network↓1.2M ↑340K throughput on the active interfacenetstat -ib deltas
diskBoot-volume percent used and free spacedf
batteryCharge, time remaining, and ⚡ while charging — the parts the menu bar icon hidespmset -g batt

Developer

Developer widgets in the bar
WidgetShowsSource
portsHow many ports are listening; click for the list with the process owning each onelsof -iTCP -sTCP:LISTEN
dockerRunning container count (hides itself when Docker isn't installed)docker ps
cloud-contextkubectl context and/or AWS_PROFILE, tinted with your accent when the name looks like production — a guard against running something against the wrong clusterkubectl, environment
brew-updatesOutdated Homebrew packages waiting to be upgradedbrew outdated, hourly
vpnAppears when a VPN tunnel is actually carrying trafficifconfig

Media & input

Media and input widgets in the bar
WidgetShowsClick does
mic-muteMicrophone live or mutedToggles mute, restoring your previous input level
volumeOutput volumeMutes / unmutes
now-playingCurrent track from Spotify or MusicPlay / pause
keyboard-layoutActive input source
focus-modeShows when a macOS Focus is silencing notifications — answers "why am I not getting notifications?"
weatherCurrent conditions, refreshed hourly

Window manager

WidgetShows
scratchpadHow many windows are stashed away, so parked windows aren't forgotten

Companions

The to-do list and window pills are widgets too — they appear in the same Widgets… picker. They build their own bar items rather than being driver-polled, so toggling one rebuilds the bar (briefly) where the others switch instantly.

WidgetShows
[todo] enabledTasks as pills with a + button, always grouped together; click a pill to edit or resolve it
[pills] enabledParked windows; click to peek, right-click to give the window back to the tiling tree

Work trackers

GitHub, GitLab, Jira, Confluence, and Microsoft Teams are widgets too — they appear in the same Widgets… picker, and their pills sit on the right of the bar. They carry credentials, so they keep their own config section; see Work items, Confluence, and Microsoft Teams for setup.

Configuration

The picker writes these for you, but the config file is the source of truth:

[modules]
system-monitor = true
system-graphs = true
network = true
disk = true
battery = true
ports = true
docker = false
cloud-context = false
brew-updates = true
vpn = false
mic-mute = true
volume = true
now-playing = false
keyboard-layout = false
focus-mode = false
weather = true
scratchpad = true
# Left-to-right bar order. Anything omitted follows in the default order,
# so a widget added in a later release still shows up.
order = ["weather", "battery", "disk", "network"]
Hover any widget for three seconds and it names itself. All widgets are refreshed by a single batched driver — one process per cycle, not one per widget — and none needs sudo or SIP disabled. Widgets with nothing to report (no Docker, no cloud context, an empty scratchpad, no VPN) hide themselves rather than showing zeros.

To-do list

Tasks live as pills on the right of the bar, newest last, with a + button at the end.

Storage is ~/.config/panewright/todo.txt: one task per line, title tab notes. Plain text on purpose — edit it in vim, grep it, sync it in a dotfile repo; the bar picks up outside changes.

[todo]
enabled = true

Window pills

Park a window you want out of the way but close at hand — a per-window scratchpad you can see. This works on any window, including Panewright's own: pilling the cheat sheet or the Confluence reader keeps a reference one click away, and that's intentional. (One honest limit: pills of Panewright's own windows don't survive a Panewright restart — the windows belong to the app process.)

Parked windows wait on a hidden workspace, and a pill disappears by itself when its window closes.

[pills]
enabled = true
drag-to-bar = true   # drop a dragged window on the bar to park it

Work items

Pills on the right show how much is waiting on you in each service; click one for a panel listing everything, one click from the browser.

ServiceShowsCredentials
GitHubPRs awaiting your review, plus your own open PRsA token, or your gh CLI login automatically
GitLabMRs you opened or were assigned, with pipeline status bubblesPersonal access token, api or read_api scope
JiraUnresolved issues assigned to youAtlassian API token + your account email
[integrations.github]
enabled = true

[integrations.gitlab]
enabled = true
host = "gitlab.example.com"

[integrations.jira]
enabled = true
host = "company.atlassian.net"
user = "you@example.com"
Tokens are never written to panewright.toml — they live in your login Keychain. Add them in Settings… → Status Bar, or from the panel when a service reports missing credentials.

Panel

Search across titles and statuses, sort by updated, priority, status, or title, and filter to one status. Status pills are color-coded — blue in progress, purple in review, red blocked, green done — using the same vocabulary across every service.

Jira hierarchy

The tree toggle in the panel header shows your issues in context: parents and epics above, subtasks below, your own work bolded with a you badge. Double-click any node to open it.

Microsoft Teams

Your next meeting in the bar, and one click to join it — without Teams or Outlook installed. Sign in through the browser with your Microsoft account; Panewright reads your Microsoft 365 calendar over Microsoft Graph.

[integrations.teams]
enabled = true
host = "common"        # or your Azure tenant ID to lock sign-in to one org
user = "<client-id>"    # Azure app registration ID — public, not a secret

Real SSO. Sign-in opens your system browser, so an existing Microsoft session logs you straight in, and MFA, passkeys, and conditional-access policies all work normally. Panewright never sees your password. The flow is OAuth authorization code with PKCE as a public client, so no client secret ships in the app or sits in your config — tokens land in your login Keychain and refresh themselves.

Setting up the app registration

Microsoft Graph requires an Azure app registration, which takes a minute in the Azure portal (Microsoft Entra ID → App registrations → New registration):

Some corporate tenants require an admin to approve third-party apps before they can be used. If sign-in reports that admin consent is needed, your IT team has to approve the registration (or register it for you) — that gate is Microsoft's, not Panewright's.

Confluence

A reader, not a list. The WIKI pill opens a split-view window.

[integrations.confluence]
enabled = true
host = "company.atlassian.net"
user = "you@example.com"

Visual editor

Menu → Settings… (⌘,) Sliders, pickers, and binding rows over the same config file — drag the gap slider and your windows reshuffle live. Invalid binding entries highlight in red and are never silently dropped. Saving from Settings rewrites panewright.toml cleanly (hand-written comments don't survive).

Profiles

Named snapshots of your entire config, stored in ~/.config/panewright/profiles/.

i3 import

panewright import ~/.config/i3/config

Translates bindings, modes, gaps, colors, scratchpad, kill, focus_follows_mouse, and workspace→output pins. Everything untranslatable is flagged with its line number and a reason — never silently dropped. The result lands as an i3-imported profile to inspect before activating.

Scripting

Run scripts from keys

[[binding]]
key = "p"
action = "exec python3 ~/.config/panewright/user-scripts/toggle.py"

Run scripts on events

[hooks]
workspace-changed = "python3 ~/.config/panewright/user-scripts/on-ws.py"
focus-changed = "python3 ~/.config/panewright/user-scripts/on-focus.py"
HookFiresEnvironment
workspace-changedOn every workspace switchWORKSPACE, PREV_WORKSPACE
focus-changedWhenever window focus changesFOCUSED_APP, FOCUSED_WINDOW_ID, WORKSPACE
window-openedA window appears, anywhereWINDOW_ID, APP_NAME, APP_BUNDLE_ID
window-closedA window goes away — the environment still names the app that owned itsame
mode-changedEntering or leaving a binding modeMODE
focus-changed fires often — on every focus change, and in bursts around system dialogs. Keep the command light (it forks a process each time); heavy work belongs in a debounced background job, not the hook itself.

Menus from scripts — the dmenu contract

panewright menu turns any script into a fuzzy picker: lines in on stdin, the pick out on stdout, exit 1 on Escape. Every dmenu/rofi pipeline ever written ports by changing one word:

git branch | panewright menu "checkout:" | xargs git checkout
grep "^Host " ~/.ssh/config | awk '{print $2}' | panewright menu | xargs -o ssh

The menu uses the same panel and fuzzy ranking as the $mod D palette, and dismissing it returns focus to wherever it was — so a binding like action = "exec ~/bin/ssh-picker.sh" gives you rofi-style custom menus on any key.

Three classics ship ready to use (find them in the $mod D palette, or bind them): menu-ssh.sh picks a host from ~/.ssh/config and connects in your default terminal; menu-kill.sh lists the heaviest processes and TERMs your pick; menu-power.sh is the rofi power menu (lock, sleep, restart…). They live in ~/.config/panewright/scripts/ as ordinary scripts on the menu primitive — worked examples for writing your own.

Query and control from scripts

The aerospace CLI is the full API — everything Panewright does, your scripts can too:

aerospace list-windows --workspace focused --json
aerospace focus --window-id 12345
aerospace move-node-to-workspace --window-id 12345 3
Keep your scripts anywhere except ~/.config/panewright/scripts/ — that directory is generated and overwritten on every apply.

Raycast

A Raycast extension lives in the repo (Integrations/raycast/): switch workspaces, move the focused window, jump between layouts, and open Panewright's settings — all from Raycast's launcher. It drives the same aerospace CLI the app installs, so it works with a brew-installed Panewright with no extra setup once the extension is in place.

Until it lands in the Raycast Store, it runs from a source checkout:

git clone https://github.com/nitschw/Panewright && cd Panewright/Integrations/raycast
npm ci && npx ray develop   # registers it in your Raycast as a dev extension

Releases

Install and update through Homebrew; installed apps also update themselves via Sparkle.

VersionReleasedmacOS
0.6.312026-07-2814.0+
0.6.302026-07-2814.0+
0.6.292026-07-2814.0+
0.6.282026-07-2814.0+
0.6.272026-07-2814.0+
0.6.262026-07-2814.0+
0.6.252026-07-2814.0+
0.6.242026-07-2814.0+
0.6.232026-07-2814.0+
0.6.222026-07-2814.0+
0.6.212026-07-2814.0+
0.6.202026-07-2714.0+
0.6.192026-07-2714.0+
0.6.182026-07-2714.0+
0.6.172026-07-2714.0+
0.6.162026-07-2714.0+
0.6.152026-07-2714.0+
0.6.142026-07-2714.0+
0.6.132026-07-2714.0+
0.6.122026-07-2714.0+
0.6.112026-07-2714.0+
0.6.102026-07-2714.0+
0.6.92026-07-2714.0+
0.6.82026-07-2714.0+
0.6.72026-07-2714.0+
0.6.62026-07-2714.0+
0.6.52026-07-2714.0+
0.6.42026-07-2714.0+
0.6.32026-07-2714.0+
0.6.22026-07-2714.0+
0.6.12026-07-2714.0+
0.6.02026-07-2714.0+
0.5.12026-07-2614.0+
0.5.02026-07-2614.0+
0.4.22026-07-2614.0+
0.4.12026-07-2614.0+
0.4.02026-07-2614.0+
0.3.62026-07-2514.0+
0.3.52026-07-2514.0+
0.3.42026-07-2514.0+
0.3.32026-07-2514.0+
0.3.22026-07-2514.0+
0.3.12026-07-2414.0+
0.3.02026-07-2414.0+
0.2.92026-07-2414.0+
0.2.82026-07-2314.0+
0.2.72026-07-2314.0+
0.2.62026-07-2314.0+
0.2.52026-07-2314.0+
0.2.42026-07-2314.0+
0.2.32026-07-2314.0+
0.2.22026-07-2314.0+
0.2.12026-07-2314.0+
0.2.02026-07-2314.0+
0.1.12026-07-2314.0+
0.1.02026-07-2314.0+
First release coming soon.

URL scheme

Panewright answers panewright:// links, so scripts and bar items can drive its windows:

open "panewright://todo/add"
open "panewright://confluence"
open "panewright://confluence/page/123456"
open "panewright://integrations/jira"

Updates & crash reports

Panewright checks for new versions once a day and offers them; Check for Updates… in the menu asks immediately. Updates are cryptographically signed and delivered via Sparkle. Installed through Homebrew? Same story — the cask declares auto_updates, so brew knows Sparkle owns updates and won't nag you to reinstall one you already have. If Panewright crashes, the next launch shows the full report text and offers to open a pre-filled GitHub issue — nothing is ever sent except by your own submit click.

Quitting

Quit Panewright and macOS returns to stock: borders off, bar gone, every hidden window brought back on-screen, tiling stopped. Relaunch and the whole environment reassembles. Launch at Login (in the menu) makes it part of your Mac's boot.

Troubleshooting

SymptomFix
Windows won't tilePanewright probably lacks (or was granted after launch) Accessibility — the engine runs under Panewright's grant. Grant it, then Restart Environment from the menu
No drag ghostsPanewright needs Accessibility + Input Monitoring; grant both, quit and reopen the app
Layout got weird$mod ⇧ G flattens the workspace; rebuild with join mode
Config errorErrors arrive as notifications with the line's problem; fix and save — applies instantly
Everything's stacked full-screenYou're in accordion — $mod E returns to tiles
Engine restarted and my layout scrambledIt shouldn't — workspace assignments are snapshotted and restored automatically. If it happens, that's a bug worth reporting
Deeper digging~/Library/Logs/Panewright.log records the drag engine, fitting decisions, and survived exceptions; PanewrightEngine.log records the engine's own exit reasons