fix(overlay): prevent field grouping modal from freezing overlay on Hyprland (#138)

This commit is contained in:
2026-07-06 22:13:14 -07:00
committed by GitHub
parent 35ca2afc6f
commit a042b04357
27 changed files with 878 additions and 65 deletions
@@ -26,6 +26,7 @@ function createWorkflowHarness() {
const deleted: number[][] = [];
const addedTags: Array<{ noteIds: number[]; tags: string[] }> = [];
const statuses: string[] = [];
const osdMessages: string[] = [];
const rememberedMerges: Array<{ deletedNoteId: number; keptNoteId: number }> = [];
const mergeCalls: Array<{
keepNoteId: number;
@@ -112,7 +113,9 @@ function createWorkflowHarness() {
statuses.push(message);
},
showNotification: async () => undefined,
showOsdNotification: () => undefined,
showOsdNotification: (message: string) => {
osdMessages.push(message);
},
logError: () => undefined,
logInfo: () => undefined,
truncateSentence: (value: string) => value,
@@ -125,6 +128,7 @@ function createWorkflowHarness() {
addedTags,
rememberedMerges,
statuses,
osdMessages,
mergeCalls,
setManualChoice: (choice: typeof manualChoice) => {
manualChoice = choice;
@@ -191,6 +195,50 @@ test('FieldGroupingWorkflow manual mode returns false when callback unavailable'
assert.equal(harness.updates.length, 0);
});
test('FieldGroupingWorkflow manual cancel notifies exactly once', async () => {
const harness = createWorkflowHarness();
harness.setManualChoice({
keepNoteId: 0,
deleteNoteId: 0,
deleteDuplicate: true,
cancelled: true,
});
const handled = await harness.workflow.handleManual(1, 2, {
noteId: 2,
fields: {
Expression: { value: 'word-2' },
Sentence: { value: 'line-2' },
},
});
assert.equal(handled, false);
assert.deepEqual(harness.osdMessages, ['Field grouping cancelled']);
assert.equal(harness.updates.length, 0);
});
test('FieldGroupingWorkflow manual mode notifies when the original card cannot be loaded', async () => {
const harness = createWorkflowHarness();
harness.setManualChoice({
keepNoteId: 1,
deleteNoteId: 2,
deleteDuplicate: true,
cancelled: false,
});
harness.deps.client.notesInfo = async () => [];
const handled = await harness.workflow.handleManual(1, 2, {
noteId: 2,
fields: {
Expression: { value: 'word-2' },
Sentence: { value: 'line-2' },
},
});
assert.equal(handled, false);
assert.deepEqual(harness.osdMessages, ['Field grouping failed: original card not found']);
});
test('FieldGroupingWorkflow manual keep-new uses new note as merge target and old note as source', async () => {
const harness = createWorkflowHarness();
harness.setManualChoice({
@@ -98,6 +98,8 @@ export class FieldGroupingWorkflow {
const originalNotesInfoResult = await this.deps.client.notesInfo([originalNoteId]);
const originalNotesInfo = originalNotesInfoResult as FieldGroupingWorkflowNoteInfo[];
if (!originalNotesInfo || originalNotesInfo.length === 0) {
// handleManual owns all user-facing notifications; callers must not re-notify on false.
this.deps.showOsdNotification('Field grouping failed: original card not found');
return false;
}
+4 -2
View File
@@ -302,7 +302,7 @@ test('triggerFieldGroupingForLastAddedCard refreshes the card when configured fi
assert.deepEqual(harness.manualCalls, []);
});
test('triggerFieldGroupingForLastAddedCard shows a cancellation message when manual grouping is declined', async () => {
test('triggerFieldGroupingForLastAddedCard does not re-notify when manual grouping is declined', async () => {
const harness = createHarness({
kikuFieldGrouping: 'manual',
noteIds: [9],
@@ -339,7 +339,9 @@ test('triggerFieldGroupingForLastAddedCard shows a cancellation message when man
expression: 'word-9',
},
]);
assert.equal(harness.calls.at(-1), 'osd:Field grouping cancelled');
// The manual workflow already notifies about its outcome (cancelled/unavailable/failed);
// the trigger wrapper re-notifying produced two "Field grouping cancelled" toasts.
assert.equal(harness.calls.filter((call) => call === 'osd:Field grouping cancelled').length, 0);
});
test('buildFieldGroupingPreview returns merged compact and full previews', async () => {
+3 -4
View File
@@ -156,15 +156,14 @@ export class FieldGroupingService {
);
return;
}
const handled = await this.deps.handleFieldGroupingManual(
// The manual workflow owns all user-facing notifications for its outcomes (cancelled,
// unavailable, failed) — re-notifying on a false return here duplicated them.
await this.deps.handleFieldGroupingManual(
duplicateNoteId,
noteId,
noteInfo,
expressionText,
);
if (!handled) {
this.deps.showOsdNotification('Field grouping cancelled');
}
});
} catch (error) {
log.error('Error triggering field grouping:', (error as Error).message);