electron-cation · GitHub

relixiaobo added a commit to relixiaobo/lin-outliner that referenced this pull request

@relixiaobo @claude

…ectors (#58)
* feat(native): macOS window_corner addon for 24pt window corner
Electron exposes only roundedCorners: true|false (OS default ~10pt) and the
transparent-window route removes the traffic lights + OS shadow on macOS. Add a
minimal Node-API addon that reaches the NSWindow directly:
contentView.layer.cornerRadius + masksToBounds (continuous curve), opaque=NO +
clear backing so the shadow follows the rounded shape, and invalidateShadow.
The window stays standard, so traffic lights and shadow survive.
- native/window-corner: binding.gyp + window_corner.mm (zero npm deps; built
  against Electron headers via `bun run build:native`)
- src/main/nativeWindowCorner.ts: loader that degrades to a silent no-op when
  the addon is missing / off-darwin / load fails
- main.ts: roundedCorners:false on macOS, apply 24pt on create + ready-to-show,
  toggle to 0 in fullscreen
- chromeGeometry.ts: MAC_WINDOW_CORNER_RADIUS = 24 (concentric with rails)
- packaging: build:native script + extraResources copy of the .node
This is the repo's first native module: it adds a C++ compile step to the
build/packaging chain and touches package.json (coordination file). For main
agent review before merge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(native): don't force opaque=NO/clearColor — preserve vibrancy frost
Setting window.opaque=NO + backgroundColor=clearColor on top of an already
non-opaque vibrancy window stripped the NSVisualEffectView frost, so the whole
deck showed the raw desktop instead of the material backing. The vibrancy window
is already non-opaque, so clipping the content layer alone makes the corners
transparent; invalidateShadow then makes the shadow follow the rounded shape.
Drop the opaque/backgroundColor mutation; keep only cornerRadius + masksToBounds
+ cornerCurve + invalidateShadow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(native): round vibrancy via maskImage + CSS clip; stop breaking frost
masksToBounds on the content view clipped the NSVisualEffectView ancestor and
killed its behind-window vibrancy blending, so the deck showed the raw desktop
instead of frost. The macOS frost is actually two layers — the OS vibrancy view
plus the CSS .app-shell tint on top — so both must round:
- native: round every NSVisualEffectView with a resizable rounded-rect maskImage
  (Apple's sanctioned way; preserves blending) instead of masksToBounds; nil
  mask at radius 0 for fullscreen. invalidateShadow so the shadow follows.
- CSS: [data-window-material] .app { border-radius: --window-radius; overflow
  hidden } clips the web frost + content to the same rounded shape.
- --window-radius = panel-radius + layout-gap = 24, matching
  MAC_WINDOW_CORNER_RADIUS.
Note: the CSS lives in styles.css here (off main). When the design-system branch
adopts this, the same .app rule must move into its split shell module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(native): _cornerMask override — true custom corner + rounded shadow
Replicate Electron's removed ElectronNSWindow approach (electron#48376) via
runtime injection: override -[NSWindow _cornerMask] on the live window's class to
return a resizable rounded-rect mask (stored per-window as an associated object),
plus keep the vibrantView maskImage. WindowServer uses _cornerMask to shape both
the window clip and its shadow, so this is what rounds the *shadow* at a custom
radius while the standard window keeps native traffic lights, OS shadow, and
vibrancy. Stays on titleBarStyle:'hiddenInset' (no transparent/frameless).
Known trade-off being measured: the custom _cornerMask forces the shadow to
render from a transparent surface → potential persistent WindowServer GPU load on
Tahoe (the reason Electron dropped it). This commit is the experiment to see
whether the visual result is correct and the GPU cost acceptable on-device.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(native): LIN_WINDOW_CORNER=off A/B switch for _cornerMask GPU cost
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(native): ⌘⌃⌥C live toggle for _cornerMask GPU A/B
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(native): match corner toggle by input.code (KeyC), not key (⌥ remaps c→ç)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(native): 1px bounce on corner toggle to force WindowServer _cornerMask re-read
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(native): drop A/B scaffolding; final _cornerMask corner at 30pt
GPU A/B (powermetrics, corner on vs off) showed no measurable difference in GPU
active residency, so the _cornerMask approach is kept. Remove the dev-only
measurement scaffolding (⌘⌃⌥C live toggle, 1px bounce, LIN_WINDOW_CORNER env
switch) and keep just the clean apply-on-create/show + fullscreen-0 path. Bump
MAC_WINDOW_CORNER_RADIUS 24 → 30 to match the large macOS Tahoe window corner of
Finder / Raycast. Update the module README to the final _cornerMask mechanism.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(native): continuous (squircle) corner mask; radius 22 to match Raycast
Apple's window corners are a continuous curve, not a circular arc, so a circular
mask at any radius reads differently from Finder/Raycast. Render the mask from a
CALayer with cornerCurve = continuous (CoreAnimation draws the exact system
shape; no magic-number superellipse) instead of bezierPathWithRoundedRect. Set
MAC_WINDOW_CORNER_RADIUS to 22 to match Raycast's measured ~20pt continuous
corner.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(native): revert to reliable circular mask; radius 28 to match Raycast
CALayer renderInContext rendered the continuous-curve corner smaller than the
requested radius, so the corner came out much smaller than Raycast. Revert to the
bezierPathWithRoundedRect circular mask (renders at the requested radius, proven
earlier) and set MAC_WINDOW_CORNER_RADIUS to 28 to match Raycast's visible size.
Shape is a circular arc, not Apple's continuous squircle — a future polish.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(native): bump corner radius to 80 to confirm it visibly applies
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(native): log _cornerMask apply result at startup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Drive macOS 26 window corner via private radius selectors
The _cornerMask override is a dead mechanism on macOS 26 Tahoe:
WindowServer no longer reads it to shape the window frame/shadow
(confirmed on-device — the override returned the correct mask yet the
frame kept its 16pt default). Tahoe instead drives the corner from the
private radius selectors.
Set the radius the way CornerFix reshapes Tahoe windows: swizzle the
radius getters (_cornerRadius / _effectiveCornerRadius / _topCornerRadius
/ _bottomCornerRadius) to return the per-window radius — the system reads
these on every relayout, so the value persists instead of reverting after
startup — and call the setters once for an immediate first paint. The
_cornerMask override + NSVisualEffectView.maskImage are kept for macOS
< 26 and frost rounding. Swizzle the real KVO dispatch class, not
[window class].
This uses Apple's own corner + default shadow path, so it does not
reintroduce the WindowServer GPU regression that electron/electron#48376
fixed by removing the custom _cornerMask. Radius tuned to 24pt to match
Raycast's continuous corner.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix stale comment contradicting the custom corner radius
The window-creation comment was left over from the abandoned
"don't chase a custom radius" approach and contradicted the
applyMacWindowCorner call right below it. Rewrite it to describe the
actual behavior: hiddenInset keeps native traffic lights, and the addon
sets the window's native corner radius so the OS still owns the corner
clip + shadow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Read the original on github.com ↗