Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
00c9a3d
feat(desktop): add Warp-style window vibrancy (macOS, opt-in)
MocA-Love Apr 15, 2026
d52685d
fix(desktop): address Codex review on vibrancy PR
MocA-Love Apr 15, 2026
593f0f4
fix(desktop): vibrancy overlay must be inline to beat theme store
MocA-Love Apr 15, 2026
860ba1d
feat(desktop): extend vibrancy to terminal pane + stronger defaults
MocA-Love Apr 15, 2026
69a66b5
fix(desktop): flatten vibrancy layers + make blur slider continuous
MocA-Love Apr 15, 2026
6066d72
fix(desktop): make FileViewer + CodeMirror + terminal honour vibrancy
MocA-Love Apr 15, 2026
bd55165
feat(desktop): continuous blur radius via native CIGaussianBlur addon
MocA-Love Apr 15, 2026
59a9c3a
fix(desktop): let the 4-step blur slider move during drag
MocA-Love Apr 15, 2026
579bbd4
fix(desktop): make native vibrancy blur actually take effect + persist
MocA-Love Apr 15, 2026
e695650
fix(desktop): drive vibrancy blur via CAFilter on real CABackdropLayer
MocA-Love Apr 15, 2026
398fd8d
debug(desktop): vibrancy trace behind SUPERSET_VIBRANCY_DEBUG
MocA-Love Apr 15, 2026
6c2133c
debug(desktop): make vibrancy trace unconditional
MocA-Love Apr 15, 2026
07dc99d
fix(desktop): trigger actual re-render when vibrancy blur radius changes
MocA-Love Apr 15, 2026
6ec2817
fix(desktop): re-apply vibrancy blur a few times to beat internal ove…
MocA-Love Apr 15, 2026
149fd09
fix(desktop): cancel stale blur retries so rapid slider drags settle
MocA-Love Apr 15, 2026
b367cb0
chore(desktop): remove vibrancy debug logging + address review feedback
MocA-Love Apr 15, 2026
044d1c6
fix(desktop): address new vibrancy PR review comments
MocA-Love Apr 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"@superset/host-service": "workspace:*",
"@superset/local-db": "workspace:*",
"@superset/macos-process-metrics": "workspace:*",
"@superset/macos-window-blur": "workspace:*",
"@superset/panes": "workspace:*",
"@superset/shared": "workspace:*",
"@superset/trpc": "workspace:*",
Expand Down
6 changes: 6 additions & 0 deletions apps/desktop/runtime-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ const externalizedRuntimeModules: ExternalizedRuntimeModule[] = [
packagedCopies: [copyWholeModule("@superset/macos-process-metrics")],
asarUnpackGlobs: ["**/node_modules/@superset/macos-process-metrics/**/*"],
},
{
specifier: "@superset/macos-window-blur",
materialize: ["@superset/macos-window-blur"],
packagedCopies: [copyWholeModule("@superset/macos-window-blur")],
asarUnpackGlobs: ["**/node_modules/@superset/macos-window-blur/**/*"],
},
{
specifier: "@ast-grep/napi",
materialize: ["@ast-grep/napi"],
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/lib/trpc/routers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { createSettingsRouter } from "./settings";
import { createTabTearoffRouter } from "./tab-tearoff";
import { createTerminalRouter } from "./terminal";
import { createUiStateRouter } from "./ui-state";
import { createVibrancyRouter } from "./vibrancy";
import { createVscodeExtensionsRouter } from "./vscode-extensions";
import { createWindowRouter } from "./window";
import { createWorkspacesRouter } from "./workspaces";
Expand Down Expand Up @@ -76,6 +77,7 @@ export const createAppRouter = (
hostServiceCoordinator: createHostServiceCoordinatorRouter(),
tabTearoff: createTabTearoffRouter(wm),
extensions: createExtensionsRouter(getWindow),
vibrancy: createVibrancyRouter(wm),
vscodeExtensions: createVscodeExtensionsRouter(),
});
};
Expand Down
92 changes: 92 additions & 0 deletions apps/desktop/src/lib/trpc/routers/vibrancy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { observable } from "@trpc/server/observable";
import { nativeTheme } from "electron";
import { appState } from "main/lib/app-state";
import {
applyVibrancy,
DEFAULT_VIBRANCY_STATE,
isNativeContinuousBlurSupported,
isVibrancySupported,
normalizeVibrancyState,
type VibrancyBlurLevel,
type VibrancyState,
} from "main/lib/vibrancy";
import { VIBRANCY_EVENTS, vibrancyEmitter } from "main/lib/vibrancy/emitter";
import type { WindowManager } from "main/lib/window-manager";
import { z } from "zod";
import { publicProcedure, router } from "..";

