fix(web-client): various bugs (#4257)

* fix: background color issues and server recovery bug

* fix: display title in web client

* style(fmt): rustfmt

* docs(changelog): add PR
This commit is contained in:
Aram Drevekenin 2025-07-02 13:38:00 +02:00 committed by GitHub
parent ca0048bdcb
commit c5487fb1b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 60 additions and 29 deletions

View file

@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## [Unreleased] ## [Unreleased]
* feat: multiple select and bulk pane actions (https://github.com/zellij-org/zellij/pull/4169 and https://github.com/zellij-org/zellij/pull/4171 and https://github.com/zellij-org/zellij/pull/4221) * feat: multiple select and bulk pane actions (https://github.com/zellij-org/zellij/pull/4169 and https://github.com/zellij-org/zellij/pull/4171 and https://github.com/zellij-org/zellij/pull/4221)
* feat: add an optional key tooltip to show the current keybindings for the compact bar (https://github.com/zellij-org/zellij/pull/4225) * feat: add an optional key tooltip to show the current keybindings for the compact bar (https://github.com/zellij-org/zellij/pull/4225)
* feat: web-client, allowing users to share sessions in the browser (https://github.com/zellij-org/zellij/pull/4242) * feat: web-client, allowing users to share sessions in the browser (https://github.com/zellij-org/zellij/pull/4242 and https://github.com/zellij-org/zellij/pull/4257)
* performance: consolidate renders (https://github.com/zellij-org/zellij/pull/4245) * performance: consolidate renders (https://github.com/zellij-org/zellij/pull/4245)
* feat: add plugin API to replace a pane with another existing pane (https://github.com/zellij-org/zellij/pull/4246) * feat: add plugin API to replace a pane with another existing pane (https://github.com/zellij-org/zellij/pull/4246)
* feat: add "stack" keybinding and CLI action to add a stacked pane to the current pane (https://github.com/zellij-org/zellij/pull/4255) * feat: add "stack" keybinding and CLI action to add a stacked pane to the current pane (https://github.com/zellij-org/zellij/pull/4255)

View file

@ -42,7 +42,16 @@ export function initWebSockets(webClientId, sessionName, term, fitAddon, sendAns
} }
let data = event.data; let data = event.data;
if (typeof data === 'string' && data.includes('\x1b[0 q')) {
if (typeof data === 'string') {
// Handle ANSI title change sequences
const titleRegex = /\x1b\]0;([^\x07\x1b]*?)(?:\x07|\x1b\\)/g;
let match;
while ((match = titleRegex.exec(data)) !== null) {
document.title = match[1];
}
if (data.includes('\x1b[0 q')) {
const shouldBlink = term.options.cursorBlink; const shouldBlink = term.options.cursorBlink;
const cursorStyle = term.options.cursorStyle; const cursorStyle = term.options.cursorStyle;
let replacement; let replacement;
@ -62,6 +71,8 @@ export function initWebSockets(webClientId, sessionName, term, fitAddon, sendAns
} }
data = data.replace(/\x1b\[0 q/g, replacement); data = data.replace(/\x1b\[0 q/g, replacement);
} }
}
term.write(data); term.write(data);
}; };
@ -145,7 +156,7 @@ function startWsControl(wsControl, term, fitAddon, ownWebClientId) {
term.options.cursorInactiveStyle = cursor_inactive_style; term.options.cursorInactiveStyle = cursor_inactive_style;
} }
const body = document.querySelector("body"); const body = document.querySelector("body");
body.style.background = theme.background; body.style.background = theme.background || "black";
const terminal = document.getElementById("terminal"); const terminal = document.getElementById("terminal");
terminal.style.background = theme.background; terminal.style.background = theme.background;

View file

@ -145,7 +145,11 @@ pub fn zellij_server_listener(
WebServerToWebClientControlMessage::LogError { lines }, WebServerToWebClientControlMessage::LogError { lines },
); );
}, },
_ => {}, _ => {
// server disconnected, stop trying to listen otherwise we retry
// indefinitely and get 100% CPU
break;
},
} }
} }
if reconnect_to_session.is_none() { if reconnect_to_session.is_none() {

View file

@ -49,6 +49,7 @@ pub struct FloatingPanes {
show_panes: bool, show_panes: bool,
pane_being_moved_with_mouse: Option<(PaneId, Position)>, pane_being_moved_with_mouse: Option<(PaneId, Position)>,
senders: ThreadSenders, senders: ThreadSenders,
window_title: Option<String>,
} }
#[allow(clippy::borrowed_box)] #[allow(clippy::borrowed_box)]
@ -84,6 +85,7 @@ impl FloatingPanes {
active_panes: ActivePanes::new(&os_input), active_panes: ActivePanes::new(&os_input),
pane_being_moved_with_mouse: None, pane_being_moved_with_mouse: None,
senders, senders,
window_title: None,
} }
} }
pub fn stack(&self) -> Option<FloatingPanesStack> { pub fn stack(&self) -> Option<FloatingPanesStack> {
@ -234,6 +236,7 @@ impl FloatingPanes {
.or_else(|| self.panes.keys().next().copied()) .or_else(|| self.panes.keys().next().copied())
} }
pub fn toggle_show_panes(&mut self, should_show_floating_panes: bool) { pub fn toggle_show_panes(&mut self, should_show_floating_panes: bool) {
self.window_title = None; // clear so that it will be re-rendered once we toggle back
self.show_panes = should_show_floating_panes; self.show_panes = should_show_floating_panes;
if should_show_floating_panes { if should_show_floating_panes {
self.active_panes.focus_all_panes(&mut self.panes); self.active_panes.focus_all_panes(&mut self.panes);
@ -438,6 +441,11 @@ impl FloatingPanes {
.render_pane_contents_for_client(*client_id) .render_pane_contents_for_client(*client_id)
.with_context(err_context)?; .with_context(err_context)?;
} }
pane_contents_and_ui.render_terminal_title_if_needed(
*client_id,
client_mode,
&mut self.window_title,
);
// this is done for panes that don't have their own cursor (eg. panes of // this is done for panes that don't have their own cursor (eg. panes of
// another user) // another user)
pane_contents_and_ui pane_contents_and_ui

View file

@ -459,7 +459,7 @@ impl Pane for TerminalPane {
let pane_title = if self.pane_name.is_empty() && input_mode == InputMode::RenamePane { let pane_title = if self.pane_name.is_empty() && input_mode == InputMode::RenamePane {
"Enter name..." "Enter name..."
} else if self.pane_name.is_empty() { } else if self.pane_name.is_empty() {
self.grid.title.as_deref().unwrap_or(&self.pane_title) self.grid.title.as_deref().unwrap_or("")
} else { } else {
&self.pane_name &self.pane_name
}; };

View file

@ -1143,6 +1143,10 @@ impl TiledPanes {
.add_character_chunks_to_client(client_id, boundaries_to_render, None) .add_character_chunks_to_client(client_id, boundaries_to_render, None)
.with_context(err_context)?; .with_context(err_context)?;
} }
if floating_panes_are_visible {
// we do this here so that when they are toggled off, we will make sure to re-render the title
self.window_title = None;
}
Ok(()) Ok(())
} }
pub fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { pub fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {

View file

@ -54,13 +54,17 @@ pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String {
} }
pub fn make_terminal_title(pane_title: &str) -> String { pub fn make_terminal_title(pane_title: &str) -> String {
// if we receive a title, we display it, otherwise we display the session name
if pane_title.is_empty() {
format!( format!(
"\u{1b}]0;Zellij {}- {}\u{07}", "\u{1b}]0;Zellij {}\u{07}",
get_session_name() get_session_name()
.map(|n| format!("({}) ", n)) .map(|n| format!("({}) ", n))
.unwrap_or_default(), .unwrap_or_default()
pane_title,
) )
} else {
format!("\u{1b}]0;{}\u{07}", pane_title,)
}
} }
// Colors // Colors