feat(reports): rebuild the cover letter as a business letter (#1932) - #1951
Conversation
The user's scope ruling shaped the whole story: plain text with line breaks, no WYSIWYG editor, no new client dependency. - The body's line breaks already survived rendering (pdfmake treats `\n` as a required line end); what was actually missing was the letter itself. Blank-line preservation is now pinned by a real pdfmake-render assertion instead of a content-tree string check. - The ruling surfaced a genuine defect: the LLM prompt never constrained output *format*, so a generated letter could print literal `**asterisks**` and `- hyphens` in a PDF handed to a bank. Rule 4 of the report-content prompt now mandates plain prose (appended; #1931's pinned safety constraints are untouched). - `signature` becomes a first-class editable field; an explicit signature edit is no longer silently overwritten when the sender is edited afterward. - The sender is now the logged-in person's display name plus the household address, not the household name. - A real signature block — closing salutation, reserved signature space, signatory name — is emitted unconditionally: an empty pdfmake text node was measured to reserve the same line height as a non-empty one, so no NBSP workaround is needed. - Letter hierarchy via varied margins, a right-aligned date, and a bold/larger subject style. - The oversized reset-"X" in the shared EditableField is fixed: intrinsic width instead of full-container stretch, glyph sized off a font token instead of 100%, the 44x44 hit area kept, and a focus ring added where none existed before. Folds in and supersedes #1925 (date-caption language mixing) — close it as a duplicate once this merges. Note: no new E2E coverage was added for this story (deferred); will flag in the PR description. Fixes #1932 Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com> Co-Authored-By: Claude backend-developer <noreply@anthropic.com> Co-Authored-By: Claude frontend-developer <noreply@anthropic.com> Co-Authored-By: Claude translator <noreply@anthropic.com> Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com> Co-Authored-By: Claude ux-designer <noreply@anthropic.com> Co-Authored-By: Claude product-owner <noreply@anthropic.com>
#1932) Closes AC 7.3, which was left unrouted in the initial PR (#1951). Adds Scenarios 21-24 to reportWizardEditableContent.spec.ts: - 21: desktop/light — editing signature and a genuine two-paragraph body, then opening the on-demand PDF preview, reflects both edits (the load-bearing AC 7.3 case: a single-line body would not exercise the line-break round trip at all). - 22: mobile viewport + dark mode, same edit flow. - 23: AC 2.6 regression guard — signature edited first, then sender; the explicit signature must not be silently recomputed. - 24: reset interaction on the new signature field, doubling as a live guard that the CSS-only §5 reset-button fix never broke the existing reset-button locators. ReportWizardPage.ts gains one locator addition (`signature: 'Signature'` in LETTER_FIELD_LABELS) plus a docstring paragraph on the #1932 DOM changes. No POM change was needed for the reset-button fix itself — diffed EditableField.module.css against origin/beta and confirmed it's CSS-only (svg sizing + a new :focus-visible ring), with className/DOM structure unchanged. These four scenarios have never executed locally: Playwright's Chromium download is network-policy-blocked in this sandbox (cdn.playwright.dev and playwright.download.prss.microsoft.com both 403 under the default-deny policy) — CI is their first real browser run. Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com> Co-Authored-By: Claude e2e-test-engineer <noreply@anthropic.com>
|
[ux-designer] Review of PR #1951 against my visual spec (issue #1932, comment
|
|
[product-owner] Verdict: APPROVED (3 MUST FIX before merge, none blocking)Reviewed Local 1. Every AC, individually§1 — Body formatting (plain text with preserved line breaks)
§2 — Signature
§3 — Sender
§4 — Letter layout
§5 — Reset button
The accompanying unit test is honest about its own limits (jsdom has no layout engine, so it pins class presence and DOM position rather than pretending to measure pixels) and says so explicitly, citing the #1929 postmortem. That is the right call — a fake computed-dimension assertion would have been worse than no test. §7 — Tests7.1 MET · 7.2 MET · 7.4 MET · 7.3 in flight (see item 6). 2. Is AC 1.2 met by a line-count-plus-spacing proof?Yes. I own the strengthening and I judge this satisfies it. My bar was explicit: "asserted against a real render (pdfmake's resolved layout), not merely against the string content of the emitted text node" — because
The one thing it does not do is reconstruct per-line text. That does not matter here, because the body is a single text node whose The helper is also right about the mechanism, and documents why: 3. AC 2.6, both directions — and the pinned assertionBoth directions met.
The realRender pin uses On "updated, not deleted": 4. AC 6.1 / 6.2 — does Option B satisfy #1925?Yes, and the split reads as deliberate.
The real defect was never the caption's language — it was On the adjacent-different-languages question: yes, 5. #1925 bookkeeping — and a correction to my own §6My §6 transcription was incomplete, and I'm recording that as my error. #1925 has six ACs, not four. I carried its AC1→6.1, AC2→6.2, AC4→6.3, AC6→6.4, and dropped its AC3 and AC5. Checking all six rather than the four I carried:
Parity verified programmatically across all 17 namespaces × en/de: zero missing, zero extra, budget at 1055/1055 as claimed. Action taken: #1925 closed as a duplicate of #1932, board → Wont-Do, with a comment enumerating all six ACs and where each landed. Duplicate closure transfers ownership; the residual AC 5 pin is tracked as MUST FIX 3 on this PR, where it will actually be acted on. 6. AC 7.3 orderingYour ordering is right — don't change it. AC 7.3 is part of #1932's AC set, so it belongs in the PR that closes the story, not in a follow-up issue. Deferring it would let #1932 merge with a documented AC unmet, and my §7 exists precisely so the test obligations are atomic with the feature. Landing I reviewed Two conditions on the merge, both mechanical:
MUST FIX before merge (non-blocking)
Informational (no action requested)
Verdict: APPROVED — all 40 acceptance criteria met, including both criteria I struck as vacuous, whose retained negative constraints hold in full. AC 1.5 in particular — the one I said reviewers should treat as blocking — is clean on every axis I could check. Three MUST FIX items before merge, none of which affects a functional criterion; two of them are the same test file and land with the E2E commit. #1932 stays out of Done until UAT: the user rejected the previous output by looking at a generated PDF, and this story is answering that rejection. It should be accepted the same way — a human reading a real exported cover letter in both EN and DE. |
|
[product-architect] Architecture review — PR #1951 (#1932, cover letter overhaul)Verdict: CHANGES_REQUIRED — one HIGH finding (a shipped E2E scenario that cannot pass). Everything else below is non-blocking. Reviewed against issue #1932 (amended body), the HIGH-1 (blocking) — E2E Scenario 24 seeds a source with neither
|
…#1932) Closes the architect's blocking finding and the PO's three MUST FIX items from PR #1951 review. - E2E: Scenario 24's seed source had no contactAddress/reference, so the cover letter never mounted — content.coverLetter stayed null and letterField('signature') matched zero elements. Every assertion on it (including the .not.toBeVisible() checks) passed vacuously against nothing, and the scenario then failed on fill(), exactly as the architect predicted from a static read. Shards 2 and 5 went red on d60a98b. Fixed the seed and added positive existence checks (coverLetterCard/signature/body/sender all asserted visible) to Scenarios 21 and 23 too, so a future regression of this shape fails loudly instead of vacuously. - Unit: the Closing row (label + value, positioned between body and signature) had zero coverage — deleting its JSX left every existing test green. New test locates it by content, not index math, and pins its position between the body and signature fields. Verified by actually deleting the JSX first: exactly 2 failures, 64 others still green, then restored to 66/66. - Unit: pins #1925 AC5 — the date and closing read-only captions render without a trailing colon — which had no assertion on the editor side before this (only the PDF side was checked). - Unit: closes the AC 1.2 residual the architect flagged — line count and spacing alone don't rule out a rewrite that alters the body TEXT while preserving both. Added an exact `.text` pin before and after the real render. - i18n: German closing salutation drops the comma per DIN 5008 ("Mit freundlichen Grüßen", no trailing comma) — English keeps it. This string is printed in a letter handed to a German bank. - Wiki: documents that letterBody's plain-prose formatting is instructed at the prompt level (REPORT_CONTENT_SYSTEM_PROMPT rule 4) and not enforced by the response validator, which only truncates for length. Already pushed to origin/master (df8a462f) — only the parent pointer is staged here. Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com> Co-Authored-By: Claude e2e-test-engineer <noreply@anthropic.com> Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com> Co-Authored-By: Claude translator <noreply@anthropic.com> Co-Authored-By: Claude backend-developer <noreply@anthropic.com> Co-Authored-By: Claude product-owner <noreply@anthropic.com> Co-Authored-By: Claude ux-designer <noreply@anthropic.com> Co-Authored-By: Claude product-architect <noreply@anthropic.com>
|
[product-owner] Follow-ups filed from the architecture reviewBoth non-blocking findings from the architect's review are now tracked. Neither blocks this PR.
Product rulings made in the issues, so they are not relitigated#1952 — strip, do not reject. The architect left three options open (strip / reject / accept prompt-level-only). Ruled strip, for four reasons: one LLM call produces the subject, body and all per-invoice descriptions, so rejecting over two asterisks discards unrelated correct output and may fail identically on retry; the validator's existing policy for these very fields is already coercion (it truncates rather than throws), so a harsher policy for a milder violation would be incoherent; the fields are human-editable in the preview, which argues for repair-and-move-on rather than hard failure; and accepting prompt-level-only simply leaves the known-wrong output reachable. Scope covers Note for whoever picks it up: the wiki bullet this PR adds ("instructed but not enforced") is the honest interim statement. #1952 AC 4.1 amends it rather than deleting it. #1953 — the equality is coincidental; split with an independent literal, not an alias. The UX spec did direct the reuse, so this needed settling rather than leaving to taste. Its stated rationale (comment 5160362068 line 52) is "don't hand-write #1953 also records the architect's Deliberately not filed
PR bodyThe stale "No new E2E coverage was added — deferred" line is already corrected on the current body: it now lists Scenarios 21-24 with the E2E checkbox ticked. Checked the two places it could have propagated — there is no open |
|
🎉 This PR is included in version 2.13.0-beta.48 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
- Carry product-owner review notes that were written after #1932's final commit had landed: the #1952 strip-not-reject ruling with its false-positive guards, the #1953 coincidental- constant reading, and two reusable review patterns. Memory only — no production code, tests, or configuration. Co-Authored-By: Claude product-owner <noreply@anthropic.com>
|
🎉 This PR is included in version 2.13.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
The user's scope ruling shaped the whole story: plain text with line breaks, no WYSIWYG editor, no new client dependency.
\nas a required line end); what was actually missing was the letter itself. Blank-line preservation is now pinned by a real pdfmake-render assertion (line count, spacing, and an exact.textpin before/after render) instead of a content-tree string check.**asterisks**and- hyphensin a PDF handed to a bank. Rule 4 of the report-content prompt now mandates plain prose (appended; Single "Enhance with AI" action and purpose-focused report content prompt #1931's pinned safety constraints are untouched). The wiki now also documents that this is a prompt-level instruction, not a validator-enforced guarantee.signaturebecomes a first-class editable field; an explicit signature edit is no longer silently overwritten when the sender is edited afterward (unit + real-render + E2E coverage, both key-insertion orders).EditableFieldis fixed: intrinsic width instead of full-container stretch, glyph sized off a font token instead of 100%, the 44×44 hit area kept, and a focus ring added where none existed before.Folds in and supersedes #1925 (date-caption language mixing) — close it as a duplicate once this merges.
Fixes #1932
Test plan
applyOverrides.test.ts), the real-render level (realRender.test.ts), and end-to-end (E2E Scenario 23), in both key-insertion ordersreportWizardEditableContent.spec.ts— signature + multi-paragraph body edit and PDF-preview round trip (desktop light, mobile dark), the AC 2.6 sender-after-signature regression guard, and the signature reset interactionCo-Authored-By: Claude dev-team-lead noreply@anthropic.com
Co-Authored-By: Claude backend-developer noreply@anthropic.com
Co-Authored-By: Claude frontend-developer noreply@anthropic.com
Co-Authored-By: Claude translator noreply@anthropic.com
Co-Authored-By: Claude qa-integration-tester noreply@anthropic.com
Co-Authored-By: Claude e2e-test-engineer noreply@anthropic.com
Co-Authored-By: Claude ux-designer noreply@anthropic.com
Co-Authored-By: Claude product-owner noreply@anthropic.com
Co-Authored-By: Claude product-architect noreply@anthropic.com