const blurLevelSchema: z.ZodType<VibrancyBlurLevel> = z.enum([
"subtle",
"standard",
"strong",
"ultra",
]);

const vibrancyInputSchema = z.object({
enabled: z.boolean().optional(),
opacity: z.number().int().min(0).max(100).optional(),
blurLevel: blurLevelSchema.optional(),
blurRadius: z.number().min(0).max(100).optional(),
});

function getCurrentState(): VibrancyState {
const stored = appState.data?.vibrancyState;
// Merge over defaults so older on-disk states (written before we added
// blurRadius) still produce a complete VibrancyState. Otherwise the
// missing field would round-trip as `undefined` and the slider would
// appear to reset on every restart.
return { ...DEFAULT_VIBRANCY_STATE, ...stored };
}

async function writeState(next: VibrancyState): Promise<void> {
if (!appState.data) return;
appState.data.vibrancyState = next;
await appState.write();
}

function broadcastVibrancy(wm: WindowManager, state: VibrancyState): void {
const isDark = nativeTheme.shouldUseDarkColors;
for (const window of wm.getAll().values()) {
applyVibrancy(window, state, isDark);
}
}

export const createVibrancyRouter = (wm: WindowManager) => {
return router({
getSupported: publicProcedure.query(() => {
return {
supported: isVibrancySupported(),
nativeBlurSupported: isNativeContinuousBlurSupported(),
};
}),

get: publicProcedure.query(() => {
return getCurrentState();
}),

set: publicProcedure
.input(vibrancyInputSchema)
.mutation(async ({ input }) => {
const current = getCurrentState();
const next = normalizeVibrancyState(input, current);
await writeState(next);
broadcastVibrancy(wm, next);
vibrancyEmitter.emit(VIBRANCY_EVENTS.CHANGED, next);
return next;
}),

onChanged: publicProcedure.subscription(() => {
return observable<VibrancyState>((emit) => {
const handler = (state: VibrancyState) => {
emit.next(state);
};
vibrancyEmitter.on(VIBRANCY_EVENTS.CHANGED, handler);
return () => {
vibrancyEmitter.off(VIBRANCY_EVENTS.CHANGED, handler);
};
});
}),
});
};

export type VibrancyRouter = ReturnType<typeof createVibrancyRouter>;
4 changes: 4 additions & 0 deletions apps/desktop/src/main/lib/app-state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ function ensureValidShape(data: Partial<AppState>): AppState {
...(data.hotkeysState?.byPlatform ?? {}),
},
},
vibrancyState: {
...defaultAppState.vibrancyState,
...(data.vibrancyState ?? {}),
},
};
}

Expand Down
6 changes: 6 additions & 0 deletions apps/desktop/src/main/lib/app-state/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
*/
import type { BaseTabsState } from "shared/tabs-types";
import type { Theme } from "shared/themes";
import {
DEFAULT_VIBRANCY_STATE,
type VibrancyState,
} from "shared/vibrancy-types";

// Re-export for convenience
export type { BaseTabsState as TabsState, Pane } from "shared/tabs-types";
Expand All @@ -24,6 +28,7 @@ export interface AppState {
tabsState: BaseTabsState;
themeState: ThemeState;
hotkeysState: LegacyHotkeysState;
vibrancyState: VibrancyState;
}

export const defaultAppState: AppState = {
Expand All @@ -44,4 +49,5 @@ export const defaultAppState: AppState = {
version: 1,
byPlatform: { darwin: {}, win32: {}, linux: {} },
},
vibrancyState: DEFAULT_VIBRANCY_STATE,
};
25 changes: 25 additions & 0 deletions apps/desktop/src/main/lib/vibrancy/emitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { EventEmitter } from "node:events";
import type { VibrancyState } from "./index";

export const VIBRANCY_EVENTS = {
CHANGED: "vibrancy:changed",
} as const;

type VibrancyEvents = {
[VIBRANCY_EVENTS.CHANGED]: [VibrancyState];
};

export const vibrancyEmitter = new EventEmitter() as EventEmitter & {
on<K extends keyof VibrancyEvents>(
event: K,
listener: (...args: VibrancyEvents[K]) => void,
): EventEmitter;
off<K extends keyof VibrancyEvents>(
event: K,
listener: (...args: VibrancyEvents[K]) => void,
): EventEmitter;
emit<K extends keyof VibrancyEvents>(
event: K,
...args: VibrancyEvents[K]
): boolean;
};
Loading
Loading