diff --git a/backend/app/services/pdf/generators/resume_generator.py b/backend/app/services/pdf/generators/resume_generator.py
index 20500efa..4e967b67 100644
--- a/backend/app/services/pdf/generators/resume_generator.py
+++ b/backend/app/services/pdf/generators/resume_generator.py
@@ -166,12 +166,15 @@ def _build_project_html(project, fp: str) -> str:
# 体制は表のカラムではなく役割行に併記する(line2 で処理済み)。
# 左列見出しに構造化情報(info_header)を入れ、独立ヘッダーは廃止した。
+ # 見出し行は thead ではなく tbody の先頭行に置く。thead だと WeasyPrint が
+ # ページ跨ぎ時に各ページ先頭で見出しを繰り返してしまうため、tbody 内の通常行に
+ # することで見出しは 1 回だけ表示し、長い詳細はページ境界で見切れる挙動にする。
return (
f'
'
f'
'
- f'| {info_header} | '
- f"スキルセット |
"
- f'| {left_content} | '
+ f'
| {info_header} | '
+ f"スキルセット |
"
+ f'| {left_content} | '
f'{right_content} |
'
f"
"
)
diff --git a/backend/app/services/pdf/templates/resume.css b/backend/app/services/pdf/templates/resume.css
index b2275d92..82bbf891 100644
--- a/backend/app/services/pdf/templates/resume.css
+++ b/backend/app/services/pdf/templates/resume.css
@@ -127,7 +127,8 @@ h2 {
/* 案件が残りスペースに収まらない時に丸ごと次ページへ送られると、
枠付き company 内に大きな余白が残る。ページを詰めるため分割を許可する
(長い案件はページ境界で割れるが、余白を残すより詰めることを優先)。
- 分割時の列見出しは project-table の thead で繰り返す。 */
+ 見出し行は tbody の通常行に置いており、ページ跨ぎ時に繰り返さない
+ (詳細はページ境界で見切れる)。 */
}
.project-table {
diff --git a/backend/tests/test_pdf_generator.py b/backend/tests/test_pdf_generator.py
index a2e5e4e9..26971e1d 100644
--- a/backend/tests/test_pdf_generator.py
+++ b/backend/tests/test_pdf_generator.py
@@ -180,6 +180,9 @@ def test_project_structured_info_in_header_cell() -> None:
# 構造化情報は左列見出しセル(th.proj-info)に入る
assert 'class="proj-info"' in html
+ # 見出し行は thead に置かない(thead だと WeasyPrint がページ跨ぎ時に
+ # 各ページ先頭で見出しを繰り返すため)。tbody の通常行に置く。
+ assert "" not in html
# 旧来の独立ヘッダーと「業務内容」ラベルは廃止
assert "project-header" not in html
assert "業務内容 | " not in html