Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 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 { writeSessionColor } from './sessionColor';
import {
restoreNativeBinaryFromBackup,
restoreClijsFromBackup,
Expand Down Expand Up @@ -174,6 +175,13 @@ const PATCH_DEFINITIONS = [
group: PatchGroup.ALWAYS_APPLIED,
description: `Statusline updates will be properly throttled instead of queued (or debounced)`,
},
{
id: 'session-color',
name: 'Session color from env',
group: PatchGroup.ALWAYS_APPLIED,
description:
'Set session prompt bar color via TWEAKCC_SESSION_COLOR env var',
},
// Misc Configurable
{
id: 'model-customizations',
Expand Down Expand Up @@ -888,6 +896,9 @@ export const applyCustomization = async (
fn: c => writeChannelsMode(c),
condition: !!config.settings.misc?.enableChannelsMode,
},
'session-color': {
fn: c => writeSessionColor(c),
},
};

// ==========================================================================
Expand Down
55 changes: 55 additions & 0 deletions src/patches/sessionColor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, it, expect } from 'vitest';
import { writeSessionColor } from './sessionColor';

const makeCLIState = () =>
'effortValue:oR(w.effort),' +
'activeOverlays:new Set,fastMode:cP8(N5),' +
'...(uF()&&d1&&{advisorModel:d1})';

const makeDefaultState = () =>
'effortValue:void 0,' + 'activeOverlays:new Set,fastMode:!1}';

const makeBoth = () =>
'first{' + makeDefaultState() + 'second{' + makeCLIState();

describe('sessionColor', () => {
describe('writeSessionColor', () => {
it('should inject into CLI initialState', () => {
const result = writeSessionColor(makeCLIState());
expect(result).not.toBeNull();
expect(result).toContain('TWEAKCC_SESSION_COLOR');
expect(result).toContain('{name:"",color:__c}');
});

it('should inject into default app state', () => {
const result = writeSessionColor(makeDefaultState());
expect(result).not.toBeNull();
expect(result).toContain('TWEAKCC_SESSION_COLOR');
});

it('should patch both locations when both present', () => {
const result = writeSessionColor(makeBoth())!;
expect(result).not.toBeNull();
const count = (result.match(/TWEAKCC_SESSION_COLOR/g) || []).length;
expect(count).toBe(2);
});

it('should validate color against allowed list', () => {
const result = writeSessionColor(makeCLIState())!;
expect(result).toContain('.includes(__c)');
expect(result).toContain('"green"');
expect(result).toContain('"cyan"');
});

it('should be idempotent', () => {
const first = writeSessionColor(makeBoth())!;
const second = writeSessionColor(first)!;
expect(second).toBe(first);
});

it('should return null when no pattern found', () => {
const result = writeSessionColor('not a valid file');
expect(result).toBeNull();
});
});
});
68 changes: 68 additions & 0 deletions src/patches/sessionColor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { showDiff } from './index';
import { debug } from '../utils';

const VALID_COLORS = [
'red',
'blue',
'green',
'yellow',
'purple',
'orange',
'pink',
'cyan',
];

const INJECTION =
`,standaloneAgentContext:` +
`(()=>{` +
`let __c=process.env.TWEAKCC_SESSION_COLOR;` +
`return __c&&${JSON.stringify(VALID_COLORS)}.includes(__c)` +
`?{name:"",color:__c}` +
`:void 0` +
`})()`;

export const writeSessionColor = (oldFile: string): string | null => {
if (
oldFile.includes(
'standaloneAgentContext:(()=>{let __c=process.env.TWEAKCC_SESSION_COLOR;'
)
) {
return oldFile;
}

const patterns = [
/,activeOverlays:new Set,fastMode:[$\w]+\([$\w]+\)/,
/,activeOverlays:new Set,fastMode:!1\}/,
];

let result = oldFile;
let patched = false;

