docs: archive the finished backlogs (RD-30)

Two backlog trees are complete: `docs/project/backlog/` (75 files, every
WP done) and `docs/project/refactor-backlog-setup/` (the arc before it).
Move both under `docs/project/archive/` with `git mv`, so history stays
intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them,
because it points at the now-archived backlog README.

Add `docs/project/archive/README.md`. It states that these trees are
historical and names the two directories that are still live.

Repoint every inbound reference named in RD-30's Files table: CLAUDE.md,
the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the
`document-feature` and `new-ssp` skills, and the readable-codebase PLAN,
README, and RD-19 ticket. Fix two upward-relative links inside the moved
WP files (WP-68, WP-69) that gained a directory level and would otherwise
break. Repoint `.prettierignore`'s two agent-prompt exclusions to their
new path, so prettier keeps leaving those files' exact wording alone.

Mark RD-30 done and check off its acceptance criteria; flip its README
row to done.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-09-08 23:00:38 +02:00
co-authored by Claude Opus 5
parent 097e8468e0
commit 12f17d9d73
161 changed files with 154 additions and 24 deletions
@@ -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<ParagraphDto> paragraphs, IReadOnlyDictionary<string, PlaceholderDefDto> defs,
string at)
{
// ... unchanged body, forwards `at` to RenderNode ...
}
private static void RenderNode(
StringBuilder sb, RichTextNodeDto node, IReadOnlyDictionary<string, PlaceholderDefDto> defs, string at)
{
// ... unchanged body, forwards `at` to ResolveAuto ...
}
private static string ResolveAuto(string key, string label, string at) => key switch
{
"naam_zorgverlener" => SeedData.Registration.Naam,
"big_nummer" => SeedData.Registration.BigNummer,
"datum" => FormatDatumNl(at),
_ => label,
};
```
Both call sites already had `at` in scope (`Render`'s own parameter), so this is a pure
threading change — no new state, no new dependency.
## Tests added
TE-007 named the exact gap: the golden-file fixture declares the `datum` placeholder but
never renders it in the body, so no existing assertion could catch a body/letterhead
mismatch. A new fixture and two focused tests close it:
1. **`Render_resolves_the_body_datum_placeholder_from_the_given_at_not_the_wall_clock`** —
renders `FixtureBriefWithDatumInBody()` with a fixed historical `at`
(`2019-03-14T08:00:00.0000000+00:00`) and asserts the body's rendered paragraph is the
exact string `"14 maart 2019"`. A test using today's date would have passed before
and after the fix and proven nothing — this one pins a date nowhere near "now", so it
fails whenever the resolver reads the wall clock instead of `at`.
2. **`Render_keeps_the_letterhead_date_and_the_body_datum_in_agreement_for_a_historical_at`**
— same fixture and historical `at`, asserts the letterhead `<dd>` date and the body's
rendered `datum` paragraph are equal. This is TE-007's stated payoff: not a shipped
bug today (every current caller passes `Now()` at render time, so the two dates always
coincided even with the bug present), but a latent one — the moment `Render` is ever
called with a historical `at` (re-rendering an archive, back-dating a letter), the
letterhead and body would disagree within a single document. This test is the one
that would have caught that.
Both tests use the repo's one date formatter (`FormatDatumNl`, already used by both call
sites under test) only indirectly, through the literal expected string `"14 maart
2019"` — no second hand-rolled `ToString` format was introduced in the test file either.
## Verification
- **Verified red without the fix.** Reverted only the `ResolveAuto` expression (via
`Edit`, not `git checkout`) back to
`"datum" => FormatDatumNl(DateTimeOffset.UtcNow.ToString("o"))`, leaving the new
tests and the threaded signatures in place. Ran the two new tests:
```
Render_resolves_the_body_datum_placeholder_from_the_given_at_not_the_wall_clock [FAIL]
Assert.Equal() Failure: Strings differ
Expected: "14 maart 2019"
Actual: "27 augustus 2026"
Render_keeps_the_letterhead_date_and_the_body_datum_in_agreement_for_a_historical_at [FAIL]
Assert.Equal() Failure: Strings differ
Expected: "14 maart 2019"
Actual: "27 augustus 2026"
```
Both failures show the body rendering the run's actual wall-clock date (today,
2026-08-27) instead of the pinned historical `at` — the precise defect TE-007
describes. Restored the fix with a second `Edit` and re-ran: all 4 tests in
`LetterHtmlTests` green (2 pre-existing + 2 new).
- `grep -n "UtcNow\|DateTime.Now\|DateTime.Today\|DateTimeOffset.Now"
backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs` — no matches. No ambient
clock read remains anywhere in the file.
- `npm run ci` (foreground, no background/Monitor): see result reported alongside this
ticket.
## What this ticket did not touch
`LetterHtml.cs`'s overall structure and CC (TE-007 records it at 21, the third-highest
in the backend) are unchanged — reducing that is out of scope for this ticket, per its
own text. `Data/BriefStore.cs` and any `BriefRules.cs` file were not touched — a
concurrent ticket owns that file.