fix(ui): make character accent color stable across process restarts#79
Conversation
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>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
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.
| digest = hashlib.md5(name.encode("utf-8"), usedforsecurity=False).digest() | ||
| index = digest[0] % len(CHARACTER_ACCENT_COLORS) | ||
| r, g, b = CHARACTER_ACCENT_COLORS[index] |
There was a problem hiding this comment.
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.
| 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] |
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 plan
🤖 Generated with Claude Code