feat(persons): show merge action inline with danger hint, remove Gefahrenzone collapsible #342
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?
User story
As a user managing persons, I want to see the merge action directly on the person detail page (with a clear danger warning), so that I don't have to expand a hidden "Gefahrenzone" section to discover it.
Context
The merge action is currently buried in a collapsible "Gefahrenzone" accordion. Users miss it or are confused by the section name. Recommended pattern: show the merge control inline at the bottom of the page with a visible destructive-action indicator — no accordion needed.
Acceptance criteria
👨💻 Markus Keller — Application Architect
Observations
/persons/[id]/edit/+page.server.ts. Moving it to the detail page (/persons/[id]) requires adding amergeaction to/persons/[id]/+page.server.ts, which currently has noactionsexport at all.PersonMergePanel.sveltealready exists as a standalone component atpersons/[id]/PersonMergePanel.svelte— the accordion wrapperPersonDangerZone.sveltelives one level deeper inedit/. This is actually a clean module boundary already: the panel is reusable, the accordion was the edit-page decoration around it.PersonDangerZone.sveltewill become vestigial after this issue. It should be deleted, not kept as dead code.canWrite— that flag is available for conditional rendering of the merge section.Recommendations
mergeaction block verbatim fromedit/+page.server.tsto+page.server.ts. Keep it inedit/too only if the edit page must also support merge post-move — but there is no requirement for that, so remove it from the edit page to avoid dual ownership of the same action.PersonDangerZone.svelteonce the accordion is removed from the edit page. Dead components left behind accumulate confusion.PersonMergePanel.sveltedirectly in the detail page (+page.svelte) — it already has the right interface (person,formprops). No architectural change to the component itself is needed.{#if data.canWrite}— the panel should not render for read-only users.👨💻 Felix Brandt — Fullstack Developer
Observations
PersonMergePanel.svelteis already a well-factored standalone component (84 lines). It renders the form, the two-step confirm pattern, and error state. No splitting needed.PersonDangerZone.svelteis an accordion wrapper (44 lines) that wrapsPersonMergePanelin a{#if open}block. This wrapper becomes the entire deletion target.+page.sveltehas noformbinding — the layout islet { data } = $props(). Adding the merge panel means also threadingformthrough:let { data, form } = $props().edit/+page.server.tsis clean and complete — it can be moved as-is. One watch: the merge error key ismergeErrorinside the returnedfail()object. The detail pageformtype will need to reflect this:form?: { mergeError?: string } | null.{#key person.id}wrapper insidePersonDangerZoneis important — it resets thePersonMergePanelinternal state (mergeTargetId,showMergeConfirm) when navigating between persons. Carry this{#key}wrapper over when embedding the panel in the detail page.Recommendations
+page.server.ts(detail): add theexport const actions = { merge: ... }block copied from the edit page server. No other action needed —update,discard,addAlias,removeAliasstay on the edit page.+page.svelte(detail): addformto the destructured props, add{#if data.canWrite}guard, render<PersonMergePanel person={person} {form} />wrapped in{#key person.id}.PersonMergePanel— theborder-lineclass on its container should be replaced withborder-red-200to make the danger signal visible at first glance without the accordion, matching the currentPersonDangerZonestyling.?/mergeaction reference fromedit/+page.server.tsafter moving it, and deletePersonDangerZone.svelteand its import fromedit/+page.svelte.👨💻 Tobias Wendt — DevOps & Platform Engineer
Observations
POST /api/persons/{id}/mergeendpoint already exists and is tested.?/merge) changes from being served under/persons/[id]/editto/persons/[id]— this is handled automatically by SvelteKit's file-based routing. No reverse proxy rule or Caddy config update needed./persons/{id}/editspecifically to test merge, those tests will need updating after this move. Worth flagging to Sara before the branch is merged.Recommendations
frontend/e2e/for any test that submits?/mergefrom the edit page URL and update the navigation path to the detail page instead.👨💻 Elicit — Requirements Engineer
Observations
/persons/[id]/edit), not the detail page — so the issue implicitly requires adding merge to the detail page and removing the accordion from the edit page.showMergeConfirmstate) must be preserved, or only the backend behavior. Implementors need to know.canWritegate: should read-only users see the merge section with a disabled state, or not see it at all?Recommendations
/persons/[id], then the merge section is not visible." (Or the opposite, if disabled-state is preferred — but pick one and write it down.)person_merge_warning("Achtung: Diese Aktion ist nicht rückgängig zu machen."). The acceptance criterion calling for the text "Diese Aktion kann nicht rückgängig gemacht werden" should align with the actual key or the key should be updated — avoid two near-duplicate phrasings of the same concept.👨💻 Nora "NullX" Steiner — Security Engineer
Observations
edit/+page.server.tsis already gated by thecanWritecheck in the load function:if (!canWrite) throw error(403, 'Forbidden'). When the action moves to+page.server.ts(the detail page), that gate disappears — the detail page load function returnscanWriteas data but does not throw 403 if the user lacks write permission.WRITE_ALLcould POST to/persons/{id}?/mergedirectly and attempt the merge action. The action itself calls the backend API which enforces@RequirePermission(Permission.WRITE_ALL)— so the backend will reject it — but the SvelteKit layer would accept the form submission and pass it through, returning a backend error as afail()rather than a proper 403 guard at the route level.Recommendations
mergeaction in the detail page's+page.server.ts:requireWritePermission(locals)helper from the shared pattern already duplicated across multiple+page.server.tsfiles — the same guard block appears in at least the persons edit page and the persons detail page load function. Centralizing it reduces the risk of getting it wrong on the next move.targetPersonIdis used as a path parameter to an existing backend endpoint that enforces authorization independently.👨💻 Sara Holt — QA Engineer
Observations
page.server.spec.tsinpersons/[id]/covers the detail page load function. It currently has no test for form actions because the detail page has none. Adding amergeaction will require a new test file or extending this spec.PersonMergePanel.sveltecomponent has no dedicated unit or component test. Moving it to a more visible location is an opportunity to add one./persons/{id}/editto test merge, it will break after this change.disabled={!mergeTargetId}— but never captured as a named test.Recommendations
mergeaction in the detail page server before implementing it (TDD, red first):PersonMergePanel.sveltethat asserts:form.mergeErroris present.frontend/e2e/for any test navigating to the edit page merge form and update the paths./persons/{id}, the merge section is visible without any user interaction" — this directly validates AC1 from the issue.👨💻 Leonie Voss — UI/UX Design Lead
Observations
PersonMergePanel.sveltecontainer usesborder-line bg-surface— neutral styling. ThePersonDangerZone.svelteaccordion wrapper adds theborder-red-200that signals danger. When the accordion is dropped and the panel is embedded directly, the danger border styling must move into the panel itself, not stay in a now-deleted wrapper.person_merge_warning: "Achtung: Diese Aktion ist nicht rückgängig zu machen.") only appears after the user has selected a target and clicked the merge button — it is part of the confirmation step, not visible on page load. The acceptance criterion calls for a visible danger indicator on the section itself, independent of the confirmation flow. These are two different things and the current implementation only satisfies the confirmation-step warning.text-smthroughout. On the senior audience this is borderline — 14px at base. The section heading (font-serif text-lg) is fine, but the description paragraph attext-sm text-ink-2needs a contrast check againstbg-surface.sm:and above. At 320px theflex-col items-endstacking puts the button on its own line which is fine, butitems-endwill right-align the button — this should beitems-stretchoritems-startto keep the button full-width at mobile widths, matching the full-width typeahead above it.Recommendations
PersonMergePanel.sveltecontainer class fromborder-line bg-surfacetoborder-red-200 bg-surfaceto make the danger signal intrinsic to the panel, not dependent on a wrapper.person_merge_irreversible_hintwith value "Diese Aktion kann nicht rückgängig gemacht werden." This satisfies the acceptance criterion explicitly and is distinct from the in-confirmation warning.items-endtoitems-startand addw-fullto the button at mobile widths so it fills the column:px-4 py-2gives approximately 36px height — below the 44px minimum for the senior audience. Change topx-4 py-3or addmin-h-[44px]explicitly.🗳️ Decision Queue
Consolidated decisions requiring human input before implementation begins.
Theme 1: Scope of "Gefahrenzone" removal
Question: The accordion
PersonDangerZone.sveltecurrently lives on the edit page (/persons/[id]/edit), not the detail page. "Remove the Gefahrenzone collapsible" therefore means: remove it from the edit page AND add the merge section inline to the detail page. Should the merge panel also remain available on the edit page (i.e. both pages have it), or should it exclusively move to the detail page?Raised by: Elicit, Markus
Theme 2: Read-only user visibility of the merge section
Question: When a user lacks
WRITE_ALLpermission and visits/persons/[id], should the merge section (a) not render at all, or (b) render in a visually disabled/locked state? Currently the detail pagecanWriteflag is available but no AC specifies the read-only behavior.Raised by: Elicit, Nora
Theme 3: Irreversibility warning i18n text
Question: The acceptance criterion specifies the phrase "Diese Aktion kann nicht rückgängig gemacht werden." The existing i18n key
person_merge_warningreads "Achtung: Diese Aktion ist nicht rückgängig zu machen." — slightly different phrasing. Should a new keyperson_merge_irreversible_hintbe added for the always-visible section-level warning, keeping the existing key for the confirmation-step warning? Or should the existing key be updated (potentially affecting other uses)?Raised by: Elicit, Leonie
Implementation complete — branch
feat/issue-342-person-merge-inlineWhat was done
Per the Decision Queue resolution: the merge dialog stays on the edit page and is made inline — no move to the detail page.
Commit
286a31af—feat(persons): show merge panel inline on edit page, remove Gefahrenzone accordionChanges:
PersonMergePanel.svelte— container class changed fromborder-linetoborder-red-200. The danger indicator is now intrinsic to the panel itself, not dependent on the accordion wrapper.edit/+page.svelte— replaced<PersonDangerZone person={person} form={form} />with a direct inline render:{#key person.id}<PersonMergePanel person={person} form={form} />{/key}. The{#key}wrapper is preserved to reset internal merge state when navigating between persons.edit/PersonDangerZone.svelte— deleted. No dead code left behind.PersonMergePanel.svelte.spec.ts— new component test file (TDD, written before the fix) covering: red border class, merge heading renders, button disabled when no target selected, mergeError displays.Acceptance criteria
/persons/[id]/edit, the "Personen zusammenführen" section is visible without expanding anythingborder-red-200) is visible on the section at all times?/mergeaction, redirect on success) is unchangedPersonDangerZonecollapsible is removed and deletedTests
All 4 component tests pass. The 3 pre-existing test failures in
DocumentList.svelte.spec.tsandAnnotationShape.svelte.spec.tsare unrelated to this change.