From 82af906608450dbb03eb955b9bf02f46255e6dce Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 12 Jun 2026 19:27:13 +0200 Subject: [PATCH] fix(person): type mention items as PersonSummaryDTO, regenerate api The dropdown and editor typed /api/persons list items as the full Person entity. The actual wire shape is PersonSummaryDTO, which until the previous commit had no date fields - so the life-date subtitle rendered blank in production while fixtures (built from the entity type) kept the tests green. Retype items as the summary projection and guard the two personId consumers against the schema-optional id. Co-Authored-By: Claude Fable 5 --- frontend/src/lib/generated/api.ts | 12 ++++++++++-- .../lib/shared/discussion/MentionDropdown.svelte | 4 +++- .../discussion/MentionDropdown.svelte.test.ts | 4 +++- .../discussion/MentionDropdown.test-fixture.svelte | 2 +- .../shared/discussion/PersonMentionEditor.svelte | 13 +++++++++---- .../discussion/PersonMentionEditor.svelte.spec.ts | 2 +- 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/frontend/src/lib/generated/api.ts b/frontend/src/lib/generated/api.ts index 5c3f05a3..44151d3d 100644 --- a/frontend/src/lib/generated/api.ts +++ b/frontend/src/lib/generated/api.ts @@ -2381,6 +2381,14 @@ export interface components { documentCount?: number; alias?: string; notes?: string; + /** Format: date */ + birthDate?: string; + /** @enum {string} */ + birthDatePrecision?: "DAY" | "MONTH" | "SEASON" | "YEAR" | "RANGE" | "APPROX" | "UNKNOWN"; + /** Format: date */ + deathDate?: string; + /** @enum {string} */ + deathDatePrecision?: "DAY" | "MONTH" | "SEASON" | "YEAR" | "RANGE" | "APPROX" | "UNKNOWN"; personType?: string; familyMember?: boolean; provisional?: boolean; @@ -2490,10 +2498,10 @@ export interface components { /** Format: int32 */ number?: number; sort?: components["schemas"]["SortObject"]; - first?: boolean; - last?: boolean; /** Format: int32 */ numberOfElements?: number; + first?: boolean; + last?: boolean; empty?: boolean; }; PageableObject: { diff --git a/frontend/src/lib/shared/discussion/MentionDropdown.svelte b/frontend/src/lib/shared/discussion/MentionDropdown.svelte index 0dd6a0b7..2185ad12 100644 --- a/frontend/src/lib/shared/discussion/MentionDropdown.svelte +++ b/frontend/src/lib/shared/discussion/MentionDropdown.svelte @@ -15,7 +15,9 @@ import { m } from '$lib/paraglide/messages.js'; // — see Felix #3 on PR #629. import { MAX_QUERY_LENGTH } from './mentionConstants'; -type Person = components['schemas']['Person']; +// PersonSummaryDTO, not Person: /api/persons list items are the summary projection. +// Typing them as the full entity hid a runtime bug (missing date fields, #812). +type Person = components['schemas']['PersonSummaryDTO']; // The dropdown receives a single reactive state object. PersonMentionEditor // mutates fields on this object (model.items = ..., etc.) and Svelte's $state diff --git a/frontend/src/lib/shared/discussion/MentionDropdown.svelte.test.ts b/frontend/src/lib/shared/discussion/MentionDropdown.svelte.test.ts index a73dcf93..74976e6b 100644 --- a/frontend/src/lib/shared/discussion/MentionDropdown.svelte.test.ts +++ b/frontend/src/lib/shared/discussion/MentionDropdown.svelte.test.ts @@ -7,7 +7,9 @@ import MentionDropdownFixture from './MentionDropdown.test-fixture.svelte'; import { m } from '$lib/paraglide/messages.js'; import type { components } from '$lib/generated/api'; -type Person = components['schemas']['Person']; +// PersonSummaryDTO mirrors the real runtime shape: /api/persons list items are +// the summary projection, not the full entity (#812). +type Person = components['schemas']['PersonSummaryDTO']; afterEach(cleanup); diff --git a/frontend/src/lib/shared/discussion/MentionDropdown.test-fixture.svelte b/frontend/src/lib/shared/discussion/MentionDropdown.test-fixture.svelte index 4a69d9e5..e0705f0b 100644 --- a/frontend/src/lib/shared/discussion/MentionDropdown.test-fixture.svelte +++ b/frontend/src/lib/shared/discussion/MentionDropdown.test-fixture.svelte @@ -3,7 +3,7 @@ import { untrack } from 'svelte'; import MentionDropdown from './MentionDropdown.svelte'; import type { components } from '$lib/generated/api'; -type Person = components['schemas']['Person']; +type Person = components['schemas']['PersonSummaryDTO']; type DropdownState = { items: Person[]; command: (item: Person) => void; diff --git a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte index 274f97ce..d9059c62 100644 --- a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte +++ b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte @@ -12,7 +12,9 @@ import MentionDropdown from './MentionDropdown.svelte'; import { createMentionNodeView } from './mentionNodeView'; import { MAX_QUERY_LENGTH, SEARCH_DEBOUNCE_MS, SEARCH_RESULT_LIMIT } from './mentionConstants'; -type Person = components['schemas']['Person']; +// PersonSummaryDTO, not Person: /api/persons list items are the summary projection. +// Typing them as the full entity hid a runtime bug (missing date fields, #812). +type Person = components['schemas']['PersonSummaryDTO']; type Props = { value: string; @@ -216,14 +218,15 @@ const controller = createMentionController(); // reflected data-person-id or the search input). function commitRelink(pos: number): CommitFn { return (item: Person) => { - if (!editor) return; + if (!editor || !item.id) return; + const personId = item.id; editor .chain() .focus() .command(({ tr, state }) => { const node = state.doc.nodeAt(pos); if (!node || node.type.name !== 'mention') return false; - tr.setNodeMarkup(pos, undefined, { ...node.attrs, personId: item.id }); + tr.setNodeMarkup(pos, undefined, { ...node.attrs, personId }); return true; }) .run(); @@ -364,8 +367,10 @@ onMount(() => { render() { const buildFreshCommit = (loose: LooseRenderProps): CommitFn => { const clippedQuery = loose.query.slice(0, MAX_QUERY_LENGTH); - return (item: Person) => + return (item: Person) => { + if (!item.id) return; loose.command({ personId: item.id, displayName: clippedQuery }); + }; }; return { onStart(renderProps) { diff --git a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts index cbc06aa7..d0f3912a 100644 --- a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts +++ b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts @@ -16,7 +16,7 @@ import { m } from '$lib/paraglide/messages.js'; // module so the test cannot drift from production. Sara on PR #629 round 3. import { SEARCH_DEBOUNCE_MS } from './mentionConstants'; -type Person = components['schemas']['Person']; +type Person = components['schemas']['PersonSummaryDTO']; type PersonMention = components['schemas']['PersonMention']; /**