feat(stammbaum): show maiden name (geb. Schmidt) below person name in tree and side panel #364
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Display the maiden name as
geb. Schmidtbelow the person's display name in the Stammbaum tree nodes and the side-panel relationship list. The data already exists as aPersonNameAliaswithtype = MAIDEN_NAME.Where it appears
StammbaumCard/ the D3 force graph node labelsdisplayName; maiden name would appear there too if aPersonNodeDTOis usedBackend changes
1. Add
maidenNametoPersonNodeDTO2. Avoid N+1 — single batch query
Person.nameAliasesis lazy-loaded. Calling it per-person in a loop fires one query per person.Instead, after loading the list of family members, do one extra query:
Then build a
Map<UUID, String>and look up duringPersonNodeDTOconstruction. Net cost: +1 query for the entire network load.Apply the same pattern wherever
PersonNodeDTOis built:RelationshipService.getFamilyNetwork()RelationshipService.getInferredRelationships()(viaRelationshipInferenceService)3. Regenerate TypeScript types
Frontend changes
Anywhere a person's name is rendered in a Stammbaum context, add:
Components to update:
StammbaumSidePanel.svelte— relationship list entriesStammbaumPage.svelte(SVG<text>element or foreignObject)PersonRelationshipsCard.svelte— relationship chip sub-labels if applicableAdd i18n key
person_maiden_name_prefix="geb."(de),"née"(en/es).Acceptance criteria
MAIDEN_NAMEalias showsgeb. {lastName}below their name in the Stammbaum treeGET /api/networkresponse includesmaidenNamefield (nullable) on each nodenpm run check)🏛️ Markus Keller — Senior Application Architect
Observations
PersonNameAliasRepository.findByPersonIdInAndType()fromRelationshipService. ButPersonNameAliasRepositorylives in the person domain.RelationshipServicealready callsPersonServicefor cross-domain access — the pattern is established. Injecting the alias repository directly intoRelationshipServicewould break that boundary.PersonNodeDTObuild sites.RelationshipService.getFamilyNetwork()(line 62) andRelationshipInferenceService.findAllFor()(line 98) both constructPersonNodeDTOinline. Both need updating. Since they share the same enrichment logic, they should share the same helper — not duplicate it.PersonNodeDTOas a record — adding a nullableString maidenNamefield is clean and idiomatic. No concerns there.Recommendations
Move the batch alias lookup into
PersonService, notRelationshipService. Add a method like:Then
RelationshipServicecallspersonService.findMaidenNames(familyIds)— cross-domain access through the service layer, as the architecture requires.Extract a private helper in the relationship package to avoid duplicating the DTO construction logic:
Both
getFamilyNetwork()andRelationshipInferenceService.findAllFor()use it.Add
findByPersonIdInAndTypetoPersonNameAliasRepository— this is a Spring Data derived query, no custom JPQL needed:Remember to regenerate TypeScript types after the DTO change.
👨💻 Felix Brandt — Senior Fullstack Developer
Observations
SVG layout blocker.
StammbaumTree.svelte(notStammbaumPage.svelte— that file doesn't exist) usesNODE_H = 56. The existing two text lines sit aty = NODE_H/2 - 6 = 22(name) andy = NODE_H/2 + 12 = 40(dates). A third line for maiden name aty ≈ 52–54would overflow the 56px box. The issue proposes adding a maiden name line without addressing this constraint. Decide before implementing: either (a) increaseNODE_Hto ~76, (b) render maiden name only in the side panel, not on the SVG node card itself, or (c) useforeignObject. Option (b) is the simplest and avoids a layout algorithm overhaul.StammbaumSidePanelrelationship list entries useRelationshipDTO, notPersonNodeDTO. The side panel's relationship list renders names viaotherName(rel, node.id)which readspersonName/relatedPersonNamefromRelationshipDTO. AddingmaidenNametoPersonNodeDTOwill NOT make maiden names appear there. The inferred relationships section (topDerived) usesderived.person.displayName— that ISPersonNodeDTO, so maiden name there is achievable. Direct relationship entries are out of scope unlessRelationshipDTOis also updated.Existing i18n key.
person_born_name_prefix="geb."already exists inmessages/de.json(line 431). Adding a newperson_maiden_name_prefixkey with the same value creates a duplicate. Reuseperson_born_name_prefixor justify the distinction.Component naming. The issue mentions "D3 force graph" — the current implementation uses a custom SVG layout in
StammbaumTree.sveltewith no D3 dependency. The issue should referenceStammbaumTree.sveltespecifically.Recommendations
should_render_maiden_name_below_display_name_when_alias_exists()inStammbaumTree.svelte.test.ts(red), then implement. Same forshould_not_render_maiden_name_line_when_absent().PersonNodeDTOconstruction inRelationshipInferenceService(line 98), the maiden name lookup map must be built before the loop — not inside it.node.maidenNamein the panel header (easy — just add a<p>below the<h2>), and (b) inferred relationship entries viaderived.person.maidenName. Remove "relationship list entries" from scope unlessRelationshipDTOis extended.aria-labelon SVG tree nodes to include maiden name:🔒 Nora Steiner — Application Security Engineer
Observations
maidenNameis historical personal data of deceased persons (1899–1950). In the project's context (family-only archive, private deployment), this is low-risk. The data is already stored and displayed in other parts of the app (PersonNameAliasis already exposed on the person detail page). No new surface area for privacy concerns.GET /api/network. It's already authenticated.maidenName: nullfor persons without an alias, so no empty-string leakage.findByPersonIdInAndTypeis a Spring Data derived query — parameterized by design, injection-safe. No custom JPQL string concatenation.Recommendations
maidenNamefield is not accidentally marked@Schema(requiredMode = REQUIRED)in the DTO — it must remain optional in the OpenAPI spec so TypeScript generates it asstring | undefined(nullable), preventing frontend crashes when the field is absent on older cached responses.maidenNamevia the inferred-relationships endpoint (GET /api/persons/{id}/inferred-relationships), verify the same auth guards apply there — they do today, but worth a sanity check after the change.🧪 Sara Holt — QA Engineer & Test Strategist
Observations
PersonNodeDTOconstruction sites (RelationshipService.getFamilyNetwork()andRelationshipInferenceService.findAllFor()) means two separate unit test targets — each needs its own happy path and no-alias path test.<text>SVG element. Vitest browser tests can assertgetByRole('button', { name: /geb\./ })against the compositearia-labelon the<g>element (which already includes name + years) — this is testable today once the aria-label includes the maiden name.Recommendations
Backend — unit tests (Mockito):
Backend — integration test (Testcontainers) for N+1:
@SqlStatementInspectororStatisticsto assert exactly 2 SQL queries fire duringgetFamilyNetwork(): one for persons, one for aliases. This is automatable and belongs in the test suite permanently.Frontend — vitest-browser component tests:
For
StammbaumSidePanel: test that the panel header showsgeb. Schmidtbelow the display name whennode.maidenNameis set. UsegetByText('geb. Schmidt').CI note: Add
npm run checkto the PR CI step if it isn't already — the acceptance criterion mentions it but it only catches issues if it runs on the branch.🎨 Leonie Voss — UX Designer & Accessibility Strategist
Observations
SVG layout cannot fit three text lines at NODE_H=56. Current node: name at
y=22, dates aty=40, box bottom aty=56. A third line aty≈52–54clips at the border. Anyfont-sizebelow 12px to compensate falls below the minimum for our senior audience (60+). This is a blocking layout issue that needs a decision before frontend work starts.The side panel header is the right primary location for maiden name.
StammbaumSidePanel.svelterenders the name in a<h2>with an HTML layout — adding a<p>below is straightforward and the correct semantic structure. The tree node card is the wrong place for dense text given space constraints.Accessibility: SVG node
aria-labelis the only text a screen reader gets. Currently:"{displayName}, {birthYear}–{deathYear}". Maiden name must be included here even if it's not visible on the node face.Recommendations
Preferred approach: side-panel first, SVG node optional
For the side panel header (no layout risk):
For the SVG tree node — if maiden name must appear there — increase
NODE_Hto 76 and add:Note: a
NODE_Hincrease affects the entire layout algorithm spacing — test the tree with a 10+ person family after the change.Accessibility fix regardless of approach:
i18n note: Use
m.person_born_name_prefix()— that key already returns"geb."in de.json. No need for a new key.Open Decisions
NODE_H(layout algorithm impact). Showing it only in the side panel header is lower risk. Which scope is intended? The decision affects NODE_H, layout spacing, and test coverage.📋 Elicit — Requirements Engineer
Observations
Component naming is imprecise. The issue references "D3 force graph node labels", "StammbaumPage.svelte", and "StammbaumCard". None of these exist. The actual rendering file is
StammbaumTree.svelte(custom SVG layout, no D3 dependency). Precise file names prevent wasted developer time.Scope gap: side-panel relationship list. The issue says "StammbaumSidePanel — the relationship list entries (inferred relationships)." The direct relationship entries use
RelationshipDTO(specificallyotherName(rel, node.id)→personName/relatedPersonName) — these are notPersonNodeDTOfields. AddingmaidenNametoPersonNodeDTOwill only affect the inferred relationships section (which does usePersonNodeDTOviaInferredRelationshipWithPersonDTO), not the direct relationship entries. This scope statement will create confusion during implementation.Duplicate i18n key.
person_born_name_prefix="geb."already exists inmessages/de.json. Addingperson_maiden_name_prefixwith the same value creates a redundant key. If the semantic distinction matters (maiden name vs. birth alias), document it explicitly; otherwise reuse the existing key.Missing edge case: long maiden names. What happens when
maidenNameis 25+ characters (e.g.,"von Schönhausen-Bülow")? In the SVG tree node (160px wide), this will overflow. Truncation behavior should be specified.Missing NFR: mobile viewport. The Stammbaum tree is used by the reader audience on phones. After NODE_H adjustment, the tree must still render correctly at 375px width.
Recommendations
Suggested acceptance criteria additions:
maidenNameis> 20 charactersis truncated in the SVG node card (with full name visible in the side panel)StammbaumSidePanelshowgeb. {lastName}below each person name where a maiden name existsStammbaumSidePanelare not expected to show maiden names (different DTO — clarify this explicitly to avoid scope creep)Suggested spec corrections:
<text>element or foreignObject)" → "StammbaumTree.svelte"<g>node elements in StammbaumTree.svelte"person_maiden_name_prefix" → "Reuse existing keyperson_born_name_prefix"🚀 Tobias Wendt — DevOps & Platform Engineer
Observations
npm run generate:api). Without it, the TypeScript types won't includemaidenNameand the svelte-check gate will pass on stale types while the frontend silently ignores the new field.EntityManagerFactory.unwrap(SessionFactory.class).getStatistics()— no extra infra needed.Recommendations
--spring.profiles.active=dev— document this prerequisite in the PR checklist so it doesn't get skipped.npm run checkon the frontend. If it doesn't, this PR is a good occasion to add it as a mandatory step — it's the gate for TypeScript and svelte-check issues.🗳️ Decision Queue — Action Required
1 decision needs your input before implementation starts.
Frontend Architecture
SVG tree node card vs. side panel only for maiden name display. The current
StammbaumTree.svelteSVG nodes haveNODE_H = 56with two text lines already aty=22(name) andy=40(dates). A third line for maiden name overflows the box. Options:geb. Schmidtin theStammbaumSidePanelheader and inferred relationship entries. No change toNODE_Hor the layout algorithm. Lower risk, faster to ship.NODE_Hto ~76. The layout algorithm usesNODE_Has a global constant so this affects spacing for all nodes — needs testing with a 10+ person tree. Also need to decide whether the maiden name line is conditional (only shown when present) or whether all nodes grow to the new height.This decision affects:
NODE_Hvalue, layout algorithm testing scope, accessibilityaria-labelupdate, and which test cases Sara needs to write. (Raised by: Leonie, Felix, Elicit)🎨 Leonie Voss — UX/Accessibility follow-up discussion
Decisions reached on the four open UX items:
✅ 1. Maiden name on the SVG tree node card
Show
geb. Schmidtdirectly on the node card inStammbaumTree.svelte— not side-panel only. Option B.✅ 2. Uniform NODE_H=76, vertically centered text block
All nodes grow to
NODE_H = 76regardless of whether a maiden name is present. The text block (1–3 lines) is centered vertically using a dynamicyBase:Each line renders at
yBase,yBase + lineSpacing,yBase + 2 * lineSpacingas applicable. Nodes without a maiden name don't look like something is missing — they're simply balanced with fewer lines.✅ 3. Truncate at 18 characters in the SVG node, full name in aria-label and side panel
The
aria-labelon the<g>element must use the full untruncated maiden name. The side panel header always shows the complete value.✅ 4. No maiden name in inferred relationship entries
Maiden name appears only in the selected node's side panel header — not in the inferred relationship list entries. Keeps the list clean.
👨💻 Felix Brandt — Developer follow-up discussion
Decisions reached on three open implementation items:
✅ 1. i18n key: reuse
person_born_name_prefixThe existing key already returns
"geb."in de.json. The newperson_maiden_name_prefixkey proposed in the issue body is dropped — no duplicate needed.✅ 2.
PersonRelationshipsCard.sveltedropped from scopeLeonie's UX decision (maiden name only in the
StammbaumSidePanelheader) makes this component irrelevant to the issue. It is not touched in this implementation.✅ 3. Truncation extracted to
utils.tsastruncate(value, max)The 18-char cap logic lives in a named, unit-testable utility function — not inline in the template:
Called from
StammbaumTree.sveltefor the maiden name SVG text element. Thearia-labelalways uses the full untruncated value. Write unit tests fortruncate()first (red/green), then wire into the template.The implementation surface is now fully specified between this comment and Leonie's UX decisions above.
🏛️ Markus Keller — Architect follow-up discussion
Decisions reached on two structural backend items:
✅ 1. Domain boundary: alias lookup goes on
PersonServiceRelationshipServicemust not injectPersonNameAliasRepositorydirectly — that crosses the domain boundary. The batch lookup is exposed as a method onPersonService:RelationshipServicecallspersonService.findMaidenNames(familyIds)— consistent with the established cross-domain access pattern.✅ 2. Shared
PersonNodeMapperin therelationshippackageBoth
RelationshipService.getFamilyNetwork()andRelationshipInferenceService.findAllFor()constructPersonNodeDTO. The logic is extracted once into a shared mapper in the same package:Both services call
PersonNodeMapper.toNode(p, maidenNames). No duplicated construction logic.The backend structure is clean. No domain boundary violations, no duplicated DTO construction.