electron-overlay-dimming
$
npx mdskill add sonichi/sutando/electron-overlay-dimmingAutomatically dims Electron overlay windows when the app loses focus
- Solves the problem of overlays staying visible when the user works in other apps
- Uses Electron's BrowserWindow API to track focus and adjust window opacity
- Delays blur events to avoid flickering when switching between overlays
- Respects user-configured opacity settings when restoring overlay visibility
SKILL.md
.github/skills/electron-overlay-dimmingView on GitHub ↗
---
name: electron-overlay-dimming
description: Reusable pattern for focus-based auto-dimming of Electron overlay windows — when the app loses focus, all overlay windows fade to a low opacity; when an overlay regains focus, they return to their configured opacity. Use when building always-on-top Electron overlays that should recede while the user works in other apps.
---
# Electron Overlay Auto-Dimming
A small, self-contained pattern for always-on-top Electron overlays: the
overlays stay readable while in use but **fade out of the way** when the user
clicks into another application, and **restore** when focus returns to an
overlay. First shipped in the `benchmark-overlay` app.
## Behaviour
- App loses focus (no overlay window is focused) → every overlay window's
opacity drops to a dim level (~0.2).
- Focus returns to any overlay → every overlay restores to its *configured*
opacity (not blindly to 1.0 — it respects any per-overlay opacity the user
set).
## Why it's not naive
Two pitfalls the pattern handles:
1. **Inter-overlay clicks.** Clicking from overlay A to overlay B fires a
`blur` then a `focus`. Dimming on raw `blur` would flicker. The fix: on
`blur`, defer ~80 ms and only dim if `BrowserWindow.getFocusedWindow()` is
then `null` — i.e. the *app* truly lost focus, not just one window.
2. **Configured opacity.** Overlays may each have a user-set base opacity.
Auto-dim must dim *from* and restore *to* that value, so opacity is always
computed as `appDimmed ? DIM_OPACITY : overlay.config.opacity`.
## Reference implementation (main process)
```js
const DIM_OPACITY = 0.2;
let appDimmed = false;
function effectiveOpacity(o) {
return appDimmed ? DIM_OPACITY : o.config.opacity;
}
function applyOpacityAll() {
for (const o of Object.values(OVERLAYS)) {
if (o.win && !o.win.isDestroyed()) o.win.setOpacity(effectiveOpacity(o));
}
}
app.on('browser-window-focus', () => {
if (appDimmed) { appDimmed = false; applyOpacityAll(); }
});
app.on('browser-window-blur', () => {
// Defer so an A→B overlay click doesn't briefly dim.
setTimeout(() => {
if (!appDimmed && !BrowserWindow.getFocusedWindow()) {
appDimmed = true;
applyOpacityAll();
}
}, 80);
});
```
Any code path that sets a window's opacity (e.g. a config handler) must route
through `effectiveOpacity()` so opacity changes made while dimmed don't undim
the window.
## Reference app
Live implementation: `~/projects/benchmark-overlay/main.js` — the
`benchmark-overlay` app applies this across three overlay windows (AI
Benchmarks, System Resources, Hub Overlay).
More from sonichi/sutando
- agent-registryLocal Agent Registry — a standalone, dependency-free service that tracks running Claude Code (and other) agent instances. Agents self-register on startup and heartbeat while alive; the Electron overlay and Sutando dashboard read the live list. Use when you need to know which coding agents are running, where, and since when.
- bot2bot-postPost a coordination message from this bot to the shared bot2bot channel, @-mentioning the other Sutando node.
- claude-codexBash wrapper around the local Codex CLI for non-interactive runs from inside Sutando (bridges, cron, scripts). For interactive code review or task hand-off from this Claude Code session, prefer the official `/codex:*` plugin commands; this skill is the file-bridge-compatible path that `discord-bridge.py` invokes for team-tier sandboxed delegation.
- claude-geminiUse the local Gemini CLI from Claude Code with the user's existing Gemini authentication or API configuration. Use for large-context repo scans, multimodal analysis, second-opinion planning, or structured Gemini runs in the current workspace.
- claude-routerChoose between the local Codex CLI and Gemini CLI from Claude Code. Use for automatic model selection when the user wants the best local delegate for code review, repo-wide analysis, planning, or implementation.
- cross-node-syncRsync-over-ssh sync between Sutando nodes (Mac Studio and MacBook) for shared memory + notes. Optional — core runs fine without it; enables automatic cross-bot learning and note propagation by running from the proactive-loop cron on each pass.
- deal-finderScan configured sources (Craigslist now; eBay + Facebook Marketplace planned) for used-item listings matching the owner's criteria. Currently configured for a Mac mini search (M2+, 16GB+, 512GB+, ≤$500, near 94566). Notify owner via SMS + Telegram on a match.
- gemini-ttsRender text to mp3 via Google Gemini Flash TTS. Free-tier eligible (1500 req/day). Use for video narration, demo voiceovers, audio notes. Parallels openai-tts; default for make-viral-video.
- macos-toolsmacOS native integrations: screen capture, calendar, reminders, contacts, email (Mail.app), Spotlight search. Use when the user asks about their screen, schedule, to-do list, contacts, or wants to send email on macOS.
- macos-useGUI control for macOS apps via mediar-ai's mcp-server-macos-use. Click, type, scroll, key-press, open apps — driven by accessibility tree, works in non-interactive Claude Code mode. Use this for any Sutando task that needs to drive another macOS application (Safari, Zoom, Mail, Finder, etc.).