mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 02:49:33 +01:00
fix(tmux): keep browser keys from switching a tab onto another session (#669)
Squash of PR #669. Browser terminals could be moved onto another session by tmux's prefix keys: Ctrl+B then ), (, s or w switched the tab's tmux client to a different session while the tab kept the original's name. Codeman's tmux socket now keeps only Ctrl+B Ctrl+B (send-prefix) in its prefix table, and codeman tui and scripts/tmux-manager.sh get F1 as their way out instead of Ctrl+B d. Merge-time fixes by the maintainer: on an empty prefix table the unbind-key that fails no longer skips binding send-prefix; a key tmux lists escaped (prefix C-\ shows as C-\\) compares equal, so the reset does not rerun on every create; and reconcileSessions() reduces the table at boot when the socket has live sessions, so sessions that survive an upgrade are covered too. Co-authored-by: christianhaberl <116439789+christianhaberl@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
620e7728ec
commit
6eaaa960ff
14 files changed
+775
-28
No files matched your search
@@ -13,6 +13,7 @@ import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
applyLiveMetrics,
|
||||
applyMuxNames,
|
||||
beginAttachHandoff,
|
||||
buildAttachBanner,
|
||||
buildListLines,
|
||||
confirmKillStep,
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
tmuxSocketFromEnv,
|
||||
} from '../../src/tui/tui-app.js';
|
||||
import { createTuiModel, stateSince } from '../../src/tui/tui-model.js';
|
||||
import type { TuiClient } from '../../src/tui/tui-client.js';
|
||||
import { glyphsFor } from '../../src/tui/tui-render.js';
|
||||
import type { TuiLiveSessionMetrics, TuiTmuxSession } from '../../src/tui/tui-client.js';
|
||||
import type { TuiConfirmState, TuiRow, TuiSessionRow } from '../../src/tui/tui-types.js';
|
||||
@@ -596,6 +598,40 @@ describe('the held-Ctrl detach alias', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('the way out when F1 is taken, on a socket with no detach key', () => {
|
||||
// Codeman keeps only `<prefix> send-prefix` in its socket's prefix table
|
||||
// (src/tmux-prefix-table.ts), so tmux's stock `Ctrl+B then d` does nothing there.
|
||||
it('names the held-Ctrl chord on the bar instead of the stock d', () => {
|
||||
const bar = buildAttachBanner({ prefix: 'C-b', heldAlias: 'C-d' })['status-format[0]'];
|
||||
expect(bar).toContain('#[bold]Ctrl+B then Ctrl+D#[nobold]');
|
||||
expect(bar).not.toContain('then d');
|
||||
});
|
||||
|
||||
it('hands the same chord back from the attach handoff, for the help overlay and the hint line', async () => {
|
||||
const free = new Set(['C-d', 'M-1']);
|
||||
const client = {
|
||||
readPrefixKey: async () => 'C-b',
|
||||
readDetachKey: async () => null,
|
||||
readPrefixBinding: async (key: string) => (free.has(key) ? null : 'display-message mine'),
|
||||
bindDetachKey: async () => true,
|
||||
unbindDetachKey: async () => true,
|
||||
bindSwitchKey: async () => true,
|
||||
unbindSwitchKey: async () => true,
|
||||
readSessionOptions: async () => null,
|
||||
applySessionOptions: async () => true,
|
||||
restoreSessionOptions: async () => true,
|
||||
readWindowSizing: async () => null,
|
||||
presizeWindow: async () => true,
|
||||
followAttachingClient: async () => true,
|
||||
restoreWindowSizing: async () => true,
|
||||
} as unknown as TuiClient;
|
||||
|
||||
const handoff = await beginAttachHandoff(client, 'codeman-abc12345', 'w1-case');
|
||||
|
||||
expect(handoff.chord).toBe('Ctrl+B then Ctrl+D');
|
||||
});
|
||||
});
|
||||
|
||||
describe('naming a session the TUI starts', () => {
|
||||
it("follows the web UI's w<n>-<case> convention", () => {
|
||||
expect(nextSessionName('mirofish', [])).toBe('w1-mirofish');
|
||||
@@ -737,8 +773,11 @@ describe('the way out of an attach', () => {
|
||||
const keys = helpKeysFor(GLYPHS, { server: true, detach: 'Ctrl+A then d' });
|
||||
const detach = keys.find(([key]) => key === 'Ctrl+A then d');
|
||||
expect(detach?.[1]).toContain('detach');
|
||||
// Degraded mode still attaches, so it still needs the way out.
|
||||
expect(helpKeysFor(GLYPHS, { server: false }).map(([key]) => key)).toContain('Ctrl+B then d');
|
||||
// Degraded mode still attaches, so it still needs the way out. Before the first
|
||||
// attach that is F1: Codeman's socket has no `d` in its prefix table to name.
|
||||
const before = helpKeysFor(GLYPHS, { server: false }).map(([key]) => key);
|
||||
expect(before).toContain(ONE_KEY_DETACH);
|
||||
expect(before).not.toContain('Ctrl+B then d');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -558,6 +558,12 @@ describe('codeman tui (under a pty)', () => {
|
||||
term.write('?');
|
||||
await waitFor(() => frameLines(output).some((line) => line.includes('Keys')), 'the help overlay');
|
||||
expect(frameLines(output).join('\n')).toContain('kill (y to confirm)');
|
||||
// Nothing has been attached yet, so this is the dashboard's starting label:
|
||||
// F1, what an attach claims, not tmux's stock chord, which Codeman's socket
|
||||
// does not bind (src/tmux-prefix-table.ts).
|
||||
const detachLine = frameLines(output).find((line) => line.includes('detach from an attached session'));
|
||||
expect(detachLine).toContain('F1');
|
||||
expect(detachLine).not.toContain('Ctrl+B');
|
||||
|
||||
term.write('\u001b');
|
||||
await waitFor(() => !frameLines(output).some((line) => line.includes('Keys')), 'escape to close the overlay');
|
||||
|
||||
Reference in new issue
Block a user