Files
familienarchiv/frontend/src/lib/document/transcription/blockConflictMerge.ts
Marcel 567612761d refactor: move lib-root files to lib/shared/ and finalize domain structure
- Move api.server.ts, errors.ts, types.ts, utils.ts, relativeTime.ts to lib/shared/
- Move person relationship components to lib/person/relationship/
- Move Stammbaum components to lib/person/genealogy/
- Move HelpPopover to lib/shared/primitives/
- Update all import paths across routes, specs, and lib files
- Update vi.mock() paths in server-project test files
- Remove now-empty legacy directories (components/, hooks/, server/, etc.)
- Update vite.config.ts coverage include paths for new structure
- Update frontend/CLAUDE.md to reflect domain-based lib/ layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:53:31 +02:00

51 lines
1.9 KiB
TypeScript

import type { PersonMention, TranscriptionBlockData } from '$lib/shared/types';
/**
* Sentinel thrown by saveBlockWithConflictRetry after a 409 rename-mid-edit
* has been merged into local state. Surfaces to the autosave hook as an
* error (so the UI shows the retry indicator), but distinguishable from a
* genuine network failure via the code. Carries the merged block snapshot
* on its `merged` property so the caller can update local state without
* a second roundtrip.
*/
export class BlockConflictResolvedError extends Error {
readonly code = 'CONFLICT_RESOLVED' as const;
merged?: TranscriptionBlockData;
constructor(blockId: string) {
super(
`Block ${blockId} was rebased onto the latest server snapshot — retry to save the merged result`
);
this.name = 'BlockConflictResolvedError';
}
}
type MergeArgs = {
serverBlock: TranscriptionBlockData;
localText: string;
localMentions: PersonMention[];
};
/**
* Resolves a 409-Conflict from the server by combining the latest server
* snapshot with the transcriber's unsaved local edits (B12b).
*
* Rules:
* - The transcriber's typed text always wins — never overwrite their input.
* - Server is the source of truth for the displayName of any person it
* knows about; renames that just landed on the server replace stale local
* names by personId.
* - Local-only mentions added since the last save are preserved.
* - All non-mention fields (version, sortOrder, reviewed, updatedAt, ...)
* come from the server snapshot so the next save sends the current
* revision and matches the latest persisted state.
*/
export function mergeBlockOnConflict(args: MergeArgs): TranscriptionBlockData {
const serverIds = new Set(args.serverBlock.mentionedPersons.map((m) => m.personId));
const localOnly = args.localMentions.filter((m) => !serverIds.has(m.personId));
return {
...args.serverBlock,
text: args.localText,
mentionedPersons: [...args.serverBlock.mentionedPersons, ...localOnly]
};
}