From 69df630b5b575bcd792257fed22f6c2dbbffc735 Mon Sep 17 00:00:00 2001 From: Noel Chou Date: Wed, 10 Jun 2026 13:47:59 -0400 Subject: [PATCH 1/2] TT-7380 fix: auto-save when editing Segment Reference in Mark Verse step Convert the autosave logic in PassageDetailMarkVerses from a useEffect (triggered by toolsChanged state) to a direct function call invoked on every cell/segment change. This ensures autosave fires immediately when the user edits a Segment Reference cell rather than waiting for a separate state update cycle. Also fix SnackBar open-state detection to use Fragment type-check instead of brittle 'span' type comparison. Co-Authored-By: Claude Sonnet 4.6 --- .../PassageDetailMarkVerses.test.tsx | 11 ++++++++++- .../PassageDetail/PassageDetailMarkVerses.tsx | 19 +++++++++++++------ src/renderer/src/hoc/SnackBar.tsx | 7 ++++--- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.test.tsx b/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.test.tsx index 8f09ff81..2f26ea73 100644 --- a/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.test.tsx +++ b/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.test.tsx @@ -537,15 +537,24 @@ test('blocks autosave when markup has hard reference errors', async () => { ?.firstChild as HTMLElement; await waitFor(() => expect(tbody.children.length).toBeTruthy()); + // Load saved segment data with an out-of-passage ref ("9:9") into the table. act(() => { if (mockPlayerAction) { mockPlayerAction( '{"regions":"[{\\"start\\":0,\\"end\\":5,\\"label\\":\\"9:9\\"}]"}', - false + true ); } }); + // Simulate a user boundary edit (init=false) so checkBlockersAndScheduleAutosave + // runs with "9:9" still in the table — the blocker should be detected and warned. + act(() => { + if (mockPlayerAction) { + mockPlayerAction('{"regions":"[{\\"start\\":0,\\"end\\":6}]"}', false); + } + }); + await waitFor(() => { expect(mockShowMessage).toHaveBeenCalledWith( expect.anything(), diff --git a/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.tsx b/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.tsx index d5dab1e8..43172f21 100644 --- a/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.tsx +++ b/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.tsx @@ -616,7 +616,12 @@ export function PassageDetailMarkVerses({ width }: MarkVersesProps) { if (reset) { resetSegments(regions); } - if (!init && !isChanged(verseToolId)) toolChanged(verseToolId); + if (!init) { + if (!isChanged(verseToolId)) { + toolChanged(verseToolId); + } + checkBlockersAndScheduleAutosave(); + } } }; @@ -740,7 +745,10 @@ export function PassageDetailMarkVerses({ width }: MarkVersesProps) { if (changed) { setData(newData); setSegments(); - toolChanged(verseToolId); + if (!isChanged(verseToolId)) { + toolChanged(verseToolId); + } + checkBlockersAndScheduleAutosave(); } }; @@ -845,8 +853,8 @@ export function PassageDetailMarkVerses({ width }: MarkVersesProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [toolsChanged, scheduleAutosave]); - useEffect(() => { - if (!isChanged(verseToolId) || !hasPermission || savingRef.current) return; + const checkBlockersAndScheduleAutosave = () => { + if (!hasPermission) return; const allIssues = checkRefs(); const blockers = checkAutosaveBlockers(); setSaveIssues(allIssues); @@ -885,8 +893,7 @@ export function PassageDetailMarkVerses({ width }: MarkVersesProps) { setIssuesDialogOpen(false); } scheduleAutosave(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [toolsChanged, hasPermission, scheduleAutosave]); + }; return Boolean(mediafileId) && passType !== PassageTypeEnum.NOTE ? ( diff --git a/src/renderer/src/hoc/SnackBar.tsx b/src/renderer/src/hoc/SnackBar.tsx index d0e713df..4ea0a88f 100644 --- a/src/renderer/src/hoc/SnackBar.tsx +++ b/src/renderer/src/hoc/SnackBar.tsx @@ -1,5 +1,5 @@ /* eslint-disable react-refresh/only-export-components */ -import { useState, useEffect } from 'react'; +import { useState, useEffect, Fragment } from 'react'; import { useGlobal } from '../context/useGlobal'; import { Snackbar as MuiSnackbar, @@ -45,8 +45,9 @@ function SimpleSnackbar(props: ISBProps) { }; useEffect(() => { - if ((message?.type === 'span') !== open) { - setOpen(!open); + const hasMessage = !!message && message.type !== Fragment; + if (hasMessage !== open) { + setOpen(hasMessage); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [message]); From 54c1a207662c54f8a6979b2064b3916f03cc3d9c Mon Sep 17 00:00:00 2001 From: Noel Chou Date: Thu, 11 Jun 2026 12:01:54 -0400 Subject: [PATCH 2/2] remove unnecessary isChanged check (code review) --- .../components/PassageDetail/PassageDetailMarkVerses.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.tsx b/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.tsx index 43172f21..e8f0fd93 100644 --- a/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.tsx +++ b/src/renderer/src/components/PassageDetail/PassageDetailMarkVerses.tsx @@ -185,7 +185,6 @@ export function PassageDetailMarkVerses({ width }: MarkVersesProps) { const { toolChanged, toolsChanged, - isChanged, saveRequested, saveCompleted, clearRequested, @@ -617,9 +616,7 @@ export function PassageDetailMarkVerses({ width }: MarkVersesProps) { resetSegments(regions); } if (!init) { - if (!isChanged(verseToolId)) { - toolChanged(verseToolId); - } + toolChanged(verseToolId); checkBlockersAndScheduleAutosave(); } } @@ -745,9 +742,7 @@ export function PassageDetailMarkVerses({ width }: MarkVersesProps) { if (changed) { setData(newData); setSegments(); - if (!isChanged(verseToolId)) { - toolChanged(verseToolId); - } + toolChanged(verseToolId); checkBlockersAndScheduleAutosave(); } };