From 94d7d8099f215d3833f15fbf6342c332bb67c665 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 13 Jun 2026 23:00:18 +0200 Subject: [PATCH] fix(types): make DocumentOption precision fields optional; narrow spec data access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DocumentOption's metaDatePrecision/metaDateEnd are now optional so a TimelineEvent DocumentRef (id/title/documentDate only) maps cleanly into a picker chip — formatDocumentOption already degrades gracefully when precision is absent. The server specs read fail()'s union data via a small failData() cast that TS cannot narrow. svelte-check shows zero new errors in the #781 files. Refs #781 Co-Authored-By: Claude Opus 4.8 --- frontend/src/lib/document/documentTypeahead.ts | 12 ++++++++---- .../events/[id]/edit/page.server.spec.ts | 7 +++++-- .../zeitstrahl/events/new/page.server.spec.ts | 15 +++++++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/frontend/src/lib/document/documentTypeahead.ts b/frontend/src/lib/document/documentTypeahead.ts index a5274ad3..1b32fa78 100644 --- a/frontend/src/lib/document/documentTypeahead.ts +++ b/frontend/src/lib/document/documentTypeahead.ts @@ -5,10 +5,14 @@ import { getLocale } from '$lib/paraglide/runtime.js'; type DocumentListItem = components['schemas']['DocumentListItem']; -export type DocumentOption = Pick< - DocumentListItem, - 'id' | 'title' | 'documentDate' | 'metaDatePrecision' | 'metaDateEnd' ->; +/** + * Chip/dedup contract for document pickers. `metaDatePrecision`/`metaDateEnd` + * are optional: the typeahead always populates them, but a TimelineEvent's + * DocumentRef (#781) carries only id/title/documentDate — formatDocumentOption + * degrades gracefully (bare title or plain date) when precision is absent. + */ +export type DocumentOption = Pick & + Partial>; export function createDocumentTypeahead() { return createTypeahead({ diff --git a/frontend/src/routes/zeitstrahl/events/[id]/edit/page.server.spec.ts b/frontend/src/routes/zeitstrahl/events/[id]/edit/page.server.spec.ts index 8ae60729..652be161 100644 --- a/frontend/src/routes/zeitstrahl/events/[id]/edit/page.server.spec.ts +++ b/frontend/src/routes/zeitstrahl/events/[id]/edit/page.server.spec.ts @@ -10,6 +10,9 @@ import { load, actions } from './+page.server'; const mockFetch = vi.fn() as unknown as typeof fetch; +// fail() returns a union type that TS won't narrow; read its data loosely. +const failData = (r: unknown) => (r as { data: Record }).data; + beforeEach(() => vi.clearAllMocks()); function localsWith(perms: string[] | null) { @@ -125,7 +128,7 @@ describe('zeitstrahl/events/[id]/edit save action (REQ-005/013)', () => { actionEvent('save', { title: 'Umzug', type: 'PERSONAL', eventDate: '1925-04-01' }) ); expect(result).toMatchObject({ status: 409 }); - expect(result.data.error).toBeTruthy(); + expect(failData(result).error).toBeTruthy(); }); }); @@ -150,6 +153,6 @@ describe('zeitstrahl/events/[id]/edit delete action (REQ-006/007)', () => { const result = await actions.delete(actionEvent('delete', {})); expect(result).toMatchObject({ status: 500 }); - expect(result.data.error).toBeTruthy(); + expect(failData(result).error).toBeTruthy(); }); }); diff --git a/frontend/src/routes/zeitstrahl/events/new/page.server.spec.ts b/frontend/src/routes/zeitstrahl/events/new/page.server.spec.ts index accb4ae2..d7aaa331 100644 --- a/frontend/src/routes/zeitstrahl/events/new/page.server.spec.ts +++ b/frontend/src/routes/zeitstrahl/events/new/page.server.spec.ts @@ -10,6 +10,9 @@ import { load, actions } from './+page.server'; const mockFetch = vi.fn() as unknown as typeof fetch; +// fail() returns a union type that TS won't narrow; read its data loosely. +const failData = (r: unknown) => (r as { data: Record }).data; + beforeEach(() => vi.clearAllMocks()); function localsWith(perms: string[] | null) { @@ -142,16 +145,16 @@ describe('zeitstrahl/events/new save action (REQ-004/009/010/015)', () => { expect(post).not.toHaveBeenCalled(); expect(result).toMatchObject({ status: 400 }); - expect(result.data.personIds).toEqual(['p1', 'p2']); - expect(result.data.documentIds).toEqual(['d1']); - expect(result.data.titleError).toBeTruthy(); + expect(failData(result).personIds).toEqual(['p1', 'p2']); + expect(failData(result).documentIds).toEqual(['d1']); + expect(failData(result).titleError).toBeTruthy(); }); it('surfaces both title and date errors when both blank (REQ-011)', async () => { vi.mocked(createApiClient).mockReturnValue({ POST: vi.fn() } as never); const result = await actions.save(saveEvent({ title: '', type: 'PERSONAL', eventDate: '' })); - expect(result.data.titleError).toBeTruthy(); - expect(result.data.dateError).toBeTruthy(); + expect(failData(result).titleError).toBeTruthy(); + expect(failData(result).dateError).toBeTruthy(); }); it('redirects to /persons/{id} when originPersonId is a valid UUID', async () => { @@ -201,6 +204,6 @@ describe('zeitstrahl/events/new save action (REQ-004/009/010/015)', () => { saveEvent({ title: 'Umzug', type: 'PERSONAL', eventDate: '1925-04-01' }) ); expect(result).toMatchObject({ status: 409 }); - expect(result.data.error).toBeTruthy(); + expect(failData(result).error).toBeTruthy(); }); });