Skip to content

feat: spread pane colors so adjacent panes avoid the same hue #111

Description

@GeneralD

agent type complexity estimate AI scope

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    configPlugin config parsing & load() wiringenhancementNew feature or requestrenderingMinimap / color / label rendering

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions