feat: awareness marker for suppressed panes in the active tab (#118) - #121
Conversation
Stamp SUPPRESSED_MARKER_GLYPH in a cover pane's block-local bottom-right cell when it hides a suppressed pane, using the cover pane's own ring shade so the cue reads against its fill. The marker takes priority over a same-cell label (never overprinted) but yields to a hidden-float chip sharing the same corner. render() gains a new `suppressed_covers: &[usize]` parameter (last arg); every existing call site now passes `&[]`, keeping prior output byte- identical.
Thread a per-tab suppressed_covers list from State::render through paint::bar and tab_block::assemble/grid_lines down to minimap::render, populated for the active tab only so the marker cue shows on the tab you're currently viewing.
|
Warning Review limit reached
Next review available in: 36 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthroughAdds active-tab suppressed-pane awareness to the tab minimap. Suppressed terminal panes are projected, matched to containing tiled panes, and rendered as corner glyphs while preserving hidden-float chip precedence. The change also wires state through the bar renderer and documents the geometry behavior. ChangesSuppressed Pane Awareness
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant StateRender
participant Projection
participant CoverMatcher
participant PaintBar
participant MinimapRender
StateRender->>Projection: project suppressed and tiled panes
Projection->>CoverMatcher: pass pane rectangles
CoverMatcher-->>StateRender: return cover pane IDs
StateRender->>PaintBar: pass IDs by tab position
PaintBar->>MinimapRender: pass IDs for active tab
MinimapRender-->>PaintBar: draw suppressed marker
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds an “awareness” cue for suppressed/background panes by marking the cover tiled pane in the active tab’s minimap, without introducing new permissions or interactions. This extends the existing minimap projection/render pipeline (and the “dependency-free pure layer” pattern used for floating panes) to include suppressed-pane detection and a small corner glyph marker.
Changes:
- Add suppressed-pane projection + dependency-free geometry matcher to compute “cover pane” ids.
- Thread per-tab suppressed-cover ids through
lib → paint → tab_block → minimapand render a◳marker in the cover pane’s bottom-right cell (with chip precedence). - Add docs/spec/plan updates and extend the floating render example to demonstrate the marker.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tab_block.rs | Threads suppressed-cover ids into minimap rendering. |
| src/suppressed.rs | New pure helper module to match suppressed panes to their cover pane(s). |
| src/projection.rs | Adds suppressed-pane projection from PaneInfo to PaneRect. |
| src/paint.rs | Plumbs suppressed-cover ids per tab into tab_block::assemble. |
| src/minimap.rs | Renders suppressed cover markers in-pane, with chip precedence. |
| src/lib.rs | Computes suppressed cover ids for the active tab and passes them to paint. |
| examples/render_floating.rs | Demonstrates the suppressed marker in the visual render example. |
| docs/superpowers/specs/2026-07-16-suppressed-pane-marker-design.md | New design spec documenting decisions/spike and rendering rules. |
| docs/superpowers/plans/2026-07-16-suppressed-pane-marker.md | New implementation plan documenting the steps and rationale. |
| .claude/rules/zellij-plugin-development.md | Adds a verified rule about suppressed panes’ manifest geometry behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // A suppressed-pane marker overrides a same-cell label so the awareness cue | ||
| // is never overprinted (like the chip above). Drawn in the cover pane's ring | ||
| // shade so it contrasts with its own fill (reusing the #47/#116 ring | ||
| // vocabulary). Markers are passed for the active tab only, so no inactive blend. | ||
| if let Some((_, _, cover_id)) = suppressed_marks | ||
| .iter() | ||
| .find(|(mc, mr, _)| *mc == c && *mr == tr) | ||
| { | ||
| let fill = pixel_color(&grid, &ring, panes, palette, &sweeps, pw, c, 2 * tr); | ||
| match fill { | ||
| Some(f) => put_bg(&mut out, f), | ||
| None => put_default_bg(&mut out), | ||
| } | ||
| put_fg(&mut out, palette.ring_for(*cover_id)); | ||
| out.push(crate::suppressed::SUPPRESSED_MARKER_GLYPH); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Fixed in 7cf6336. The marker now yields to a visible float that owns the cell (cell_covered) and falls through to the float paint path, and it darkens its background in the drop-shadow band like the fill beneath it — so it no longer punches through a float or its shadow. Regression test: a_visible_float_occludes_the_suppressed_marker.
| pub fn bar( | ||
| rows: usize, | ||
| layout: &LineLayout, | ||
| panes_by_position: &BTreeMap<usize, Vec<PaneRect>>, | ||
| palette: &Palette, | ||
| prefix: &str, | ||
| gradient: GradientSpec, | ||
| inactive_dim: bool, | ||
| perspective: bool, | ||
| close: Close, | ||
| floats_by_position: &BTreeMap<usize, crate::floating::FloatSpec>, | ||
| suppressed_covers_by_position: &BTreeMap<usize, Vec<usize>>, | ||
| ) -> String { |
There was a problem hiding this comment.
Fixed in 7afb246. Every example caller now passes the suppressed-cover argument — render_demo, render_active_cue, render_close_button, render_perspective, render_new_tab_button, render_top_label, render_palette (for paint::bar) and render_gradient (for minimap::render) — so cargo build --examples is green again.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d11d4ee6d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| perspective: bool, | ||
| close: Close, | ||
| floats_by_position: &BTreeMap<usize, crate::floating::FloatSpec>, | ||
| suppressed_covers_by_position: &BTreeMap<usize, Vec<usize>>, |
There was a problem hiding this comment.
Update example callers for the new render arguments
This new required argument was only propagated through library/test call sites and render_floating; I checked examples with rg "paint::bar\\(|render\\(" examples and the other demos still call the old arity (render_gradient.rs for minimap::render, plus render_demo.rs, render_perspective.rs, render_close_button.rs, render_active_cue.rs, render_new_tab_button.rs, and render_top_label.rs for paint::bar). Users running any of those examples will hit a compile error, so please pass an empty suppressed-cover slice/map there as well.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7afb246. Updated all example callers to pass the suppressed-cover argument (also caught render_palette, which wasn't in the list). cargo build --examples passes.
| out.push(crate::suppressed::SUPPRESSED_MARKER_GLYPH); | ||
| continue; |
There was a problem hiding this comment.
Let visible floats cover suppressed markers
When floating=hybrid and the active tab's float layer is visible, this marker branch still pushes ◳ and continues before the later visible-float paint path can run. A float whose projected rect covers the cover-pane corner will therefore be drawn underneath the marker, while router::route_click still gives that same cell to float_overlay_at before tiled panes; please treat float-covered marker cells like occluded cells and let them fall through to the float overlay.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7cf6336. The marker branch now yields when cell_covered[..] is set (a visible float owns the cell) and falls through so the float overlay paints it — matching the tiled-label occlusion order. Test: a_visible_float_occludes_the_suppressed_marker.
| out.push(crate::suppressed::SUPPRESSED_MARKER_GLYPH); | ||
| continue; |
There was a problem hiding this comment.
Reserve marker cells from wide labels
When a two-column title lands so its continuation cell is the pane's bottom-right marker cell (for example, a two-text-row pane with a CJK label ending at px1), the wide glyph emitted in the previous column has already advanced through this cell, but this branch still emits ◳ before LabelDraw::Skip can preserve the row width. That shifts the rest of the row and breaks the renderer's one-visible-cell-per-column invariant; the marker cell should be reserved from label placement the way chip and close cells are, or skipped when it is a continuation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7cf6336. The marker now yields when its cell is a wide glyph's continuation (LabelDraw::Skip), falling through to the label Skip arm that emits nothing — so the one-visible-cell-per-column invariant is preserved.
| put_fg(&mut out, palette.ring_for(*cover_id)); | ||
| out.push(crate::suppressed::SUPPRESSED_MARKER_GLYPH); |
There was a problem hiding this comment.
Avoid drawing marker foreground in the ring color
When the cover pane is focused in the active tab — the common edit-scrollback case — the chosen bottom-right marker cell is on the focus-ring edge, so pixel_color returns palette.ring_for(cover_id) for the background and this line uses the same color for the glyph foreground. The ◳ marker then renders foreground-on-background in the same RGB and effectively disappears; use a contrasting foreground or sample the pane fill without the focus ring for this cell.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7cf6336. The marker background is now sampled from the pane fill (fill_at) instead of pixel_color (which returns the ring on a focused cover's border), so the ring-shade glyph always contrasts — including the common focused edit-scrollback cover. Test: a_focused_cover_marker_stays_legible asserts fg ≠ bg.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/minimap.rs`:
- Around line 1379-1395: Update the suppressed-marker branch in the minimap
rendering flow so markers yield to visible floating panes and still pass through
the floating-layer draw step, including drop-shadow handling. Do not
unconditionally draw the marker and continue before `float_px`; match the
existing tiled-label ordering and occlusion behavior, preserving marker
rendering only where no visible float covers the cell.
In `@src/projection.rs`:
- Around line 106-136: Update is_suppressed_terminal to require suppressed,
non-plugin panes that are not floating, matching the tiled-pane filtering
semantics. Keep project_suppressed using this predicate, and update its
associated test to verify suppressed floating panes are excluded while
suppressed tiled panes remain projected.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b339be90-ac50-497e-9f96-af5773bbbfe4
📒 Files selected for processing (10)
.claude/rules/zellij-plugin-development.mddocs/superpowers/plans/2026-07-16-suppressed-pane-marker.mddocs/superpowers/specs/2026-07-16-suppressed-pane-marker-design.mdexamples/render_floating.rssrc/lib.rssrc/minimap.rssrc/paint.rssrc/projection.rssrc/suppressed.rssrc/tab_block.rs
… labels (#118) The marker short-circuited before the label/float paint step, so it: - punched through a visible float that covered its cell (spec §7.1), - ignored the float drop-shadow band, - overrode a wide label's continuation cell, breaking row width, and - sampled the focus ring for both fg and bg on a focused cover, hiding itself. It now yields to cell_covered floats and LabelDraw::Skip continuations, takes the drop-shadow, and samples the pane fill (not the ring) for its background.
#118) Cover-matching runs against the tiled set only, so a suppressed float has no cover to match and could false-match an unrelated tiled pane whose geometry contains it. Drop floats from is_suppressed_terminal, making it the true suppressed sibling of is_tiled_terminal.
Closes #118.
The active tab (⌘2) hides a suppressed pane behind its full-width
gitpane, so that pane carries a◳in its bottom-right corner; the inactive tabs stay clean.Zoomed — the
◳marker on the cover paneWhat & why
A suppressed pane is one hidden behind the pane that replaced its slot — e.g.
edit-scrollbackopens$EDITORover the current pane, ornew-pane --in-placesuspends the pane it replaces. The floating-panes work (#110/#112, focus ring #116) deliberately left suppressed panes out of scope, so today they leave no trace on the bar.This PR adds a small awareness marker (
◳) in the active tab's minimap on any tiled pane that hides a suppressed pane behind it — signalling "something is stashed behind this pane" without trying to depict the hidden layout. It answers existence, not contents.Design decisions (per spec)
is_suppressed(already within the existingReadApplicationStategrant), so it adds no new zellij permission and can't trigger the #15 bar-freeze trap.How it works
A suppressed pane stays in the
PaneManifestsharing its cover pane's exact rect (spike-verified against zellij 0.44.3 — see rule #19). So the cover is found by pure geometry: the tiled pane whose rectangle contains a suppressed pane's rectangle.projection.rs—is_suppressed_terminal(is_suppressed && !is_plugin) +project_suppressed(), mirroringproject_floating(). The#110tiled filteris_tiled_terminalis unchanged (byte-identity preserved).suppressed.rs(new) — dependency-freecover_ids(suppressed, tiled)containment matcher, unit-tested off-wasm.minimap.rs— stamps the glyph in the cover pane's bottom-right cell, in that pane's ring shade. A cell already reserved by a hidden-float chip (feat: show floating panes in the minimap, selectable when hidden #110) is skipped — the chip owns its corner.lib.rs— computes the cover set for the active tab only and threads it throughpaint::bar → tab_block::assemble → minimap::render.Testing
CARGO_BUILD_TARGET=aarch64-apple-darwin cargo test --lib→ 314 passing (new: cover-matcher geometry, suppressed projection, marker render, and the chip-vs-marker corner-precedence regression).cargo clippy --target wasm32-wasip1 --all-features --lib→ clean.cargo build --target wasm32-wasip1→ builds.cargo run --example render_floating --target aarch64-apple-darwin→ the visual above.Deferred (out of scope, per spec §9)
Full layout depiction of the suppressed pane, reveal/focus from the bar, inactive-tab display, and hidden-count display — all stay deferred.
Summary by CodeRabbit
New Features
Documentation