<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppij.git/zellij-client/src/web_client, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppij.git/atom/zellij-client/src/web_client?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppij.git/atom/zellij-client/src/web_client?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/'/>
<updated>2026-08-12T14:15:19Z</updated>
<entry>
<title>sl0p: add an indexed style for status bar buttons</title>
<updated>2026-08-12T14:15:19Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T14:15:19Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=c3764fca00c1d91c06612a33162b2354e9d42be0'/>
<id>urn:sha1:c3764fca00c1d91c06612a33162b2354e9d42be0</id>
<content type='text'>
`status_bar_button_style "indexed"` renders `1:Approve 2:Reject` instead of
`[Approve] [Reject]`, so the bar doubles as a key legend for a harness that binds
alt+1..9 to the same actions.

A config option rather than a protocol field: whether the numbers mean anything
depends on what the USER has bound, not on what the program knows. It needs no
wire change and so no version bump, and the click report still carries the id --
never the index -- so a program is unaffected by which style is in force.

The style is threaded into layout_status_bar, the one function the renderer draws
from and the hit-test resolves against. That is what makes this safe: changing a
button's rendered width necessarily changes its clickable span too, verified by a
test asserting span width equals the drawn width and that every drawn column
resolves back, for both styles.

Also fixes the web client's Style construction to read the option from config
rather than defaulting it -- my first pass claimed that code had no pane_frames
config available, which was wrong; it uses it two lines further down.
</content>
</entry>
<entry>
<title>sl0p: optionally hide the 'Pane #N' placeholder title</title>
<updated>2026-08-08T20:47:16Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-08T20:47:16Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=9aa323fe6b9d15360ba54a1c73a216228664a289'/>
<id>urn:sha1:9aa323fe6b9d15360ba54a1c73a216228664a289</id>
<content type='text'>
`ui { pane_frames { hide_default_pane_names true } }`, default false.

A pane now shows no title at all until something actually names it: the user
renaming it (including by double-clicking the title row) or the running
program setting a title. Upstream always shows an auto-generated 'Pane #N'.

Implemented by tracking whether pane_title is still the constructor's
placeholder, NOT by matching the string: a program that legitimately titles
itself 'Pane #3' must not be mistaken for an unnamed pane. set_title clears
the flag, since something then chose that title.

current_title reads the option at render time rather than baking an empty
title in at construction, so it hot-reloads and turning the option back off
restores the placeholder on existing panes.

Display precedence is unchanged and now explicit in the code:
    user-given name &gt; title set by the running program &gt; placeholder

render_terminal_title already fell back to an empty string rather than the
placeholder, so the terminal WINDOW title is unaffected.

Empty titles were already handled by the frame renderer
(render_title_left_side returns None for an empty title), including the
sl0p centred/right alignment path.

Baselines held: zellij-server 1436/30, zellij-utils 436/5.
</content>
</entry>
<entry>
<title>sl0p: grayscale the contents of unfocused panes</title>
<updated>2026-08-08T20:36:30Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-08T20:36:30Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=cfae103a7afae1541a46cbfe21b011ffe4d09f2d'/>
<id>urn:sha1:cfae103a7afae1541a46cbfe21b011ffe4d09f2d</id>
<content type='text'>
`ui { pane_frames { inactive_grayscale 0-100 } }`, default 0 (off).

Real colour transformation, not the SGR 'faint' attribute zellij already uses
for frames: faint is terminal-dependent, several terminals only apply it to
the 16 ANSI colours, and it does not remove colour. Each cell's colour is
resolved to RGB and blended toward its own Rec.709 luminance, so an unfocused
pane keeps its brightness but drains of colour.

Colour resolution covers the cases that actually occur: RgbCode directly,
ColorIndex via eightbit_to_rgb, and NamedColor by mapping to its 0-15 index.
Cells with NO explicit colour are deliberately left alone - they render in the
terminal default, which is already unsaturated, and rewriting them would mean
emitting an explicit colour for nearly every cell for no visible gain.

Two non-obvious pieces were needed to make it actually work:

- The output buffer only emits CHANGED cells, so an idle pane would keep its
  old colours indefinitely after losing focus. Pane::refresh_for_focus_change
  forces a full repaint, called from ActivePanes::insert (the single place
  focus changes). It is a no-op when the feature is off, so it costs nothing
  by default.

- Terminal panes render through the SHARED multi-client path. Focus is per
  client, so once unfocused panes are desaturated two clients may need
  different output for the same pane. Both tiled and floating panes now fall
  back to per-client rendering when grayscale is enabled - the reason this is
  opt-in.

Known limitation: sixel/kitty image chunks are not colour-transformed and stay
in colour.

Hot-reloads like the other sl0p options. Tests cover the transform: full
strength yields a true neutral grey, partial sits between original and grey,
indexed and named colours resolve rather than being skipped, luminance is
perceptual (green brighter than blue) not a flat average, already-grey is
unchanged, and non-colour codes are untouched.

