Skip to content

Commit 59ee454

Browse files
Make overwrite more robust
1 parent 0b9f94c commit 59ee454

3 files changed

Lines changed: 70 additions & 3 deletions

File tree

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,36 @@ describe('ProjectModals', () => {
578578
expect(setModal).toHaveBeenCalledWith('none');
579579
});
580580

581+
it('reconciles the overwritten project metadata with the draft config', async () => {
582+
const draftWithConfig: DraftProject = {
583+
sourceProjectId: 'source-proj',
584+
analysisLanguages: ['sw', 'fr'],
585+
targetProjectId: 'tgt-9',
586+
analysis: emptyAnalysis(),
587+
dirty: true,
588+
};
589+
render(
590+
<ProjectModals
591+
{...buildProps({ modal: 'saveAs', getDraftSnapshot: () => draftWithConfig })}
592+
/>,
593+
);
594+
595+
await userEvent.click(screen.getByTestId('saveas-overwrite'));
596+
597+
// The project keeps its own name ('My Project') / description (undefined) but adopts the
598+
// draft's analysis languages and alignment target so its metadata matches the stored glosses.
599+
await waitFor(() =>
600+
expect(papi.commands.sendCommand).toHaveBeenCalledWith(
601+
'interlinearizer.updateProjectMetadata',
602+
'proj-1',
603+
'My Project',
604+
undefined,
605+
['sw', 'fr'],
606+
'tgt-9',
607+
),
608+
);
609+
});
610+
581611
it('does not crash when overwriting rejects', async () => {
582612
jest.mocked(papi.commands.sendCommand).mockRejectedValueOnce(new Error('boom'));
583613
const markSynced = jest.fn();

src/components/modals/ProjectModals.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,10 @@ export default function ProjectModals({
295295

296296
/**
297297
* Overwrites an existing project with the current draft: writes the draft's analysis into the
298-
* chosen project, makes it the active Save target, and clears the dirty flag. The backend
299-
* surfaces its own error notification; here we only log.
298+
* chosen project, reconciles the project's declared config (analysis languages / alignment
299+
* target) with the draft so the metadata matches the glosses now stored in it, makes it the
300+
* active Save target, and clears the dirty flag. The backend surfaces its own error notification;
301+
* here we only log.
300302
*
301303
* @param project - The existing project to overwrite.
302304
* @returns A promise that resolves once the overwrite completes or the failure has been handled.
@@ -312,7 +314,25 @@ export default function ProjectModals({
312314
project.id,
313315
JSON.stringify(snapshot.analysis),
314316
);
315-
setActiveProject(project);
317+
// Push the draft's analysis languages / alignment target onto the project so its declared
318+
// metadata stays consistent with the glosses just written (mirroring how Save As → New
319+
// carries the draft's config into the created project). The project's name and description
320+
// are intentionally preserved — overwriting keeps the target's identity.
321+
await papi.commands.sendCommand(
322+
'interlinearizer.updateProjectMetadata',
323+
project.id,
324+
project.name,
325+
project.description,
326+
snapshot.analysisLanguages,
327+
snapshot.targetProjectId,
328+
);
329+
setActiveProject({
330+
...project,
331+
analysisLanguages: snapshot.analysisLanguages,
332+
// Assign explicitly (rather than a conditional spread) so a target binding on the
333+
// overwritten project is cleared when the draft has none, matching what was persisted.
334+
targetProjectId: snapshot.targetProjectId,
335+
});
316336
markSynced(snapshot.analysis);
317337
setModal('none');
318338
} catch (e) {

user-questions.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,20 @@ Decisions made during development that we'd like reviewed:
6060
Save target, so a subsequent Save still writes the (now empty) draft to it. "Wipe Current Book"
6161
stays flagged as unsaved, since it is a partial edit the user will usually want to save. Is this
6262
split right, or should both wipes behave the same?
63+
64+
9. **Save As → Overwrite and the target's metadata.** "Save As → Overwrite an existing project"
65+
replaces that project's analysis with the draft's. The draft's _config_ (analysis languages and
66+
alignment target) can differ from the chosen project's — e.g. you Open project A (languages
67+
`[en]`), then Save As → Overwrite project B (languages `[fr]`). We currently push the draft's
68+
analysis languages and alignment target onto the overwritten project so its declared metadata
69+
matches the glosses now stored in it, while keeping the project's existing **name and
70+
description** (overwriting an existing named project keeps its identity). This mirrors how
71+
Save As → New carries the draft's config into the newly created project. Is this the right split?
72+
Options to review:
73+
- Should overwrite also adopt the draft's **name/description** (i.e. fully replace the target),
74+
or keep the target's identity as it does now?
75+
- Should overwrite instead be **analysis-only**, leaving the target's languages/target untouched
76+
(accepting that the stored glosses may then be tagged with languages the project doesn't
77+
declare)?
78+
- Should the Overwrite confirmation surface when the draft's languages differ from the target's,
79+
so the user is aware their language tags are about to change?

0 commit comments

Comments
 (0)