feat(desktop): Warpライクなウィンドウ透過 (macOS、デフォルトOFF)#184
Conversation
macOS only, default OFF. Adds a new Appearance section with an enable toggle, opacity slider, and blur-level select. Backed by NSVisualEffectView via BrowserWindow.setVibrancy + setBackgroundColor(rgba), dynamically switchable without recreating the window. BrowserPane (webview) stays opaque due to Chromium compositor constraints — documented in the UI copy. - main: new lib/vibrancy module (types in shared/ for layer-safety) - main: transparent:true + initial vibrancy spread into main and tearoff windows on macOS only; no-op on Win/Linux to avoid the Wayland black-window pitfall - state: AppState.vibrancyState persisted via app-state (lowdb JSON) - trpc: new vibrancy router with observable onChanged subscription - renderer: vibrancy zustand store with hydrate-once guard, subscription cleanup, optimistic updates, and early bootstrap in index.tsx - css: :root[data-vibrancy="on"] overrides background/card/muted/sidebar to rgba variants controlled by --vibrancy-alpha; uses min/max() to clamp calc() derivations safely - ui: new VibrancySection in Appearance settings; disabled on non-macOS
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 32 minutes and 38 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (19)
📝 WalkthroughウォークスルーこのPRはmacOSウィンドウビブランシー機能をElectronデスクトップアプリケーションに追加します。共有型定義、主プロセスビブランシーライブラリ、アプリ状態の永続化、TRPCルーター、レンダラーストア、CSS変数、設定UIをサポートしており、ターミナルと各エディタで透明度効果を実現します。 変更内容
シーケンス図sequenceDiagram
participant User as ユーザー
participant Settings as 設定画面
participant Store as VibrancyStore
participant TRPC as TRPC Router
participant Main as メインプロセス
participant WM as WindowManager
participant Window as BrowserWindow
User->>Settings: ビブランシー設定を変更
Settings->>Store: setState({enabled, opacity, ...})
activate Store
Store->>Store: DOM更新<br/>(data-vibrancy, CSS変数)
Store->>TRPC: set(state)
deactivate Store
activate TRPC
TRPC->>Main: normalizeVibrancyState(state)
TRPC->>Main: appState.vibrancyStateに永続化
TRPC->>Main: broadcastVibrancy()
deactivate TRPC
activate Main
Main->>WM: 全ウィンドウ取得
WM->>WM: 各ウィンドウに対して
Main->>Window: applyVibrancy()<br/>(背景色、vibrancy型設定)
deactivate Main
activate Window
Window->>Window: イベント発行<br/>(VIBRANCY_CHANGED)
deactivate Window
TRPC->>Store: onChanged購読から<br/>イベント受信
Store->>Settings: 画面更新反映
Settings->>User: 新しい外観表示
推定コードレビュー工数🎯 4 (複雑) | ⏱️ ~60 分 関連している可能性のあるPR
詩
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 00c9a3da0e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- slider: drive --vibrancy-alpha directly during drag and defer persistence to onValueCommit so a single interaction no longer produces dozens of app-state writes and IPC broadcasts - store: coerce enabled=false on unsupported platforms before applying to DOM, both on initial hydrate and on subsequent subscription broadcasts
The Appearance/theme store writes --background, --sidebar, --muted, etc. directly onto documentElement.style via applyUIColors. Inline styles always win the CSS cascade, so the :root[data-vibrancy="on"] rules in globals.css were silently shadowed — toggling the slider did nothing. Fix: the vibrancy store now derives translucent variants from the active theme via color-mix() and writes them back onto documentElement.style itself, at the same specificity level. When vibrancy is turned off we reapply the theme's solid colors to restore the original palette. Also subscribe to theme changes so switching themes while vibrancy is on re-derives the overlay against the new palette, and expose a previewOpacity() action that lets the slider drive live updates without hitting tRPC on every tick.
- xterm: set allowTransparency on both terminal constructors (main and v2 pane) so the WebGL renderer honours rgba background colors - new useEffectiveTerminalTheme hook that derives a terminal theme with an rgba-mixed background when vibrancy is active, and switch both terminal consumers over to it - overlay: drop the +/-0.1 and +/-0.05 deltas so every chrome surface shares the same alpha — the old variance made sidebars and cards feel much more opaque than the main background - default opacity: 60 -> 35 so the factory setting already feels like vibrancy rather than "slightly translucent"
The previous overlay gave --background an rgba value, so any nested container with bg-background (the file viewer card, the terminal pane wrapper, etc) stacked the same translucent tint onto itself and showed up as a visibly darker rectangle inside the window. Let the window's own setBackgroundColor(rgba) be the single source of truth for the base tint and set --background to `transparent` instead. Chrome-specific surfaces (card, sidebar, muted, popover) stay tinted so they remain distinct from the transparent body, and get a small alpha bias so they're still legible at very low opacity settings. Also convert the blur-strength Select into a slider that snaps across four internal buckets (subtle/standard/strong/ultra). Feels continuous to match the opacity slider while still mapping onto the discrete NSVisualEffectView materials.
- FileViewerContent / SpreadsheetViewer / SpreadsheetDiffViewer: drop hardcoded bg-white, bg-[#0d0d0d], and #d0d0d0 in favor of bg-background and var(--border) so the file viewer can pick up the vibrancy overlay - createCodeMirrorTheme: new optional vibrancyOpacity param that mixes editor background + gutter background with `transparent` via color-mix, plumbed through CodeEditor, CodeMirrorDiffViewer, and ConflictViewer via a fresh useVibrancyStore read - globals.css: defensive .xterm overlay override that forces nested xterm viewport / screen / canvas backgrounds to transparent when vibrancy is on, so the rgba theme.background reaches the compositor
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
apps/desktop/src/renderer/stores/vibrancy/store.ts (2)
124-140: モジュールレベル変数は HMR で問題になる可能性があります。
hydratePromise、subscriptionEstablished、themeSubscriptionEstablishedがモジュールスコープで定義されています。開発環境での Hot Module Replacement 時に、これらの変数がリセットされずに残り、subscription の二重登録や hydration のスキップが発生する可能性があります。本番環境では問題ありませんが、開発体験向上のため、HMR cleanup を検討してください。
📝 HMR 対応の例
// 開発環境でのみ HMR cleanup を追加 if (import.meta.hot) { import.meta.hot.dispose(() => { hydratePromise = null; subscriptionEstablished = false; themeSubscriptionEstablished = false; }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/stores/vibrancy/store.ts` around lines 124 - 140, The module-level flags hydratePromise, subscriptionEstablished, and themeSubscriptionEstablished can persist across HMR updates and cause duplicate subscriptions or skipped hydration; add an HMR dispose handler that resets these variables to their initial values so state is cleared on hot reload (use import.meta.hot.dispose and reset hydratePromise = null, subscriptionEstablished = false, themeSubscriptionEstablished = false). Place the dispose logic near the top-level where these symbols and ensureThemeSubscription/useThemeStore.subscribe are defined so it runs during module teardown in development only.
172-188: subscription のクリーンアップが欠けています。
electronTrpcClient.vibrancy.onChanged.subscribeの戻り値(unsubscribe 関数)が保持されていません。現在のシングルトン store 設計では実際にはリークしませんが、defensive coding として unsubscribe 関数を保持することを推奨します。📝 クリーンアップ対応案
+let vibrancyUnsubscribe: (() => void) | null = null; + // Inside hydrate function: if (!subscriptionEstablished) { subscriptionEstablished = true; - electronTrpcClient.vibrancy.onChanged.subscribe(undefined, { + vibrancyUnsubscribe = electronTrpcClient.vibrancy.onChanged.subscribe(undefined, { onData: (incoming) => { // ... }, onError: (err) => { console.error("[vibrancy] subscription error:", err); subscriptionEstablished = false; + vibrancyUnsubscribe = null; }, }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/stores/vibrancy/store.ts` around lines 172 - 188, The subscription created by electronTrpcClient.vibrancy.onChanged.subscribe is not retained so it can't be unsubscribed; capture its unsubscribe function (e.g., store it in a module-scoped variable like subscriptionUnsubscribe alongside subscriptionEstablished) when subscribing in the vibrancy store, call that unsubscribe and set subscriptionEstablished = false in the onError handler and provide a public cleanup method on the store (e.g., dispose/unsubscribe/cleanup) that invokes the stored unsubscribe if present and clears the variable; update applyToDom/onData flow to still use set(effectiveIncoming) but ensure the unsubscribe is called from the store's lifecycle or when tearing down the renderer.apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/VibrancySection/VibrancySection.tsx (1)
151-165: ブラー強度スライダーにドラッグ中のプレビューがありません。不透明度スライダーには
onValueChangeでライブプレビューがありますが、ブラー強度スライダーにはonValueCommitのみで、ドラッグ中の視覚的フィードバックがありません。離散的な4段階なので大きな問題ではありませんが、一貫性のため検討してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/VibrancySection/VibrancySection.tsx` around lines 151 - 165, The vibrancy blur slider (in VibrancySection) only updates on commit via onValueCommit, so add an onValueChange handler that mirrors onValueCommit behavior to provide live preview during drag: in the Slider component add onValueChange={(values) => { const value = values[0]; if (typeof value !== "number") return; const nextLevel = sliderValueToBlurLevel(value); if (nextLevel !== blurLevel) { void setState({ blurLevel: nextLevel }); } }} so the temporary slider value is converted with sliderValueToBlurLevel and applied via setState for live feedback while keeping the existing onValueCommit logic intact.apps/desktop/src/renderer/globals.css (1)
141-157: ライトモードの rgb() 関数のフォーマットを修正してください。Stylelint が
/演算子の後の改行をエラーとして報告しています。CSS Color Level 4 の構文としては有効ですが、一貫性のためダークモードのブロック(Lines 119-139)と同様にrgba()関数を使用するか、改行なしで記述することを検討してください。📝 フォーマット修正案
:root[data-vibrancy="on"].light { --background: rgb(255 255 255 / var(--vibrancy-alpha)); --card: rgb(255 255 255 / min(1, calc(var(--vibrancy-alpha) + 0.1))); --popover: rgb(255 255 255 / 0.95); --muted: rgb(247 247 247 / min(1, calc(var(--vibrancy-alpha) + 0.1))); --accent: rgb(247 247 247 / min(1, calc(var(--vibrancy-alpha) + 0.1))); --sidebar: rgb(251 251 251 / max(0, calc(var(--vibrancy-alpha) - 0.05))); - --sidebar-accent: rgb( - 247 247 247 / - min(1, calc(var(--vibrancy-alpha) + 0.05)) - ); + --sidebar-accent: rgb(247 247 247 / min(1, calc(var(--vibrancy-alpha) + 0.05))); --tertiary: rgb(243 243 243 / max(0, calc(var(--vibrancy-alpha) - 0.05))); - --tertiary-active: rgb( - 233 233 233 / - min(1, calc(var(--vibrancy-alpha) + 0.05)) - ); + --tertiary-active: rgb(233 233 233 / min(1, calc(var(--vibrancy-alpha) + 0.05))); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/globals.css` around lines 141 - 157, The rgb(... / ...) color declarations in the :root[data-vibrancy="on"].light block (variables like --background, --card, --popover, --muted, --accent, --sidebar, --sidebar-accent, --tertiary, --tertiary-active) must be reformatted to avoid the line-break after the "/" that Stylelint flags; update them to match the dark block's style by either converting to rgba(r, g, b, alpha) calls or writing the rgb(...) with the /alpha on a single line (no newline between the slash and the alpha expression) so the CSS passes linting and remains consistent with the dark-mode declarations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/main/lib/vibrancy/index.ts`:
- Around line 117-123: The code unnecessarily casts null when calling
window.setVibrancy; remove the if/else and the cast so that the value returned
from resolveVibrancyType (typed as "sidebar" | "header" | "content" |
"fullscreen-ui" | null) is passed directly to BrowserWindow.setVibrancy. Replace
the block that checks vibrancyType and uses null as unknown as
Parameters<BrowserWindow["setVibrancy"]>[0] with a single call to
window.setVibrancy(vibrancyType), keeping resolveVibrancyType and vibrancyType
as-is.
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx`:
- Line 71: VibrancySection is always rendered because it lacks the same
visibility guard used by other sections; update the render so VibrancySection
only mounts when the search-filtered visibleItems includes "vibrancy" (use the
same visibleItems check pattern used for other sections) — locate the
VibrancySection key="vibrancy" line and wrap it with the visibleItems.contains/
includes conditional used elsewhere in AppearanceSettings so it only renders
when visibleItems indicates it should be visible.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/ConflictViewer.tsx`:
- Around line 226-229: activeTheme/vibrancyOpacity changes currently rebuild the
entire EditorView (created via createCodeMirrorTheme), causing editor
re-creation and loss of unsaved content because the file-sync effect only
depends on fileData; instead refactor so the EditorView is created once and
theme updates use a CodeMirror Compartment: create a Compartment for the theme
near where EditorView is instantiated, install the initial theme extension using
createCodeMirrorTheme(activeTheme, ..., { vibrancyOpacity }), and on
vibrancyOpacity or activeTheme change call
compartment.reconfigure(newThemeExtension) (or use view.dispatch to apply the
reconfigure) rather than re-creating the EditorView; keep the fileData sync
effect dependency as-is so it runs after editor is mounted.
---
Nitpick comments:
In `@apps/desktop/src/renderer/globals.css`:
- Around line 141-157: The rgb(... / ...) color declarations in the
:root[data-vibrancy="on"].light block (variables like --background, --card,
--popover, --muted, --accent, --sidebar, --sidebar-accent, --tertiary,
--tertiary-active) must be reformatted to avoid the line-break after the "/"
that Stylelint flags; update them to match the dark block's style by either
converting to rgba(r, g, b, alpha) calls or writing the rgb(...) with the /alpha
on a single line (no newline between the slash and the alpha expression) so the
CSS passes linting and remains consistent with the dark-mode declarations.
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/VibrancySection/VibrancySection.tsx`:
- Around line 151-165: The vibrancy blur slider (in VibrancySection) only
updates on commit via onValueCommit, so add an onValueChange handler that
mirrors onValueCommit behavior to provide live preview during drag: in the
Slider component add onValueChange={(values) => { const value = values[0]; if
(typeof value !== "number") return; const nextLevel =
sliderValueToBlurLevel(value); if (nextLevel !== blurLevel) { void setState({
blurLevel: nextLevel }); } }} so the temporary slider value is converted with
sliderValueToBlurLevel and applied via setState for live feedback while keeping
the existing onValueCommit logic intact.
In `@apps/desktop/src/renderer/stores/vibrancy/store.ts`:
- Around line 124-140: The module-level flags hydratePromise,
subscriptionEstablished, and themeSubscriptionEstablished can persist across HMR
updates and cause duplicate subscriptions or skipped hydration; add an HMR
dispose handler that resets these variables to their initial values so state is
cleared on hot reload (use import.meta.hot.dispose and reset hydratePromise =
null, subscriptionEstablished = false, themeSubscriptionEstablished = false).
Place the dispose logic near the top-level where these symbols and
ensureThemeSubscription/useThemeStore.subscribe are defined so it runs during
module teardown in development only.
- Around line 172-188: The subscription created by
electronTrpcClient.vibrancy.onChanged.subscribe is not retained so it can't be
unsubscribed; capture its unsubscribe function (e.g., store it in a
module-scoped variable like subscriptionUnsubscribe alongside
subscriptionEstablished) when subscribing in the vibrancy store, call that
unsubscribe and set subscriptionEstablished = false in the onError handler and
provide a public cleanup method on the store (e.g., dispose/unsubscribe/cleanup)
that invokes the stored unsubscribe if present and clears the variable; update
applyToDom/onData flow to still use set(effectiveIncoming) but ensure the
unsubscribe is called from the store's lifecycle or when tearing down the
renderer.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a1aa87af-4039-43a7-97ec-bae50947c65b
📒 Files selected for processing (28)
apps/desktop/src/lib/trpc/routers/index.tsapps/desktop/src/lib/trpc/routers/vibrancy.tsapps/desktop/src/main/lib/app-state/index.tsapps/desktop/src/main/lib/app-state/schemas.tsapps/desktop/src/main/lib/vibrancy/emitter.tsapps/desktop/src/main/lib/vibrancy/index.tsapps/desktop/src/main/lib/window-manager/index.tsapps/desktop/src/main/windows/main.tsapps/desktop/src/renderer/globals.cssapps/desktop/src/renderer/index.tsxapps/desktop/src/renderer/lib/terminal/terminal-runtime.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/hooks/useTerminalAppearance/useTerminalAppearance.tsapps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsxapps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/VibrancySection/VibrancySection.tsxapps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/VibrancySection/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/ConflictViewer.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/SpreadsheetViewer/SpreadsheetDiffViewer.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/SpreadsheetViewer/SpreadsheetViewer.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.tsapps/desktop/src/renderer/stores/index.tsapps/desktop/src/renderer/stores/vibrancy/index.tsapps/desktop/src/renderer/stores/vibrancy/store.tsapps/desktop/src/shared/vibrancy-types.ts
Adds a new @superset/macos-window-blur workspace package modelled on @superset/macos-process-metrics. Its Objective-C++ addon walks the BrowserWindow's NSView hierarchy to find Electron's NSVisualEffectView and attaches a CIGaussianBlur filter to the backing CALayer, giving us a 1-unit continuous blur slider instead of the four NSVisualEffectView material presets. - packages/macos-window-blur: new native addon, binding.gyp with Cocoa / QuartzCore / CoreImage frameworks, graceful fallback when the binary fails to load - runtime-dependencies.ts: materialize + asarUnpack the addon so electron-builder bundles it like the existing macos-process-metrics - shared/vibrancy-types: add blurRadius field (0-100) and the MIN/MAX constants used by the slider - main/lib/vibrancy: applyVibrancy now also calls setWindowBlurRadius via the addon, passing 0 when vibrancy is disabled to clear the override. isNativeContinuousBlurSupported gates the UI toggle - trpc router: getSupported returns nativeBlurSupported, set accepts blurRadius inputs - renderer store: new nativeBlurSupported flag, carries blurRadius through hydrate/set/optimistic paths - VibrancySection: shows a continuous ブラー半径 slider when the native addon is available; falls back to the 4-step material selector otherwise so the feature stays usable when the .node build was skipped
Radix Slider's `value` is fully controlled, so a slider without an `onValueChange` handler ignores drag input and the thumb appears pinned to the computed blurLevel bucket. Mirror the opacity-slider pattern: hold a draft value in local state while dragging and only commit to the store in `onValueCommit`. Also reflect the drag value in the label so users see which bucket they're about to land in.
Two bugs discovered after landing the native addon: 1. Slider had no visible effect. NSVisualEffectView's backing layer is a CABackdropLayer subclass on macOS 11+, and that class ignores `backgroundFilters` because the system manages the backdrop separately — instead, `filters` is interpreted as the backdrop filter stack on backdrop-style layers. Detect CABackdropLayer via runtime class lookup and write the CIGaussianBlur into `filters` when the layer is backdrop-style, falling back to backgroundFilters otherwise. Also setNeedsDisplay after mutating the filter stack so Core Animation re-renders the frame. 2. Value wasn't persisted across restarts. Older on-disk app-state files (written before blurRadius existed) were round-tripped back through the router as a partial VibrancyState without blurRadius, so the mutation ended up writing `undefined` and the next restart landed on whatever normalizeVibrancyState defaulted to. Spread the stored state over DEFAULT_VIBRANCY_STATE inside getCurrentState so callers always see a complete shape.
The previous CIFilter + vev.layer attempt was a silent no-op: NSVisualEffectView's real gaussian blur lives several sublayers deep on a CABackdropLayer instance, and that layer's filter stack is populated with `CAFilter` (private) instances — not CIFilter. Writing a CIFilter onto an outer CALayer is accepted by the setter but never rendered by the backdrop pipeline. Rewrite the addon to: 1. Walk down into the NSVisualEffectView's layer tree (via FindLayerByClassName) to locate the actual CABackdropLayer. 2. Look up the private `CAFilter` class at runtime and call `+filterWithType:@"gaussianBlur"` through a performSelector shim, keeping the binary compilable on SDKs that don't expose CAFilter. 3. Edit the existing gaussianBlur filter in place (creating it when absent) instead of replacing the whole filter stack, so we keep the system's own filter pipeline intact. 4. Cache the original inputRadius via an associated object so callers can pass radius <= 0 to restore the default material feel. 5. Wrap the mutation in a CATransaction with actions disabled plus a flush, so Core Animation commits the new radius immediately. With this in place the slider finally moves the real backdrop radius instead of shadowing an unused filter on the outer layer.
Add opt-in logging at every layer so we can see what the native blur addon is actually doing when the slider has no visible effect: - addon.mm: VDBG macro that writes to stderr when the env var is set, covering window/contentView/NSVisualEffectView lookup, the vev.layer class name, a sublayer dump when CABackdropLayer can't be found, and the final inputRadius read back from the filter after mutation - packages/macos-window-blur/index.js: log load success/failure plus every setWindowBlurRadius call and its boolean result - main/lib/vibrancy/index.ts: console.log the full state snapshot going into applyVibrancy and the addon's return value - main/windows/main.ts: re-apply vibrancy on did-finish-load so the addon runs once the window actually has an on-screen NSVisualEffectView (important for the first launch path where no mutation fires) All logging is gated on `SUPERSET_VIBRANCY_DEBUG=1`.
Drop the SUPERSET_VIBRANCY_DEBUG gate in the addon, the Node loader, and applyVibrancy so the trace lines fire on every run. Debugging is still opt-out with a quick revert, but the user does not need to set an env var before reproducing the native-blur issue.
Debug trace revealed the addon *was* finding the CABackdropLayer and writing the requested inputRadius onto the existing gaussianBlur CAFilter, but Core Animation never re-rendered the window. The reason is that mutating a property on a CAFilter instance after it is already installed on a layer does not fire any KVO / display-invalidation path, because CALayer only observes the `filters` array itself. Fix: replace the existing gaussianBlur filter with a brand-new CAFilter instance on every call and reassign backdrop.filters so the layer's property observer runs and the backdrop pipeline re-renders with the new radius. Also call setNeedsDisplay on the backdrop as a belt-and- braces trigger. Also: NSVisualEffectView builds its CABackdropLayer lazily one runloop tick after setVibrancy is called, so the very first toggle used to miss the layer and silently fail. Retry setWindowBlurRadius a few times with exponential backoff from applyVibrancy whenever the initial call comes back false — subsequent interactions already worked, only the first frame needed this treatment.
…rwrites NSVisualEffectView occasionally rewrites its own backdrop filter stack in response to internal state changes (focus, material refresh, the lazy CABackdropLayer construction that happens the first frame after setVibrancy is called). A single call to setWindowBlurRadius can land before that happens and then get silently reverted, which shows up as "the slider didn't take effect this time". Instead of a conditional retry only on failure, always schedule a short burst of re-applies on the next few runloop ticks (0ms, 16ms, 64ms, 180ms). Each call is cheap, only touches the filters array via a KVO-firing reassignment, and keeps us ahead of any system overwrite. Also on the native side: - Dump the existing filter names/types in the debug trace so future regressions can show which filter stack we're mutating. - After replacing backdrop.filters, call setNeedsDisplay + setNeedsLayout and then displayIfNeeded so Core Animation commits the new radius synchronously rather than waiting for the next vsync.
Previous burst-retry logic scheduled 4 timers per applyVibrancy call that each closed over the radius at schedule time. When the user dragged the blur slider quickly the old radius' retries (up to 180ms later) would land after a newer radius had already been applied, so the layer would snap back to an earlier value and the visual wouldn't match the slider position. Track one BlurSchedule per window in a WeakMap that carries both the latest requested radius and the list of still-pending timers. On each applyVibrancy call we update latestRadius, clearTimeout all prior retries, and reschedule a fresh burst. Every timer now reads from the shared latestRadius, so no matter how often the user drags, the window only ever ends up showing the most recently committed value. Also stretch the retry schedule to 16/64/180/480/960ms so slower NSVisualEffectView refresh events (focus changes landing seconds later) still get corrected.
- native addon: drop the VDBG macro, #include <stdio.h>, and all
dispatch_block stderr traces so the macOS binary no longer writes to
stderr on every setWindowBlurRadius call (rv-pr items [2][3][7])
- @superset/macos-window-blur/index.js: remove the success-path
console.log added while debugging the initial integration
- main/lib/vibrancy/index.ts: delete the vdbg() helper and every
console.log call site; keep the scheduleNativeBlur retry loop
otherwise unchanged
- main/windows/main.ts: subscribe to nativeTheme.on("updated") so
macOS dark/light appearance changes re-run applyVibrancy against
the latest isDark; the previous implementation pinned the
BrowserWindow background color to whatever isDark was at the last
mutation, which left the tint stale on system theme transitions
when the user picked the "system" theme (rv-pr item [13])
- SpreadsheetViewer / SpreadsheetDiffViewer: replace the remaining
hardcoded #d0d0d0 / #f5f5f5 / #c0c0c0 / #f0f0f0 / superset-sh#666 / superset-sh#999 /
#fafafa / #e0e0e0 / #f8f8f8 / superset-sh#888 cells, borders, and text colors
with var(--border) / var(--muted) / var(--muted-foreground) so the
spreadsheet grid honours both dark mode and the vibrancy overlay
(rv-pr item [15] plus the neighbouring SpreadsheetViewer cells
found while fixing it)
|
@codex review |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b367cb0a68
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
CodeRabbit / Codex raised five issues against the latest commits: - vibrancy/index.ts: drop the \`null as unknown as ...\` cast on \`window.setVibrancy\`. Electron 30+ types \`setVibrancy\` as \`string | null\`, so the raw value from resolveVibrancyType can pass through directly and we can collapse the if/else into a single call. - AppearanceSettings.tsx / settings-search catalog: register a new \`APPEARANCE_VIBRANCY\` setting id with appropriate search keywords and wire \`showVibrancy = isItemVisible(...)\` so the vibrancy section participates in the settings search filter like the neighbouring sections. - ConflictViewer.tsx: drop the \`vibrancyOpacity\` wiring entirely. The init useEffect's guarded cleanup (view.destroy) ran whenever vibrancyOpacity changed, tearing down and re-creating the EditorView while the content-sync effect (keyed on \`fileData\`) did not re-run — so the re-created editor kept an empty doc and unsaved merge-conflict edits could be lost. ConflictViewer is a transient merge-assist surface so we can just skip vibrancy here; the other two CodeMirror hosts still use proper Compartment reconfigure. - window-manager/index.ts: call applyVibrancy inside the tearoff window's did-finish-load handler, mirroring MainWindow. Without it, newly opened tearoff windows stuck with the default NSVisualEffectView blur until the user bumped the slider, because the native addon relies on the NSVisualEffectView being on-screen before it can find the CABackdropLayer. - stores/vibrancy/store.ts: memoize the return value of \`useEffectiveTerminalTheme\` via useMemo([base, enabled, opacity]). Terminal.tsx runs \`xterm.options.theme = terminalTheme\` inside an effect keyed on the theme identity, so a fresh object on every render used to force repeated xterm reconfiguration on unrelated renders.
概要
Warpターミナルのような半透明ウィンドウを macOS ネイティブの vibrancy (NSVisualEffectView) で実装。デフォルト OFF で opt-in。Appearance 設定に新セクションを追加し、有効/無効・不透明度・ブラー強度をリアルタイムで切り替えられる。
スコープ
実装内容
Main プロセス
tRPC
Renderer
動的変更の仕組み
macOS で `transparent: true` は生成時しか指定できないため、常に true で生成し、オフ状態では不透明な `backgroundColor` を使う。ユーザーがトグルを操作すると tRPC mutation が走り、main 側で `setBackgroundColor(rgba)` と `setVibrancy(type)` を呼び出して即時反映。ウィンドウ再生成は不要。
既存機能への影響
セルフレビュー済み項目
テスト計画
Summary by CodeRabbit
リリースノート