diff options
| author | user <user@clank> | 2026-08-13 18:46:58 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-13 18:46:58 +0200 |
| commit | 2f0750285b1b174a552113c4aed9288f44985ae3 (patch) | |
| tree | e8033ba0b3d3ac04e4e49fb4fa53f7049ac21499 /docs | |
| parent | docs: handover (diff) | |
| download | sl0ppi-2f0750285b1b174a552113c4aed9288f44985ae3.tar.gz sl0ppi-2f0750285b1b174a552113c4aed9288f44985ae3.tar.xz sl0ppi-2f0750285b1b174a552113c4aed9288f44985ae3.zip | |
sl0p: make the split button visible, and clickable where it is drawn
The `+` shipped in 0.1.7 and 0.1.8 without ever appearing on a live session,
while rendering in 265 snapshots. Four faults, each enough on its own:
- title_line_without_middle computed the right side only inside the left
side's and_then, so a pane with no title (hide_default_pane_names, the live
default) got none at all;
- title_line_with_middle discarded the right side whenever the left was
absent -- that is every frame in a session with a second client, which the
live box always had, and "MY FOCUS AND:" on every frame was saying so;
- the title was budgeted before the button, so a title longer than the frame
(any shell showing a path) consumed its columns. The button is anchored and
hit-tested by pure geometry, so it cannot be the part that loses;
- the click hit-tested position_and_size() while the frame is drawn inset by
the pane gap, putting the clickable columns two right of the glyph and a row
above it at the top of the screen. Hence "the click works" and "nothing is
drawn" both being true, and that click test proving nothing.
Adds Pane::frame_rect(), the rect the frame is really drawn in, which the
status bar buttons had already had to work around by hand.
Also fixes render_my_focus building its short form from separators that
Vec::append had already drained: 4 cells wide while reporting 6, a title line
two cells short of its pane. Found by asserting the width in the new test.
Verification, since unit tests have now been insufficient here four times:
split_button_is_drawn_where_it_is_hit_tested covers the combinations the live
config produces, and packaging/checks/split-button-live.py (make check-live)
reads the glyph off a real session, attaches a second client to force the
middle builder, and clicks the columns it found the glyph in. Both confirmed
to fail on the parent commit.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/HANDOVER.md | 101 | ||||
| -rw-r--r-- | docs/roadmap.md | 31 |
2 files changed, 91 insertions, 41 deletions
diff --git a/docs/HANDOVER.md b/docs/HANDOVER.md index 467c29b..6f7f89b 100644 --- a/docs/HANDOVER.md +++ b/docs/HANDOVER.md @@ -2,54 +2,64 @@ Written at the end of a long session. Start here. -## Pick this up first: the `+` split button does not render +## Done: the `+` split button renders (and clicks where it is drawn) **Feature:** a `+` at the right end of every pane's title line; clicking it -splits that pane. Shipped in 0.1.7 (hover-gated) and 0.1.8 (always drawn). +splits that pane. Shipped invisible in 0.1.7 and 0.1.8; fixed after both. -**Status: the click works, the button is invisible on a live session.** +It was never one bug. The title line has three builders, and the button was +falling out of two of them, while the click was hit-testing a rect the frame is +not drawn in: -- clicking those 3 columns splits the pane — verified by injecting real SGR - mouse events into an attached client's pty (`1 -> 2 panes`; clicking the title - or mid-frame correctly does nothing) -- the button renders in **265 unit snapshots** -- it has never once been seen on a real session +1. **No title, no right side.** `title_line_without_middle` only computed the + right side *inside* the left side's `and_then`, so a pane with an empty title + got none. Live sets `hide_default_pane_names true`, so unnamed panes have + exactly that. +2. **A "MY FOCUS" middle dropped the right side entirely.** + `title_line_with_middle` fell through to `middle_only_title_line` whenever + the left side was absent. Any second client in the session puts a middle + there — including the phantom client of open item 1 below, which is why the + live box *always* took this path (`MY FOCUS AND:` on every frame was the + clue, and it was in the bug report all along). +3. **A long title ate the button.** The title was budgeted first and the right + side got the remainder; shell titles are long paths. The anchored button is + now reserved before the title, since its hit-test is pure geometry and cannot + ask the renderer what fitted. +4. **The click hit-tested `position_and_size()`**, the pane's allocation, while + the frame is drawn inset by the pane gap (`gap 1` → 2 columns, 1 row). The + clickable columns sat out in the blank gap, two right of the drawn `+`. This + is why "clicking works" and "nothing is drawn" were both true, and why that + click test proved nothing. There is now `Pane::frame_rect()` — the rect the + frame is actually drawn in — and both the button and (already) the status bar + buttons derive from it. -Two wrong diagnoses so far, both mine: +The centre-aligned path, the strong lead in the previous handover, was fine. -1. "It's hover-gated and hover is broken" → removed the gate in 0.1.8, still - invisible. (`mouse_is_hovering_over_pane` does appear to be false live, but - that was not the only problem.) -2. "The click test proves the feature" → it does not. The hit-test is purely - geometric and never consults the renderer, so it kept working while nothing - was drawn. +**What was missing was the reproduction, not the analysis.** Two things now +exist so this cannot come back: -**The strong lead, untested:** every snapshot uses zellij's DEFAULT frame -config. The live config does not: +- `split_button_is_drawn_where_it_is_hit_tested` — asserts the button occupies + exactly `split_button_columns()` across alignments × rounded × titled/untitled + × one/two clients × 4 widths. Positional, not `contains("+")`: the short + multi-client indication is itself a `+`. +- `packaging/checks/split-button-live.py` (`make check-live`) — spawns a real + session at a pty, reads the glyph off the screen, attaches a second client to + force the middle builder, then clicks the columns it *found the glyph in* and + confirms the split. Verified to fail on the pre-fix commit for both reasons + (button absent with 2 clients; click in the gap does nothing). -```kdl -pane_frames { rounded_corners true; title_alignment "center"; gap 1; padding 0 0 0 0 } -``` - -`render_title_right_side` is called from **three** different title builders -(`zellij-server/src/ui/pane_boundaries_frame.rs`, lines ~889, ~915, ~983) with -different `max_length` budgets. The snapshots only ever exercised the -left-aligned path; `title_alignment "center"` takes another one. - -**Ten-second check before writing any code:** set `title_alignment "left"` in -`~/.config/sl0ppij/config.kdl` on the test box, re-attach, look for the `+`. If -it appears, that is the bug confirmed. - -**Then:** add a frame unit test using the live config's alignment/rounded/gap -settings — the reproduction that was missing all along — and fix whichever -builder drops the button. +One more bug fell out of the width assertion: `render_my_focus` built its full +and short forms from the same separator vectors, and `Vec::append` drains — so +the short "ME" form rendered 4 cells wide while reporting 6, two cells short of +the pane. Fixed. Code involved: | what | where | |---|---| -| render + geometry | `ui/pane_boundaries_frame.rs`: `render_split_button`, `split_button_columns`, `SPLIT_BUTTON_*` | +| render + geometry | `ui/pane_boundaries_frame.rs`: `render_split_button`, `split_button_columns`, `anchored_right_side_len`, `SPLIT_BUTTON_*` | | click | `tab/mouse_handler.rs`: `intercept_split_button_click` | +| where the frame really is | `tab/mod.rs`: `Pane::frame_rect` (+ the two impls) | Not drawn on floating panes on purpose: the PIN control owns that corner and is hit-tested by its drawn position, so anything to its right moves it out from @@ -75,8 +85,7 @@ URL afterwards. ## Open, in the order I would do them -1. **The `+` rendering** (above). -2. **Phantom client on headless `sl0ppi up`.** `attach --create-background` +1. **Phantom client on headless `sl0ppi up`.** `attach --create-background` leaves one client in the session forever, so every pane frame shows `MY FOCUS AND:`. Worked around by creating the session *by attaching* when there is a tty; the headless path still leaks. Ruled out: the CLI-action @@ -86,14 +95,15 @@ URL afterwards. `ScreenInstruction::RemoveClient` on a debug build to find where it is lost. Cheap alternative: give the headless path its own pty (`script -qec`) and kill the client afterwards — killed clients demonstrably do not leak. -3. **bun is the AVX2 build.** Caused `Illegal instruction` on a plain KVM guest. +2. **bun is the AVX2 build.** Caused `Illegal instruction` on a plain KVM guest. One line in `packaging/fetch-vendor.sh`: `bun-linux-x64-baseline.zip`. -4. **`pir` needs python3 + fzf**, neither shipped. The fix is folding it into +3. **`pir` needs python3 + fzf**, neither shipped. The fix is folding it into `sl0ppi agent --resume` (already designed in `docs/cli-design.md`). -5. **Tab overflow.** `sl0ppi open <name>` wants to be a fuzzy picker once a root +4. **Tab overflow.** `sl0ppi open <name>` wants to be a fuzzy picker once a root has a dozen projects. -6. Button polish: shift/right-click for the other split direction; reuse - `status_bar_button_style` so it looks pressable. +5. Button polish: shift/right-click for the other split direction; reuse + `status_bar_button_style` so it looks pressable. Extend `make check-live` + rather than trusting a snapshot. Fixed this session, for context: client ids were recycled (silently ate ~25% of `zellij action` output), suspended panes spammed resize errors, responsive @@ -123,6 +133,15 @@ bar. See `docs/roadmap.md` for each with its measurements. template into each tab instead of emitting `default_tab_template`. - **The tab test harness does no rendering**, so `panes_to_hide` is always empty and stack-related bugs cannot be reproduced in it. Use a scripted pty. +- **A hit-test that never consults the renderer is not evidence of anything.** + The split button "worked" for two releases while being invisible. If an + affordance is drawn, the check has to *find the glyph* and click where it + found it — `packaging/checks/split-button-live.py` is the pattern. +- **`os.read` on a settled pty blocks forever.** `os.set_blocking(fd, False)` + and catch `BlockingIOError`, or a live check hangs instead of failing. +- **The pane's geometry is not where its frame is drawn.** With a gap, the frame + is inset from the allocation: use `Pane::frame_rect()` for anything the user + can see, `position_and_size()` only for the layout solver. - **Clean up test sessions.** A background-created session I left behind is what produced a "MY FOCUS" bug report from the user. diff --git a/docs/roadmap.md b/docs/roadmap.md index bc8aa6f..98c8949 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -215,6 +215,37 @@ Two panes sharing one rect means a stack; distinct rects mean it really expanded. Check both directions (create narrow then widen, create wide then narrow) and add panes afterwards. +## Fixed: the `+` split button was drawn nowhere and clicked in the gap + +Shipped in 0.1.7 and 0.1.8 without ever being visible on a live session, while +rendering in 265 unit snapshots. Four separate faults, each individually enough +to hide it: + +- `title_line_without_middle` computed the right side only if the pane had a + title -- and `hide_default_pane_names true` means unnamed panes have none; +- `title_line_with_middle` dropped the right side whenever the left was absent, + which is every frame in a session with a second client (`MY FOCUS AND:`); +- the title was budgeted before the button, so a title longer than the frame + (i.e. any shell showing a path) consumed its columns; +- the click hit-tested the pane's allocation while the frame is drawn inset by + the pane gap, putting the clickable columns two to the right of the glyph. + +The first three are fixed in `ui/pane_boundaries_frame.rs` by reserving the +anchored button before anything else and never discarding the right side; the +fourth by `Pane::frame_rect()`, the rect the frame is actually drawn in. + +**How to re-run the check** -- unit tests were necessary and insufficient here +for the fourth time, so this one reads the screen: + +```bash +make check-live # packaging/checks/split-button-live.py +``` + +It starts a session at a pty with the live frame config, finds the `+` on the +frame, attaches a second client (the middle builder), and clicks the columns the +glyph was found in, expecting `2 -> 3` panes. Confirmed to fail on the commit +before the fix, for both the render and the click. + ## Standing risks **`sl0ppij/target` is a symlink to `~/lol/zellij/target`.** Introduced when the |
