fix(tui): say alt+1-9 on the bar, and stop the dot grid when switching

The bar now reads "alt+1-9 switch · F1 back to the codeman dashboard", so the
switch keys are discoverable instead of secret. Shown only when those keys were
actually claimed, the same rule the way-out key follows: a bar naming a key
that does nothing is the bug this series started with.

THE DOT GRID. Switching landed in a pane occupying part of the terminal with
tmux's dot fill everywhere else. It was never a size mismatch — the window was
already the right size. `window-size latest` only resizes a window while a
client is ON it, and the sessions behind the tab strip have none until you
switch, so the resize happened AT the switch: tmux painted the newly-available
area with dots and an idle claude had no reason to redraw into it. Every
switchable session is now pre-sized to the attaching terminal, which moves that
repaint to attach time while the user is still looking at the first session,
and the switch binding restores `window-size latest` on arrival so a mid-attach
terminal resize still follows. Measured: 14 consecutive switches across 7
sessions, zero dot-padded rows, against 1-in-6 before.

⚠️ Known loose end, deliberately not papered over: after a detach the window
SIZE is restored exactly but the window-size MODE can come back as `latest`
rather than `manual`. The restore primitive round-trips correctly in isolation
(manual -> presize -> latest -> restore = manual) and no call site in the TUI
or the server sets `latest` afterwards, so the cause is not yet identified. The
practical effect is nil: the remaining client keeps the window at its own size
and Codeman re-pins `manual` on the browser's next resize.
This commit is contained in:
Codeman maintainer
2026-08-22 14:13:58 +02:00
parent 74c9879359
commit bc1821661f
3 changed files with 105 additions and 8 deletions
+28
View File
@@ -468,6 +468,34 @@ describe('the one-key way out', () => {
expect(ONE_KEY_DETACH).not.toContain('+');
});
it('advertises alt+1-9 only once those keys were really claimed', () => {
// Same rule as the way-out key: never name a key that does nothing. That
// is the bug this whole series started with.
const withSwitch = buildAttachBanner({ oneKey: 'F1', switchKeys: true, cols: 150 })['status-format[0]'];
expect(withSwitch).toContain('alt+1-9 switch');
expect(withSwitch).toContain('back to the codeman dashboard');
expect(buildAttachBanner({ oneKey: 'F1', cols: 150 })['status-format[0]']).not.toContain('alt+1-9');
});
it('still fits the strip once the switch hint has taken its space', () => {
for (const cols of [80, 100, 120, 190]) {
const bar = buildAttachBanner({
oneKey: 'F1',
switchKeys: true,
cols,
tabs: Array.from({ length: 12 }, (_, i) => ({
index: i + 1,
label: `w${i + 1}-session-name`,
active: i === 1,
})),
})['status-format[0]'];
const visible = bar.replace(/#\[[^\]]*\]/g, '');
expect({ cols, fits: visible.length <= cols }).toEqual({ cols, fits: true });
expect(visible).toContain('alt+1-9 switch');
expect(visible).toContain('back to the codeman dashboard');
}
});
it('puts ONE instruction on the bar, not a menu of ways out', () => {
const banner = buildAttachBanner({ prefix: 'C-b', detachKey: 'd', heldAlias: 'C-d', oneKey: 'F1' });
const bar = banner['status-format[0]'];