feat(transcription): swap plain textarea for PersonMentionEditor and thread mentionedPersons through autosave

- TranscriptionBlockData now carries mentionedPersons (matches backend
  schema added in PR-A).
- useBlockAutoSave.saveFn signature widens to (blockId, text, mentions);
  pendingMentions is tracked alongside pendingTexts and is preserved on
  failure so a retry resends the in-flight payload (B12).
- TranscriptionBlock.svelte renders <PersonMentionEditor>, exposing the
  textarea node back through a captureTextarea callback so the existing
  quote-selection feature still works.
- saveBlock in routes/documents/[id]/+page.svelte forwards mentions on
  PUT.
- flushOnUnload sends mentions in the keepalive payload too.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-29 00:32:09 +02:00
parent c4ee2c666b
commit 02d3e2ab61
11 changed files with 207 additions and 78 deletions

View File

@@ -16,6 +16,10 @@ type Props = {
disabled?: boolean;
onfocus?: () => void;
onblur?: () => void;
// Optional escape hatch: lets the parent observe the underlying textarea node
// (e.g. to read selection bounds for quote-selection features). Returning a
// cleanup function from the parent is not required.
captureTextarea?: (node: HTMLTextAreaElement) => void | (() => void);
};
let {
@@ -25,7 +29,8 @@ let {
rows = 1,
disabled = false,
onfocus,
onblur
onblur,
captureTextarea
}: Props = $props();
let query: string | null = $state(null);
@@ -38,7 +43,9 @@ let debounceTimer: ReturnType<typeof setTimeout> | undefined;
function attachTextarea(node: HTMLTextAreaElement) {
textarea = node;
const parentCleanup = captureTextarea?.(node);
return () => {
parentCleanup?.();
textarea = null;
};
}