electron-overlay-dimming

$npx mdskill add sonichi/sutando/electron-overlay-dimming

Automatically 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

SkillDescription
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.).