Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions src/patches/clearScreen.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { describe, expect, it } from 'vitest';
Comment thread
coderabbitai[bot] marked this conversation as resolved.

import { writeClearScreen, patchRenderFilter } from './clearScreen';

const cmds = Array.from({ length: 31 }, (_, i) => `c${i}`).join(',');
const slashCommandArray = `=>[${cmds}]`;

const renderFilter =
'function g97(H,$){if(H.type!=="user")return!0;if(H.isMeta){if(H.origin?.kind==="channel")return!0;return!1}if(H.isVisibleInTranscriptOnly&&!$)return!1;return!0}';

const makeInput = (delimiter = ';') =>
'const x=1' +
renderFilter +
slashCommandArray +
`${delimiter}let Z=G_H.useCallback(()=>{Nw.get(process.stdout)?.forceRedraw()})`;

describe('clearScreen', () => {
it('exposes forceRedraw and registers /clear-screen command', () => {
const result = writeClearScreen(makeInput());

expect(result).not.toBeNull();
expect(result).toContain(
'globalThis.__tweakccForceRedraw=()=>Nw.get(process.stdout)?.forceRedraw()'
);
expect(result).toContain('name:"clear-screen"');
expect(result).toContain('__tweakccHiddenUUIDs');
expect(result).toContain('globalThis.__tweakccForceRedraw?.()');
});

it('preserves all messages for API context (hides via UUID set, does not remove)', () => {
const result = writeClearScreen(makeInput());

expect(result).not.toBeNull();
expect(result).toContain('__tweakccHiddenUUIDs=new Set(');
expect(result).toContain('return[...m]');
expect(result).not.toContain('content:[]');
expect(result).not.toContain('return k?[');
expect(result).not.toContain('return[]');
});

it('patches render filter to check __tweakccHiddenUUIDs', () => {
const result = writeClearScreen(makeInput());

expect(result).not.toBeNull();
expect(result).toContain(
'globalThis.__tweakccHiddenUUIDs?.has(H.uuid?.slice(0,24)))return!1;if(H.type!=="user")'
);
});

it('preserves original app:redraw callback', () => {
const result = writeClearScreen(makeInput());

expect(result).not.toBeNull();
expect(result).toContain(
'let Z=G_H.useCallback(()=>{Nw.get(process.stdout)?.forceRedraw()})'
);
});

it('returns oldFile when already patched', () => {
const input = makeInput() + ',{name:"clear-screen"}';
const result = writeClearScreen(input);

expect(result).toBe(input);
});

it('returns null when app:redraw callback not found', () => {
const result = writeClearScreen('const x=1;');

expect(result).toBeNull();
});

it('returns null when render filter not found', () => {
const input =
'const x=1' +
slashCommandArray +
';let Z=G_H.useCallback(()=>{Nw.get(process.stdout)?.forceRedraw()})';
const result = writeClearScreen(input);

expect(result).toBeNull();
});

it('works with different delimiters before useCallback', () => {
for (const d of [',', ';', '}', '{']) {
const result = writeClearScreen(makeInput(d));
expect(result).not.toBeNull();
expect(result).toContain('globalThis.__tweakccForceRedraw');
}
});
});

describe('patchRenderFilter', () => {
it('adds __tweakccHiddenUUIDs check at the start of the function', () => {
const result = patchRenderFilter(renderFilter);

expect(result).not.toBeNull();
expect(result).toContain(
'function g97(H,$){if(globalThis.__tweakccHiddenUUIDs?.has(H.uuid?.slice(0,24)))return!1;if(H.type!=="user")'
);
});

it('preserves the rest of the function', () => {
const result = patchRenderFilter(renderFilter);

expect(result).not.toBeNull();
expect(result).toContain('if(H.isMeta)');
expect(result).toContain('if(H.isVisibleInTranscriptOnly&&!$)return!1');
});

it('returns null when pattern not found', () => {
const result = patchRenderFilter('const x=1;');

expect(result).toBeNull();
});

it('works with different function and argument names', () => {
const input =
'function abc(X$,Y$){if(X$.type!=="user")return!0;if(X$.isMeta){if(X$.origin?.kind==="channel")return!0;return!1}return!0}';
const result = patchRenderFilter(input);

expect(result).not.toBeNull();
expect(result).toContain(
'if(globalThis.__tweakccHiddenUUIDs?.has(X$.uuid?.slice(0,24)))return!1;'
);
});
});
100 changes: 100 additions & 0 deletions src/patches/clearScreen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Please see the note about writing patches in ./index

