Skip to content

fix(core): include full color value in getStyleLine hash to prevent collision#1664

Open
atul-upadhyay-7 wants to merge 1 commit into
Karanjot786:mainfrom
atul-upadhyay-7:fix/screen-style-hash-collision
Open

fix(core): include full color value in getStyleLine hash to prevent collision#1664
atul-upadhyay-7 wants to merge 1 commit into
Karanjot786:mainfrom
atul-upadhyay-7:fix/screen-style-hash-collision

Conversation

@atul-upadhyay-7

@atul-upadhyay-7 atul-upadhyay-7 commented Jun 19, 2026

Copy link
Copy Markdown

Summary

Fixes a critical bug where Screen.getStyleLine() produced hash collisions that caused the diff renderer to silently skip color-only updates. Two cells with the same color type but different codes (e.g. ansi256 code 1 vs code 9) generated the same hash, so the renderer treated the row as unchanged.

Closes #1659

Root Cause

getStyleLine() computed a hash seed using only fg.charCodeAt(0) and bg.charCodeAt(0) on the color type string. Since 'ansi256'.charCodeAt(0) === 97 regardless of the actual code value, all ansi256 colors hashed identically. Same issue for rgb, hex, and named colors.

Fix

Added a hashColor(c: Color): number helper that fingerprints the complete color value:

Color type Hash formula
none 0
named name.charCodeAt(0) * 31 + name.charCodeAt(name.length - 1)
ansi256 code * 7 + 1
rgb (r << 16) | (g << 8) | b
hex djb2 hash of the hex string

getStyleLine() now uses hashColor(cell.fg) and hashColor(cell.bg) instead of the type string's first character.

Tests added

Test What it verifies
different ansi256 codes produce different hashes The original bug scenario
different named colors produce different hashes Named color collision prevention
different rgb values produce different hashes RGB collision prevention
different hex values produce different hashes Hex collision prevention
same color values produce the same hash Determinism — no false positives
style flags still affect the hash Bold/italic/etc. still detected
background color changes are detected bg color changes not ignored

Verification

  • All 445 core tests pass (including 8 new tests)
  • Typecheck passes across all packages
  • No breaking changes to public API

Summary by CodeRabbit

  • Tests

    • Added comprehensive test suite validating style line behavior for color differentiation across various color formats (ansi256, named, rgb, hex) and style attributes (bold, italic, underline, dim, strikethrough, inverse).
  • Bug Fixes

    • Improved color value tracking in terminal rendering to accurately reflect actual color changes during style-only updates.

…ollision

The diff renderer uses getStyleLine to detect style-only changes. The
previous hash only used the first character of the color type string
(e.g. 'a' for 'ansi256'), so two colors of the same type but different
codes produced the same hash. This silently skipped color updates.

Add hashColor helper that fingerprints the complete color value (type +
code/name/r,g,b/hex) so different colors always produce distinct hashes.

Closes Karanjot786#1659
@github-actions github-actions Bot added area:core @termuijs/core type:testing +10 pts. Tests. type:bug +10 pts. Bug fix. needs-star PR author has not starred the repo. labels Jun 19, 2026
@github-actions

Copy link
Copy Markdown

Hi @atul-upadhyay-7 👋

Star this repo before your PR merges.

Why? GSSoC 2026 contributors who star get priority review and points credit. After you star, push any commit (or re-run this check). The needs-star label lifts automatically.

Thanks for your contribution to TermUI.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new internal hashColor function is added to Screen.ts that computes a numeric fingerprint from a color's type and its actual value fields. getStyleLine is updated to seed its hash with hashColor(cell.fg) and hashColor(cell.bg) instead of only the first character of the type string. New tests verify correctness across all color types and style flags.

Changes

hashColor fix for getStyleLine fingerprinting

Layer / File(s) Summary
hashColor helper and getStyleLine seed update
packages/core/src/terminal/Screen.ts
Adds hashColor(c: Color): number that hashes color type plus its identifying fields (name char codes, packed rgb channels, hex chars, ansi256 code). Updates getStyleLine to call hashColor(cell.fg) and hashColor(cell.bg) as the seed instead of deriving from fg.type.charCodeAt(0)/bg.type.charCodeAt(0) alone.
getStyleLine test suite
packages/core/src/terminal/Screen.test.ts
Adds describe('getStyleLine', ...) covering out-of-bounds row returns '', distinct hashes for ansi256/named/rgb/hex fg variants, hash stability for identical inputs, and hash changes when bold or bg differ.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Karanjot786/TermUI#709: Directly touches Screen.getStyleLine fingerprint logic used by the diff renderer to detect style-only row changes — same code path this PR modifies.
  • Karanjot786/TermUI#1231: Addresses hex color normalization in style fingerprinting, which overlaps with the hex case handled inside the new hashColor helper.