Baselines held: zellij-server 1436/30, zellij-utils 436/5.
</content>
</entry>
<entry>
<title>sl0p: CSS-style 1/2/4-value shorthand for inner padding</title>
<updated>2026-08-08T19:06:40Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-08T19:06:40Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=97b744a57e3ba53169781a9357d8dc86b15d387e'/>
<id>urn:sha1:97b744a57e3ba53169781a9357d8dc86b15d387e</id>
<content type='text'>
`padding` now accepts:
  padding 1          -&gt; all four sides
  padding 1 2        -&gt; up/down = 1, left/right = 2
  padding 1 2 3 4    -&gt; up=1 down=2 left=3 right=4

NOTE the 4-value order is UP DOWN LEFT RIGHT as requested, deliberately NOT
CSS's clockwise top-right-bottom-left. Documented on PanePadding and at the
parser, since the similarity to CSS invites the wrong assumption.

3 or 5 values are rejected with a message naming the accepted forms, rather
than being silently reinterpreted. Negative values clamp to 0.

ASPECT: only the 1-value form is aspect-corrected (left/right multiplied by
gap_aspect) so `padding 1` looks square on a non-square cell grid. The 2- and
4-value forms are literal: specifying axes separately means the caller is
already controlling horizontal size, and silently doubling it would surprise.

Style now carries a PanePadding {top,bottom,left,right} instead of an x/y
pair, which also collapses the reconfigure plumbing back to a single field
rather than growing another parameter per side.

to_kdl always emits the explicit 4-value form: emitting the 1-value form
would re-apply gap_aspect on the next read and silently double the padding
on every config round-trip.

Baselines held: zellij-utils 435/5, zellij-server 1421/30.
</content>
</entry>
<entry>
<title>sl0p: uniform pane gap + aspect-corrected horizontal gap</title>
<updated>2026-08-08T18:58:17Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-08T18:58:17Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=c21f7324abdb6d83300a62b95e2af27bfcc75a1e'/>
<id>urn:sha1:c21f7324abdb6d83300a62b95e2af27bfcc75a1e</id>
<content type='text'>
Two changes to how the gap is defined.

1. UNIFORM GAP. Previously every pane inset every side by `gap`, so two
   neighbours each gave up a cell and the space between them was 2*gap while
   the screen edge got only gap. Now a pane insets RIGHT and BOTTOM always,
   and LEFT/TOP only where that edge is the viewport boundary. Two neighbours
   contribute exactly one gap between them, and the outer screen edges get
   the same gap. `gap 1` now means 1 everywhere.

   The per-side offset is computed in tiled_panes, which is the layer that
   knows the viewport, and pushed to panes via Pane::set_gap_offset before the
   content-offset sweep. Floating panes keep a zero gap: they are positioned
   explicitly by the user, so an inter-pane gap does not apply to them (they
   still get padding).

2. ASPECT CORRECTION. Terminal cells are roughly twice as tall as they are
   wide, so an equal number of rows and columns does not look like an equal
   distance. `gap` and `padding` are now expressed in ROWS, and columns are
   multiplied by `gap_aspect` (default 2, minimum 1). Style carries the
   resolved pane_gap_x/y and pane_padding_x/y.

FrameConfig::Default is now hand-written rather than derived: gap_aspect must
default to 2 to match the KDL parser, otherwise a parsed config disagrees with
a default-constructed one and it surfaces as config round-trip diffs (this
broke can_define_ui_configuration_in_configfile and both
bare_config_from_default_assets_to_string tests).

Tests assert the actual invariants: stacked and side-by-side neighbours get
exactly one gap between them, screen edges get the same gap as between panes,
and inset_by refuses to collapse a pane too small to survive it.

Baselines held: zellij-server 1421/30, zellij-utils 433/5.
</content>
</entry>
<entry>
<title>sl0p: configurable pane gap (outer) and padding (inner)</title>
<updated>2026-08-08T17:49:23Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-08T17:49:23Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=30782cbb2fab4e01cadb9815927a404591472064'/>
<id>urn:sha1:30782cbb2fab4e01cadb9815927a404591472064</id>
<content type='text'>
Adds `ui { pane_frames { gap N; padding N } }`, both defaulting to 0 so
stock configs are unaffected.

  gap     - cells each pane gives up on every side, leaving real empty
            terminal between neighbouring panes (WM-style outer gaps).
            Two adjacent panes therefore show 2*gap between them.
  padding - cells between a pane's frame and its content; breathing room
            inside the pane without moving the frame.

Design: the pane KEEPS its allocated PaneGeom. Only two things move:

  1. the drawn frame, via Viewport::inset(gap), reusing the existing
     frame_geom_override mechanism already used for stacked panes;
  2. the content, via Offset::expanded_by(gap + padding) inside
     set_content_offset.

Because the allocation is untouched, the kasuari resize solver and mouse
hit-testing need no changes at all, and a click landing in a gap still
resolves to the owning pane. Content position, size and cursor are all
derived from content_offset, so widening it in one place keeps every
consumer consistent.

