feat(person): migrate birth/death year to LocalDate + DatePrecision (#773) #812

Merged
marcel merged 16 commits from feat/issue-773-person-birth-death-localdate into main 2026-06-12 21:49:17 +02:00
6 changed files with 27 additions and 10 deletions
Showing only changes of commit 82af906608 - Show all commits

View File

@@ -2381,6 +2381,14 @@ export interface components {
documentCount?: number; documentCount?: number;
alias?: string; alias?: string;
notes?: 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; personType?: string;
familyMember?: boolean; familyMember?: boolean;
provisional?: boolean; provisional?: boolean;
@@ -2490,10 +2498,10 @@ export interface components {
/** Format: int32 */ /** Format: int32 */
number?: number; number?: number;
sort?: components["schemas"]["SortObject"]; sort?: components["schemas"]["SortObject"];
first?: boolean;
last?: boolean;
/** Format: int32 */ /** Format: int32 */
numberOfElements?: number; numberOfElements?: number;
first?: boolean;
last?: boolean;
empty?: boolean; empty?: boolean;
}; };
PageableObject: { PageableObject: {

View File

@@ -15,7 +15,9 @@ import { m } from '$lib/paraglide/messages.js';
// — see Felix #3 on PR #629. // — see Felix #3 on PR #629.
import { MAX_QUERY_LENGTH } from './mentionConstants'; 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 // The dropdown receives a single reactive state object. PersonMentionEditor
// mutates fields on this object (model.items = ..., etc.) and Svelte's $state // mutates fields on this object (model.items = ..., etc.) and Svelte's $state

View File

@@ -7,7 +7,9 @@ import MentionDropdownFixture from './MentionDropdown.test-fixture.svelte';
import { m } from '$lib/paraglide/messages.js'; import { m } from '$lib/paraglide/messages.js';
import type { components } from '$lib/generated/api'; 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); afterEach(cleanup);

View File

@@ -3,7 +3,7 @@ import { untrack } from 'svelte';
import MentionDropdown from './MentionDropdown.svelte'; import MentionDropdown from './MentionDropdown.svelte';
import type { components } from '$lib/generated/api'; import type { components } from '$lib/generated/api';
type Person = components['schemas']['Person']; type Person = components['schemas']['PersonSummaryDTO'];
type DropdownState = { type DropdownState = {
items: Person[]; items: Person[];
command: (item: Person) => void; command: (item: Person) => void;

View File

@@ -12,7 +12,9 @@ import MentionDropdown from './MentionDropdown.svelte';
import { createMentionNodeView } from './mentionNodeView'; import { createMentionNodeView } from './mentionNodeView';
import { MAX_QUERY_LENGTH, SEARCH_DEBOUNCE_MS, SEARCH_RESULT_LIMIT } from './mentionConstants'; 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 = { type Props = {
value: string; value: string;
@@ -216,14 +218,15 @@ const controller = createMentionController();
// reflected data-person-id or the search input). // reflected data-person-id or the search input).
function commitRelink(pos: number): CommitFn { function commitRelink(pos: number): CommitFn {
return (item: Person) => { return (item: Person) => {
if (!editor) return; if (!editor || !item.id) return;
const personId = item.id;
editor editor
.chain() .chain()
.focus() .focus()
.command(({ tr, state }) => { .command(({ tr, state }) => {
const node = state.doc.nodeAt(pos); const node = state.doc.nodeAt(pos);
if (!node || node.type.name !== 'mention') return false; 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; return true;
}) })
.run(); .run();
@@ -364,9 +367,11 @@ onMount(() => {
render() { render() {
const buildFreshCommit = (loose: LooseRenderProps): CommitFn => { const buildFreshCommit = (loose: LooseRenderProps): CommitFn => {
const clippedQuery = loose.query.slice(0, MAX_QUERY_LENGTH); 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 }); loose.command({ personId: item.id, displayName: clippedQuery });
}; };
};
return { return {
onStart(renderProps) { onStart(renderProps) {
const loose = renderProps as unknown as LooseRenderProps; const loose = renderProps as unknown as LooseRenderProps;

View File

@@ -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. // module so the test cannot drift from production. Sara on PR #629 round 3.
import { SEARCH_DEBOUNCE_MS } from './mentionConstants'; import { SEARCH_DEBOUNCE_MS } from './mentionConstants';
type Person = components['schemas']['Person']; type Person = components['schemas']['PersonSummaryDTO'];
type PersonMention = components['schemas']['PersonMention']; type PersonMention = components['schemas']['PersonMention'];
/** /**