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
1 change: 1 addition & 0 deletions src/defaultSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,7 @@ export const DEFAULT_SETTINGS: Settings = {
},
inputBox: {
removeBorder: false,
chevronIdleThemeColor: 'success',
},
misc: {
showTweakccVersion: true,
Expand Down
19 changes: 19 additions & 0 deletions src/patches/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { writeShowMoreItemsInSelectMenus } from './showMoreItemsInSelectMenus';
import { writeThemes } from './themes';
import { writeContextLimit } from './contextLimit';
import { writeInputBoxBorder } from './inputBorderBox';
import { writeInputChevronColor } from './inputChevronColor';
import { writeThinkerFormat } from './thinkerFormat';
import { writeThinkerSymbolMirrorOption } from './thinkerMirrorOption';
import { writeThinkerSymbolChars } from './thinkerSymbolChars';
Expand Down Expand Up @@ -257,6 +258,13 @@ const PATCH_DEFINITIONS = [
description:
"Your custom styles to the main input box's border will be applied",
},
{
id: 'input-chevron-color',
name: 'Input chevron color',
group: PatchGroup.MISC_CONFIGURABLE,
description:
'The input chevron changes color based on loading state (e.g. green when idle)',
},
{
id: 'subagent-models',
name: 'Subagent models',
Expand Down Expand Up @@ -749,6 +757,17 @@ export const applyCustomization = async (
typeof config.settings.inputBox.removeBorder === 'boolean'
),
},
'input-chevron-color': {
fn: c => {
const themeColorKey = config.settings.inputBox!.chevronIdleThemeColor!;
const theme = config.settings.themes?.[0];
const resolved =
(theme?.colors as Record<string, string>)?.[themeColorKey] ??
themeColorKey;
return writeInputChevronColor(c, resolved);
},
condition: !!config.settings.inputBox?.chevronIdleThemeColor,
},
'subagent-models': {
fn: c => writeSubagentModels(c, config.settings.subagentModels!),
condition: !!config.settings.subagentModels,
Expand Down
34 changes: 34 additions & 0 deletions src/patches/inputChevronColor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { debug } from '../utils';
import { showDiff } from './index';

export const writeInputChevronColor = (
file: string,
resolvedColor: string
): string | null => {
const pattern =
/,\{isLoading:([$\w]+),themeColor:([$\w]+)\}=[$\w]+,([$\w]+)=\2\?\?void 0,[$\w]+;if\([$\w]+\[0\]!==\3\|\|[$\w]+\[1\]!==\1\)[$\w]+=[$\w]+\.createElement\([$\w]+,\{color:\3,dimColor:\1\}/;
Comment on lines +8 to +9
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 26, 2026

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Tighten the regex to include the chevron child.

This match currently stops at {color:...,dimColor:...}, so the first memoized element with the same isLoading/themeColor shape could be rewritten instead of the chevron. In these minified Claude Code patches, the pattern should include one more unique anchor such as the ...pointer," " tail.

Suggested tightening
-  const pattern =
-    /,\{isLoading:([$\w]+),themeColor:([$\w]+)\}=[$\w]+,([$\w]+)=\2\?\?void 0,[$\w]+;if\([$\w]+\[0\]!==\3\|\|[$\w]+\[1\]!==\1\)[$\w]+=[$\w]+\.createElement\([$\w]+,\{color:\3,dimColor:\1\}/;
+  const pattern =
+    /,\{isLoading:([$\w]+),themeColor:([$\w]+)\}=[$\w]+,([$\w]+)=\2\?\?void 0,[$\w]+;if\([$\w]+\[0\]!==\3\|\|[$\w]+\[1\]!==\1\)[$\w]+=[$\w]+\.createElement\([$\w]+,\{color:\3,dimColor:\1\},[$\w]+\.pointer," "\)/;

Based on learnings, patch regexes under src/patches/ must be strict and match the minified format exactly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/inputChevronColor.ts` around lines 22 - 23, The current regex
stored in the pattern constant is too permissive and stops at the object
"{color:...,dimColor:...}", which may match an earlier memoized element instead
of the chevron; update the regex in pattern to extend it to include the chevron
child tail (for example the unique minified anchor containing pointer," ") so
the match requires that exact sequence after ,{color:...,dimColor:...}; modify
the /,\{isLoading:.../.../ regex to append the chevron-specific fragment (the
...pointer," " tail) so it only matches the minified chevron element and not
other memoized elements.

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 Investigated this in detail. The actual Claude Code binary uses \xA0 (non-breaking space U+00A0), not a regular space " ", in the chevron child tail. The suggested anchor pointer," " would never match the minified output.

This is documented in commit 44996da: "Skip regex tightening with pointer tail: binary uses "\xA0" (non-breaking space), not " ", so the suggested anchor would not match."

The current pattern is already unique enough — it matches the specific memoized element with {isLoading, themeColor} destructuring followed by the createElement({color, dimColor}) call, which only appears once in the chevron component.

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, thank you for the thorough investigation and the commit documenting the finding. You're correct — my suggestion was invalid. Since the minified Claude Code binary uses \xA0 (U+00A0 non-breaking space) rather than a regular space in the chevron child tail, the pointer," " anchor would never match, making the suggested tightening worse than doing nothing. The current pattern's uniqueness via the {isLoading, themeColor} destructuring + createElement({color, dimColor}) combination is a sound justification for leaving it as-is.

I'll record this for future reviews.


✏️ Learnings added
Learnt from: VitalyOstanin
Repo: Piebald-AI/tweakcc PR: 634
File: src/patches/inputChevronColor.ts:8-9
Timestamp: 2026-03-30T16:25:20.990Z
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.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
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: 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: 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: 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: bl-ue
Repo: Piebald-AI/tweakcc PR: 419
File: src/patches/thinkingVerbs.ts:35-35
Timestamp: 2026-01-27T18:56:52.902Z
Learning: In src/patches/thinkingVerbs.ts, the regex pattern for matching thinking verbs arrays uses `{50,}` quantifier to require at least 50 items, making it specific enough without needing test coverage since it only matches capitalized verb lists with that many entries.

Learnt from: mike1858
Repo: Piebald-AI/tweakcc PR: 616
File: data/prompts/prompts-2.1.79.json:882-882
Timestamp: 2026-03-19T21:08:36.258Z
Learning: In Piebald-AI/tweakcc (data/prompts/*), escape-only changes to JSON strings that do not alter the rendered prompt text are allowed and should not be flagged as prompt body modifications. The upstream parity rule concerns the user-visible content, not serialization details.


const match = file.match(pattern);

if (!match || match.index === undefined) {
debug('patch: inputChevronColor: failed to find chevron component pattern');
return null;
}

const isLoadingVar = match[1];
const resolvedColorVar = match[3];

const oldColorPart = `color:${resolvedColorVar},dimColor:${isLoadingVar}`;
const newColorPart = `color:${isLoadingVar}?${resolvedColorVar}:${JSON.stringify(resolvedColor)},dimColor:!1`;

const colorPartIndex = match[0].indexOf(oldColorPart);
const startIndex = match.index + colorPartIndex;
const endIndex = startIndex + oldColorPart.length;

const newFile =
file.slice(0, startIndex) + newColorPart + file.slice(endIndex);

showDiff(file, newFile, newColorPart, startIndex, endIndex);

return newFile;
};
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export interface UserMessageDisplayConfig {

export interface InputBoxConfig {
removeBorder: boolean;
chevronIdleThemeColor: string | null;
}

export type TableFormat = 'default' | 'ascii' | 'clean' | 'clean-top-bottom';
Expand Down
3 changes: 2 additions & 1 deletion src/ui/components/MiscView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useContext, useState, useMemo } from 'react';
import { SettingsContext } from '../App';
import Header from './Header';
import { TableFormat } from '../../types';
import { DEFAULT_SETTINGS } from '../../defaultSettings';

interface MiscViewProps {
onSubmit: () => void;
Expand Down Expand Up @@ -167,7 +168,7 @@ export function MiscView({ onSubmit }: MiscViewProps) {
toggle: () => {
updateSettings(settings => {
if (!settings.inputBox) {
settings.inputBox = { removeBorder: false };
settings.inputBox = { ...DEFAULT_SETTINGS.inputBox };
}
settings.inputBox.removeBorder = !settings.inputBox.removeBorder;
});
Expand Down