Skip to content

Commit 31a141f

Browse files
Make analysis edits per-token; drop global-edit confirmation
Gloss, morpheme, and morpheme-gloss edits now fork a shared TokenAnalysis before writing, so editing one token never rewrites its co-linked siblings. This matches the existing clear-path behavior and removes the blank-path asymmetry between token and morpheme gloss clears. Deletes the global-edit confirmation apparatus that the old fan-out model needed: GlobalEditConfirmationModal, the gateEdit/pendingEdit gate, the confirmGlobalEdits prop and demo toggle, and the now-unused forkAnalysisForToken action and selectApprovedLinkCountForPayload selector. Editing every occurrence of a shared analysis is deferred to a dedicated control; see user-questions.md "separating per-token edits from global analysis edits". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a62a223 commit 31a141f

15 files changed

Lines changed: 370 additions & 999 deletions

contributions/localizedStrings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"%interlinearizer_viewOption_showFreeTranslation%": "Show free translation",
3434
"%interlinearizer_projectSettings_showFreeTranslation%": "Show Free Translation",
3535
"%interlinearizer_projectSettings_showFreeTranslationDescription%": "Display a free-translation input beneath each segment's tokens or baseline text",
36-
"%interlinearizer_viewOption_confirmGlobalEdits%": "Confirm shared-analysis edits",
3736
"%interlinearizer_viewOption_showSuggestions%": "Show suggestions",
3837
"%interlinearizer_glossInput_placeholder%": "gloss",
3938
"%interlinearizer_freeTranslationInput_placeholder%": "Free translation",

src/__tests__/components/AnalysisStore.test.tsx

Lines changed: 92 additions & 287 deletions
Large diffs are not rendered by default.

src/__tests__/components/TokenChip.suggestions.test.tsx

Lines changed: 0 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import userEvent from '@testing-library/user-event';
1414
import type { TextAnalysis, Token, TokenAnalysis, TokenAnalysisLink } from 'interlinearizer';
1515
import { AnalysisStoreProvider } from '../../components/AnalysisStore';
1616
import { TokenChip } from '../../components/TokenChip';
17-
import { MorphemeGlossInput } from '../../components/MorphemeEditor';
1817
import { emptyAnalysis } from '../../types/empty-factories';
1918

