pane tiling layouts for tmux
  • Shell 98.6%
  • Nix 0.9%
  • Just 0.5%
Find a file
Barrett Ruth a307a664c4
Some checks failed
quality / Format (push) Successful in 15s
quality / Lint (push) Successful in 18s
quality / Test (push) Failing after 3m55s
deploy / Nightly (push) Failing after 3m44s
fix(ci): publish nightly releases from deploy workflow (#155)
Problem
The tmux-mosaic nightly release workflow publishes tags/releases, but its status context appears under Nightly Release instead of the deploy namespace. It also only runs on schedule/manual dispatch, while vimdoc-language-server publishes a nightly release for each main-branch push.

Solution
Move the nightly release status under the deploy namespace and trigger it on main pushes:

- workflow name becomes deploy
- job name becomes Nightly, yielding deploy / Nightly
- push to main now triggers the nightly release workflow
- schedule and workflow_dispatch remain as catch-up/manual controls

The existing release script behavior is unchanged; it still waits for quality and remains idempotent when the current nightly is already published.

Verification
- Parsed .forgejo/workflows/release_nightly.yaml with PyYAML.
- Ran git diff --check.

Reviewed-on: #155
2026-05-03 16:17:49 +00:00
.forgejo fix(ci): publish nightly releases from deploy workflow (#155) 2026-05-03 16:17:49 +00:00
scripts fix(ci): wait for quality before release publish (#154) 2026-05-03 16:08:30 +00:00
tests fix: keep sync-state from masking relayouts (#152) 2026-05-02 20:01:52 +00:00
.editorconfig feat: initial scaffold 2026-04-24 18:54:34 -04:00
.gitignore chore(dev): stop tracking .devin 2026-04-28 19:02:15 -04:00
AGENTS.md test: stabilize timing-sensitive bats cases (#151) 2026-05-02 16:39:47 +00:00
biome.json style(forgejo): biome.json useIgnoreFile + biome format . 2026-04-30 21:14:55 -04:00
flake.lock feat: initial scaffold 2026-04-24 18:54:34 -04:00
flake.nix ci(forgejo): port automation workflows (#148) 2026-05-02 02:02:33 +00:00
justfile style(forgejo): biome.json useIgnoreFile + biome format . 2026-04-30 21:14:55 -04:00
LICENSE chore: switch license to GPL 2026-04-30 23:31:34 +00:00
mosaic.tmux fix noexec runner script invocation 2026-04-29 16:12:30 -04:00
README.md docs(readme): point install snippets at Forgejo 2026-04-29 19:31:22 -04:00

tmux-mosaic

Pane tiling layouts for tmux

A tmux plugin that applies tiling layouts, tracks which panes belong to them, and lets native tmux pane operations either join or stay outside the layout.

Dependencies

Installation

TPM

TPM

  1. Add mosaic to your tmux config:
set -g @plugin 'barrettruth/tmux-mosaic'
  1. Install the plugin with TPM
Manual

Manual

  1. Clone the repo somewhere tmux can reach it:
git clone https://git.barrettruth.com/barrettruth/tmux-mosaic.git \
    ${XDG_DATA_HOME:-$HOME/.local/share}/tmux/plugins/tmux-mosaic
  1. Source the plugin from your tmux config:
run-shell ${XDG_DATA_HOME:-$HOME/.local/share}/tmux/plugins/tmux-mosaic/mosaic.tmux
Nix

Nix

  1. Add the flake input:
inputs.tmux-mosaic.url = "git+https://git.barrettruth.com/barrettruth/tmux-mosaic.git";
  1. Source the packaged plugin from your tmux wrapper config:
run-shell ${tmux-mosaic.packages.${system}.default}/share/tmux-plugins/mosaic/mosaic.tmux

Quick Start

Use the master-stack layout and set @mosaic-auto-apply to managed, which keeps plain tmux splits outside the layout until you explicitly adopt them:

set-option -gwq @mosaic-layout master-stack
set-option -gwq @mosaic-auto-apply managed

Because those are global settings, new windows inherit the same default layout and apply mode. A fresh one-pane window still looks ordinary until you split it or add panes.

Set up some default mappings for adopting panes into the layout, creating owned panes, and working the current layout:

bind Enter run -b '#{E:@mosaic-exec} promote'
bind -r ,  run -b '#{E:@mosaic-exec} resize-master -5'
bind -r .  run -b '#{E:@mosaic-exec} resize-master +5'
bind N     run -b '#{E:@mosaic-exec} new-pane'
bind A     run -b '#{E:@mosaic-exec} adopt'
bind T     run -b '#{E:@mosaic-exec} toggle'

Sometimes another layout works better for your task. Change the current window layout to grid:

bind G     set-option -wq @mosaic-layout grid

or reset back to the global default:

bind U     set-option -wqu @mosaic-layout

The section below explains what managed changes and how the other apply modes behave.

See [Pane Ownership Model](#Pane Ownership Model) for pane ownership, what managed changes, and how the other apply modes work.

Layouts

Managed windows can take on a variety of layouts as specified below. The new-pane command appends to the end of the layout's pane order; see below for what that means visually for each layout. Of course, the ownership and auto-apply rules above determine whether native tmux pane operations join the layout automatically.

The following layouts are provided:

master-stack — one or more master panes plus equal-split stack

Behavior

This keeps the first @mosaic-nmaster panes in the master area and the rest in an equal-split stack. @mosaic-orientation chooses whether the master area sits on the left, right, top, or bottom. resize-master changes the size of the whole master area, not individual master panes. If you kill inside the master area, the next pane in tmux's pane order fills the gap on the next relayout, and if you drag-resize the master/stack boundary, Mosaic syncs that size back into @mosaic-mfact. If @mosaic-nmaster is at least the pane count, all panes become masters and Mosaic falls back to an equal split in the chosen axis.

Preview

master-stack layout preview

Core actions

Command Behavior
toggle Turn master-stack off on the current window.
relayout Re-apply the current orientation and @mosaic-mfact.
new-pane Create an owned pane and append it to the end of pane order; with a stack present, it lands at the stack end.
promote Focused pane becomes the first master. On the first master, rotate the next pane forward.
resize-master ±N Change the whole master-region size for the current window, clamped to 595.
select-pane -t :.- (builtin) Focus the previous pane in stack order.
select-pane -t :.+ (builtin) Focus the next pane in stack order.
swap-pane -U (builtin) Move the current pane up the stack.
swap-pane -D (builtin) Move the current pane down the stack.
split-window (builtin) Add a pane and rebalance master plus stack.
kill-pane (builtin) Remove a pane and rebalance; killing the master promotes the stack-top.
resize-pane (builtin) Resize the master live, then sync the new size back into @mosaic-mfact.

Relevant options

Option Scope Default Effect
@mosaic-orientation window→global left Chooses left, right, top, or bottom
@mosaic-nmaster window→global 1 Keeps the first N panes in the master area
@mosaic-mfact window→global 50 Stores the master size as a percent
@mosaic-step global 5 Used by resize-master when you call it without an explicit N

Example config

set-option -gwq @mosaic-layout master-stack
set-option -gwq @mosaic-orientation right
set-option -gwq @mosaic-nmaster 2

bind Enter run -b '#{E:@mosaic-exec} promote'
bind -r , run -b '#{E:@mosaic-exec} resize-master -5'
bind -r . run -b '#{E:@mosaic-exec} resize-master +5'
bind T run -b '#{E:@mosaic-exec} toggle'
bind U set-option -wqu @mosaic-layout
centered-master — center master with side stacks

Behavior

This keeps @mosaic-nmaster panes in a center column and splits the remaining panes into left and right stacks. If there is only one stack pane, it falls back to master plus right stack; otherwise the master stays centered, and an odd extra stack pane goes to the right. resize-master changes the width of the whole center region, and drag-resizing that boundary syncs back into @mosaic-mfact.

Preview

centered-master layout preview

Core actions

Command Behavior
toggle Turn centered-master off on the current window.
relayout Re-apply the centered master column and side stacks with the current @mosaic-mfact.
new-pane Create an owned pane and append it to the end of pane order; with side stacks present, it joins them instead of displacing current masters.
promote Focused pane becomes the first master. On the first master, rotate the next master forward.
resize-master ±N Change the whole center-region width for the current window, clamped to 595.
select-pane -t :.- (builtin) Focus the previous pane in tmux pane order.
select-pane -t :.+ (builtin) Focus the next pane in tmux pane order.
swap-pane -U (builtin) Move the current pane earlier in tmux pane order.
swap-pane -D (builtin) Move the current pane later in tmux pane order.
split-window (builtin) Add a pane and rebalance the center column plus both side stacks.
kill-pane (builtin) Remove a pane and rebalance the centered layout.
resize-pane (builtin) Resize the center column live, then sync the new width back into @mosaic-mfact.

Relevant options

Option Scope Default Effect
@mosaic-nmaster window→global 1 Keeps N panes in the center master column
@mosaic-mfact window→global 50 Stores the center-region width as a percent
@mosaic-step global 5 Used by resize-master when you call it without N

Example config

bind C set-option -wq @mosaic-layout centered-master
bind Enter run -b '#{E:@mosaic-exec} promote'
bind -r , run -b '#{E:@mosaic-exec} resize-master -5'
bind -r . run -b '#{E:@mosaic-exec} resize-master +5'
bind T run -b '#{E:@mosaic-exec} toggle'
bind U set-option -wqu @mosaic-layout
three-column — master column plus two slave columns

Behavior

This is the plain left-master sibling of centered-master. It keeps @mosaic-nmaster panes in a master column on the left and splits the remaining panes into middle and right slave columns. If there is only one stack pane, it falls back to master plus right stack; otherwise an odd extra stack pane goes to the middle column. resize-master changes the width of the whole master region, and drag-resizing that boundary syncs back into @mosaic-mfact.

Preview

three-column layout preview

Core actions

Command Behavior
toggle Turn three-column off on the current window.
relayout Re-apply the left master column and two slave columns with the current @mosaic-mfact.
new-pane Create an owned pane and append it to the end of pane order; with slave columns present, it joins them instead of displacing current masters.
promote Focused pane becomes the first master. On the first master, rotate the next pane forward.
resize-master ±N Change the whole master-column width for the current window, clamped to 595.
select-pane -t :.- (builtin) Focus the previous pane in tmux pane order.
select-pane -t :.+ (builtin) Focus the next pane in tmux pane order.
swap-pane -U (builtin) Move the current pane earlier in tmux pane order.
swap-pane -D (builtin) Move the current pane later in tmux pane order.
split-window (builtin) Add a pane and rebalance the master column plus both slave columns.
kill-pane (builtin) Remove a pane and rebalance the three-column layout.
resize-pane (builtin) Resize the master column live, then sync the new width back into @mosaic-mfact.

Relevant options

Option Scope Default Effect
@mosaic-nmaster window→global 1 Keeps N panes in the left master column
@mosaic-mfact window→global 50 Stores the master-region width as a percent
@mosaic-step global 5 Used by resize-master when you call it without N

Example config

bind 3 set-option -wq @mosaic-layout three-column
bind Enter run -b '#{E:@mosaic-exec} promote'
bind -r , run -b '#{E:@mosaic-exec} resize-master -5'
bind -r . run -b '#{E:@mosaic-exec} resize-master +5'
bind T run -b '#{E:@mosaic-exec} toggle'
bind U set-option -wqu @mosaic-layout
spiral — dwm-style Fibonacci spiral

Behavior

This follows the dwm fibonacci spiral layout. The first pane gets a master region on the left sized by @mosaic-mfact, and the remaining panes recurse through the leftover space in a clockwise spiral. promote bubbles the focused pane into the master slot, resize-master changes the first split, and drag-resizing that master boundary syncs back into @mosaic-mfact.

Preview

spiral layout preview

Core actions

Command Behavior
toggle Turn spiral off on the current window.
relayout Re-apply the current Fibonacci spiral with the current @mosaic-mfact.
new-pane Create an owned pane and append it to the end of pane order so it becomes the newest leaf in the spiral pattern.
promote Focused pane becomes the master pane. On the master pane, rotate the next pane forward.
resize-master ±N Change the first split width for the current window, clamped to 595.
select-pane -t :.- (builtin) Focus the previous pane in tmux pane order.
select-pane -t :.+ (builtin) Focus the next pane in tmux pane order.
swap-pane -U (builtin) Move the current pane earlier in tmux pane order.
swap-pane -D (builtin) Move the current pane later in tmux pane order.
split-window (builtin) Add a pane and rebalance the recursive spiral.
kill-pane (builtin) Remove a pane and rebalance the recursive spiral.
resize-pane (builtin) Resize the master pane live, then sync the new width back into @mosaic-mfact.

Relevant options

Option Scope Default Effect
@mosaic-mfact window→global 50 Stores the master-split width as a percent
@mosaic-step global 5 Used by resize-master when you call it without N

Example config

bind S set-option -wq @mosaic-layout spiral
bind Enter run -b '#{E:@mosaic-exec} promote'
bind -r , run -b '#{E:@mosaic-exec} resize-master -5'
bind -r . run -b '#{E:@mosaic-exec} resize-master +5'
bind T run -b '#{E:@mosaic-exec} toggle'
bind U set-option -wqu @mosaic-layout
dwindle — shrinking Fibonacci sibling of spiral

Behavior

This is the shrinking sibling of spiral, following the dwm fibonacci dwindle layout. The first pane gets a master region on the left sized by @mosaic-mfact, and the remaining panes recurse through the leftover space in a steadily shrinking bottom-right pattern. promote bubbles the focused pane into the master slot, resize-master changes the first split, and drag-resizing that master boundary syncs back into @mosaic-mfact.

Preview

dwindle layout preview

Core actions

Command Behavior
toggle Turn dwindle off on the current window.
relayout Re-apply the current shrinking Fibonacci layout with the current @mosaic-mfact.
new-pane Create an owned pane and append it to the end of pane order so it becomes the newest leaf in the dwindle pattern.
promote Focused pane becomes the master pane. On the master pane, rotate the next pane forward.
resize-master ±N Change the first split width for the current window, clamped to 595.
select-pane -t :.- (builtin) Focus the previous pane in tmux pane order.
select-pane -t :.+ (builtin) Focus the next pane in tmux pane order.
swap-pane -U (builtin) Move the current pane earlier in tmux pane order.
swap-pane -D (builtin) Move the current pane later in tmux pane order.
split-window (builtin) Add a pane and rebalance the recursive dwindle pattern.
kill-pane (builtin) Remove a pane and rebalance the recursive dwindle pattern.
resize-pane (builtin) Resize the master pane live, then sync the new width back into @mosaic-mfact.

Relevant options

Option Scope Default Effect
@mosaic-mfact window→global 50 Stores the master-split width as a percent
@mosaic-step global 5 Used by resize-master when you call it without N

Example config

bind D set-option -wq @mosaic-layout dwindle
bind Enter run -b '#{E:@mosaic-exec} promote'
bind -r , run -b '#{E:@mosaic-exec} resize-master -5'
bind -r . run -b '#{E:@mosaic-exec} resize-master +5'
bind T run -b '#{E:@mosaic-exec} toggle'
bind U set-option -wqu @mosaic-layout
even-vertical — equal-height panes in one column

Behavior

This keeps panes in a single top-to-bottom column with equal heights. While it is active, splits and kills re-apply the same column layout. There is no master pane, so promote and resize-master are not implemented.

Preview

even-vertical layout preview

Core actions

Command Behavior
toggle Turn even-vertical off on the current window.
relayout Re-apply the equal-height column.
new-pane Create an owned pane and append it to the bottom of the column.
select-pane -U (builtin) Focus the pane above.
select-pane -D (builtin) Focus the pane below.
swap-pane -U (builtin) Move the current pane toward the top of the column.
swap-pane -D (builtin) Move the current pane toward the bottom of the column.
split-window (builtin) Add a pane and rebalance the column.
kill-pane (builtin) Remove a pane and rebalance the column.

Example config

bind V set-option -wq @mosaic-layout even-vertical
bind T run -b '#{E:@mosaic-exec} toggle'
bind U set-option -wqu @mosaic-layout
even-horizontal — equal-width panes in one row

Behavior

This keeps panes in a single left-to-right row with equal widths. While it is active, splits and kills re-apply the same row layout. There is no master pane, so promote and resize-master are not implemented.

Preview

even-horizontal layout preview

Core actions

Command Behavior
toggle Turn even-horizontal off on the current window.
relayout Re-apply the equal-width row.
new-pane Create an owned pane and append it to the right end of the row.
select-pane -L (builtin) Focus the pane on the left.
select-pane -R (builtin) Focus the pane on the right.
swap-pane -U (builtin) Move the current pane toward the left side of the row.
swap-pane -D (builtin) Move the current pane toward the right side of the row.
split-window (builtin) Add a pane and rebalance the row.
kill-pane (builtin) Remove a pane and rebalance the row.

Example config

bind H set-option -wq @mosaic-layout even-horizontal
bind T run -b '#{E:@mosaic-exec} toggle'
bind U set-option -wqu @mosaic-layout
grid — equal-size tiled grid

Behavior

This uses tmux's tiled layout and lets tmux choose the row and column shape from the current pane count. With four panes, it becomes a 2x2 grid. There is no master pane, so promote and resize-master are not implemented.

Preview

grid layout preview

Core actions

Command Behavior
toggle Turn grid off on the current window.
relayout Re-apply tmux's tiled layout.
new-pane Create an owned pane and append it to the end of pane order, then let tmux retile the grid.
select-pane -L (builtin) Focus the pane on the left when one exists.
select-pane -R (builtin) Focus the pane on the right when one exists.
select-pane -U (builtin) Focus the pane above when one exists.
select-pane -D (builtin) Focus the pane below when one exists.
split-window (builtin) Add a pane and retile the grid.
kill-pane (builtin) Remove a pane and retile the grid.

Example config

bind G set-option -wq @mosaic-layout grid
bind T run -b '#{E:@mosaic-exec} toggle'
bind U set-option -wqu @mosaic-layout
monocle — keep the focused pane zoomed

Behavior

This keeps the focused pane zoomed on windows with more than one pane. Splitting the zoomed pane keeps the new pane zoomed, and changing focus re-zooms the new active pane because Mosaic relayouts on after-select-pane. There is no master pane or master size, so promote and resize-master are not implemented.

Preview

monocle layout preview

Core actions

Command Behavior
toggle Turn monocle off on the current window.
relayout Re-zoom the active pane.
new-pane Create an owned pane, append it to the end of pane order, and keep the new pane zoomed.
select-pane -t :.- (builtin) Show the previous pane and keep it zoomed.
select-pane -t :.+ (builtin) Show the next pane and keep it zoomed.
split-window (builtin) Add a pane and keep the new pane zoomed.
kill-pane (builtin) Remove a pane; any remaining active pane stays zoomed.

Example config

bind Z set-option -wq @mosaic-layout monocle
bind T run -b '#{E:@mosaic-exec} toggle'
bind U set-option -wqu @mosaic-layout
bind n select-pane -t :.+
bind p select-pane -t :.-

Explicit new-pane limits

Mosaic now tries to birth explicit new-pane directly into the active layout's semantic tail before the final relayout. Most common cases stay local, but a few layout states still need visible correction during creation:

  • master-stack
    • left and top 1 -> 2 start in the final broad branch immediately.
    • right and bottom 1 -> 2 keep append-to-end pane order, so tmux can only start on the non-mirrored side before the final relayout.
    • The nmaster > 1 all-masters -> first-stack transition still reshapes the whole master block.
  • centered-master
    • The first side-stack birth is targeted directly.
    • The default 2 -> 3 transition and later odd side-stack parity changes still shift one existing pane into or across the side stacks before the centered layout settles.
  • three-column
    • The first side-column birth is targeted directly.
    • The default 3 -> 4 transition and later even parity changes still move one existing pane from the right column into the middle column.
  • even-horizontal / even-vertical
    • The new pane is born in the right row or column, then tmux re-equalizes the whole row or column.
  • grid
    • 2 -> 3 still moves the old bottom full-width pane into the new top row.
    • Current pane counts 4, 6, 9, 12, and more generally k^2 and k(k + 1) for k >= 2, still force a true global retile on the next pane.
    • Other tested counts now split the tail directly before the final retile.
  • spiral
    • The easy leaf-node phases and the first node-leaf phase now birth in the outer recursive tail directly.
    • Later node-leaf phases still push the previous tail inward while the new pane stays on the outer branch first.
  • dwindle
    • Normal growth stays local to the recursive tail.
  • monocle
    • new-pane always ends with the new pane focused and zoomed.
    • If the window was manually unzoomed first, Mosaic reasserts zoom after creation.
  • Small windows
    • If the intended target pane is too small to split, Mosaic now falls back to a generic append path when some other pane in the window can still split.
    • If no pane in the window can split, tmux still reports no space for new pane.

Pane Ownership Model

Once a tmux window can contain both panes that should participate in a Mosaic layout and panes created or moved in outside it, Mosaic needs a way to tell which panes it owns before it can safely relayout, sync sizes, or recover.

@mosaic-layout picks a layout for a window. Mosaic separately tracks whether the panes in that window belong to the current managed layout. When Mosaic first manages a window, it stamps the current panes with a window-specific generation. Panes created or moved in outside Mosaic can stay foreign until you adopt them.

@mosaic-auto-apply

The main user-facing choice is @mosaic-auto-apply, which decides what native tmux structural commands should do when they create or expose panes that may not belong to the current layout.

Value Raw split-window When to use it
full Default. Adopt the new pane before structural relayout, so raw tmux splits join Mosaic. You want native tmux pane commands to behave like part of the active layout.
managed Leave the new pane foreign, mark the window suspended, and skip structural auto-relayout and size sync until you recover. You want temporary helper panes that Mosaic ignores until you explicitly adopt them.
none Leave the new pane foreign and skip structural auto-apply. Explicit new-pane, adopt, and local @mosaic-layout changes still work. You want Mosaic only for explicit actions.

Under the hood, that ownership tracking still leaves each window in one of three states: managed when all panes belong to the current generation, suspended when foreign panes are present and Mosaic has stopped structural auto-apply, and unowned when no layout is active for the window.

Explicit actions and recovery

Those modes mainly affect what happens when you use tmux's built-in structural commands. Mosaic's explicit actions are the tools for adding owned panes on purpose or recovering a window after foreign panes appear.

Action What it does
new-pane Creates an owned pane, preserves the current path, appends it to the end of the layout's pane order, and relayouts once.
adopt Rotates the window generation, claims all current panes, and relayouts once.
relayout Re-applies the current layout to the current window.

In managed mode, a raw split-window suspends the window until you close the foreign pane, run adopt, or set a new local @mosaic-layout.

new-pane, promote, and resize-master refuse suspended windows with mosaic: window is suspended; adopt panes first.

Acknowledgements