import * as m from '$lib/paraglide/messages.js'; import type { components } from '$lib/generated/api'; type TimelineEntryDTO = components['schemas']['TimelineEntryDTO']; /** Styling discriminant for an axis pill/band. */ export type TimelineAccent = 'derived' | 'curated' | 'historical'; export interface AccentConfig { /** Visible Unicode glyph — render `aria-hidden`, paired with an sr-only label. */ glyph: string; /** Localized layer/life-event label — used as the sr-only / aria text only. */ label: string; accent: TimelineAccent; } /** * Maps a timeline EVENT entry to its glyph, redundant non-color label, and accent * (REQ-007/008/018). Derived life-events use the * / † / ⚭ glyphs that match * `personLifeDates.ts`; HISTORICAL events get the muted world band; everything * else (curated PERSONAL) gets the mint family pill. */ export function getAccentConfig(entry: TimelineEntryDTO): AccentConfig { if (entry.derived) { switch (entry.derivedType) { case 'BIRTH': return { glyph: '*', label: m.timeline_derived_birth(), accent: 'derived' }; case 'DEATH': return { glyph: '†', label: m.timeline_derived_death(), accent: 'derived' }; case 'MARRIAGE': return { glyph: '⚭', label: m.timeline_derived_marriage(), accent: 'derived' }; } } if (entry.type === 'HISTORICAL') { return { glyph: '◍', label: m.timeline_layer_world(), accent: 'historical' }; } return { glyph: '★', label: m.timeline_layer_family(), accent: 'curated' }; } /** * The curator edit-affordance gate, in one place — the security-relevant contract documented on * CLAUDE.md's `TimelineEntryDTO` row (`derived || eventId == null` → no edit link). A curated * event's edit pencil shows only for a viewer with WRITE_ALL (`canWrite`), and only when it is a * real curated event: never a derived life-event (nothing to edit) and never a null `eventId`. * HISTORICAL events are never derived, so this also covers the world band. The gate is UX only — * the #781 route guard + backend permission are the real boundary. Shared by EventPill, WorldBand, * and EventCluster so the gate has a single source of truth (#850 finding #5). */ export function canEditEvent(entry: TimelineEntryDTO, canWrite: boolean): boolean { return canWrite && !entry.derived && entry.eventId != null; }