diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3fd201f..014ec1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,16 @@ popover, not for the person who wrote the commit.
## [Unreleased]
+### Fixed
+- **Importing a context whose rules match your current ones no longer reports a conflict.**
+ The check compared the imported and current rules as JSON text, and an export writes a
+ rule's fields in a different order than the app does - so any context with auto-switch
+ rules was flagged as conflicting with itself on every import, asking you to pick between
+ two copies of the same rule
+- **Export, import and confirmation dialogs no longer touch the window edges on a narrow
+ window.** Each had a maximum width but no side margin, so below about 670px wide the panel
+ ran edge to edge with no gap on either side
+
## [1.8.10] - 2026-09-27
### Changed
diff --git a/src/lib/components/ConfirmationDialog.svelte b/src/lib/components/ConfirmationDialog.svelte
index 576a3a4..6d6e739 100644
--- a/src/lib/components/ConfirmationDialog.svelte
+++ b/src/lib/components/ConfirmationDialog.svelte
@@ -113,7 +113,7 @@
{
+ it('ignores the order of a rule\'s fields', () => {
+ // What an export hands back: the same rule, keys sorted.
+ const exported = JSON.parse(
+ '{"matchCase":false,"matchType":"contains","ruleType":"title","useRegex":false,"value":"[coil]"}',
+ ) as ContextRule;
+ expect(sameRules([coil], [exported])).toBe(true);
+ });
+
+ it('treats a missing flag as false', () => {
+ const bare: ContextRule = { ruleType: 'title', value: '[coil]', matchType: 'contains' };
+ expect(sameRules([coil], [bare])).toBe(true);
+ });
+
+ it('treats a missing list as empty', () => {
+ expect(sameRules(undefined, [])).toBe(true);
+ expect(sameRules(undefined, [coil])).toBe(false);
+ });
+
+ it('still sees a real difference', () => {
+ expect(sameRules([coil], [{ ...coil, value: '[other]' }])).toBe(false);
+ expect(sameRules([coil], [{ ...coil, matchCase: true }])).toBe(false);
+ });
+
+ it('keeps the order of the rules significant', () => {
+ const other: ContextRule = { ...coil, ruleType: 'process', value: 'Code.exe' };
+ expect(sameRules([coil, other], [other, coil])).toBe(false);
+ });
+});
diff --git a/src/lib/utils/rules.ts b/src/lib/utils/rules.ts
new file mode 100644
index 0000000..7258351
--- /dev/null
+++ b/src/lib/utils/rules.ts
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Copyright (C) 2026 Nico Wiedemann
+//
+// This file is part of Stashpad.
+// Stashpad is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License, version 3,
+// as published by the Free Software Foundation.
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU Affero General Public License for more details.
+
+import type { ContextRule } from "$lib/types";
+
+/** One rule reduced to what it means, in a fixed shape. */
+function canonical(rule: ContextRule): string {
+ return JSON.stringify([
+ rule.ruleType,
+ rule.matchType,
+ rule.value,
+ rule.matchCase ?? false,
+ rule.useRegex ?? false,
+ ]);
+}
+
+/**
+ * Whether two rule lists do the same thing.
+ *
+ * Comparing their JSON text, which the import dialog used to do, counted the order of each
+ * rule's fields as a difference. An export writes them alphabetically - the Rust side goes
+ * through `serde_json::Value`, whose map is sorted - while the app keeps them in the order
+ * it builds them, so every context with rules came back as a conflict with itself. A
+ * missing `matchCase` or `useRegex` is also the same as `false`, which is what both sides
+ * treat it as. The order of the rules themselves is kept significant: it is the order they
+ * are shown and edited in.
+ */
+export function sameRules(
+ a: ContextRule[] | undefined,
+ b: ContextRule[] | undefined,
+): boolean {
+ const left = (a ?? []).map(canonical);
+ const right = (b ?? []).map(canonical);
+ return left.length === right.length && left.every((rule, i) => rule === right[i]);
+}