Skip to content

fix(ui): make character accent color stable across process restarts#79

Merged
AreteDriver merged 4 commits into
mainfrom
fix/accent-color-determinism
May 4, 2026
Merged

fix(ui): make character accent color stable across process restarts#79
AreteDriver merged 4 commits into
mainfrom
fix/accent-color-determinism

Conversation

@AreteDriver
Copy link
Copy Markdown
Owner

Bug

`character_accent_color()` used Python's built-in `hash()` to map names to palette indices. `PYTHONHASHSEED` is randomized by default, so the same character renders in a different color every app launch. The intra-process `test_deterministic` test in `test_status_dock.py` never caught it because both calls happened in one process.

This is debt item #1 from the post-v3.2.0 audit.

Fix

Switch to MD5 (content-addressed). Same character → same color every launch, every machine, every Python build. The hash isn't used for security; `usedforsecurity=False` quiets bandit and FIPS environments.

```python
digest = hashlib.md5(name.encode("utf-8"), usedforsecurity=False).digest()
index = digest[0] % len(CHARACTER_ACCENT_COLORS)
```

Tests

  • `test_cross_process_determinism` — runs the lookup in two subprocesses with different `PYTHONHASHSEED` values and asserts the result is identical. Would have caught the original bug.
  • `test_uniform_distribution_over_palette` — 200 distinct names hit at least 6 of 8 palette entries.

Test plan

  • 2 new tests, both pass
  • All 5 `TestCharacterAccentColor` tests pass (3 existing + 2 new)
  • Full suite: 2400 passed, 5 skipped, 0 failures
  • `ruff check` + `ruff format --check` clean

🤖 Generated with Claude Code

character_accent_color() used Python's built-in hash() to map names to
palette indices, but PYTHONHASHSEED is randomized by default — the same
character would render in a different color every app launch. The
intra-process determinism test in test_status_dock.py never caught it
because both calls happened in one process.

Switch to MD5 (content-addressed). Same character → same color every
launch, every machine, every Python build. The hash is not used for
security; usedforsecurity=False quiets bandit/FIPS environments.

New tests:
  - test_cross_process_determinism: runs the lookup in two subprocesses
    with different PYTHONHASHSEED values and asserts the result is
    identical. Would have caught the original bug.
  - test_uniform_distribution_over_palette: 200 distinct names hit at
    least 6 of 8 palette entries (sanity check the index function
    actually distributes).

Suite: 2400 passed, 5 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AreteDriver AreteDriver enabled auto-merge (squash) April 26, 2026 10:30
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request transitions the character accent color logic from hash() to hashlib.md5() to achieve cross-process determinism, addressing issues where colors changed between app launches. It also introduces unit tests to verify this determinism and ensure uniform color distribution. Feedback highlights a potential TypeError on Python versions below 3.9 due to the usedforsecurity argument and suggests refactoring another component for consistency.

Comment on lines +110 to +112
digest = hashlib.md5(name.encode("utf-8"), usedforsecurity=False).digest()
index = digest[0] % len(CHARACTER_ACCENT_COLORS)
r, g, b = CHARACTER_ACCENT_COLORS[index]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The usedforsecurity keyword argument was introduced in Python 3.9. Using it directly will cause a TypeError on older Python versions (such as 3.8), which are still common in many environments. Since this is a UI utility, consider adding a fallback for compatibility.

Additionally, please note that ArrangementGrid.add_character (line 430) still uses the unstable hash() function and a local color palette. It should be refactored to use this character_accent_color helper to ensure that character colors are stable and consistent across all UI components.

Suggested change
digest = hashlib.md5(name.encode("utf-8"), usedforsecurity=False).digest()
index = digest[0] % len(CHARACTER_ACCENT_COLORS)
r, g, b = CHARACTER_ACCENT_COLORS[index]
try:
digest = hashlib.md5(name.encode("utf-8"), usedforsecurity=False).digest()
except TypeError:
# Fallback for Python < 3.9
digest = hashlib.md5(name.encode("utf-8")).digest()
index = digest[0] % len(CHARACTER_ACCENT_COLORS)
r, g, b = CHARACTER_ACCENT_COLORS[index]

@AreteDriver AreteDriver merged commit 3d96933 into main May 4, 2026
15 checks passed
@AreteDriver AreteDriver deleted the fix/accent-color-determinism branch May 4, 2026 06:26
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.

1 participant