fix(#305): colorblind mode no longer un-fixes the whole page - #306
Merged
Conversation
`useColorblindMode` set `body.style.filter`. A filter other than `none` makes its element a containing block for position:fixed descendants -- the CSS Filter Effects spec exempts only the ROOT element, and <body> is not the root. So enabling the accessibility feature silently anchored every fixed element to <body> and made it scroll with the document: the atlas tour caption (which #297 moved to `fixed` precisely to rescue it), the diorama shell, the cookie banner, /messages. Measured on /chatt scrolled to y=150, a `fixed top:64;bottom:0` probe: filter on <body> -> top -86, bottom 721 (scrolls with the page) filter on <html> -> top 64, bottom 630 (identical to no filter) The root filter still paints: a pure-red swatch renders differently under protanopia from <html>, so the move costs nothing visually. Why this shipped: all 8 unit assertions were `expect(document.body.style .filter).toBe('url(#tritanopia)')` -- they check the property is SET. A containing-block change is a LAYOUT fact and jsdom has no layout, so they passed for the bug's entire life. The new e2e spec measures real geometry in a real browser; verified RED on the old code (expected 64, received -86) before being made to pass, and its precondition deliberately polls for the filter being applied ANYWHERE so it fails on the bug rather than on "you didn't move it". Also drops `--colorblind-filter`, set on the root and consumed by nothing. Closes #305
This was referenced Jul 17, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #305.
The bug
useColorblindModesetbody.style.filter. Afilterother thannonemakes its element acontaining block for
position: fixeddescendants — the CSS Filter Effects spec exempts onlythe root element, and
<body>is not the root.So enabling the accessibility feature silently anchored every fixed element to
<body>and madeit scroll with the document. Already affected on
main:fixedprecisely to rescue itfixed top:64)/messages(fixed inset-x-0 top-16)Measured, not theorised
On
/chattscrolled to y=150, aposition:fixed; top:64; bottom:0probe:noneurl(#protanopia)It hides wherever body height happens to equal the viewport (body's padding box is the viewport
then, so there's no offset to diverge). That coincidence is what let it live.
The fix
Move the filter to the root element. Same page, same scroll:
<html><body>The root filter still paints — verified deterministically: a pure-red swatch renders differently
under protanopia from
<html>(computed style is not proof of paint).Also drops
--colorblind-filter, which was set on the root and consumed by nothing.Why this shipped, and what stops it recurring
All 8 unit assertions were
expect(document.body.style.filter).toBe('url(#tritanopia)')— theycheck the property is SET. A containing-block change is a layout fact and jsdom has no
layout, so they passed for the bug's entire life. Fourth costume of this repo's recorded trap:
green assertion, wrong screen.
tests/e2e/colorblind-fixed.spec.tsmeasures real geometry in a real browser:expect(result.scrolled).toBe(result.unscrolled)with
Expected: 64, Received: -86— i.e. it fails on the bug it names.<html>OR<body>), so on the buggy code it fails on the geometry rather than tautologically on"you didn't move it".
is invisible.
Verification
type-check,lintcleandocker compose run --rm builder pnpm build— passedvalidate-ci.sh --quick— all checks passed, including the Adding cesium inlines a WASM module as a template literal → vendor chunk fails to parse → client JS dead app-wide #294 chunk-parse check