diff --git a/backend/alembic_migrations/versions/0043_add_contact_to_resumes.py b/backend/alembic_migrations/versions/0043_add_contact_to_resumes.py new file mode 100644 index 00000000..3652f124 --- /dev/null +++ b/backend/alembic_migrations/versions/0043_add_contact_to_resumes.py @@ -0,0 +1,41 @@ +"""職務経歴書(resumes)に連絡先カラム(email / github_url)を追加する + +- resumes に email(メールアドレス・必須運用)と github_url(GitHub URL・任意)を追加。 + 既存行は連絡先未入力のため server_default="" で後方互換を保つ(次回フォーム保存時に + 必須バリデーションが効く)。 + +libSQL (SQLite 互換) は ADD COLUMN を直接サポートするため upgrade は op.add_column を使う。 +downgrade の列削除は、resumes が子テーブル(resume_experiences / resume_qualifications)から +FK 参照される親テーブルのため batch_alter_table(テーブル再作成)を使わない。素のカラムは +SQLite/libSQL 3.35+ の ALTER TABLE DROP COLUMN で直接削除できる。 + +Revision ID: 0043_add_contact_to_resumes +Revises: 0042_drop_users_hashed_password +Create Date: 2026-06-10 00:00:00.000000 +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +revision: str = "0043_add_contact_to_resumes" +down_revision: Union[str, None] = "0042_drop_users_hashed_password" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "resumes", + sa.Column("email", sa.String(length=255), nullable=False, server_default=""), + ) + op.add_column( + "resumes", + sa.Column("github_url", sa.String(length=255), nullable=False, server_default=""), + ) + + +def downgrade() -> None: + op.drop_column("resumes", "github_url") + op.drop_column("resumes", "email") diff --git a/backend/app/messages.json b/backend/app/messages.json index c4335c30..d2899488 100644 --- a/backend/app/messages.json +++ b/backend/app/messages.json @@ -48,7 +48,9 @@ "invalid_input": "入力内容を確認してください。", "date_range_invalid": "開始日は終了日より前に設定してください。", "start_date_required": "開始年月を入力してください。", - "end_date_required": "在職中でない場合は終了年月を入力してください。" + "end_date_required": "在職中でない場合は終了年月を入力してください。", + "email_invalid": "メールアドレスの形式が正しくありません。", + "github_url_invalid": "GitHub の URL は https://github.com/ で始まる形式で入力してください。" }, "server": { "internal_error": "サーバーエラーが発生しました。しばらくしてから再度お試しください。", diff --git a/backend/app/models/resume.py b/backend/app/models/resume.py index 5435ec3a..f9b53e6c 100644 --- a/backend/app/models/resume.py +++ b/backend/app/models/resume.py @@ -28,6 +28,9 @@ class Resume(Base): String(36), ForeignKey("users.id"), nullable=False, index=True ) full_name: Mapped[str] = mapped_column(String(120), nullable=False, default="") + # 連絡先(メールは必須・GitHub URL は任意)。バリデーションは schemas/resume.py の ResumeBase が正本。 + email: Mapped[str] = mapped_column(String(255), nullable=False, default="") + github_url: Mapped[str] = mapped_column(String(255), nullable=False, default="") career_summary: Mapped[str] = mapped_column(Text, nullable=False, default="") self_pr: Mapped[str] = mapped_column(Text, nullable=False) experience_rows: Mapped[list["ResumeExperience"]] = relationship( diff --git a/backend/app/repositories/resume.py b/backend/app/repositories/resume.py index eb697ef1..e13e74d9 100644 --- a/backend/app/repositories/resume.py +++ b/backend/app/repositories/resume.py @@ -41,6 +41,8 @@ class ResumeRepository(SingleUserDocumentRepository): def _apply_payload(self, entity: Resume, payload: dict[str, object]) -> None: entity.full_name = payload["full_name"] + entity.email = payload["email"] + entity.github_url = payload.get("github_url", "") entity.career_summary = payload["career_summary"] entity.self_pr = payload["self_pr"] sorted_experiences = sort_by_period_desc( diff --git a/backend/app/routers/resumes.py b/backend/app/routers/resumes.py index b305b56f..05624117 100644 --- a/backend/app/routers/resumes.py +++ b/backend/app/routers/resumes.py @@ -31,6 +31,8 @@ def _resume_to_payload(resume: Resume) -> dict: """Resume ORM から PDF/Markdown 生成用 payload を組み立てる。""" return { "full_name": resume.full_name, + "email": resume.email, + "github_url": resume.github_url, "career_summary": resume.career_summary, "self_pr": resume.self_pr, "experiences": resume.experiences, diff --git a/backend/app/schemas/resume.py b/backend/app/schemas/resume.py index b15b335d..57a3ec10 100644 --- a/backend/app/schemas/resume.py +++ b/backend/app/schemas/resume.py @@ -12,11 +12,19 @@ ``Client.vacation_*``(期間・詳細)を使う。 """ +import re from datetime import datetime from typing import Literal from uuid import UUID -from pydantic import BaseModel, ConfigDict, Field, field_serializer, model_validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + field_serializer, + field_validator, + model_validator, +) from ..core.date_utils import to_jst from ..core.messages import get_error @@ -219,13 +227,39 @@ def validate_dates(self) -> "Experience": return self +# 簡易メール形式(RFC 5322 完全準拠ではなく UX 優先の最小チェック)。FE 側 payloadBuilders と一致させる。 +_EMAIL_PATTERN = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$") +# GitHub アカウント URL の接頭辞。値があるときのみ前方一致で検証する。 +_GITHUB_URL_PREFIX = "https://github.com/" + + class ResumeBase(BaseModel): full_name: str = Field(min_length=1, max_length=120) + email: str = Field(min_length=1, max_length=255) + github_url: str = Field(default="", max_length=255) career_summary: str = Field(min_length=1, max_length=2000) self_pr: str = Field(min_length=1, max_length=2000) experiences: list[Experience] = Field(default_factory=list) qualifications: list[ResumeQualificationItem] = Field(default_factory=list) + @field_validator("email") + @classmethod + def validate_email(cls, value: str) -> str: + """メールアドレスの簡易形式チェック。前後空白を除去して正規化し、不正なら 422(日本語)で返す。""" + trimmed = value.strip() + if not _EMAIL_PATTERN.match(trimmed): + raise ValueError(get_error("validation.email_invalid")) + return trimmed + + @field_validator("github_url") + @classmethod + def validate_github_url(cls, value: str) -> str: + """GitHub URL は任意。値があるときだけ ``https://github.com/`` 始まりを要求する。前後空白は除去する。""" + stripped = value.strip() + if stripped and not stripped.startswith(_GITHUB_URL_PREFIX): + raise ValueError(get_error("validation.github_url_invalid")) + return stripped + class ResumeCreate(ResumeBase): pass diff --git a/backend/app/services/markdown/generators/resume_generator.py b/backend/app/services/markdown/generators/resume_generator.py index 131b5f4f..0029a3b6 100644 --- a/backend/app/services/markdown/generators/resume_generator.py +++ b/backend/app/services/markdown/generators/resume_generator.py @@ -19,6 +19,15 @@ def build_resume_markdown(payload: dict[str, Any]) -> str: full_name = payload.get("full_name", "") if full_name: lines.append(field_line("氏名", full_name)) + # 連絡先(メールは必須・GitHub URL は任意。値があるときだけ行を出す) + email = payload.get("email", "") + if email: + lines.append(field_line("email", email)) + github_url = payload.get("github_url", "") + if github_url: + # github URL は Markdown リンク記法でハイパーリンク化する(GitHub/各種ビューアで + # 青・下線のリンク表示になる)。href は schema で https://github.com/ 始まりに検証済み。 + lines.append(field_line("github", f"[{github_url}]({github_url})")) lines.append("") qualifications = payload.get("qualifications", []) diff --git a/backend/app/services/pdf/generators/resume_generator.py b/backend/app/services/pdf/generators/resume_generator.py index ff93badb..26e983e7 100644 --- a/backend/app/services/pdf/generators/resume_generator.py +++ b/backend/app/services/pdf/generators/resume_generator.py @@ -220,6 +220,24 @@ def _build_html(resume: dict) -> str: f'
', ) + # 連絡先(メールは必須・GitHub URL は任意。値があるときだけ行を出す) + email = resume.get("email") or "" + if email: + parts.append( + f'', + ) + github_url = resume.get("github_url") or "" + if github_url: + # github URL はハイパーリンク表示(青・下線)にする。href は schema で + # https://github.com/ 始まりに検証済みのため安全。data-fp は左右 diff の + # ハイライト対象として span ではなく に直接付与する(annotateHtml は + # [data-fp] 全要素を対象にするため要素種別は問わない)。 + esc_url = _esc(github_url) + parts.append( + f'', + ) + # 記載日(日本時間) today = datetime.now(JST) parts.append( diff --git a/backend/app/services/pdf/templates/resume.css b/backend/app/services/pdf/templates/resume.css index e868d469..fa50a614 100644 --- a/backend/app/services/pdf/templates/resume.css +++ b/backend/app/services/pdf/templates/resume.css @@ -35,6 +35,12 @@ h1 { margin-bottom: 4mm; } +/* 連絡先の github URL をハイパーリンク表示(青・下線)にする。 */ +.meta-link { + color: #0066cc; + text-decoration: underline; +} + h2 { font-size: 12pt; border-bottom: 2px solid #333; diff --git a/backend/tests/auth/test_endpoints.py b/backend/tests/auth/test_endpoints.py index 41050703..ca1fdf75 100644 --- a/backend/tests/auth/test_endpoints.py +++ b/backend/tests/auth/test_endpoints.py @@ -129,6 +129,7 @@ def test_me_returns_current_user(client) -> None: def _csrf_resume_payload() -> dict: return { "full_name": "テスト", + "email": "test@example.com", "career_summary": "要約", "self_pr": "自己PR", "experiences": [], diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index f0229b66..aaf30c21 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -166,6 +166,7 @@ def make_resume_payload(**overrides) -> dict: """ payload: dict = { "full_name": "山田 太郎", + "email": "yamada@example.com", "career_summary": "キャリアサマリー", "self_pr": "自己PR", "experiences": [ diff --git a/backend/tests/security/_helpers.py b/backend/tests/security/_helpers.py index 19fa7f94..b9584419 100644 --- a/backend/tests/security/_helpers.py +++ b/backend/tests/security/_helpers.py @@ -22,6 +22,7 @@ RESUME_PAYLOAD: dict = { "full_name": "山田 太郎", + "email": "yamada@example.com", "career_summary": "キャリアサマリー", "self_pr": "自己PR", "experiences": [], diff --git a/backend/tests/test_endpoints.py b/backend/tests/test_endpoints.py index 2682891a..a97f4050 100644 --- a/backend/tests/test_endpoints.py +++ b/backend/tests/test_endpoints.py @@ -1,7 +1,7 @@ import pytest from fastapi.testclient import TestClient -from conftest import auth_header +from conftest import auth_header, make_resume_payload # ── CRUD: Auth required (401 without token) ──────────────────── @@ -30,6 +30,7 @@ def test_resume_crud(client: TestClient) -> None: "/api/resumes", json={ "full_name": "田中太郎", + "email": "tanaka@example.com", "career_summary": "キャリアサマリー", "self_pr": "自己PR", "experiences": [], @@ -50,6 +51,7 @@ def test_resume_crud(client: TestClient) -> None: f"/api/resumes/{resume_id}", json={ "full_name": "田中花子", + "email": "tanaka@example.com", "career_summary": "更新済みサマリー", "self_pr": "自己PR", "experiences": [], @@ -62,6 +64,40 @@ def test_resume_crud(client: TestClient) -> None: assert resp.json()["full_name"] == "田中花子" +def test_resume_contact_round_trips(client: TestClient) -> None: + """メール・GitHub URL が作成→取得で往復することを確認する。""" + headers = auth_header(client, "resume-contact-user") + payload = make_resume_payload( + full_name="連絡先 太郎", + email="contact@example.com", + github_url="https://github.com/contact-taro", + ) + resp = client.post("/api/resumes", json=payload, headers=headers) + assert resp.status_code == 201, resp.text + + resp = client.get("/api/resumes/latest", headers=headers) + assert resp.status_code == 200 + body = resp.json() + assert body["email"] == "contact@example.com" + assert body["github_url"] == "https://github.com/contact-taro" + + +def test_resume_rejects_invalid_email_via_api(client: TestClient) -> None: + """不正なメール形式は 422 で拒否される。""" + headers = auth_header(client, "resume-bademail-user") + payload = make_resume_payload(email="not-an-email") + resp = client.post("/api/resumes", json=payload, headers=headers) + assert resp.status_code == 422 + + +def test_resume_rejects_invalid_github_url_via_api(client: TestClient) -> None: + """github.com 以外の GitHub URL は 422 で拒否される。""" + headers = auth_header(client, "resume-badgithub-user") + payload = make_resume_payload(github_url="https://gitlab.com/taro") + resp = client.post("/api/resumes", json=payload, headers=headers) + assert resp.status_code == 422 + + def test_resume_qualifications_sorted_asc(client: TestClient) -> None: """資格一覧が取得日昇順で返ることを確認する。""" headers = auth_header(client, "resume-sort-user") @@ -70,6 +106,7 @@ def test_resume_qualifications_sorted_asc(client: TestClient) -> None: "/api/resumes", json={ "full_name": "ソート確認", + "email": "sort@example.com", "career_summary": "要約", "self_pr": "自己PR", "experiences": [], @@ -97,6 +134,7 @@ def test_resume_qualification_date_is_year_month(client: TestClient) -> None: "/api/resumes", json={ "full_name": "年月確認", + "email": "ym@example.com", "career_summary": "要約", "self_pr": "自己PR", "experiences": [], @@ -119,6 +157,7 @@ def test_resume_round_trips_nested_structure(client: TestClient) -> None: payload = { "full_name": "山田 太郎", + "email": "yamada@example.com", "career_summary": "キャリアサマリー", "self_pr": "自己PR", "experiences": [ @@ -179,6 +218,7 @@ def test_resume_round_trips_non_it_and_vacation(client: TestClient) -> None: payload = { "full_name": "佐藤 花子", + "email": "sato@example.com", "career_summary": "キャリアサマリー", "self_pr": "自己PR", "experiences": [ @@ -284,6 +324,7 @@ def test_resume_get_by_id(client: TestClient) -> None: "/api/resumes", json={ "full_name": "山田 太郎", + "email": "yamada@example.com", "career_summary": "サマリー", "self_pr": "自己PR", "experiences": [], @@ -318,6 +359,7 @@ def test_resume_preview_returns_annotated_html(client: TestClient) -> None: "/api/resumes/preview", json={ "full_name": "山田太郎", + "email": "yamada@example.com", "career_summary": "キャリアサマリー", "self_pr": "自己PR", "experiences": [ @@ -374,6 +416,7 @@ def test_resume_preview_validation_error(client: TestClient) -> None: "/api/resumes/preview", json={ "full_name": "", + "email": "yamada@example.com", "career_summary": "x", "self_pr": "y", "experiences": [], diff --git a/backend/tests/test_markdown_generator.py b/backend/tests/test_markdown_generator.py index 14d375a4..a7c8a54c 100644 --- a/backend/tests/test_markdown_generator.py +++ b/backend/tests/test_markdown_generator.py @@ -34,6 +34,42 @@ def test_capital_unit_defaults_to_sen_man_when_missing() -> None: assert "5千万円" in md +def test_contact_fields_rendered_when_present() -> None: + md = build_resume_markdown( + { + "full_name": "山田 太郎", + "email": "yamada@example.com", + "github_url": "https://github.com/yamada", + "career_summary": "職務要約", + "self_pr": "自己PR", + "qualifications": [], + "experiences": [], + } + ) + assert "yamada@example.com" in md + # ラベルは email / github(小文字)。 + assert "**email:**" in md + assert "**github:**" in md + # github URL は Markdown リンク記法でハイパーリンク化する。 + assert "[https://github.com/yamada](https://github.com/yamada)" in md + + +def test_contact_fields_omitted_when_empty() -> None: + # github URL は任意。空なら github 行を出さない(email は必須運用だが空入力にも耐える)。 + md = build_resume_markdown( + { + "full_name": "山田 太郎", + "email": "yamada@example.com", + "github_url": "", + "career_summary": "職務要約", + "self_pr": "自己PR", + "qualifications": [], + "experiences": [], + } + ) + assert "**github:**" not in md + + def _it_experience() -> dict: return { "company": "Example株式会社", diff --git a/backend/tests/test_pdf_generator.py b/backend/tests/test_pdf_generator.py index abf072ca..423bdfde 100644 --- a/backend/tests/test_pdf_generator.py +++ b/backend/tests/test_pdf_generator.py @@ -135,6 +135,44 @@ def test_build_html_annotates_data_fp() -> None: assert 'data-unit="qualifications.0"' in html +def test_build_html_renders_contact_fields() -> None: + """メール・GitHub URL は値があるとき data-fp 付きで出力され、空なら省略される。""" + html = _build_html( + { + "full_name": "山田太郎", + "email": "yamada@example.com", + "github_url": "https://github.com/yamada", + "career_summary": "要約", + "self_pr": "PR", + "qualifications": [], + "experiences": [], + } + ) + assert 'data-fp="email"' in html + assert "yamada@example.com" in html + # ラベルは email / github(小文字)で出す。 + assert "email " in html + assert "github " in html + assert 'data-fp="github_url"' in html + assert "https://github.com/yamada" in html + # github URL はハイパーリンク(青・下線)として で出す。 + assert ' None: """在職中の期間は「現在」を is_current に、休暇期間は各日付フィールドに紐づける。""" payload = { diff --git a/backend/tests/test_schemas.py b/backend/tests/test_schemas.py index 9f48c52b..cdee6708 100644 --- a/backend/tests/test_schemas.py +++ b/backend/tests/test_schemas.py @@ -87,6 +87,7 @@ def test_unknown_category_is_rejected() -> None: def test_resume_requires_career_summary() -> None: payload = { "full_name": "山田 太郎", + "email": "yamada@example.com", "self_pr": "自己PR", "experiences": [experience_payload()], "qualifications": [], @@ -98,6 +99,7 @@ def test_resume_requires_career_summary() -> None: def test_resume_requires_full_name() -> None: payload = { + "email": "yamada@example.com", "career_summary": "要約", "self_pr": "自己PR", "experiences": [experience_payload()], @@ -108,6 +110,57 @@ def test_resume_requires_full_name() -> None: ResumeCreate(**payload) +def _resume_payload(**overrides) -> dict: + """メール・GitHub URL 検証用の最小 ResumeCreate payload。""" + payload = { + "full_name": "山田 太郎", + "email": "yamada@example.com", + "career_summary": "要約", + "self_pr": "自己PR", + "experiences": [], + "qualifications": [], + } + payload.update(overrides) + return payload + + +def test_resume_requires_email() -> None: + """email は必須。欠落で ValidationError。""" + payload = _resume_payload() + del payload["email"] + with pytest.raises(ValidationError): + ResumeCreate(**payload) + + +@pytest.mark.parametrize("bad_email", ["", "not-an-email", "foo@bar", "a@b.", "no domain.com"]) +def test_resume_rejects_invalid_email(bad_email: str) -> None: + """不正なメール形式は ValidationError。""" + with pytest.raises(ValidationError): + ResumeCreate(**_resume_payload(email=bad_email)) + + +def test_resume_accepts_valid_github_url() -> None: + """https://github.com/ 始まりの GitHub URL は受理される。""" + resume = ResumeCreate(**_resume_payload(github_url="https://github.com/yamada")) + assert resume.github_url == "https://github.com/yamada" + + +def test_resume_allows_empty_github_url() -> None: + """GitHub URL は任意。空文字(デフォルト)でも受理される。""" + resume = ResumeCreate(**_resume_payload()) + assert resume.github_url == "" + + +@pytest.mark.parametrize( + "bad_url", + ["https://github.com/yamada", "https://gitlab.com/yamada", "github.com/yamada", "ftp://x"], +) +def test_resume_rejects_invalid_github_url(bad_url: str) -> None: + """github.com 以外・スキーム不正の URL は ValidationError。""" + with pytest.raises(ValidationError): + ResumeCreate(**_resume_payload(github_url=bad_url)) + + def test_project_migrates_scale_to_team() -> None: """旧形式 scale → team に自動変換されることを検証する。""" proj = Project( diff --git a/frontend/e2e/auth.spec.ts b/frontend/e2e/auth.spec.ts index b50b1435..745826d2 100644 --- a/frontend/e2e/auth.spec.ts +++ b/frontend/e2e/auth.spec.ts @@ -159,6 +159,8 @@ test.describe("未認証ユーザー(お試し入力)", () => { body: JSON.stringify({ id: "new-resume-id", full_name: createdName ?? "", + email: "yamada@example.com", + github_url: "", career_summary: "", self_pr: "", experiences: [], @@ -175,6 +177,8 @@ test.describe("未認証ユーザー(お試し入力)", () => { "career_draft", JSON.stringify({ full_name: "山田太郎", + email: "yamada@example.com", + github_url: "", career_summary: "テスト要約", self_pr: "テスト自己PR", experiences: [ diff --git a/frontend/e2e/career-dirty-indicator.spec.ts b/frontend/e2e/career-dirty-indicator.spec.ts index 021f1342..159a2540 100644 --- a/frontend/e2e/career-dirty-indicator.spec.ts +++ b/frontend/e2e/career-dirty-indicator.spec.ts @@ -16,6 +16,8 @@ async function setupResumeApi(page: Page) { const baseResume = { id: "resume-1", full_name: "山田 太郎", + email: "yamada@example.com", + github_url: "", career_summary: "サマリー", self_pr: "自己PR", experiences: [ @@ -94,6 +96,8 @@ test.describe("職務経歴書 未保存マーク", () => { const baseResumeWithProject = { id: "resume-1", full_name: "山田 太郎", + email: "yamada@example.com", + github_url: "", career_summary: "サマリー", self_pr: "自己PR", experiences: [ diff --git a/frontend/e2e/career-field-modal.spec.ts b/frontend/e2e/career-field-modal.spec.ts index c287d969..78f50d51 100644 --- a/frontend/e2e/career-field-modal.spec.ts +++ b/frontend/e2e/career-field-modal.spec.ts @@ -28,6 +28,8 @@ async function setupResumeApi(page: Page) { body: JSON.stringify({ id: "resume-1", full_name: "山田 太郎", + email: "yamada@example.com", + github_url: "", career_summary: "初期サマリー", self_pr: "初期自己PR", experiences: [], diff --git a/frontend/src/api/generated.ts b/frontend/src/api/generated.ts index 074970b6..a32c5a0e 100644 --- a/frontend/src/api/generated.ts +++ b/frontend/src/api/generated.ts @@ -1321,10 +1321,17 @@ export interface components { ResumeCreate: { /** Career Summary */ career_summary: string; + /** Email */ + email: string; /** Experiences */ experiences?: components["schemas"]["Experience-Input"][]; /** Full Name */ full_name: string; + /** + * Github Url + * @default + */ + github_url: string; /** Qualifications */ qualifications?: components["schemas"]["ResumeQualificationItem"][]; /** Self Pr */ @@ -1357,10 +1364,17 @@ export interface components { career_summary: string; /** Created At */ created_at: string; + /** Email */ + email: string; /** Experiences */ experiences?: components["schemas"]["Experience-Output"][]; /** Full Name */ full_name: string; + /** + * Github Url + * @default + */ + github_url: string; /** * Id * Format: uuid @@ -1377,10 +1391,17 @@ export interface components { ResumeUpdate: { /** Career Summary */ career_summary: string; + /** Email */ + email: string; /** Experiences */ experiences?: components["schemas"]["Experience-Input"][]; /** Full Name */ full_name: string; + /** + * Github Url + * @default + */ + github_url: string; /** Qualifications */ qualifications?: components["schemas"]["ResumeQualificationItem"][]; /** Self Pr */ diff --git a/frontend/src/components/forms/CareerDiffModal.module.css b/frontend/src/components/forms/CareerDiffModal.module.css index 1edd53f8..029f2562 100644 --- a/frontend/src/components/forms/CareerDiffModal.module.css +++ b/frontend/src/components/forms/CareerDiffModal.module.css @@ -41,6 +41,14 @@ min-height: 0; display: grid; grid-template-columns: 1fr 1fr 320px; + /* + * 行を明示的に minmax(0, 1fr) で固定する。未指定だと暗黙の auto 行になり、 + * 変更点が多いときにサイドバーの中身(.list)の高さに合わせて行が伸び、 + * モーダル(92vh / overflow:hidden)からはみ出した分が clip されてロール + * バックボタンや操作ボタンが見えなくなる。0 を下限にすることで中身では + * 伸びず、.list の overflow-y:auto が効いて全項目をスクロールで辿れる。 + */ + grid-template-rows: minmax(0, 1fr); gap: 0.75rem; padding: 0.5rem 1rem 0.75rem; } diff --git a/frontend/src/components/forms/CareerResumeForm.tsx b/frontend/src/components/forms/CareerResumeForm.tsx index d930b1ac..1cf029e1 100644 --- a/frontend/src/components/forms/CareerResumeForm.tsx +++ b/frontend/src/components/forms/CareerResumeForm.tsx @@ -413,14 +413,18 @@ export function CareerResumeForm({ isAuthenticated }: { isAuthenticated: boolean{validationError}
} - {/* 基本情報: 氏名・職務要約 */} + {/* 基本情報: 氏名・連絡先・職務要約 */}