relixiaobo added a commit to relixiaobo/lin-outliner that referenced this pull request
…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>