refactor: move document transcription, annotation, viewer sub-packages

- transcription/: TranscriptionBlock, Column, EditView, PanelHeader, ReadView,
  Section + transcriptionMarkers, blockConflictMerge, saveBlockWithConflictRetry
  + useBlockAutoSave, useBlockDragDrop hooks
- annotation/: AnnotationLayer, AnnotationShape, AnnotationEditOverlay
- viewer/: PdfViewer, PdfControls + useFileLoader, usePdfRenderer hooks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-05 14:01:39 +02:00
parent e7f8aa5894
commit 1e656d2db4
43 changed files with 32 additions and 27 deletions

View File

@@ -0,0 +1,50 @@
import type { PersonMention, TranscriptionBlockData } from '$lib/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]
};
}