# Report Student Header Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add a consistent PDF/HTML page header to both exam sprint reports that displays the student name from `StudentName`, with the logo area left empty for a future asset. **Architecture:** The report renderers already own final HTML generation. Add template placeholders for `studentName` and `generatedAtText`, fill them from the existing report content or payload contract, and keep escaping at the renderer boundary. The left logo slot is CSS-only empty space in both templates. **Tech Stack:** Java 17 records, Spring component renderers, static HTML templates, JUnit 5, AssertJ. --- ## Files - Modify: `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRendererTest.java` - Modify: `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRendererTest.java` - Modify: `abilities/exam-sprint/infrastructure/src/main/resources/templates/outlook-exam-sprint-report-template.html` - Modify: `abilities/exam-sprint/infrastructure/src/main/resources/templates/achievement-exam-sprint-report-template.html` - Modify: `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRenderer.java` - Modify: `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRenderer.java` ## Task 1: Add failing renderer tests for the shared page header - [ ] **Step 1: Add Outlook header assertions** In `ClasspathOutlookExamSprintReportRendererTest`, add a test that renders `callerVocabularyPayload()` and asserts: ```java assertThat(html) .contains("class=\"report-header\"") .contains("class=\"header-logo\"") .contains("个人学情报告") .contains("class=\"header-student-name\">20260318测试") .contains("class=\"header-generated-at\"") .doesNotContain("{{studentName}}") .doesNotContain("{{generatedAtText}}"); ``` - [ ] **Step 2: Add Achievement header assertions** In `ClasspathAchievementExamSprintReportRendererTest`, add a test that renders `sampleContent()` and asserts: ```java assertThat(html) .contains("class=\"report-header\"") .contains("class=\"header-logo\"") .contains("个人学情报告") .contains("class=\"header-student-name\">测试临考") .contains("class=\"header-generated-at\"") .doesNotContain("{{studentName}}") .doesNotContain("{{generatedAtText}}"); ``` - [ ] **Step 3: Add escaping coverage** Extend existing escaping tests so malicious student names are escaped. For achievement, create content with `studentName` set to `测试` and assert escaped text appears. For outlook, mutate `StudentName` in the JsonNode to the same value and assert escaped text appears. - [ ] **Step 4: Run focused tests and verify RED** Run: ```bash ./mvnw -pl abilities/exam-sprint/infrastructure -Dtest=ClasspathOutlookExamSprintReportRendererTest,ClasspathAchievementExamSprintReportRendererTest test ``` Expected: tests fail because `report-header`, `studentName`, and `generatedAtText` placeholders do not exist or are not replaced yet. ## Task 2: Implement template placeholders and renderer replacements - [ ] **Step 1: Add shared header markup to both templates** Insert this block immediately after `
` in both templates: ```html
个人学情报告
{{studentName}}
{{generatedAtText}}
``` Add CSS before `` in both templates: ```css .report-header { display: table; width: 100%; table-layout: fixed; border-bottom: 3px solid #111; margin-bottom: 28px; padding-bottom: 10px; } .header-logo, .header-main, .header-generated-at { display: table-cell; vertical-align: top; } .header-logo { width: 180px; } .header-main { text-align: center; color: #68768a; } .header-report-type { font-size: 13px; line-height: 1.5; } .header-student-name { margin-top: 4px; font-size: 13px; line-height: 1.5; } .header-generated-at { width: 260px; color: #68768a; font-size: 12px; line-height: 1.5; text-align: right; white-space: nowrap; } ``` - [ ] **Step 2: Fill placeholders in Outlook renderer** In `ClasspathOutlookExamSprintReportRenderer.render`, add replacements for `{{studentName}}` and `{{generatedAtText}}` before returning the template string: ```java .replace("{{studentName}}", escape(payloadContract.studentName())) .replace("{{generatedAtText}}", escape(formatGeneratedAt(generatedAt))) ``` Add a private formatter: ```java private String formatGeneratedAt(Instant generatedAt) { return generatedAt == null ? "" : generatedAt.toString(); } ``` Reuse the existing `escape(String value)` method in this renderer. - [ ] **Step 3: Fill placeholders in Achievement renderer** In `ClasspathAchievementExamSprintReportRenderer.placeholders`, add: ```java placeholders.put("studentName", escape(reportContent.studentName())); placeholders.put("generatedAtText", escape(formatGeneratedAt(generatedAt))); ``` Pass `generatedAt` into `placeholders(...)` from `render(...)`. Add a private formatter: ```java private String formatGeneratedAt(Instant generatedAt) { return generatedAt == null ? "" : generatedAt.toString(); } ``` - [ ] **Step 4: Run focused tests and verify GREEN** Run: ```bash ./mvnw -pl abilities/exam-sprint/infrastructure -Dtest=ClasspathOutlookExamSprintReportRendererTest,ClasspathAchievementExamSprintReportRendererTest test ``` Expected: both renderer test classes pass. ## Task 3: Final verification - [ ] **Step 1: Run module tests** Run: ```bash ./mvnw -pl abilities/exam-sprint/infrastructure test ``` Expected: infrastructure module tests pass. - [ ] **Step 2: Inspect diff** Run: ```bash git diff -- abilities/exam-sprint/infrastructure/src/main/resources/templates/outlook-exam-sprint-report-template.html abilities/exam-sprint/infrastructure/src/main/resources/templates/achievement-exam-sprint-report-template.html abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRenderer.java abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRenderer.java abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRendererTest.java abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRendererTest.java ``` Expected: only the planned header, placeholder replacement, and tests changed. ## Self-review - Spec coverage: both reports display `StudentName` in the page header; logo area is empty; generated time is present; escaping is tested. - Placeholder scan: no `TBD`, `TODO`, or unresolved implementation placeholders in this plan. - Type consistency: all referenced classes and paths exist in the current project; `generatedAt` is already part of renderer signatures.