fix(web): Cool Darker glass survives past the composer and the window losing focus - #93
Conversation
… losing focus The glass palette had three defects that all presented as "the theme is wrong" rather than as the layering bugs they were. The composer rendered as a flat grey slab in an otherwise translucent window. Its vessel and context chips carried a backdrop-filter, and a filter cannot coexist with native vibrancy: the NSVisualEffectView sits behind the window, outside anything the page compositor owns, so Chromium flattens the region to composite the filter and the desktop is lost. Measured through the app, the wallpaper reached the sidebar at a channel spread of 25 and the stage beside the composer at 14-20, against 3 inside it. Both filters are now cleared explicitly. Removing them is safe because the transcript is masked to end above the composer, which took over the blur's original job of hiding text passing underneath. The window went opaque whenever it lost focus, because the material follows the window's active state by default and the CSS behind it stays translucent either way. It is built with visualEffectState "active" now. The material itself was the ceiling on everything else. "under-window" is the darkest sampler macOS offers and returned a flat ~49 grey, so stage and composer alphas were both arithmetically correct and had almost nothing to show. "fullscreen-ui" returns ~99 and carries colour. It is named once in ForkGlassState.ts because the window's construction options and the runtime call have to agree and neither file can import the other. Retuned around the brighter material by holding the desktop's contribution constant rather than the alpha: sidebar 88%, stage 95% easing to 85% across its bottom third, where the cutoff mask has already ended the transcript. The composer is white washes over that — 6% tray, the same 6% compounded for the ring, 12% chips — with the input darkening back toward the column and the focus ring taking an alpha instead of the palette's opaque fill. The composer also sits 32px off the bottom. Guards were rewritten alongside, several of them inverted: no backdrop-filter may survive in the glass set, the stage is read as gradient stops rather than one fill, and the reading area alone answers to "never more transparent than the sidebar" now that the lower stops sit below the mask. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
REQUEST_CHANGES — thermo-nuclear code quality review.
The glass/cutoff direction is right (mask the transcript instead of frosting the composer; share the vibrancy material; keep the material active when unfocused). The structure does not clear the approval bar.
Blockers / high-priority structure
- Stop growing
ChatView.tsx(~6.6k) with new fork shell stamping — put--fork-composer-insetinapps/web/src/custom/(or a tiny fork-owned wrapper). Inline fences are last resort per.fork/AGENTS.md. - The cutoff mask model is buried in opaque midpoints (
* 0.28/* 0.6) and a 7-stop top fade, duplicated across-webkit-mask/mask. Name the stops or collapse the ramp. - The glass retune in
theme.custom.palettes.cssis mostly essay (~180 comment lines vs ~27 declarations), and the same narrative is triplicated across YAML / CSS / guards. One intent home; short outcome assertions.
Also delete the identity VIBRANCY_MATERIAL = FORK_VIBRANCY_MATERIAL alias, and keep new guards short/loud rather than re-litigating design history.
What looks fine: unconditional fork-timeline-cutoff in MessagesTimeline, shared FORK_VIBRANCY_MATERIAL + visualEffectState: "active" in the desktop fork layer, and dual vendor mask declarations.
Sent by Cursor Automation: Thermo-nuclear PR review
NoahHendrickson
left a comment
There was a problem hiding this comment.
Found one blocking cross-browser issue. I am posting this as a comment review because GitHub does not allow the PR author account to request changes on its own PR. Local verification: 350/350 fork guards, 20/20 focused timeline tests, web typecheck, and desktop typecheck all pass.
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
Code reviewReviewed all 11 files / ~700 lines. I read every hunk with surrounding CSS/TSX/Electron context and ran the three touched guard suites — 57 passed. What checks out
Findings1. Stage gradient bottoms out below the sidebar on non-chat pages — The 95%→85% gradient is justified by the chat-only cutoff mask, but it lands on 2. Cutoff ramp overlaps live content instead of the empty pad — The ramp runs ~6rem above 3. Manifest text no longer matches the shipped values — The manifest says the guard pins "every stage stop >= 80% and never below the sidebar's", but the guard now checks only the top stop, and the shipped bottom stop (85%) is below the sidebar's 88%. The same paragraph still carries the 4. Guard loosened on a false premise — The guard went from "no backdrop-filter on the chips" to "must be vibrancy-scoped", on a comment claiming the chips carry a filter under glass. They don't — the block declares Findings 1 and 2 are the ones with user-visible consequences; 3 and 4 are docs/guard drift that will mislead the next change in this area. 🤖 Generated with Claude Code |
…cut the essays - ForkSidebarVibrancy called setVibrancy through a local alias of the shared constant. Uses FORK_VIBRANCY_MATERIAL directly. - The timeline mask's tail midpoints were bare arithmetic repeated in both the prefixed and unprefixed declaration, so the ramp's shape could only be read by evaluating four expressions. Named as tail-early / tail-late / tail-end beside the edge and ramp tokens. - Trimmed the glass comments and the guard prose by roughly two thirds. Kept the traps that cost round trips to find — bg-background under the gradient, the grain utility's repeat and 128px size, backdrop-filter versus the native material, alphas not being portable across materials — and dropped the Figma walkthroughs, the superseded-value history and the per-token restatements that the manifest already carries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Upstream's glass utilities carry backdrop-filter for the same reason the composer did, and hit the same wall: a filter cannot sample the native material, so a menu opened over the sidebar or the stage renders that patch as flat grey and reads as a card sitting on the glass. .dropdown-glass, .dialog-glass, .alert-glass, .chat-composer-glass and .dialog-backdrop are cleared under the glass marker, and the first four are made opaque in the same rule by overriding --glass-opacity to 100%. Each computes its fill from that variable, so this keeps the dropdown's denser popover tint and the alert's variant colour while removing the see-through part that has nothing good left to show. Without the blur a translucent menu leaks the text underneath instead, which is worse than the grey card: these are transient overlays whose job is to be read. .dialog-backdrop keeps its own 60% dim; only its blur goes, since that one covers the whole window. The guard reads the class list out of index.css rather than hardcoding it, so a new `*-glass` utility upstream fails the test instead of shipping a grey card. Also drops the composer's bottom offset from 32px to 24px. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 95%->85% stage gradient landed on [data-slot="sidebar-inset"], which also roots Settings, Usage and the empty state. Those pages have no transcript and no cutoff mask, so the bottom third of a scrolling text panel faded to 85% under an 88% sidebar with nothing ending it. The ramp is now scoped to insets carrying .fork-timeline-cutoff and everything else keeps the flat reading value; chat is unchanged. Also re-syncs two pieces of drift the change surfaced: the manifest described a guard constraint the guard had stopped enforcing and still carried under-window-era alpha ranges next to a 95% top stop, and the chip guard had been loosened to "must be vibrancy-scoped" on a comment claiming the chips carry a filter under glass. They do not — the rule declares none. It now matches the value instead of the property, which accepts that rule and still fails on a real blur. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>


Three defects in the Cool Darker wallpaper glass that all presented as "the theme is wrong" rather than as the layering bugs they were.
The composer rendered as a flat grey slab
Its vessel and context chips carried a
backdrop-filter, and a filter cannot coexist with native vibrancy. TheNSVisualEffectViewsits behind the window, outside anything the page compositor owns, so Chromium flattens the region to composite the filter and the desktop is lost — the element keeps its brightness and shows none of the wallpaper.Measured through the running app, as a channel spread (max − min):
Both filters are now cleared explicitly, stated as a rule rather than deleted so re-adding one is deliberate. Removing them is safe because the transcript is masked to end above the composer, which took over the blur's original job of destroying text passing underneath.
The window went opaque whenever it lost focus
NSVisualEffectViewfollows the window's active state by default and drops to a flat inactive fill. The CSS behind it stays translucent either way, so it read as the whole app going opaque rather than as a window losing focus. The window is built withvisualEffectState: "active"now — construction-only, and Electron requires it paired withvibrancy.The material was the ceiling on everything else
under-windowis the darkest sampler macOS offers and returned a flat ~49 grey, so the stage and composer alphas were both arithmetically correct and had almost nothing to show.fullscreen-uireturns ~99 and carries colour. It is named once inForkGlassState.tsbecause the window's construction options and the runtime call have to agree and neither file can import the other.Its trade:
under-windowsamples the desktop picture alone, so the glass ignored other apps moving beneath the window. The behind-window materials sample what is actually there, so the glass now reacts as windows pass under.Retuning
Alphas are not portable across materials, so these were reset by holding the desktop's contribution constant rather than the alpha — 15% of 99 is 14.8 where 30% of 49 was 14.7. Sidebar 88%; stage 95% easing to 85% across its bottom third, where the cutoff mask has already ended the transcript and there is no long-form text to protect.
The composer is white washes over that stage — 6% tray, the same 6% compounded for the ring, 12% chips — with the input darkening back toward the column and the focus ring taking an alpha instead of the palette's opaque
#444a4f. Following Figma322:6306for the tray, ring and chips and322:6316for the recessed input. The composer also sits 32px off the bottom now, via the spacer that already owned that gap.Guards
Rewritten alongside, several of them inverted:
backdrop-filtermay survive anywhere in the glass set.bg-backgroundon the inset is pinned — abackground-imagealone composites over that opaque utility fill and the glass silently does nothing.repeatand128pxsize must be overridden, which is what made the gradient tile down the column.350/350 fork guards,
typecheckclean inapps/webandapps/desktop.Verification
Verified in the running desktop app across the session, with values measured from screenshots rather than eyeballed — the arithmetic in the CSS comments is the measured composite, not an estimate. Not attached here: before/after images live in the working session. Requires a restart to see the desktop-side changes.
Model: Claude Opus 5 (1M context), harness: Claude Code.
🤖 Generated with Claude Code