render: add theme.minimum_contrast #231

Merged
natsukium merged 1 commit from render/minimum-contrast into main 2026-10-23 11:25:45 +09:00
Owner

Adds theme.minimum_contrast, a WCAG contrast ratio (1–21, default 1 = off) that every cell's text color is raised to against its resolved background, so a palette or a program's color choice cannot leave text unreadable.

  • felis-render-core: a contrast module (WCAG luminance and ratio; the text color moves along the Oklab line toward white or black, keeping its hue, by the smallest step that meets the ratio). It runs last in resolve_cell, after reverse video, cursor, selection and faint. Text drawn in its own background color is left hidden; the bidi warning cell is untouched. No glyph-class exemption.
  • The ratio is judged against the straight (not premultiplied) background, so a translucent window does not change the result.
  • Config: clamped to [1, 21] with a validation warning, NaN falls back to off; hot-reloads like the other theme keys. Schema regenerated.
  • Tests: unit tests and proptests on the math, resolve_cell cases, config clamp, and a golden paint fixture at 4.5.
  • Docs: config reference, rendering pipeline; CHANGELOG.

Closes #218

Adds `theme.minimum_contrast`, a WCAG contrast ratio (1–21, default 1 = off) that every cell's text color is raised to against its resolved background, so a palette or a program's color choice cannot leave text unreadable. - `felis-render-core`: a `contrast` module (WCAG luminance and ratio; the text color moves along the Oklab line toward white or black, keeping its hue, by the smallest step that meets the ratio). It runs last in `resolve_cell`, after reverse video, cursor, selection and faint. Text drawn in its own background color is left hidden; the bidi warning cell is untouched. No glyph-class exemption. - The ratio is judged against the straight (not premultiplied) background, so a translucent window does not change the result. - Config: clamped to [1, 21] with a validation warning, NaN falls back to off; hot-reloads like the other theme keys. Schema regenerated. - Tests: unit tests and proptests on the math, `resolve_cell` cases, config clamp, and a golden paint fixture at 4.5. - Docs: config reference, rendering pipeline; CHANGELOG. Closes #218
render: add theme.minimum_contrast
Some checks failed
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 9s
windows / detect relevant changes (pull_request) Successful in 10s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / windows docs-only no-op (pull_request) Has been skipped
bench / instruction-count regression gate (pull_request) Successful in 53s
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m13s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 36s
pr / nix flake check (pull_request) Successful in 49s
windows / cargo nextest (Windows) (pull_request) Successful in 3m18s
windows / frontend smoke (Windows) (pull_request) Successful in 1m9s
pr / cargo build / clippy / test / deny (pull_request) Failing after 2m26s
pr / wire schema is compatible with the base (pull_request) Successful in 3s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 27s
windows / cargo clippy (Windows cross) (pull_request) Successful in 15s
bbf7e3cc40
A program that hard-codes SGR colors can print text the user's theme
makes unreadable, such as blue on a dark background. Neither side can
fix it: the program does not know the theme, and the theme does not
know the program. kitty, Ghostty and WezTerm offer a minimum-contrast
setting for this; felis had none.

The adjustment runs last in resolve_cell, so it judges the colors a
cell is painted with after reverse video, cursor, selection and faint.
It moves the fg along the OKLab line toward white or black, which keeps
the hue, unlike Ghostty's snap to black or white. A translucent window
is judged by the configured background, not the premultiplied one the
surface draws, which goes black at zero opacity. The default of 1
leaves output unchanged.

Assisted-by: Claude Code
natsukium force-pushed render/minimum-contrast from bbf7e3cc40
Some checks failed
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 9s
windows / detect relevant changes (pull_request) Successful in 10s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / windows docs-only no-op (pull_request) Has been skipped
bench / instruction-count regression gate (pull_request) Successful in 53s
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m13s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 36s
pr / nix flake check (pull_request) Successful in 49s
windows / cargo nextest (Windows) (pull_request) Successful in 3m18s
windows / frontend smoke (Windows) (pull_request) Successful in 1m9s
pr / cargo build / clippy / test / deny (pull_request) Failing after 2m26s
pr / wire schema is compatible with the base (pull_request) Successful in 3s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 27s
windows / cargo clippy (Windows cross) (pull_request) Successful in 15s
to b29c54ee80
Some checks failed
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 9s
windows / detect relevant changes (pull_request) Successful in 10s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / windows docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Has been cancelled
bench / instruction-count regression gate (pull_request) Has been cancelled
pr / nix flake check (pull_request) Has been cancelled
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
pr / wire schema is compatible with the base (pull_request) Has been cancelled
pr / frontend smoke (x86_64-linux) (pull_request) Has been cancelled
windows / cargo clippy (Windows cross) (pull_request) Has been cancelled
windows / cargo nextest (Windows) (pull_request) Has been cancelled
windows / frontend smoke (Windows) (pull_request) Has been cancelled
2026-10-23 11:07:10 +09:00
Compare
natsukium force-pushed render/minimum-contrast from b29c54ee80
Some checks failed
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 9s
windows / detect relevant changes (pull_request) Successful in 10s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / windows docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Has been cancelled
bench / instruction-count regression gate (pull_request) Has been cancelled
pr / nix flake check (pull_request) Has been cancelled
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
pr / wire schema is compatible with the base (pull_request) Has been cancelled
pr / frontend smoke (x86_64-linux) (pull_request) Has been cancelled
windows / cargo clippy (Windows cross) (pull_request) Has been cancelled
windows / cargo nextest (Windows) (pull_request) Has been cancelled
windows / frontend smoke (Windows) (pull_request) Has been cancelled
to 8042d07544
All checks were successful
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 9s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 16s
windows / windows docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m11s
windows / cargo nextest (Windows) (pull_request) Successful in 2m58s
windows / frontend smoke (Windows) (pull_request) Successful in 50s
bench / instruction-count regression gate (pull_request) Successful in 1m4s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 46s
pr / nix flake check (pull_request) Successful in 38s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m26s
pr / wire schema is compatible with the base (pull_request) Successful in 3s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 36s
windows / cargo clippy (Windows cross) (pull_request) Successful in 14s
2026-10-23 11:07:24 +09:00
Compare
natsukium deleted branch render/minimum-contrast 2026-10-23 11:25:45 +09:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
felis-terminal/felis!231
No description provided.