diff options
| author | user <user@clank> | 2026-08-12 18:44:24 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-12 18:44:24 +0200 |
| commit | 2c67579398835d51e625085ce3b22c22bc8f0cba (patch) | |
| tree | 2b22d137d988f0bbf658ec4d37336f813933bfa1 /zellij-server/src/panes/grid.rs | |
| parent | sl0p: document workspace hints for consumers (diff) | |
| download | sl0ppij-2c67579398835d51e625085ce3b22c22bc8f0cba.tar.gz sl0ppij-2c67579398835d51e625085ce3b22c22bc8f0cba.tar.xz sl0ppij-2c67579398835d51e625085ce3b22c22bc8f0cba.zip | |
sl0p: --purpose flag and tab-level purpose
Completes the two pieces deferred from the first workspace-hints pass.
`zellij run --purpose X` / `zellij action new-pane --purpose X` tag a pane at
spawn time, which is what an agent wants for panes it creates itself; the OSC
wrapper still works for programs that declare their own role. Not supported with
--in-place, which replaces another pane rather than being a pane of its own: clap
rejects the combination rather than accepting a flag that would do nothing.
`tab name="work" purpose="agent-work"` gives a whole tab a role, reported by
`list-tabs --json`. It rides the tab's ROOT layout node, reusing the pane field
because a tab's root is the tab container and never itself a pane -- asserted by a
test for the tightest case (a tab holding exactly one pane), since a leak there
would silently mislabel a pane.
The interesting part is why the flag first shipped broken. There are TWO protobuf
boundaries -- the plugin API and the client->server IPC contract -- and a CLI flag
must cross the second one. Defaulting the field in the conversion (what "no
protoc" usually forces) compiles cleanly and then silently drops the value in
transit, so --purpose parsed, printed a pane id, and did nothing. Fixed by adding
the field to both contracts by hand, in the .proto AND the committed generated
prost, kept in lockstep so a future regeneration reproduces it: protoc is only
needed to regenerate, not to build. Both boundaries now have a round-trip test,
and the IPC one was confirmed to fail when the wire read is removed.
Sanitising moved to Grid::set_authoritative_purpose, the single choke point every
out-of-band source passes through, after the CLI path was observed delivering
`bad purpose; rm -rf /` intact into list-panes --json.
Diffstat (limited to 'zellij-server/src/panes/grid.rs')
| -rw-r--r-- | zellij-server/src/panes/grid.rs | 14 |
1 files changed, 11 insertions, 3 deletions
diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 688a454a..1b935557 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1039,10 +1039,18 @@ impl Grid { self.pane_purpose = zellij_utils::input::layout::sanitize_pane_purpose(raw); true } - /// Set the purpose from outside the pane (layout application, resurrection). - /// This wins over anything the pane itself declares, now or later. + /// Set the purpose from outside the pane (layout, CLI `--purpose`, + /// resurrection). This wins over anything the pane itself declares, now or + /// later. + /// + /// Sanitised here rather than at each caller: this is the one choke point + /// every out-of-band source passes through, and a purpose reaching a + /// consumer's shell or JSON pipeline unsanitised is the whole risk. A layout + /// value has already been sanitised at parse time, which is idempotent. pub fn set_authoritative_purpose(&mut self, purpose: Option<String>) { - self.pane_purpose = purpose; + self.pane_purpose = purpose + .as_deref() + .and_then(zellij_utils::input::layout::sanitize_pane_purpose); self.pane_purpose_is_authoritative = true; } pub fn new( |
