Repository navigation
Adopt native Liquid Glass across the chrome (macOS 26) #31
Description
Why we don't have Liquid Glass today
macOS 26 upgrades apps to Liquid Glass largely for free — if they use the system's building blocks. We don't, on four counts, and each one independently blocks the material:
- None of our chrome is a system control. Auto-adoption applies to
NSToolbar, standard buttons, sidebars, titlebars. Our toolbar, tab pills, ⌘K palette, and popovers are all hand-builtNSViews drawing their own backgrounds — there is nothing for the OS to upgrade. To the system, our chrome is opaque custom content. - Our "glass" is the previous material system. The backdrop is
NSVisualEffectViewwithHUDWindowmaterial (src/bro_mac.mm:258) plus hand-tuned scrims — that's static blur from the pre-26 API family. Liquid Glass is a different material with a different API (NSGlassEffectView); visual-effect views do not become glass, ever. Blur samples what's behind it once; glass refracts and responds — they're not the same thing wearing different settings. - The appearance is pinned dark by fiat (
NSApp.appearance = DarkAqua,src/bro_mac.mm:1313) with hardcoded colors throughout. Liquid Glass is adaptive by nature; a forced appearance and literal-white text fight it at the root. This exact combination already produced the invisible-URL-text bug — the material system re-resolves colors in ways our hand-rolled setup doesn't anticipate. - The build may not even link the 26 SDK — and the new look is gated on linking against it (unverified; phase 1 checks).
So: the fix isn't a setting, it's swapping our hand-rolled material layer for the system's — surface by surface. The good news is the app's structure makes that unusually cheap: every chrome surface is already a discrete view, and our concentric-corner system maps 1:1 onto the glass API's corner model. The web content stays opaque throughout — glass is the control layer, never the content layer.
The payoff move: tab pills placed in one NSGlassEffectContainerView merge and separate fluidly as they approach during drag/reorder — the system does it, we currently fake none of it.
Verified API surface (read from the local macOS 26 SDK headers on this machine, not docs-from-memory)
NSGlassEffectView(macOS 26.0+):contentView,cornerRadius,tintColor,style—NSGlassEffectViewStyleRegular/...ClearNSGlassEffectContainerView(macOS 26.0+):contentView,spacing— merges nearby glass descendants, batches rendering- SDK present at
/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk(Xcode 26.3 installed)
Design decisions reserved for @ragojose
- Regular vs Clear per surface — Clear reads lighter but risks legibility over busy pages; likely Regular for the chrome row, decide per overlay.
- Tint —
tintColorper surface or fully neutral; interacts with the theme work below. - Pill merge behavior — embrace the merge (container
spacing> 0) or keep pills discrete (spacing 0, still batched). - Light mode look — dark-only ends when the forced appearance goes; light mode becomes real and needs a designer's eye, not a code default.
Implementation phases (bounded, each independently shippable — for the agent)
Phase 0 — themes (hard prerequisite). Blocker 3 above.
- Remove
NSApp.appearance = DarkAqua(src/bro_mac.mm:1313); add a System/Light/Dark preference (default System), persisted via the existingbro_persistpattern, menu under the app menu - Sweep hardcoded chrome colors to dynamic/semantic colors:
src/bro_toolbar.mm(multiplewhiteColor/labelColorsites — see PR Fix invisible idle address text in the glass chrome #23/Rebuild browse input and refine native browser chrome #24 history for the trap),src/bro_tabstrip.mm,src/bro_palette.mm,src/bro_downloads.mm - Verify: build clean; launch in both appearances; URL text visible idle AND after edit+Return in both (the exact regression path from the invisible-text bug)
Phase 1 — SDK + gating. Blocker 4.
- Pin
CMAKE_OSX_SYSROOTto the 26 SDK inCMakeLists.txt; first verify what the default currently resolves to - Keep
LSMinimumSystemVersion12.0: every glass surface behindif (@available(macOS 26.0, *))with the currentNSVisualEffectViewpath as the else-branch - Verify: build clean; fallback path still launches; no new warnings
Phase 2 — surface swaps (blockers 1–2; one commit each, in this order).
- Window backdrop:
src/bro_mac.mm:258→NSGlassEffectView— blank tabs and the screenshot-mode gutter become real glass with zero further work - Chrome row → glass, replacing the hand-rolled treatment
- Tab strip: pills inside one
NSGlassEffectContainerView(src/bro_tabstrip.mm) - Overlays, one each: ⌘K palette (
src/bro_palette.mm), downloads popover (src/bro_downloads.mm), hover card, zoom HUD
- Verify per commit: build clean; AX smoke (launch, new tab, toggle screenshot mode); both appearances eyeballed
Phase 3 — corner handoff. Where a surface is glass-wrapped, hand its radius to NSGlassEffectView.cornerRadius derived from src/bro_geometry.h's concentric rule; bro_geometry stays authoritative for non-glass surfaces.
Risks to test in the first afternoon, before committing to the full sweep
- Compositor stacking: CEF renders its own GPU surface; glass sampling around it is the perf unknown. Test: chrome-row glass over a WebGL-heavy page (e.g. wakawaka.world), watch for dropped frames during tab drag
- Legibility: Clear style over dark pages vs the 8pt screenshot-mode gutter — pick styles per-surface with real pages, not blank tabs
Context for the agent
- Screenshot mode (Screenshot mode: Cmd+S frames the page in a glass gutter #25) and the soft-navigation crash fix (Fix browser-process crash on SPA soft navigations #28) touch adjacent code; branch from post-Fix browser-process crash on SPA soft navigations #28 main
- The audit reports in
docs/audits/document the overlay-dismissal and color conventions this must not regress - Estimated: themes ~1–2 days, glass ~1–2 days after, per this codebase's measured pace
Activity