
Motivation
Two adjacent panes in a minimap can end up the same fill color, which makes them blend into one block — you lose the very thing the bar exists to show (where the splits are). The same happens across the strip: two neighboring single-pane tabs can both land on the same hue.
It is not random, even though it looks like it. A pane's fill is palette.color_for(pane_id) = slots[pane_id % slots.len()] (src/color.rs:247-249, called at src/minimap.rs:455). The slots are the theme's multiplayer_user_colors (src/lib.rs:108-127). Because zellij assigns pane ids in creation order, and a pane's layout position has no correlation with its id, two panes that happen to sit side by side collide whenever their ids are congruent mod the slot count. With, say, 8 slots, ids 3 and 11 are both slot 3 — and nothing stops them from being neighbors.
Goal: adjacent panes should use different colors as much as possible, and the palette should be spread evenly rather than clustering on a few slots.
The core tension (this is the real decision)
Today's scheme exists on purpose. Issue #5's cardinal rule: a color is keyed on a pane's stable identity, so a pane keeps its hue across repaints even as siblings open, close, or move — and focus is marked by the ring, not by recoloring the fill (#47).
Any "avoid the neighbor's color" or "use the palette evenly" strategy has to look at the set of panes and their adjacency, so a pane's color becomes a function of its neighbors. The cost: when a neighbor opens/closes/moves, colors can reshuffle — the stability guarantee weakens. The decision this issue needs to settle is how much identity-stability we trade for adjacency-distinctness.
Proposal — options
Option A — per-tab greedy de-collision, config-gated (recommended)
Keep id-keying as the default. Add an opt-in strategy that, per tab, walks panes in reading order and assigns each the first palette slot not already used by an adjacent pane (falling back to the least-recently-used slot when the palette is too small). Deterministic given a tab's pane set.
Option B — adjacency graph coloring
Build an adjacency graph from pane geometry (panes sharing an edge are adjacent) and greedily color it so no two touching panes share a hue.
- ➕ The most faithful "neighbors never match" result.
- ➖ Needs ≥ (max degree + 1) hues or it must reuse; more logic (edge detection); full stability cost.
Option C — scramble the id before modulo
Hash/permute pane_id before % slots.len() to break up the arithmetic clustering.
- ➕ Trivial; preserves perfect identity-stability.
- ➖ Still id-based, so it does not guarantee adjacent distinctness — only changes the collision pattern. Likely insufficient for the actual ask.
Leaning Option A: it satisfies the request where the user wants it, keeps the proven default, and confines any instability to the tab being edited.
Open questions
- Stability vs. distinctness — is it acceptable for a pane's color to change when a neighbor opens/closes? (If "never", only Option C is viable, and it can't fully meet the goal.) This drives everything.
- Adjacency definition — true geometric edge-sharing (Option B) or reading-order neighbors (Option A's cheaper proxy)?
- Scope — within-tab pane adjacency only, or also adjacent single-pane tabs across the strip?
- Palette smaller than pane count — when collisions are unavoidable, minimize them / maximize color distance between forced repeats.
- Config surface — e.g.
color_strategy = "stable" | "distinct" (default stable), keeping the change opt-in.
Relevant code
Relates to
Motivation
Two adjacent panes in a minimap can end up the same fill color, which makes them blend into one block — you lose the very thing the bar exists to show (where the splits are). The same happens across the strip: two neighboring single-pane tabs can both land on the same hue.
It is not random, even though it looks like it. A pane's fill is
palette.color_for(pane_id) = slots[pane_id % slots.len()](src/color.rs:247-249, called atsrc/minimap.rs:455). The slots are the theme'smultiplayer_user_colors(src/lib.rs:108-127). Because zellij assigns pane ids in creation order, and a pane's layout position has no correlation with its id, two panes that happen to sit side by side collide whenever their ids are congruent mod the slot count. With, say, 8 slots, ids 3 and 11 are both slot 3 — and nothing stops them from being neighbors.Goal: adjacent panes should use different colors as much as possible, and the palette should be spread evenly rather than clustering on a few slots.
The core tension (this is the real decision)
Today's scheme exists on purpose. Issue #5's cardinal rule: a color is keyed on a pane's stable identity, so a pane keeps its hue across repaints even as siblings open, close, or move — and focus is marked by the ring, not by recoloring the fill (#47).
Any "avoid the neighbor's color" or "use the palette evenly" strategy has to look at the set of panes and their adjacency, so a pane's color becomes a function of its neighbors. The cost: when a neighbor opens/closes/moves, colors can reshuffle — the stability guarantee weakens. The decision this issue needs to settle is how much identity-stability we trade for adjacency-distinctness.
Proposal — options
Option A — per-tab greedy de-collision, config-gated (recommended)
Keep id-keying as the default. Add an opt-in strategy that, per tab, walks panes in reading order and assigns each the first palette slot not already used by an adjacent pane (falling back to the least-recently-used slot when the palette is too small). Deterministic given a tab's pane set.
Option B — adjacency graph coloring
Build an adjacency graph from pane geometry (panes sharing an edge are adjacent) and greedily color it so no two touching panes share a hue.
Option C — scramble the id before modulo
Hash/permute
pane_idbefore% slots.len()to break up the arithmetic clustering.Leaning Option A: it satisfies the request where the user wants it, keeps the proven default, and confines any instability to the tab being edited.
Open questions
color_strategy = "stable" | "distinct"(defaultstable), keeping the change opt-in.Relevant code
src/color.rs:245-249—color_for, theid % slots.len()mapping at the heart of this.src/minimap.rs:455— where a pane's fill is resolved from its id during paint.src/lib.rs:108-127—palette_from_style, how the slot list is built from the theme.Relates to