Suggested labels

gssoc:approved, quality:clean, type:bug, type:testing, area:core, level:intermediate

Suggested reviewers

  • Karanjot786

Poem

🐰 A rabbit once painted with ANSI code nine,
But the diff saw code one — said "that color is fine!"
Now hashColor counts every channel and hue,
No silent-skip skips when the palette is new.
The terminal sees what the rabbit intends! 🎨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and concisely describes the main fix: including full color values in the hash to prevent collisions that were causing missed updates.
Description check ✅ Passed The description covers all required sections: clear summary of the bug, root cause analysis, fix methodology with implementation details, test coverage, and verification of passing tests.
Linked Issues check ✅ Passed The PR fully addresses issue #1659 by implementing the hashColor() helper to compute distinct fingerprints for complete color values, ensuring different color codes produce different hashes for proper rendering.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the hash collision bug: the hashColor() helper, integration into getStyleLine(), and comprehensive test coverage for the fix.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/terminal/Screen.test.ts (1)

154-163: ⚡ Quick win

Consider testing named colors that could collide.

The test uses 'red' vs 'blue' which produce different hashes (3634 vs 3139). If NamedColor includes bright variants, adding a test like 'blue' vs 'brightBlue' would verify collision resistance for all valid named colors.

it('bright and non-bright named colors produce different hashes', () => {
    const screen = new Screen(5, 1);
    screen.setCell(0, 0, { char: 'A', fg: { type: 'named', name: 'blue' } });
    const hash1 = screen.getStyleLine(0);

    screen.setCell(0, 0, { char: 'A', fg: { type: 'named', name: 'brightBlue' } });
    const hash2 = screen.getStyleLine(0);

    expect(hash1).not.toBe(hash2);
});

This test would currently fail if brightBlue is a valid NamedColor, catching the collision bug in hashColor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/terminal/Screen.test.ts` around lines 154 - 163, Add a new
test case after the existing test in the Screen.test.ts file that specifically
checks for hash collisions between bright and non-bright variants of the same
color. Create a test named something like "bright and non-bright named colors
produce different hashes" that follows the same pattern as the current test,
using a Screen instance and calling setCell twice with 'blue' and 'brightBlue'
as the named color values, then comparing the hashes returned by getStyleLine to
ensure they are different. This will verify that the hashColor function properly
distinguishes between color variants that differ only in brightness.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/terminal/Screen.ts`:
- Around line 20-21: The hash function for named colors in the 'named' case of
Screen.ts only considers the first and last character of the color name, causing
collisions between different colors like blue/brightBlue/brightWhite which all
hash to the same value. This breaks style-change detection in getStyleLine().
Replace the current hash formula with a proper string hash function that
processes the entire color name string to ensure unique hash values for
different colors and prevent incorrect style-change detection.

---

Nitpick comments:
In `@packages/core/src/terminal/Screen.test.ts`:
- Around line 154-163: Add a new test case after the existing test in the
Screen.test.ts file that specifically checks for hash collisions between bright
and non-bright variants of the same color. Create a test named something like
"bright and non-bright named colors produce different hashes" that follows the
same pattern as the current test, using a Screen instance and calling setCell
twice with 'blue' and 'brightBlue' as the named color values, then comparing the
hashes returned by getStyleLine to ensure they are different. This will verify
that the hashColor function properly distinguishes between color variants that
differ only in brightness.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e1eeb64c-99fa-43f2-ab61-00d625eb811a

📥 Commits

Reviewing files that changed from the base of the PR and between 4b874c0 and ec800a3.

📒 Files selected for processing (2)
  • packages/core/src/terminal/Screen.test.ts
  • packages/core/src/terminal/Screen.ts

Comment thread packages/core/src/terminal/Screen.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core @termuijs/core needs-star PR author has not starred the repo. type:bug +10 pts. Bug fix. type:testing +10 pts. Tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Screen.setStyleLine hash collision silently skips color code updates

1 participant