mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 14:22:09 +03:00
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:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user