mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
fix(tui): keep the way out on the bar, and make Alt+1..9 actually switch
Four faults, all reported at once, and three of them were mine from the last two commits. THE HINT VANISHED. Two independent causes. First, a leaked F1 binding: an attach whose TUI was killed leaves `F1 -> detach-client` in tmux's root table, and the claim treated "already bound" as someone else's key, so every later attach fell back to advertising the tmux chord — the bar stopped saying F1 while F1 still worked. A key already bound to `detach-client` now counts as ours. Second, width: tmux truncates a status line that overflows and drops the RIGHT-aligned segment, which is the hint. The strip now gets a budget measured from the terminal's width minus the hint, and it drops tabs from the far end until it fits. ⚠️ Measured on VISIBLE columns, not format bytes: `#[reverse]` costs zero columns, and counting it made a strip that "fitted" still truncate the hint at 80, 100, 120 and 176 columns on a real terminal. ALT+N DID NOT SWITCH. On the dashboard, a bare digit meant jump AND ATTACH, and a terminal sends Alt+N as ESC then N: when those land in separate reads — routine over SSH — the chord decodes as Escape plus a bare digit, so "switch to tab 2" threw the user into tab 2's pane. A digit now SELECTS, matching what Alt+N means in the web UI; Enter is how you go in. Inside a pane the keys never reached the TUI at all, since tmux owns the terminal, so the attach now binds Alt+1..9 in tmux's root table to `switch-client` — the strip is usable rather than decorative. ⚠️ The bar is applied to every session the strip can reach, each highlighting its own tab: with it on the attached session only, switching landed the user in a pane with no strip and no way out on screen. ⚠️ The leaked-state sweep was missing `status-position`, so it removed the marker and left the position behind — and with no marker the leftover no longer matched, making it permanently unsweepable. Found by diffing every session's options after a detach.
This commit is contained in:
@@ -951,6 +951,36 @@ export class TuiClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a prefix-less key to switch this client to another session, so the tab
|
||||
* strip on the attach bar is not just a picture: Alt+1..9 moves between
|
||||
* sessions from INSIDE a pane, the way it does in the web UI.
|
||||
*
|
||||
* `switch-client` rather than detach-then-attach: it keeps the terminal, so
|
||||
* the move is instant and the TUI stays blocked in its `spawnSync` exactly as
|
||||
* before, still holding the restore it owes.
|
||||
*/
|
||||
async bindSwitchKey(key: string, target: string): Promise<boolean> {
|
||||
if (!MUX_NAME_PATTERN.test(target)) return false;
|
||||
try {
|
||||
await this.exec('tmux', ['-L', this.socket, 'bind-key', '-T', 'root', key, 'switch-client', '-t', target]);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Give back a switch key, but only while it still points at a session. */
|
||||
async unbindSwitchKey(key: string): Promise<void> {
|
||||
const bound = await this.readPrefixBinding(key, 'root');
|
||||
if (!bound || !bound.startsWith('switch-client')) return;
|
||||
try {
|
||||
await this.exec('tmux', ['-L', this.socket, 'unbind-key', '-T', 'root', key]);
|
||||
} catch {
|
||||
/* a stray switch binding is harmless next to failing an attach */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a key back, but ONLY while it still means `detach-client`. Anything
|
||||
* else there is the user's, arrived after we bound ours, and must not be
|
||||
@@ -1103,6 +1133,12 @@ export class TuiClient {
|
||||
// unsetting one index leaves an empty array, which renders as a blank bar.
|
||||
await this.setOption(['-u', '-t', name, 'status-format']);
|
||||
await this.setOption(['-u', '-t', name, 'status-style']);
|
||||
// ⚠️ `status-position` MUST be swept with the rest. It was missing here,
|
||||
// so a sweep cleaned the marker and left the position behind — and with
|
||||
// the marker gone the leftover no longer matched, which made it
|
||||
// permanently unsweepable. Every option the banner writes has to be
|
||||
// undone by the same pass that recognises it.
|
||||
await this.setOption(['-u', '-t', name, 'status-position']);
|
||||
await this.setOption(['-t', name, 'status', 'off']);
|
||||
cleared += 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user