Viewport::inset refuses to shrink a pane below a frame's minimum viable
extent (two borders + one content cell) rather than emitting a degenerate
or zero-sized frame. Negative KDL values clamp to 0 instead of wrapping.

Tests cover: inset identity/shrink/recentre, the containment invariant
(an inset frame never escapes its allocation), small-pane refusal, and
the key safety property that content inset by gap+border+padding lands
strictly inside the frame drawn at inset(gap).
</content>
</entry>
<entry>
<title>sl0p: configurable pane title alignment</title>
<updated>2026-08-08T17:31:55Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-08T17:31:55Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=3f767ee91b08eebf10b693c08c173b8329ba2c14'/>
<id>urn:sha1:3f767ee91b08eebf10b693c08c173b8329ba2c14</id>
<content type='text'>
Adds `ui { pane_frames { title_alignment "left"|"center"|"right" } }`.

Full-frame panes previously hardcoded the title flush left, while frameless
panes (pane_frame_style "titles") already centered it via
compose_bracketed_title. This brings full frames up to parity and makes the
position configurable.

Implementation notes:
- The value rides on `Style`, which is already threaded to FrameParams, so no
  new plumbing through Tab/PaneContentsAndUi was needed.
- It is deliberately NOT round-tripped over the plugin protobuf boundary:
  plugins have no use for frame title alignment and adding a proto field
  would force regenerating the committed prost assets in assets/prost/.
- Left keeps the original code path untouched, so default configs render
  exactly as before.
- The geometry loop clamps the title start against the right-side indicators
  and breaks on `&gt;=` rather than `==`, so a mis-clamped segment cannot spin.

Tests: alignment ordering, frame width preservation across widths, and
narrow-pane safety (cols 1..24 must not panic or corrupt width).
</content>
</entry>
<entry>
<title>Even more debian upgrades (#5424)</title>
<updated>2026-07-30T08:43:00Z</updated>
<author>
<name>Aram Drevekenin</name>
<email>aram@poor.dev</email>
</author>
<published>2026-07-30T08:43:00Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=19befc2118f5943862629fd9ce11de855056c3ef'/>
<id>urn:sha1:19befc2118f5943862629fd9ce11de855056c3ef</id>
<content type='text'>
* Upgrade axum-extra 0.12, axum-server 0.8, rcgen 0.14

* rustfmt</content>
</entry>
<entry>
<title>More dependency upgrades for debian (#5421)</title>
<updated>2026-07-29T15:08:08Z</updated>
<author>
<name>Aram Drevekenin</name>
<email>aram@poor.dev</email>
</author>
<published>2026-07-29T15:08:08Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=cab75c58fec089d7ee97c0e7ca1abafa9ef5e33f'/>
<id>urn:sha1:cab75c58fec089d7ee97c0e7ca1abafa9ef5e33f</id>
<content type='text'>
* upgrade insta, rand, sha2, strum, thiserror, tokio-tungstenite, toml, which; drop unused semver and typetag

* upgrade sysinfo to 0.39.5, bump toolchain (msrv) to 1.95.0

* remote legacy yaml config converter and thus drop serde_yaml

* upgrade unicode-width to 0.2.2

* fix(tests): run ipc roundtrip test on a larger stack</content>
</entry>
<entry>
<title>Nested session handling (#5417)</title>
<updated>2026-07-27T16:24:37Z</updated>
<author>
<name>Aram Drevekenin</name>
<email>aram@poor.dev</email>
</author>
<published>2026-07-27T16:24:37Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=ea07e2d5b6bbc9ea6f9c765b0838b3e91156a58d'/>
<id>urn:sha1:ea07e2d5b6bbc9ea6f9c765b0838b3e91156a58d</id>
<content type='text'>
* fix: theme query and updated pixel dimensions

* add no-ui-fullscreen key/action/capability

* protocol core

* integration test harness

* fix integration tests

* fix flaky tests

* nested ui (dimming) and directional focus initial implementation

* integration tests for nested ui dimming and directional focus

* initial full screen nesteed implementation

* fix: keep hidden floating panes hidden across nested no-ui fullscreen

* fix integration tests

* no ui fullscreen integration tests

* hardening behavior/tests

* fix(nested): re-derive descend/dim on host tab switch and new-tab so clicking dimmed chrome ascends

* initial modal implementation

* initial ui improvements

* fix integration tests

* improve nested selection ui

* nested session modal refactoring and tests

* improve integration test speed somewhat

* some ui adjustments

* simplify full/auto nested session handling

* hints in compact-bar

* guest stdin leak fix

* allow floating panes to be fullscreen

* make nested session handling configurable

* protocol reannounce

* refactor: naming confusion

* improve ux somewhat

* rustfmt

* merge fixes

* improve integration test rate limiting (fix ci)

* add pr</content>
</entry>
</feed>