import { debug } from '../utils';
import { showDiff } from './index';
import { writeSlashCommandDefinition } from './slashCommands';

export const writeClearScreen = (oldFile: string): string | null => {
const alreadyPatchedPattern = /name:"clear-screen"/;
if (alreadyPatchedPattern.test(oldFile)) {
return oldFile;
}

const redrawPattern =
/([,;{}])(let [$\w]+=[$\w]+\.useCallback\(\(\)=>\{)([$\w]+)\.get\(process\.stdout\)\?\.forceRedraw\(\)\}/;
const redrawMatch = oldFile.match(redrawPattern);
if (!redrawMatch || redrawMatch.index === undefined) {
debug('patch: clearScreen: failed to find app:redraw callback');
return null;
}

const delimiter = redrawMatch[1];
const mapVar = redrawMatch[3];
const redrawReplacement =
`${delimiter}globalThis.__tweakccForceRedraw=()=>${mapVar}.get(process.stdout)?.forceRedraw();` +
redrawMatch[0].slice(1);

let file =
oldFile.slice(0, redrawMatch.index) +
redrawReplacement +
oldFile.slice(redrawMatch.index + redrawMatch[0].length);

showDiff(
oldFile,
file,
redrawReplacement,
redrawMatch.index,
redrawMatch.index + redrawMatch[0].length
);

const renderFilterResult = patchRenderFilter(file);
if (!renderFilterResult) {
debug('patch: clearScreen: failed to patch render filter g97');
return null;
}
file = renderFilterResult;

const commandDef =
',{type:"local",name:"clear-screen",' +
'description:"Clear screen without resetting conversation context",' +
'supportsNonInteractive:!1,' +
'load:()=>Promise.resolve().then(()=>({call:(H,$)=>{' +
'$.setMessages(m=>{' +
'globalThis.__tweakccHiddenUUIDs=new Set(m.map(x=>x.uuid?.slice(0,24)).filter(Boolean));' +
'return[...m]});' +
'process.stdout.write("\\x1b[2J\\x1b[H\\x1b[3J");' +
'globalThis.__tweakccForceRedraw?.();' +
'return{type:"skip"}}}))}';

const result = writeSlashCommandDefinition(file, commandDef);
if (!result) {
debug('patch: clearScreen: failed to register slash command');
return null;
}

return result;
};

export const patchRenderFilter = (oldFile: string): string | null => {
const pattern =
/(function [$\w]+\([$\w]+,[$\w]+\)\{)if\([$\w]+\.type!=="user"\)return!0;if\([$\w]+\.isMeta\)/;
const match = oldFile.match(pattern);
if (!match || match.index === undefined) {
return null;
}

const funcPrefix = match[1];
const firstArg = match[0].match(/function ([$\w]+)\(([$\w]+),/)?.[2];
if (!firstArg) {
return null;
}

const replacement =
`${funcPrefix}if(globalThis.__tweakccHiddenUUIDs?.has(${firstArg}.uuid?.slice(0,24)))return!1;` +
match[0].slice(funcPrefix.length);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

const result =
oldFile.slice(0, match.index) +
replacement +
oldFile.slice(match.index + match[0].length);

showDiff(
oldFile,
result,
replacement,
match.index,
match.index + match[0].length
);

return result;
};
10 changes: 10 additions & 0 deletions src/patches/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import { writeWorktreeMode } from './worktreeMode';
import { writeAllowCustomAgentModels } from './allowCustomAgentModels';
import { writeVoiceMode } from './voiceMode';
import { writeChannelsMode } from './channelsMode';
import { writeClearScreen } from './clearScreen';
import {
restoreNativeBinaryFromBackup,
restoreClijsFromBackup,
Expand Down Expand Up @@ -174,6 +175,12 @@ const PATCH_DEFINITIONS = [
group: PatchGroup.ALWAYS_APPLIED,
description: `Statusline updates will be properly throttled instead of queued (or debounced)`,
},
{
id: 'clear-screen',
name: 'Clear screen command',
group: PatchGroup.ALWAYS_APPLIED,
description: 'Register /clear-screen command (clear scrollback + redraw)',
},
// Misc Configurable
{
id: 'model-customizations',
Expand Down Expand Up @@ -663,6 +670,9 @@ export const applyCustomization = async (
),
condition: config.settings.misc?.statuslineThrottleMs != null,
},
'clear-screen': {
fn: c => writeClearScreen(c),
},
// Misc Configurable
'patches-applied-indication': {
fn: c =>
Expand Down