mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
- Replace the 'missing ?tail means reload' overload with an explicit ?full=1 query param: the frontend's first buffer load after a page load (selectSession) now requests full=1, tab switches keep ?tail=, and the legacy no-param callers (response-viewer fallback, clearTerminal refresh) keep the cheap visible-frame path — the COD-47 feature was previously unreachable from a real reload. - When the full-history capture succeeds, return it ALONE instead of prepending the byte buffer + \x1b[H\x1b[2J: the capture is the rendered superset of the byte history, and ED2 clears only the viewport so the concat replayed the whole conversation twice in xterm scrollback. The history+clear+frame concat stays for the visible-frame/tab-switch path. - Pass an explicit execSync maxBuffer for the full-history capture (configured terminalBufferMaxBytes + slack) — the 1MB Node default ENOBUFS-killed exactly the multi-MB captures the feature exists for; log ENOBUFS concisely instead of dumping the truncated stdout. - Bound the capture itself via -S -<N> derived from the configured tmux history limit (was unbounded -S -), and add -J so lines hard-wrapped at the capture-time pane width reflow in the browser xterm. - Cap the concatenated buffer to terminalBufferMaxBytes EARLY (before the regex normalization passes) so multi-MB captures don't stall the event loop normalizing bytes that get sliced away. - Tests: route tests updated for ?full=1 semantics (capture-alone response, config-forwarded capture bounds, byte-history fallback, no-param requests stay on the visible-frame path); source-scan tests cover the bounded -J -S -<N> flags and explicit maxBuffer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
62 lines
2.9 KiB
TypeScript
62 lines
2.9 KiB
TypeScript
/**
|
|
* COD-47: full tmux scrollback replay on reload.
|
|
*
|
|
* Under VITEST, TmuxManager no-ops execSync (IS_TEST_MODE), so we can't drive
|
|
* real tmux. Instead we assert the capture-arg construction directly from
|
|
* source (same approach as tmux-capture-color.test.ts): a full-history capture
|
|
* must use `capture-pane -p -e -J -S -<N>` (bounded to the configured history
|
|
* limit, with an explicit exec maxBuffer) and skip the single-screen snapshot
|
|
* repaint, while the visible capture keeps `capture-pane -p -e`.
|
|
*/
|
|
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
describe('tmux full-history pane capture (COD-47)', () => {
|
|
const source = readFileSync(resolve(import.meta.dirname, '../src/tmux-manager.ts'), 'utf8');
|
|
const methodStart = source.indexOf('capturePaneBuffer(muxName: string');
|
|
const methodBody = source.slice(methodStart, methodStart + 4000);
|
|
|
|
it('capturePaneBuffer accepts pane-capture options with a fullHistory flag', () => {
|
|
expect(methodStart).toBeGreaterThan(-1);
|
|
// The method signature must carry the opts channel...
|
|
expect(source.slice(methodStart, methodStart + 160)).toContain('PaneCaptureOptions');
|
|
// ...and the body must branch on opts.fullHistory.
|
|
expect(methodBody).toContain('opts?.fullHistory === true');
|
|
});
|
|
|
|
it('full-history mode captures scrollback bounded to the configured history limit (-J -S -<N>)', () => {
|
|
// `-S -<N>` (not unbounded `-S -`) keeps tmux from serializing more
|
|
// scrollback than the configured history limit retains; `-J` re-joins
|
|
// lines hard-wrapped at the capture-time pane width.
|
|
expect(source).toContain('capture-pane -p -e -J -S -${historyLines}');
|
|
});
|
|
|
|
it('full-history exec sets an explicit maxBuffer (default 1MB would ENOBUFS multi-MB dumps)', () => {
|
|
expect(methodBody).toContain('maxBuffer');
|
|
expect(methodBody).toContain('FULL_HISTORY_CAPTURE_SLACK_BYTES');
|
|
});
|
|
|
|
it('still offers the visible single-screen capture for fast tab switches', () => {
|
|
expect(source).toContain("'capture-pane -p -e'");
|
|
});
|
|
|
|
it('returns full-history capture as raw scrollback (skips the single-screen repaint)', () => {
|
|
// When fullHistory, return the raw buffer BEFORE the formatPaneSnapshot
|
|
// repaint (which is single-screen and would clip a multi-screen history).
|
|
const earlyReturn = methodBody.indexOf('return normalizeScrollbackEol(buffer);');
|
|
const snapshot = methodBody.indexOf('formatPaneSnapshot(');
|
|
expect(earlyReturn).toBeGreaterThan(-1);
|
|
expect(snapshot).toBeGreaterThan(-1);
|
|
expect(earlyReturn).toBeLessThan(snapshot);
|
|
});
|
|
|
|
it('captureActivePaneBuffer forwards the capture options', () => {
|
|
const sig = source.indexOf('captureActivePaneBuffer(muxName: string');
|
|
expect(sig).toBeGreaterThan(-1);
|
|
const body = source.slice(sig, sig + 800);
|
|
expect(body).toContain('opts');
|
|
expect(body).toContain('this.capturePaneBuffer(muxName, target, opts)');
|
|
});
|
|
});
|