mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
Every keyboard open and close resized the PTY, and Claude Code's inline renderer answers each height change by clearing the screen and reprinting only the transcript tail that fits, so rows above that tail were lost from tmux for good. Measured on Claude Code 2.1.306 with tmux 3.2a: five keyboard cycles (62x38 to 62x15 and back) left 75 of 100 numbered lines in the pane's history; with the rows held the PTY stays 62x38, no SIGWINCH reaches the pane and all 100 remain (#672). While the keyboard is up the rows captured at the show transition are held through getTerminalDimensions, columns still follow the box, and the screen, now taller than its box, is translated up by --kb-clip so the last content row sits at the bottom of the visible area. This is the one documented exception to the columns-only geometry rule: there the extra rows came from another device and nothing kept the composer in view, here they are this device's own rows held on purpose and the clip keeps the composer visible. The local-echo overlay's setRowLimit narrows the renderer's existing totalRows bound, so one place decides where a long draft stops growing downward. Only inline renderers hold, through a new registry capability keyboardHold: 'inline' (Claude alone) published to the page as window.__codemanKeyboardHoldModes. Full-height layouts such as codex, Claude's fullscreen mode (mouse tracking on, unmeasured), and remote or docker panes resize as before. The hold is released on keyboard close, a shape change, a tab switch, a session update, a reconnect, or the setting turned off. Per-device setting Keep Size Under Keyboard, default on.
68 lines
2.8 KiB
TypeScript
68 lines
2.8 KiB
TypeScript
/**
|
|
* @fileoverview The "Keep Size Under Keyboard" App Settings row (#672) in Chinese (zh-CN):
|
|
* its label and description run through the real translator over the real index.html in
|
|
* JSDOM, read in Chinese with no English left beyond `Claude` and `Codex`, and each key is
|
|
* in the dictionary once (a repeated key in the object literal silently replaces the first).
|
|
*
|
|
* Port: N/A.
|
|
*/
|
|
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import vm from 'node:vm';
|
|
import { JSDOM } from 'jsdom';
|
|
import { afterAll, describe, expect, it } from 'vitest';
|
|
|
|
const PUBLIC = resolve(import.meta.dirname, '../src/web/public');
|
|
const I18N = readFileSync(resolve(PUBLIC, 'i18n.js'), 'utf8');
|
|
const INDEX = readFileSync(resolve(PUBLIC, 'index.html'), 'utf8');
|
|
|
|
type Api = { configure(o: Record<string, string>): void; start(): void };
|
|
|
|
/** What may stay Latin: the two CLI names. */
|
|
const leftover = (text: string) => text.replace(/\b(Claude|Codex)\b/g, '').match(/[A-Za-z]+/g) ?? [];
|
|
|
|
function rowStrings(doc: Document): { label: string; description: string } {
|
|
const row = doc.getElementById('appSettingsKeyboardHoldsRows')!.closest('.set-row')!;
|
|
return {
|
|
label: row.querySelector('.set-row-label')!.textContent!.trim(),
|
|
description: row.querySelector('.set-row-desc')!.textContent!.trim(),
|
|
};
|
|
}
|
|
|
|
const source = new JSDOM(INDEX);
|
|
const english = rowStrings(source.window.document);
|
|
|
|
const dom = new JSDOM(INDEX, { runScripts: 'outside-only', url: 'http://localhost/' });
|
|
vm.runInContext(I18N, dom.getInternalVMContext(), { filename: 'i18n.js' });
|
|
const api = (dom.window as unknown as { CodemanI18n: Api }).CodemanI18n;
|
|
api.start();
|
|
api.configure({ language: 'zh-CN' });
|
|
const chinese = rowStrings(dom.window.document);
|
|
|
|
afterAll(() => {
|
|
source.window.close();
|
|
dom.window.close();
|
|
});
|
|
|
|
describe('the Keep Size Under Keyboard row in zh-CN', () => {
|
|
it('finds the row (the check is not vacuous)', () => {
|
|
expect(english.label).toBe('Keep Size Under Keyboard');
|
|
expect(english.description).toMatch(/^For local Claude sessions, opening the on-screen keyboard/);
|
|
});
|
|
|
|
it('translates the label and the description, with no English left', () => {
|
|
expect(chinese).toEqual({
|
|
label: '键盘弹出时保持尺寸',
|
|
description:
|
|
'对于本地 Claude 会话,打开屏幕键盘时会保持会话尺寸并改为滚动视图,这样 Claude 不会重绘屏幕而丢失对话记录行。全屏界面(Codex、Claude 的全屏模式)仍像以前一样调整尺寸。',
|
|
});
|
|
for (const key of ['label', 'description'] as const) expect(leftover(chinese[key])).toEqual([]);
|
|
});
|
|
|
|
it('adds each key to the dictionary once', () => {
|
|
for (const text of [english.label, english.description]) {
|
|
expect(I18N.split(`'${text}'`).length + I18N.split(`"${text}"`).length - 2).toBe(1);
|
|
}
|
|
});
|
|
});
|