Skip to content

feat: allow transparent background via config or flag#392

Open
benvinegar wants to merge 2 commits into
mainfrom
feat/transparent-background
Open

feat: allow transparent background via config or flag#392
benvinegar wants to merge 2 commits into
mainfrom
feat/transparent-background

Conversation

@benvinegar
Copy link
Copy Markdown
Member

Summary

  • Adds --transparent-background / --no-transparent-background CLI flags.
  • Adds transparent_background / transparentBackground config support.
  • Applies transparency after theme resolution so custom theme syntax colors are preserved while painted surfaces use the terminal background.

Rebased version of #322 with conflicts resolved against current main.

Tests

  • bun run typecheck
  • bun test

This PR description was generated by Pi using GPT-5.1 Codex

so terminal/IDE themes show through. Adds a `--transparent` CLI flag
and a `transparent_background` config key. When set, the diff theme
omits the background color and uses the terminal default.

Fixes #245
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 2, 2026

Greptile Summary

This PR adds --transparent-background / --no-transparent-background CLI flags and a matching transparent_background / transparentBackground config option, applying the effect as a post-resolution theme transform so custom syntax colors are preserved. The config, CLI, and theme layers are well-integrated, but the word-diff highlight path in pierre.ts breaks silently under this mode.

  • withTransparentBackground in themes.ts cleanly overrides all painted surface fields to \"transparent\", and the pierre.ts cache key is correctly extended to include those fields so stale results are not served.
  • wordDiffHighlightBg in pierre.ts passes these \"transparent\" strings to hexColorDistance and blendHex, both of which silently fall back to #000000 (black) for non-hex input; the result is opaque dark word-diff highlight boxes instead of transparent ones.
  • baseTheme in App.tsx is not wrapped in useMemo, so the activeTheme memo is invalidated on every render for users with a custom theme.

Confidence Score: 3/5

The transparent background feature works for most surfaces, but word-diff highlights are visually broken — they render as opaque dark boxes — whenever the flag is active.

The word-diff highlight path in pierre.ts silently misinterprets transparent as black, so any user who enables --transparent-background and scrolls through a diff with word-level changes will see solid dark highlight boxes instead of a see-through effect. This is the primary advertised use-case of the feature.

src/ui/diff/pierre.ts — the wordDiffHighlightBg function needs a guard for transparent background values before computing color distances.

Important Files Changed

