fix(hover-card): maiden name false positive, placeholder on non-empty editor, card persistence

- PersonHoverCard: alias is compared against both `lastName` and `displayName`
  before showing as maiden name — prevents false positive when alias is stored
  as the full current name (e.g. "Maria Schmidt" ≠ "Schmidt" but name unchanged)
- PersonMentionEditor: data-placeholder was set statically so the CSS ::before
  rule showed the placeholder on any blur even with content; now a $effect
  toggles the attribute based on editor.isEmpty
- TranscriptionReadView: hovering onto the card itself cancels the 150ms close
  timer so the card stays open while reading it; leaving the card closes it
  immediately — onmouseenter/onmouseleave wired through PersonHoverCard props
- hoverCardPosition: removed scrollX/scrollY offset since the card is now
  position:fixed (scroll is already baked into getBoundingClientRect coords)
- MentionDropdown: raised z-index from z-20 to z-50 to render above the hover card
- vite.config.ts: pre-bundle Tiptap packages to avoid HMR waterfall on first load

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-29 18:26:44 +02:00
parent 49443ad16a
commit 37edac4da6
7 changed files with 97 additions and 74 deletions

View File

@@ -93,13 +93,28 @@ function getOrFetchHoverData(personId: string): Promise<HoverData | null> {
function currentViewport() {
return {
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight,
scrollX: window.scrollX,
scrollY: window.scrollY
viewportHeight: window.innerHeight
};
}
let closeTimer: ReturnType<typeof setTimeout> | null = null;
function scheduleCardClose() {
closeTimer = setTimeout(() => {
activeCard = null;
closeTimer = null;
}, 150);
}
function cancelCardClose() {
if (closeTimer !== null) {
clearTimeout(closeTimer);
closeTimer = null;
}
}
async function handleMentionEnter(event: Event) {
cancelCardClose();
const link = event.target as HTMLAnchorElement;
const personId = link.dataset.personId;
if (!personId) return;
@@ -140,7 +155,11 @@ async function handleMentionEnter(event: Event) {
function handleMentionLeave(event: Event) {
const link = event.target as HTMLAnchorElement;
link.removeAttribute('aria-describedby');
activeCard = null;
if (event.type === 'mouseleave') {
scheduleCardClose();
} else {
activeCard = null;
}
}
/**
@@ -231,6 +250,8 @@ function attachMentionHandlers(node: HTMLElement) {
cardId={activeCard.cardId}
position={activeCard.position}
state={activeCard.state}
onmouseenter={cancelCardClose}
onmouseleave={() => { activeCard = null; }}
/>
{/if}