mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 22:49:41 +02:00
fix(mobile): show the Codex shift-arrow keys only on codex sessions
The two keys #408 adds to the mobile keyboard accessory bar send Shift+Left and Shift+Right, which are Codex bindings (edit the last queued message, step back through the prompt stack). They shipped on both agent layouts, so a claude, pi, grok, omp, deepseek or gemini session got two keys that do nothing. That was not only cosmetic: a tap goes through sendNavKey(), which adds the session to _echoPassthroughSessions and hands editing to plain PTY echo until Enter or Ctrl+C, so on a phone a dead key also switched off the local echo that makes typing feel instant there. The reveal now follows the shape the 🧠 key already uses. The buttons stay in both templates, carry an accessory-btn-codex marker class, and are display:none in styles.css until the bar element carries codex-enabled. The class has to live on the bar rather than on the keys because setMode() rebuilds the buttons' innerHTML on every layout switch. syncCodexKeys() toggles it from the active session's mode (the same lookup _isShellSession() uses) and is called at init and from refreshForActiveSession(), which selectSession() already invokes on every switch. A session's mode is readonly on the server and fixed at create, so no other event can change the answer; the welcome screen (no active session) reads as not codex and hides the keys. The frontend id-branching guard (test/cli-registry-no-id-branching.test.ts) scans only src/**/*.ts, so the mode comparison in a public JS file is in bounds, the same as the existing shell check beside it. Tests: the new describe block in test/mobile-shell-keyboard.test.ts pins the marker class in both templates, the CSS pair, the class for a codex session in both layouts, its absence for claude/shell/pi/omp/deepseek, the re-sync in both directions on a session switch, the no-session case, and the init + refresh wiring. All six positive assertions fail without the source change. README and the changeset now say the keys are Codex-only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -668,3 +668,75 @@ describe('composer nav keys from the bar', () => {
|
||||
expect(app.sendInput).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Codex shift-arrow keys are gated on the active session', () => {
|
||||
// ⇧←/⇧→ are Codex bindings. They ship in both agent templates, but a tap in
|
||||
// any other CLI would do nothing AND hand the session to PTY echo
|
||||
// (sendNavKey adds it to _echoPassthroughSessions), so on a phone a dead key
|
||||
// would also switch local echo off for the rest of the prompt. The reveal
|
||||
// follows the 🧠 key's shape: a marker class on the BAR element, because
|
||||
// setMode() rebuilds the buttons' innerHTML on every layout switch.
|
||||
const stylesSource = readFileSync(resolve('src/web/public/styles.css'), 'utf8');
|
||||
|
||||
it('marks both shift keys in both agent templates so one CSS rule can hide them', () => {
|
||||
const simple = keyboardSource.match(/_simpleButtons\s*:\s*`([\s\S]*?)`/)?.[1] ?? '';
|
||||
const extended = keyboardSource.match(/_extendedButtons\s*:\s*`([\s\S]*?)`/)?.[1] ?? '';
|
||||
for (const template of [simple, extended]) {
|
||||
expect(template).toMatch(/accessory-btn-codex[^>]*data-action="shift-left"/);
|
||||
expect(template).toMatch(/accessory-btn-codex[^>]*data-action="shift-right"/);
|
||||
}
|
||||
});
|
||||
|
||||
it('hides the keys in styles.css until the bar carries codex-enabled', () => {
|
||||
expect(stylesSource).toMatch(/\.keyboard-accessory-bar \.accessory-btn-codex \{\s*display: none;/);
|
||||
expect(stylesSource).toMatch(
|
||||
/\.keyboard-accessory-bar\.codex-enabled \.accessory-btn-codex \{\s*display: inline-flex;/
|
||||
);
|
||||
});
|
||||
|
||||
it.each(['simple', 'extended'])('carries codex-enabled for a codex session in the %s layout', (mode) => {
|
||||
const { bar, barElement } = loadBar('codex');
|
||||
bar.setMode(mode);
|
||||
expect(barElement.classList.contains('codex-enabled')).toBe(true);
|
||||
// The buttons themselves are still in the DOM; the class is what reveals them.
|
||||
expect(barElement.actions).toContain('shift-left');
|
||||
expect(barElement.actions).toContain('shift-right');
|
||||
});
|
||||
|
||||
it.each(['claude', 'shell', 'pi', 'omp', 'deepseek'])(
|
||||
'does not carry codex-enabled for a %s session',
|
||||
(sessionMode) => {
|
||||
const { bar, barElement } = loadBar(sessionMode);
|
||||
bar.setMode('extended');
|
||||
expect(barElement.classList.contains('codex-enabled')).toBe(false);
|
||||
}
|
||||
);
|
||||
|
||||
it('re-syncs the class on a session switch, in both directions', () => {
|
||||
const { app, bar, barElement } = loadBar('codex');
|
||||
expect(barElement.classList.contains('codex-enabled')).toBe(true);
|
||||
|
||||
app.sessions.set('session-2', { mode: 'claude' });
|
||||
app.activeSessionId = 'session-2';
|
||||
bar.refreshForActiveSession();
|
||||
expect(barElement.classList.contains('codex-enabled')).toBe(false);
|
||||
|
||||
app.activeSessionId = 'session-1';
|
||||
bar.refreshForActiveSession();
|
||||
expect(barElement.classList.contains('codex-enabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('drops the class when no session is active (welcome screen)', () => {
|
||||
const { app, bar, barElement } = loadBar('codex');
|
||||
app.activeSessionId = '';
|
||||
bar.refreshForActiveSession();
|
||||
expect(barElement.classList.contains('codex-enabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('is wired at init and on every session switch, like the 🧠 key', () => {
|
||||
const initBody = keyboardSource.match(/\n init\(\) \{([\s\S]*?)\n \},/)?.[1] ?? '';
|
||||
const refreshBody = keyboardSource.match(/\n refreshForActiveSession\(\) \{([\s\S]*?)\n \},/)?.[1] ?? '';
|
||||
expect(initBody).toContain('this.syncCodexKeys();');
|
||||
expect(refreshBody).toContain('this.syncCodexKeys();');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user