for (const pattern of patterns) {
const match = result.match(pattern);
if (!match || match.index === undefined) continue;

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

showDiff(
prePatch,
result,
replacement,
match.index,
match.index + match[0].length
);
Comment on lines +52 to +58
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

showDiff args are inconsistent after the first patch.

Use the pre-patch buffer for indices and pass only INJECTION as injectedText; current call can produce misleading diffs when both patterns are patched.

Suggested fix
   for (const pattern of patterns) {
-    const match = result.match(pattern);
+    const beforePatch = result;
+    const match = beforePatch.match(pattern);
     if (!match || match.index === undefined) continue;

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

     showDiff(
-      oldFile,
+      beforePatch,
       result,
-      replacement,
+      INJECTION,
       match.index,
       match.index + match[0].length
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/sessionColor.ts` around lines 51 - 57, The current showDiff call
uses the post-patch `result` and the full `replacement`, which can produce
misleading diffs after the first patch; update the call to use the pre-patch
buffer (the buffer state before applying the current replacement) for the index
calculations and pass only the INJECTION text as the injectedText argument to
showDiff (keep the same start/end as match.index and match.index +
match[0].length but computed against the pre-patch buffer), so replace the
showDiff(oldFile, result, replacement, ...) invocation with one that uses the
pre-patch buffer for indices and INJECTION for the injectedText parameter.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai Fixed in 35e30f0. showDiff now uses prePatch (pre-patch buffer) for both slicing and diff indices, ensuring consistent results when multiple patterns are patched.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VitalyOstanin, thanks for the update! Using prePatch as the base for both slicing and the showDiff indices ensures that each iteration operates on a stable snapshot, so the displayed diffs remain accurate even when both patterns are matched and patched in the same run. The fix looks correct. ✅


🧠 Learnings used
Learnt from: VitalyOstanin
Repo: Piebald-AI/tweakcc PR: 634
File: src/patches/inputChevronColor.ts:8-9
Timestamp: 2026-03-30T16:29:19.451Z
Learning: In src/patches/inputChevronColor.ts, the Claude Code minified binary uses `\xA0` (U+00A0 non-breaking space) rather than a regular ASCII space `" "` in the chevron component child tail (e.g., after `s6.pointer`). Any regex anchor using `," "` (regular space) to uniquely identify the chevron element will never match the minified output. The pattern ending at `{color:VAR,dimColor:VAR}` is already unique due to the `{isLoading, themeColor}` destructuring context.

Learnt from: bl-ue
Repo: Piebald-AI/tweakcc PR: 421
File: src/patches/thinkerSymbolSpeed.ts:46-58
Timestamp: 2026-01-27T21:16:01.318Z
Learning: In patches under src/patches/, assume code works with minified, original Claude Code installations. Patch regexes must be strict and match the minified format exactly (ignore formatting differences like whitespace/newlines). This guidance applies to all .ts files in src/patches and should be followed when adding or reviewing patches.

Learnt from: ljepson
Repo: Piebald-AI/tweakcc PR: 437
File: src/patches/toolsets.ts:811-833
Timestamp: 2026-01-29T21:19:04.770Z
Learning: In src/patches/toolsets.ts, when matching mode change patterns like `let X=Y(Z,{type:"setMode",mode:vv(TA),destination:"session"});`, the regex should capture the inner variable name (TA) not the entire expression (vv(TA)), because the variable holds the raw mode string value (e.g., "plan", "default", "acceptEdits") and is used in direct string comparisons like `if(${modeVar}==="plan")`. The wrapping function vv() is only for dispatch transformation.

Learnt from: CR
Repo: Piebald-AI/tweakcc PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-06T21:30:11.641Z
Learning: Applies to **/*.{ts,tsx} : Use `chalk` for terminal colors

Learnt from: CR
Repo: Piebald-AI/tweakcc PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-06T21:30:11.641Z
Learning: Applies to **/patches/**/*.{ts,tsx} : Use `,` `;` `}` `{` literal characters at regex start instead of `\\b` for performance in patch patterns

Learnt from: CR
Repo: Piebald-AI/tweakcc PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-06T21:30:11.641Z
Learning: Applies to **/patches/**/*.{ts,tsx} : Avoid `\\b` in regex patterns due to V8 performance issues; use literal character alternatives

Learnt from: CR
Repo: Piebald-AI/tweakcc PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-06T21:30:11.641Z
Learning: Applies to **/patches/**/*.{ts,tsx} : Use `[\$\\w]+` instead of `\\w+` for identifier matching in regex patterns to include `$` for React refs

Learnt from: VitalyOstanin
Repo: Piebald-AI/tweakcc PR: 647
File: src/patches/clearScreen.test.ts:1-1
Timestamp: 2026-03-31T12:24:56.066Z
Learning: In Piebald-AI/tweakcc, test files (`**/*.test.{ts,tsx}`) must explicitly import Vitest functions (`describe`, `it`, `expect`, `beforeEach`, `vi`) from `'vitest'` rather than relying on globals. Although `vitest.config.ts` sets `globals: true`, the `tsconfig.json` does NOT include `"types": ["vitest/globals"]`, so `tsc --noEmit` (run by the pre-commit hook via `pnpm lint`) fails if globals are used without explicit imports. All test files in the repo follow this explicit-import pattern.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

patched = true;
}

if (!patched) {
debug('patch: sessionColor: failed to find app state init patterns');
return null;
}

return result;
};