feat(person): add notes / biography field #23

Closed
opened 2026-03-19 21:14:20 +01:00 by marcel · 0 comments
Owner

User Journey

The family archivist is digitising the archive and knows rich biographical context for many persons — professions, places of residence, historical events they lived through. Currently there is nowhere to record this; it exists only in a separate Word document that nobody else can see.

With this feature, the archivist opens Heinrich's edit form, types a short biography into the "Notizen" textarea, and saves. Future visitors to Heinrich's page see this context directly, grounding the documents in a human story.


High-Level Plan

Add an optional notes text column to the persons table. Expose it through the existing update endpoint and edit form. Display it in view mode only when non-empty.

Layers touched: Flyway migration → JPA entity → DTO → OpenAPI types → frontend form + display.


Detailed Plan

Backend

  1. Migration V{n}__add_person_notes.sql:

    ALTER TABLE persons ADD COLUMN notes TEXT;
    
  2. Person entity:

    @Schema(description = "Biographical notes")
    private String notes;
    
  3. PersonUpdateDTO:

    private String notes;
    
  4. PersonService.update() — map notes from DTO to entity (treat empty string as null).

  5. Rebuild JAR, run npm run generate:api.

Frontend

  1. Edit form — add a textarea at the bottom of the edit section in persons/[id]/+page.svelte:

    <div class="md:col-span-2">
        <label for="notes" class="block text-xs font-bold uppercase tracking-widest text-gray-400 mb-1">
            {m.form_label_notes()}
        </label>
        <textarea
            id="notes"
            name="notes"
            rows="4"
            value={person.notes ?? ''}
            class="block w-full border border-gray-300 rounded px-3 py-2 font-serif text-brand-navy focus:outline-none focus:border-brand-navy resize-y"
        ></textarea>
    </div>
    
  2. View mode — render a "Notizen" card below the person header, only when person.notes is non-empty:

    {#if person.notes}
    <div class="bg-white shadow-sm border border-brand-sand rounded-sm p-6 mb-6">
        <h2 class="text-xs font-bold uppercase tracking-widest text-gray-400 mb-3">{m.person_notes_heading()}</h2>
        <p class="font-serif text-brand-navy whitespace-pre-wrap">{person.notes}</p>
    </div>
    {/if}
    
  3. +page.server.ts update action — read and forward notes from form data.

  4. i18n keys:

    • form_label_notes: DE "Notizen" / EN "Notes" / ES "Notas"
    • person_notes_heading: DE "Biographische Notizen" / EN "Biographical notes" / ES "Notas biográficas"

Acceptance Criteria

  • Notes field is optional — saving with empty notes clears the field
  • Notes display with preserved line breaks (whitespace-pre-wrap)
  • Notes section is hidden entirely when empty
  • Notes survive a round-trip (save → reload → same content)
  • Existing persons are unaffected (null notes = hidden section)
## User Journey The family archivist is digitising the archive and knows rich biographical context for many persons — professions, places of residence, historical events they lived through. Currently there is nowhere to record this; it exists only in a separate Word document that nobody else can see. With this feature, the archivist opens Heinrich's edit form, types a short biography into the **"Notizen"** textarea, and saves. Future visitors to Heinrich's page see this context directly, grounding the documents in a human story. --- ## High-Level Plan Add an optional `notes` text column to the `persons` table. Expose it through the existing update endpoint and edit form. Display it in view mode only when non-empty. **Layers touched:** Flyway migration → JPA entity → DTO → OpenAPI types → frontend form + display. --- ## Detailed Plan ### Backend 1. **Migration** `V{n}__add_person_notes.sql`: ```sql ALTER TABLE persons ADD COLUMN notes TEXT; ``` 2. **`Person` entity**: ```java @Schema(description = "Biographical notes") private String notes; ``` 3. **`PersonUpdateDTO`**: ```java private String notes; ``` 4. **`PersonService.update()`** — map `notes` from DTO to entity (treat empty string as null). 5. Rebuild JAR, run `npm run generate:api`. ### Frontend 6. **Edit form** — add a textarea at the bottom of the edit section in `persons/[id]/+page.svelte`: ```svelte <div class="md:col-span-2"> <label for="notes" class="block text-xs font-bold uppercase tracking-widest text-gray-400 mb-1"> {m.form_label_notes()} </label> <textarea id="notes" name="notes" rows="4" value={person.notes ?? ''} class="block w-full border border-gray-300 rounded px-3 py-2 font-serif text-brand-navy focus:outline-none focus:border-brand-navy resize-y" ></textarea> </div> ``` 7. **View mode** — render a "Notizen" card below the person header, only when `person.notes` is non-empty: ```svelte {#if person.notes} <div class="bg-white shadow-sm border border-brand-sand rounded-sm p-6 mb-6"> <h2 class="text-xs font-bold uppercase tracking-widest text-gray-400 mb-3">{m.person_notes_heading()}</h2> <p class="font-serif text-brand-navy whitespace-pre-wrap">{person.notes}</p> </div> {/if} ``` 8. **`+page.server.ts` `update` action** — read and forward `notes` from form data. 9. **i18n keys**: - `form_label_notes`: DE `"Notizen"` / EN `"Notes"` / ES `"Notas"` - `person_notes_heading`: DE `"Biographische Notizen"` / EN `"Biographical notes"` / ES `"Notas biográficas"` ### Acceptance Criteria - [ ] Notes field is optional — saving with empty notes clears the field - [ ] Notes display with preserved line breaks (`whitespace-pre-wrap`) - [ ] Notes section is hidden entirely when empty - [ ] Notes survive a round-trip (save → reload → same content) - [ ] Existing persons are unaffected (null notes = hidden section)
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#23