fix: improve macOS Electron window chrome (#3029)

Integrated into release/v3.8.8. macOS Electron window-chrome: tray icon template + draggable header region (excludes interactive controls) with a safe fallback + MutationObserver cleanup. Squash-merged (drops the AI co-author trailers per repo policy). Thanks @bobbyunknown!
This commit is contained in:
Insomnia
2026-06-01 18:08:01 +07:00
committed by GitHub
parent 57dfa25312
commit baa4e56997
4 changed files with 134 additions and 0 deletions

View File

@@ -260,6 +260,14 @@
- **mcp:** move `enforceScopes` guard before `MCP_TOOL_MAP` lookup, add inline `scopes` parameter to `withScopeEnforcement()`, and declare scopes on all 24 dynamic tool definitions (memory, skills, plugins, gamification, compression) to fix scope enforcement for dynamic MCP tool groups (#2958)
### ✨ New Features
- **notion:** add Notion as an MCP context source — 6 tools (`notion_search`, `notion_list_databases`, `notion_get_database`, `notion_query_database`, `notion_read`, `notion_append_blocks`) scoped under `read:notion` / `write:notion`, with dashboard "Context Sources" tab, settings API, and token persistence in `key_value` table (#2959)
### 🔧 Bug Fixes
- **mcp:** move `enforceScopes` guard before `MCP_TOOL_MAP` lookup, add inline `scopes` parameter to `withScopeEnforcement()`, and declare scopes on all 24 dynamic tool definitions (memory, skills, plugins, gamification, compression) to fix scope enforcement for dynamic MCP tool groups (#2958)
---
## [3.8.7] — 2026-05-29

View File

@@ -410,6 +410,10 @@ function createTray() {
try {
icon = nativeImage.createFromPath(iconPath);
if (icon.isEmpty()) icon = nativeImage.createEmpty();
if (process.platform === "darwin" && !icon.isEmpty()) {
icon = icon.resize({ width: 20, height: 20 });
icon.setTemplateImage(true);
}
} catch {
icon = nativeImage.createEmpty();
}

View File

@@ -11,6 +11,84 @@
const { contextBridge, ipcRenderer } = require("electron");
const MAC_DRAG_STYLE_ID = "omniroute-electron-drag-region-style";
const MAC_DRAG_FALLBACK_ID = "omniroute-electron-drag-region";
const MAC_DRAG_OBSERVER_KEY = "__omnirouteMacDragRegionObserver";
function installMacDragRegion() {
if (process.platform !== "darwin") return;
const attach = () => {
if (!document.head || !document.body) return;
document.getElementById(MAC_DRAG_STYLE_ID)?.remove();
document.getElementById(MAC_DRAG_FALLBACK_ID)?.remove();
const style = document.createElement("style");
style.id = MAC_DRAG_STYLE_ID;
style.textContent = `
header,
.omniroute-electron-drag-region {
app-region: drag;
-webkit-app-region: drag;
user-select: none;
}
header a,
header button,
header input,
header select,
header textarea,
header [role="button"],
header [role="link"],
header [tabindex]:not([tabindex="-1"]) {
app-region: no-drag;
-webkit-app-region: no-drag;
}
.omniroute-electron-drag-region {
position: fixed;
top: 0;
left: 96px;
right: 180px;
height: 46px;
z-index: 9999;
}
`;
const dragRegion = document.createElement("div");
dragRegion.id = MAC_DRAG_FALLBACK_ID;
dragRegion.className = "omniroute-electron-drag-region";
dragRegion.setAttribute("aria-hidden", "true");
document.head.appendChild(style);
document.body.appendChild(dragRegion);
const syncDragFallback = () => {
const hasHeader = Boolean(document.querySelector("header"));
dragRegion.hidden = hasHeader;
if (hasHeader) observer.disconnect();
};
const previousObserver = window[MAC_DRAG_OBSERVER_KEY];
if (previousObserver) previousObserver.disconnect();
const observer = new MutationObserver(syncDragFallback);
observer.observe(document.body, { childList: true, subtree: true });
window[MAC_DRAG_OBSERVER_KEY] = observer;
window.setTimeout(() => observer.disconnect(), 5000);
window.addEventListener("pagehide", () => observer.disconnect(), { once: true });
syncDragFallback();
};
if (document.readyState === "loading") {
window.addEventListener("DOMContentLoaded", attach, { once: true });
} else {
attach();
}
}
installMacDragRegion();
// ── Channel Whitelist ──────────────────────────────────────
const VALID_CHANNELS = {
invoke: [

View File

@@ -10,6 +10,10 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const preloadSource = readFileSync(resolve(process.cwd(), "electron/preload.js"), "utf8");
// ─── Channel Whitelist Tests ─────────────────────────────────
@@ -179,6 +183,46 @@ describe("Preload Listener Disposer Pattern", () => {
});
});
// ─── macOS Drag Region Tests ─────────────────────────────────
describe("macOS Drag Region", () => {
it("should make the real header draggable when available", () => {
assert.match(preloadSource, /header,\s*\.omniroute-electron-drag-region/);
assert.match(preloadSource, /document\.querySelector\("header"\)/);
});
it("should preserve pointer events on header controls", () => {
for (const selector of ["a", "button", "input", "select", "textarea"]) {
assert.ok(preloadSource.includes(selector));
}
assert.match(preloadSource, /-webkit-app-region: no-drag/);
});
it("should use a moderate fallback layer", () => {
assert.match(preloadSource, /z-index: 9999/);
assert.match(preloadSource, /left: 96px/);
assert.match(preloadSource, /right: 180px/);
assert.ok(!preloadSource.includes("2147483647"));
});
it("should guard DOM attachment and replace prior injected elements", () => {
assert.match(preloadSource, /if \(!document\.head \|\| !document\.body\) return/);
assert.match(preloadSource, /getElementById\(MAC_DRAG_STYLE_ID\)\?\.remove\(\)/);
assert.match(preloadSource, /getElementById\(MAC_DRAG_FALLBACK_ID\)\?\.remove\(\)/);
});
it("should avoid modern CSS pseudo-classes for drag selectors", () => {
assert.ok(!preloadSource.includes(":is("));
assert.ok(!preloadSource.includes(":has("));
assert.match(preloadSource, /new MutationObserver\(syncDragFallback\)/);
});
it("should stop observing after the real header appears", () => {
assert.match(preloadSource, /if \(hasHeader\) observer\.disconnect\(\)/);
assert.match(preloadSource, /setTimeout\(\(\) => observer\.disconnect\(\), 5000\)/);
});
});
// ─── Generic Wrapper Tests (#16) ─────────────────────────────
describe("Generic IPC Wrappers", () => {