Filename Overview
src/ui/diff/pierre.ts Cache key correctly extended to include background fields, but wordDiffHighlightBg silently treats "transparent" as black (#000000), producing opaque dark word-diff highlights instead of transparent ones.
src/ui/themes.ts Adds TRANSPARENT_BACKGROUND constant and withTransparentBackground helper that spreads a theme and overrides all painted surface fields; implementation is clean.
src/ui/App.tsx Applies transparent theme via useMemo after resolveTheme, but baseTheme itself is not memoized, making the activeTheme memo ineffective for custom themes.
src/core/config.ts Accepts both snake_case and camelCase config keys for transparentBackground, merges correctly, and defaults to false.
src/core/cli.ts Adds --transparent-background / --no-transparent-background flags with proper resolveBooleanFlag wiring and help text.
src/core/types.ts Adds optional transparentBackground field to CommonOptions interface.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    CLI["CLI flags\n--transparent-background"] --> ParseCli["parseCli()\ncli.ts"]
    Config["config.toml\ntransparent_background"] --> ReadConfig["readConfigPreferences()\nconfig.ts"]
    ParseCli --> Merge["mergeOptions()\nconfig.ts"]
    ReadConfig --> Merge
    Merge --> Bootstrap["AppBootstrap\ninput.options.transparentBackground"]
    Bootstrap --> App["App.tsx\nresolveTheme → baseTheme"]
    App -->|"transparentBackground === true"| WithTransparent["withTransparentBackground(baseTheme)\nthemes.ts"]
    App -->|"transparentBackground === false"| BaseTheme["baseTheme (unchanged)"]
    WithTransparent --> ActiveTheme["activeTheme\nall bg fields = 'transparent'"]
    BaseTheme --> ActiveTheme
    ActiveTheme --> Render["UI rendering"]
    ActiveTheme --> WordDiff["wordDiffHighlightBg()\npierre.ts"]
    WordDiff -->|"'transparent' treated as black"| BugPath["hexColorDistance / blendHex\nfall back to #000000 ⚠️"]
    BugPath --> OpaqueDark["Opaque dark word-diff\nhighlights (bug)"]
Loading

Comments Outside Diff (1)

  1. src/ui/diff/pierre.ts, line 239-246 (link)

    P1 Word-diff highlights compute as opaque dark colors in transparent mode

    hexToRgb matches only #rrggbb strings; it silently falls back to "000000" (black) for any other input. When transparentBackground is enabled, theme.addedBg and theme.addedContentBg are both "transparent", so hexColorDistance("transparent", "transparent") returns 0 — below the MIN_WORD_DIFF_BG_DISTANCE threshold — causing the code to call strengthenWordDiffBg("transparent", signColor). Inside that function, blendHex also treats "transparent" as black, so the loop blends the sign color toward black and returns an opaque dark hex value. The result is that word-diff highlights are rendered as solid dark boxes instead of being transparent, visibly breaking the see-through effect the feature promises.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/ui/diff/pierre.ts
    Line: 239-246
    
    Comment:
    **Word-diff highlights compute as opaque dark colors in transparent mode**
    
    `hexToRgb` matches only `#rrggbb` strings; it silently falls back to `"000000"` (black) for any other input. When `transparentBackground` is enabled, `theme.addedBg` and `theme.addedContentBg` are both `"transparent"`, so `hexColorDistance("transparent", "transparent")` returns 0 — below the `MIN_WORD_DIFF_BG_DISTANCE` threshold — causing the code to call `strengthenWordDiffBg("transparent", signColor)`. Inside that function, `blendHex` also treats `"transparent"` as black, so the loop blends the sign color toward black and returns an opaque dark hex value. The result is that word-diff highlights are rendered as solid dark boxes instead of being transparent, visibly breaking the see-through effect the feature promises.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/ui/diff/pierre.ts:239-246
**Word-diff highlights compute as opaque dark colors in transparent mode**

`hexToRgb` matches only `#rrggbb` strings; it silently falls back to `"000000"` (black) for any other input. When `transparentBackground` is enabled, `theme.addedBg` and `theme.addedContentBg` are both `"transparent"`, so `hexColorDistance("transparent", "transparent")` returns 0 — below the `MIN_WORD_DIFF_BG_DISTANCE` threshold — causing the code to call `strengthenWordDiffBg("transparent", signColor)`. Inside that function, `blendHex` also treats `"transparent"` as black, so the loop blends the sign color toward black and returns an opaque dark hex value. The result is that word-diff highlights are rendered as solid dark boxes instead of being transparent, visibly breaking the see-through effect the feature promises.

### Issue 2 of 2
src/ui/App.tsx:141-142
`baseTheme` is recomputed on every render because `resolveTheme` is called without `useMemo`. For built-in themes `resolveTheme` returns a stable reference from the `THEMES` array, so the current `activeTheme` memo happens to be stable in the common case — but for users with a custom theme `buildCustomTheme` constructs a fresh object every render, defeating the memo and invoking `withTransparentBackground` unnecessarily on every render cycle.

```suggestion
  const baseTheme = useMemo(
    () => resolveTheme(themeId, detectedThemeMode ?? null, bootstrap.customTheme),
    [themeId, detectedThemeMode, bootstrap.customTheme],
  );
  const activeTheme = useMemo(
```

Reviews (1): Last reviewed commit: "feat: allow transparent background via c..." | Re-trigger Greptile

Comment thread src/ui/App.tsx Outdated
Comment on lines +141 to +142
const baseTheme = resolveTheme(themeId, detectedThemeMode ?? null, bootstrap.customTheme);
const activeTheme = useMemo(
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.

P2 baseTheme is recomputed on every render because resolveTheme is called without useMemo. For built-in themes resolveTheme returns a stable reference from the THEMES array, so the current activeTheme memo happens to be stable in the common case — but for users with a custom theme buildCustomTheme constructs a fresh object every render, defeating the memo and invoking withTransparentBackground unnecessarily on every render cycle.

Suggested change
const baseTheme = resolveTheme(themeId, detectedThemeMode ?? null, bootstrap.customTheme);
const activeTheme = useMemo(
const baseTheme = useMemo(
() => resolveTheme(themeId, detectedThemeMode ?? null, bootstrap.customTheme),
[themeId, detectedThemeMode, bootstrap.customTheme],
);
const activeTheme = useMemo(
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ui/App.tsx
Line: 141-142

Comment:
`baseTheme` is recomputed on every render because `resolveTheme` is called without `useMemo`. For built-in themes `resolveTheme` returns a stable reference from the `THEMES` array, so the current `activeTheme` memo happens to be stable in the common case — but for users with a custom theme `buildCustomTheme` constructs a fresh object every render, defeating the memo and invoking `withTransparentBackground` unnecessarily on every render cycle.

```suggestion
  const baseTheme = useMemo(
    () => resolveTheme(themeId, detectedThemeMode ?? null, bootstrap.customTheme),
    [themeId, detectedThemeMode, bootstrap.customTheme],
  );
  const activeTheme = useMemo(
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Applied the suggested memoization so custom themes are not rebuilt on every render before transparent-background wrapping.

This comment was generated by Pi using GPT-5.1 Codex

@benvinegar
Copy link
Copy Markdown
Member Author

Addressed the Greptile findings in 23b2582:

  • Guarded the word-diff highlight path so transparent backgrounds stay transparent instead of being passed through hex blend/distance math as black.
  • Added a regression test covering transparent word-diff highlight spans.
  • Memoized baseTheme resolution for custom themes before transparent wrapping.

Validated with bun test src/ui/diff/pierre.test.ts and bun run typecheck.

This comment was generated by Pi using GPT-5.1 Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants