fix(window): stop macOS traffic lights jittering and fix local open from remote windows (#641)

AppKit puts the title bar container and window buttons back at their defaults on every live resize step, title change and fullscreen exit. They were corrected from window notifications after that layout had been drawn, so the buttons bounced between positions, or stayed at the defaults when no notification followed. The frames are now corrected in a main run loop observer ordered just before the Core Animation commit, project windows are created hidden on macOS so the buttons are placed before the first frame, and repeated title bar height updates are skipped.

Choosing This Window for a local project in a remote window was routed to the remote host or skipped when the path matched the remote root. open_project and light_edit_open now run on the local backend and leave remote mode once they succeed, project trust for these opens is checked locally, and a failed open cancels the pending close.
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-01 16:58:49 +08:00
1 джерело ec5193f827
коміт f6d7facd82
14 files changed
+215 -49

No files matched your search

+1 -1
Переглянути файл
@@ -49,7 +49,7 @@ export function startupLaunchHandled(): Promise<void> {
}
export async function openProject(path: string): Promise<ProjectInfo> {
await confirmProjectTrust(path);
await confirmProjectTrust(path, "local");
useProject.setState({ opening: true });
try {
const project = await call(commands.openProject(path));
+8 -4
Переглянути файл
@@ -1,16 +1,20 @@
import { IpcError } from "./ipc";
let prompt: ((path: string) => Promise<boolean>) | null = null;
export type ProjectTrustScope = "current" | "local";
export function provideProjectTrustPrompt(value: (path: string) => Promise<boolean>): () => void {
let prompt: ((path: string, scope: ProjectTrustScope) => Promise<boolean>) | null = null;
export function provideProjectTrustPrompt(
value: (path: string, scope: ProjectTrustScope) => Promise<boolean>,
): () => void {
prompt = value;
return () => {
if (prompt === value) prompt = null;
};
}
export async function confirmProjectTrust(path: string): Promise<void> {
if (prompt && !(await prompt(path))) {
export async function confirmProjectTrust(path: string, scope: ProjectTrustScope = "current"): Promise<void> {
if (prompt && !(await prompt(path, scope))) {
throw new IpcError({ kind: "cancelled", message: "Project opening cancelled" });
}
}
+6
Переглянути файл
@@ -72,6 +72,12 @@ async function clientSettings<T extends Settings>(settings: T): Promise<T> {
}
const route: InvokeRoute = (cmd, args, options) => {
if (cmd === "open_project" || cmd === "light_edit_open") {
return invokeLocal(cmd, args, options).then((result) => {
disableRemote();
return result;
});
}
const destination = remoteRouting(cmd, args, routed);
if (cmd === "settings_set") {
const setting = args as { scope: string; key: string; value: string };
+18 -1
Переглянути файл
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { CLIENT_SETTINGS_KEYS } from "./clientSettings";
import { commands, call, type Settings } from "./ipc";
import { loadSettings, useProject } from "./project";
import { connectRemote, disableRemote, enableRemote, remoteRouting } from "./remote";
import { connectRemote, disableRemote, enableRemote, isRemoteWindow, remoteRouting } from "./remote";
const handlers = vi.hoisted(() => new Map<string, () => void>());
vi.mock("./ipc", async (original) => ({
@@ -49,6 +49,23 @@ afterEach(() => {
});
describe("remote client settings", () => {
it("ends remote routing before a successful local open is published, even at the same path", async () => {
invoke.mockResolvedValueOnce({ root: "/remote", settings: local });
await call(commands.openProject("/remote"));
expect(isRemoteWindow()).toBe(false);
await loadSettings();
expect(useProject.getState().settings).toEqual(local);
expect(invoke.mock.calls.map(([cmd]) => cmd)).toEqual(["open_project", "settings_get"]);
});
it("preserves remote routing when opening a local project fails", async () => {
invoke.mockRejectedValueOnce({ kind: "notFound", message: "missing folder" });
await expect(call(commands.openProject("/missing"))).rejects.toThrow("missing folder");
expect(isRemoteWindow()).toBe(true);
await loadSettings();
expect(useProject.getState().settings?.editor?.tabSize).toBe(2);
});
it.each([...CLIENT_SETTINGS_KEYS, "customKeymaps.mine", "keymapOverrides.editor.copy", "appearance.theme"])(
"keeps %s writes local in either scope",
(key) => {