Clear meta_date_end when a document's precision leaves RANGE #707

Open
opened 2026-06-01 11:04:08 +02:00 by marcel · 0 comments
Owner

Context

Follow-up from PR #706 / issue #678 (review by "Elicit" — Requirements Engineer).

Issue #678 assumed "the edit form clears the end field off-RANGE", and on that basis treated the "end date set without RANGE precision" guard (predicate 2 of validateDateRange) as API-only defense. That assumption is factually wrong:

  • The edit form's hidden field is value={showEndDate ? endDateIso : ''}, so off-RANGE it POSTs metaDateEnd=''.
  • An empty string binds to a null LocalDate on DocumentUpdateDTO.
  • DocumentService.applyDatePrecision only applies a DTO field when it is non-null — so a null end is skipped, not cleared.

Net effect: if a document is stored as RANGE with an end date and the user switches its precision to, say, MONTH on the form, the previously-stored end date is retained. The post-apply state is then precision != RANGE && metaDateEnd != null, which:

  • Before #678: hit the DB CHECK chk_meta_date_end_only_for_range → HTTP 500.
  • After #678: hits predicate 2 → a clean 400 INVALID_DATE_RANGE, but the user-facing message is "The end date must not be before the start date." — the wrong sentence for this situation, and the action (a legitimate precision change) still fails.

#678 deliberately kept predicate 2 as a reject (its AC6 asserts a 400), so fixing this could not be done within that PR without contradicting a locked acceptance criterion. Hence this follow-up.

Requirement

When a document's meta_date_precision is changed to a non-RANGE value, the system shall clear meta_date_end so the change is persisted successfully (rather than rejected).

This is the inverse of the progressive-disclosure UI: leaving RANGE should discard the now-meaningless end date, not preserve-and-reject it.

Acceptance criteria

AC1  Given a stored document with precision = RANGE and an end date
     When the user changes precision to a non-RANGE value and saves (via the form)
     Then the save succeeds and meta_date_end is cleared (null)

AC2  Given an API client submits precision != RANGE together with a non-null end date
     Then decide & document the contract: either (a) clear the end (consistent with the
     form), or (b) keep rejecting with a *correctly-worded* dedicated message — NOT the
     end-before-start sentence. (Recommend (a) for consistency.)

AC3  The "end-before-start" path (predicate 1) is unaffected and still rejects with
     INVALID_DATE_RANGE.

Implementation sketch

  • In DocumentService.applyDatePrecision (or a small dedicated step right after it), when the resulting metaDatePrecision != RANGE, set metaDateEnd = null (and arguably metaDateRaw left as-is). This removes predicate 2's only realistic user-facing trigger.
  • Revisit validateDateRange's second predicate: once the end is cleared on precision change, predicate 2 becomes pure API-defense as #678 originally intended — keep it, but if AC2 picks option (b), give it its own ErrorCode/message rather than sharing INVALID_DATE_RANGE.
  • Tests: a DocumentServiceTest case for the RANGE→MONTH form switch clearing the end; keep #678's predicate-2 test aligned with whichever AC2 contract is chosen.

Notes / scope

  • UX-only correctness + message accuracy; no migration, no new infra. Correctness is not at risk — the DB CHECK + #678's guard remain the backstop.
  • Discovered during the multi-persona review of #706; see Elicit's review comment there.
## Context Follow-up from PR #706 / issue #678 (review by "Elicit" — Requirements Engineer). Issue #678 assumed *"the edit form clears the end field off-RANGE"*, and on that basis treated the "end date set without RANGE precision" guard (predicate 2 of `validateDateRange`) as **API-only defense**. That assumption is **factually wrong**: - The edit form's hidden field is `value={showEndDate ? endDateIso : ''}`, so off-RANGE it POSTs `metaDateEnd=''`. - An empty string binds to a **`null`** `LocalDate` on `DocumentUpdateDTO`. - `DocumentService.applyDatePrecision` only applies a DTO field when it is **non-null** — so a null end is **skipped, not cleared**. Net effect: if a document is stored as `RANGE` with an end date and the user switches its precision to, say, `MONTH` on the form, the previously-stored end date is **retained**. The post-apply state is then `precision != RANGE && metaDateEnd != null`, which: - **Before #678:** hit the DB CHECK `chk_meta_date_end_only_for_range` → HTTP 500. - **After #678:** hits predicate 2 → a clean **400 `INVALID_DATE_RANGE`**, but the user-facing message is *"The end date must not be before the start date."* — the **wrong sentence** for this situation, and the action (a legitimate precision change) still **fails**. #678 deliberately kept predicate 2 as a *reject* (its **AC6** asserts a 400), so fixing this could not be done within that PR without contradicting a locked acceptance criterion. Hence this follow-up. ## Requirement > **When a document's `meta_date_precision` is changed to a non-RANGE value, the system shall clear `meta_date_end`** so the change is persisted successfully (rather than rejected). This is the inverse of the progressive-disclosure UI: leaving RANGE should *discard* the now-meaningless end date, not preserve-and-reject it. ## Acceptance criteria ``` AC1 Given a stored document with precision = RANGE and an end date When the user changes precision to a non-RANGE value and saves (via the form) Then the save succeeds and meta_date_end is cleared (null) AC2 Given an API client submits precision != RANGE together with a non-null end date Then decide & document the contract: either (a) clear the end (consistent with the form), or (b) keep rejecting with a *correctly-worded* dedicated message — NOT the end-before-start sentence. (Recommend (a) for consistency.) AC3 The "end-before-start" path (predicate 1) is unaffected and still rejects with INVALID_DATE_RANGE. ``` ## Implementation sketch - In `DocumentService.applyDatePrecision` (or a small dedicated step right after it), when the resulting `metaDatePrecision != RANGE`, set `metaDateEnd = null` (and arguably `metaDateRaw` left as-is). This removes predicate 2's only realistic user-facing trigger. - Revisit `validateDateRange`'s second predicate: once the end is cleared on precision change, predicate 2 becomes pure API-defense as #678 originally intended — keep it, but if AC2 picks option (b), give it its own ErrorCode/message rather than sharing `INVALID_DATE_RANGE`. - Tests: a `DocumentServiceTest` case for the RANGE→MONTH form switch clearing the end; keep #678's predicate-2 test aligned with whichever AC2 contract is chosen. ## Notes / scope - UX-only correctness + message accuracy; no migration, no new infra. Correctness is not at risk — the DB CHECK + #678's guard remain the backstop. - Discovered during the multi-persona review of #706; see Elicit's review comment there.
marcel added the P3-laterfeatureui labels 2026-06-01 11:04:21 +02:00
Sign in to join this conversation.
No Label P3-later feature ui
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#707