2019
beforeEach(() => {
@@ -684,98 +683,3 @@ describe('TokenChip suggestion dropdown scrolling', () => {
684683
expect(screen.getByTestId('suggestion-accept')).toBeInTheDocument();
685684
});
686685
});
687-
688-
describe('held global-edit reverts the input draft', () => {
689-
// The confirmation modal needs its real strings (resetMocks wipes the file-level default).
690-
beforeEach(() => {
691-
jest.mocked(useLocalizedStrings).mockReturnValue([
692-
{
693-
'%interlinearizer_glossInput_placeholder%': 'gloss',
694-
'%interlinearizer_morphemeGloss_label%': 'Gloss for {form}',
695-
'%interlinearizer_globalEdit_title%': 'Used by {count}',
696-
'%interlinearizer_globalEdit_body%': 'Shared by {count}',
697-
'%interlinearizer_globalEdit_updateAll%': 'Update all {count}',
698-
'%interlinearizer_globalEdit_fork%': 'Make a separate analysis',
699-
'%interlinearizer_globalEdit_cancel%': 'Cancel',
700-
},
701-
false,
702-
]);
703-
});
704-
705-
it('reverts the gloss input draft when a shared-payload gloss edit is held for confirmation', async () => {
706-
const shared: TextAnalysis = {
707-
...emptyAnalysis(),
708-
tokenAnalyses: [{ id: 'shared', surfaceText: 'logos', gloss: { en: 'word' } }],
709-
tokenAnalysisLinks: [
710-
{
711-
analysisId: 'shared',
712-
status: 'approved',
713-
token: { tokenRef: 'tok-1', surfaceText: 'logos' },
714-
},
715-
{
716-
analysisId: 'shared',
717-
status: 'approved',
718-
token: { tokenRef: 'tok-2', surfaceText: 'logos' },
719-
},
720-
],
721-
};
722-
render(
723-
<AnalysisStoreProvider analysisLanguage="en" initialAnalysis={shared} confirmGlobalEdits>
724-
<TokenChip token={wordToken('tok-1', 'logos')} onFocus={() => {}} />
725-
</AnalysisStoreProvider>,
726-
);
727-
728-
const input = screen.getByLabelText('Gloss for logos');
729-
expect(input).toHaveValue('word');
730-
await userEvent.clear(input);
731-
await userEvent.type(input, 'changed');
732-
await userEvent.tab(); // blur holds the edit for the modal
733-
734-
expect(screen.getByTestId('global-edit-update-all')).toBeInTheDocument();
735-
// The abandoned draft is reverted to the committed gloss rather than stranded in the input.
736-
expect(input).toHaveValue('word');
737-
});
738-
739-
it('reverts a morpheme gloss input draft when a shared-payload edit is held for confirmation', async () => {
740-
const shared: TextAnalysis = {
741-
...emptyAnalysis(),
742-
tokenAnalyses: [
743-
{
744-
id: 'shared',
745-
surfaceText: 'cats',
746-
morphemes: [{ id: 'm1', form: 'cat', writingSystem: 'en' }],
747-
},
748-
],
749-
tokenAnalysisLinks: [
750-
{
751-
analysisId: 'shared',
752-
status: 'approved',
753-
token: { tokenRef: 'tok-1', surfaceText: 'cats' },
754-
},
755-
{
756-
analysisId: 'shared',
757-
status: 'approved',
758-
token: { tokenRef: 'tok-2', surfaceText: 'cats' },
759-
},
760-
],
761-
};
762-
render(
763-
<AnalysisStoreProvider analysisLanguage="en" initialAnalysis={shared} confirmGlobalEdits>
764-
<MorphemeGlossInput
765-
morpheme={{ id: 'm1', form: 'cat', writingSystem: 'en' }}
766-
tokenRef="tok-1"
767-
analysisLanguage="en"
768-
disabled={false}
769-
/>
770-
</AnalysisStoreProvider>,
771-
);
772-
773-
const input = screen.getByLabelText('Gloss for cat');
774-
await userEvent.type(input, 'feline');
775-
await userEvent.tab(); // blur holds the edit for the modal
776-
777-
expect(screen.getByTestId('global-edit-update-all')).toBeInTheDocument();
778-
// The abandoned morpheme gloss draft reverts to its (empty) committed value.
779-
expect(input).toHaveValue('');
780-
});
781-
});

src/__tests__/components/controls/ViewOptionsDropdown.test.tsx

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ const DEFAULT_PROPS = {
3131
onShowMorphologyChange: jest.fn(),
3232
showFreeTranslation: false,
3333
onShowFreeTranslationChange: jest.fn(),
34-
confirmGlobalEdits: false,
35-
onConfirmGlobalEditsChange: jest.fn(),
3634
showSuggestions: false,
3735
onShowSuggestionsChange: jest.fn(),
3836
};
@@ -284,31 +282,6 @@ describe('ViewOptionsDropdown', () => {
284282
});
285283
});
286284

287-
describe('confirm global edits toggle', () => {
288-
it('reflects the checked value', async () => {
289-
render(<ViewOptionsDropdown {...DEFAULT_PROPS} confirmGlobalEdits />);
290-
await userEvent.click(screen.getByTestId('view-options-button'));
291-
292-
expect(screen.getByRole('checkbox', { name: /confirmGlobalEdits/i })).toBeChecked();
293-
});
294-
295-
it('calls onConfirmGlobalEditsChange when toggled', async () => {
296-
const onConfirmGlobalEditsChange = jest.fn();
297-
render(
298-
<ViewOptionsDropdown
299-
{...DEFAULT_PROPS}
300-
confirmGlobalEdits={false}
301-
onConfirmGlobalEditsChange={onConfirmGlobalEditsChange}
302-
/>,
303-
);
304-
await userEvent.click(screen.getByTestId('view-options-button'));
305-
306-
await userEvent.click(screen.getByRole('checkbox', { name: /confirmGlobalEdits/i }));
307-
308-
expect(onConfirmGlobalEditsChange).toHaveBeenCalledWith(true);
309-
});
310-
});
311-
312285
describe('show suggestions toggle', () => {
313286
it('reflects the checked value', async () => {
314287
render(<ViewOptionsDropdown {...DEFAULT_PROPS} showSuggestions />);

src/__tests__/components/modals/GlobalEditConfirmationModal.test.tsx

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)