diff --git a/backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs b/backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs
index fffb309..8ddc879 100644
--- a/backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs
+++ b/backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs
@@ -56,7 +56,7 @@ public static class LetterHtml
{
sb.Append(" " : "").Append(Enc(section.Title)).Append("
");
foreach (var block in section.Blocks)
- RenderParagraphs(sb, block.Content.Paragraphs, defs);
+ RenderParagraphs(sb, block.Content.Paragraphs, defs, at);
sb.Append("
([^<]+)
").Groups[1].Value; + } + [Fact] public void Render_matches_the_golden_file() { @@ -88,6 +126,29 @@ public class LetterHtmlTests Assert.Equal(golden, html); } + [Fact] + public void Render_resolves_the_body_datum_placeholder_from_the_given_at_not_the_wall_clock() + { + const string historicalAt = "2019-03-14T08:00:00.0000000+00:00"; + + var html = LetterHtml.Render(FixtureBriefWithDatumInBody(), Template, historicalAt, watermark: false); + + Assert.Equal("14 maart 2019", ExtractBodyDatumParagraph(html)); + } + + [Fact] + public void Render_keeps_the_letterhead_date_and_the_body_datum_in_agreement_for_a_historical_at() + { + // A historical `at` (an archive re-render, a back-dated letter) is the case + // where the letterhead and the body datum placeholder could disagree within + // one document, if the body still read the wall clock (TE-007). + const string historicalAt = "2019-03-14T08:00:00.0000000+00:00"; + + var html = LetterHtml.Render(FixtureBriefWithDatumInBody(), Template, historicalAt, watermark: false); + + Assert.Equal(ExtractLetterheadDate(html), ExtractBodyDatumParagraph(html)); + } + [Fact] public void Every_letter_prefixed_class_exists_in_letter_css() { diff --git a/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md b/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md index d55b046..c0068d7 100644 --- a/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md +++ b/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md @@ -130,7 +130,7 @@ Every ticket tracing to a `BIO-` finding, plus every row on agent 07's authorita | **RB-26** | libs/shared/upload | testability | Move the accept/reject decision to `planFileSelection` in `upload.machine.ts` | §3a upload 52.0%/50.0%; §4a module max CC 27 | S | Low | P2 | 5 | RB-24 | **SIGN-OFF** | open | | **RB-27** | libs/shared/upload | testability | Extract `uploadOutcome(status, responseText)` out of the XHR closure | file LH 5/64 (**7.8% line**), BRH 3/57 (**5.3% branch**) | S–M | Low | P2 | 5 | RB-25 | **SIGN-OFF** | open | | **RB-28** | libs/beheer + ssp/brief | testability | `BLOB_PRESENTER` token; the 3 commands' success paths become assertable | §3a beheer/application **40.5% branch — worst FE**; brief.store BRH 32/64 | S–M | Low | P2 | 5 | — | **SIGN-OFF** | open | -| **RB-29** | backend/Domain | testability | Thread the existing `at` through `LetterHtml.ResolveAuto` instead of reading `UtcNow` | §3c Domain 82.0% branch; §4b `LetterHtml.cs` CC 21 | S | Low | P2 | 5 | — | — | open | +| **RB-29** | backend/Domain | testability | Thread the existing `at` through `LetterHtml.ResolveAuto` instead of reading `UtcNow` | §3c Domain 82.0% branch; §4b `LetterHtml.cs` CC 21 | S | Low | P2 | 5 | — | — | **done** | | **RB-30** | backend/Data + Domain | testability | Extract 5 brief guards into `Domain/Letters/BriefRules.cs`; add `tests/Domain/BriefRuleTests.cs` | §3c Data **75.5% branch** (BL-005); §4b `BriefStore.cs` CC 17, `ToDto` CC 16 | M | Med | P2 | 5 | — | **SIGN-OFF** | open | | **RB-31** | 4 app contexts (specs only) | ADR conform. | Replace hand-rolled state literals with `given(reduce, initial)` replays in 4 machine specs | §7 Elm machines 9 (1 has a `*.testing.ts`); §3a herreg 67.8% / brief 68.8% branch | M | Low | P2 | 6 | — | — | open | | **RB-32** | libs/shared/docs | ADR conform. | Add the missing `language-switcher` row to the CIBG gap register (9 markers vs 8 rows) | §2 libs/shared 86 files / 5 194 lines; §6 layout Ca 22 | S | Low | P3 | 6 | — | — | open | diff --git a/docs/project/refactor-backlog-setup/refactor-backlog/implementation/rb-29.md b/docs/project/refactor-backlog-setup/refactor-backlog/implementation/rb-29.md new file mode 100644 index 0000000..e5181c4 --- /dev/null +++ b/docs/project/refactor-backlog-setup/refactor-backlog/implementation/rb-29.md @@ -0,0 +1,125 @@ +# RB-29 — Thread `at` through `LetterHtml.ResolveAuto`'s `datum` case + +Status: **implemented** · 2026-08-27 · Source findings: `02-testability.md` TE-007 · +`99-backlog.md` RB-29 + +## What was wrong + +`LetterHtml.Render(BriefEntity brief, OrgTemplateDto template, string at, bool watermark)` +(`backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs`) already took the letter's +instant and used it correctly for the letterhead date +(`sb.Append(Enc(FormatDatumNl(at)))`). The body's `datum` placeholder resolved through +the private `ResolveAuto(string key, string label)`, which ignored `at` and called +`FormatDatumNl(DateTimeOffset.UtcNow.ToString("o"))` instead — a pure `Domain/` rule +class reading the wall clock. `ResolveAuto` is reached only through the private chain +`RenderNode` ← `RenderParagraphs` ← `Render`, so no caller outside this file could pin +the value a test would see. + +The ticket read as filed against the current code: `Render`'s signature, the letterhead's +correct use of `at`, and `ResolveAuto`'s `UtcNow` read were all exactly as TE-007 +described (line numbers had moved — CC around the file has grown since the finding was +written — but the code shape had not). One thing TE-007 named as the visible symptom +also checked out: `LetterHtmlTests.cs` already declares a +`new PlaceholderDefDto("datum", "Datum", true)` in its golden-file fixture, but no +`RichTextNodeDto` in that fixture's `Sections` actually references the `datum` key in +the body — it is declared but never rendered there, so the existing golden-file test +could not have caught this even if it asserted on dates (which it does not either). + +## What changed + +| File | Change | +| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs` | `ResolveAuto(string key, string label)` → `ResolveAuto(string key, string label, string at)`; `"datum" => FormatDatumNl(at)`. `at` threaded down through the two private call sites in the chain: `RenderParagraphs` and `RenderNode` both gained an `at` parameter, passed from `Render`'s own `at`. | +| `backend/tests/BigRegister.Tests/LetterHtmlTests.cs` | New fixture `FixtureBriefWithDatumInBody()` — a minimal brief whose body actually references the `datum` placeholder (the golden fixture never does). Two new `[Fact]`s (see below) plus two small extraction helpers. | +| `docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md` | RB-29 status cell: `open` → `implemented`. | + +`Render`'s own signature is unchanged — TE-007's "zero public API change, zero +call-site change" held exactly. `Program.cs:697`, `:708`, and `BriefStore.cs:120` (the +three callers) needed no edit. + +## What the fix looks like + +```csharp +private static void RenderParagraphs( + StringBuilder sb, IReadOnlyList