fix(ui): keep main window closable after a native dialog on Windows (#6319)

A native Windows MessageBox attached to a parent window disables that
window (WS_DISABLED) for its lifetime and re-enables it on dismissal.
When the parent is the main window — whose WindowClosing hook hides
instead of closes — the enable/hide sequence races and leaves the window
unable to process its close (X) button afterwards, so e.g. a rejected
login error dialog left the main window stuck open.

Route all native dialogs through src/lib/dialogs.ts, which forces
Detached: true on Windows (NULL owner, no window ever disabled) and is a
no-op on macOS/Linux (keeps the attached sheet-style presentation).
This commit is contained in:
Zoltan Papp
2026-06-01 17:03:53 +02:00
committed by GitHub
parent 49df24b18c
commit 53d43980ad
13 changed files with 80 additions and 32 deletions
@@ -1,6 +1,6 @@
import { forwardRef, useLayoutEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Dialogs } from "@wailsio/runtime";
import { errorDialog } from "@/lib/dialogs.ts";
import * as Popover from "@radix-ui/react-popover";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { Command } from "cmdk";
@@ -36,7 +36,7 @@ export const ProfileDropdown = ({ onManageProfiles }: ProfileDropdownProps) => {
try {
await fn();
} catch (e) {
await Dialogs.Error({
await errorDialog({
Title: title,
Message: formatErrorMessage(e),
});
@@ -1,6 +1,6 @@
import { useLayoutEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Dialogs } from "@wailsio/runtime";
import { errorDialog, warningDialog } from "@/lib/dialogs.ts";
import { CircleMinus, PlusCircle, Trash2, UserCircle } from "lucide-react";
import type { Profile } from "@bindings/services/models.js";
import { Badge } from "@/components/Badge";
@@ -44,7 +44,7 @@ export function ProfilesTab() {
try {
await fn();
} catch (e) {
await Dialogs.Error({
await errorDialog({
Title: title,
Message: formatErrorMessage(e),
});
@@ -56,7 +56,7 @@ export function ProfilesTab() {
const handleDeregister = async (name: string) => {
const cancelLabel = i18next.t("common.cancel");
const confirmLabel = i18next.t("profile.deregister.confirm");
const result = await Dialogs.Warning({
const result = await warningDialog({
Title: i18next.t("profile.deregister.title"),
Message: i18next.t("profile.deregister.message", { name }),
Buttons: [
@@ -72,7 +72,7 @@ export function ProfilesTab() {
if (name === DEFAULT_PROFILE) return;
const cancelLabel = i18next.t("common.cancel");
const confirmLabel = i18next.t("common.delete");
const result = await Dialogs.Warning({
const result = await warningDialog({
Title: i18next.t("profile.delete.title"),
Message: i18next.t("profile.delete.message", { name }),
Buttons: [
@@ -89,7 +89,7 @@ export function ProfilesTab() {
await addProfile(name);
await switchProfile(name);
} catch (e) {
await Dialogs.Error({
await errorDialog({
Title: i18next.t("profile.error.createTitle"),
Message: formatErrorMessage(e),
});