Skip to content

refactor(frontend): メッセージ SSoT 統一と職務経歴 mutator の重複解消#317

Merged
yusuke0610 merged 3 commits into
mainfrom
refactor/fe-message-ssot-nested-update
Jun 10, 2026
Merged

refactor(frontend): メッセージ SSoT 統一と職務経歴 mutator の重複解消#317
yusuke0610 merged 3 commits into
mainfrom
refactor/fe-message-ssot-nested-update

Conversation

@yusuke0610

@yusuke0610 yusuke0610 commented Jun 9, 2026

Copy link
Copy Markdown
Owner

概要

FE_refacter レビュー(report/FE_report_20260609_2245.md)に基づく frontend 保守性リファクタ。Medium 全件 + Low(Props 型合成)+ テスト追加を適用。破壊的変更なし(表示文言の値・公開 API・戻り値契約は不変)。

変更内容

メッセージ SSoT 統一(Medium)

  • 削除確認ダイアログ・保存ボタンの日本語リテラルを UI_MESSAGES に集約。ESLint をすり抜けていた隠れた SSoT 違反を解消。
    • ConfirmDialog.tsx"削除中..." / "キャンセル"
    • CareerResumeForm.tsx(削除確認 message/confirmLabel
    • useDocumentForm.ts(保存ボタン文言 "保存中..."/"更新する"/"保存する"
    • 文言の値は従来表示と byte 一致を維持。

nested-update ヘルパ抽出(Medium)

  • useCareerExperienceMutatorsupdateExperienceAt / updateClientAt を導入し、三階層 immutable 更新の同形ボイラープレート 6 箇所を集約。戻り値契約は不変。

Props 型合成(Low)

  • 取引先配下の 7 ハンドラを formTypes.tsClientMutationHandlers 型へ切り出し、CareerExperienceEditor / ClientEditor で共有。

テスト追加

  • CareerResumeForm.test.tsx: 未ログイン保存導線(onSubmit 匿名分岐)を守る 2 ケース。
    1. 氏名空で保存 → ログイン促進を呼ばず氏名必須エラーを表示
    2. 氏名入力後に保存 → バリデーションを通し requestLogin を 1 回呼ぶ

検証

  • make lint-frontend: ✅
  • make lint-frontend-messages: ✅
  • make test-frontend: ✅(vitest 299 / 38 files、node:test 4)
  • make build-frontend: ✅(tsc -b + vite build)
  • make dupe-check: 対象重複の解消を確認(残る非テスト clone はアイコン SVG / setAtPath / Rule of Three 保留の handleSelection のみ)
  • E2E: 非該当のため未実行(新規ルート・認証・レイアウト・API 契約変更を含まない)

保留(Follow-up)

  • MarkdownDocumentViewPdfDocumentViewhandleSelection 共通化は現状 2 箇所で Rule of Three 未達のため保留。3 箇所目が出た時点で useTextSelectionFill 化。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Confirmation dialogs and common buttons now use centralized, localized UI messages.
    • Form save button labels reflect saving/update/create states via shared messages.
  • Tests

    • Added tests for unauthenticated form save flow covering validation and login prompt behavior.
  • Refactor

    • Consolidated shared mutation handler types for client/project edits.
    • Simplified nested experience/client/project state update logic for consistency.

- 削除確認ダイアログ/保存ボタンの日本語リテラルを UI_MESSAGES に集約
  (ConfirmDialog・CareerResumeForm・useDocumentForm の隠れた SSoT 違反を解消)
- useCareerExperienceMutators に updateExperienceAt/updateClientAt を導入し
  三階層 immutable 更新の重複を集約(戻り値契約は不変)
- 取引先配下のハンドラ Props を ClientMutationHandlers 型に切り出し
  CareerExperienceEditor/ClientEditor で共有
- 未ログイン保存導線(onSubmit 匿名分岐)の component テストを追加

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yusuke0610, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 40 minutes and 26 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 315cac79-197d-41d4-97a6-e0707d68f41f

📥 Commits

Reviewing files that changed from the base of the PR and between cceb239 and 5371e25.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • frontend/package.json
📝 Walkthrough

Walkthrough

The PR centralizes UI message constants, introduces a shared type for client mutation handlers, refactors the career experience mutator hook with immutable update helpers, consolidates related component props, and adds test coverage for the unauthenticated resume save flow.

Changes

Message Constants and Type Consolidation with Hook Refactoring

Layer / File(s) Summary
UI Messages for dialogs and form buttons
frontend/src/constants/messages.ts
Adds centralized message constants for delete confirmation dialogs (RESUME_DELETE_CONFIRM, RESUME_DELETE_CONFIRM_LABEL), common confirmation button labels (CONFIRM_DELETING, CONFIRM_CANCEL), and form button text (FORM_SAVING, FORM_UPDATE, FORM_SAVE).
ClientMutationHandlers type abstraction
frontend/src/formTypes.ts
Defines and exports ClientMutationHandlers, a new type that groups callbacks for client field updates, boolean toggles (has_client, is_vacation, vacation_is_current), project removal, project modal operations, and client removal.
Hook refactoring with centralized update helpers
frontend/src/hooks/career/useCareerExperienceMutators.ts
Introduces updateExperienceAt and updateClientAt helpers to centralize nested immutable updates. Refactors all experience/client/project mutators (updateExperienceField, updateClientField, updateClientHasClient, updateClientIsVacation, updateVacationIsCurrent, addClient, removeClient, removeProject, onProjectSave) to use these helpers, eliminating repetitive setForm patterns while preserving field-specific semantics.
Component props consolidation with ClientMutationHandlers
frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx, frontend/src/components/forms/CareerFormEditors/ClientEditor.tsx
Refactor CareerExperienceEditorProps and ClientEditorProps to use ClientMutationHandlers & { ... } intersection type, deriving client-related handler signatures from the shared type instead of declaring them inline. CareerExperienceEditor retains explicit onAddClient in its local props shape.
Component and hook usage of centralized UI messages
frontend/src/components/ConfirmDialog.tsx, frontend/src/components/forms/CareerResumeForm.tsx, frontend/src/hooks/useDocumentForm.ts
ConfirmDialog imports and uses UI_MESSAGES.CONFIRM_DELETING and UI_MESSAGES.CONFIRM_CANCEL for button text; CareerResumeForm uses UI_MESSAGES.RESUME_DELETE_CONFIRM and UI_MESSAGES.RESUME_DELETE_CONFIRM_LABEL for the delete dialog; useDocumentForm uses UI_MESSAGES.FORM_SAVING, UI_MESSAGES.FORM_UPDATE, and UI_MESSAGES.FORM_SAVE for save button label logic.
Test coverage for unauthenticated save flow
frontend/src/components/forms/CareerResumeForm.test.tsx
Adds comprehensive Vitest suite with Redux store setup (minimal formCache), mocked master-data API calls, and LoginPromptContext wiring. Validates two cases: empty name blocks login prompt with validation error, and valid name triggers requestLogin once without validation error.

Sequence Diagrams

sequenceDiagram
  participant Test
  participant CareerResumeForm
  participant LoginPromptContext
  Test->>CareerResumeForm: render with empty name
  Test->>CareerResumeForm: click save button
  CareerResumeForm->>Test: show FULL_NAME_REQUIRED validation
  Test->>LoginPromptContext: verify requestLogin not called
  Test->>CareerResumeForm: enter full name
  Test->>CareerResumeForm: click save button
  CareerResumeForm->>LoginPromptContext: call requestLogin
  Test->>LoginPromptContext: verify requestLogin called once
  Test->>CareerResumeForm: verify validation message absent
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

  • yusuke0610/devforge#273: Refactors useCareerExperienceMutators.ts hook implementation; this PR builds on that foundation with centralized helpers and new test coverage for the refactored mutation handlers.

Poem

🐰 I nibble strings and tidy seams,

messages shared across the beams,
handler types all hop in line,
helpers nest and keep things fine,
tests applaud with one small chime.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring objectives: consolidating message constants (メッセージ SSoT 統一) and eliminating duplication in career mutators (職務経歴 mutator の重複解消), which are the primary changes across the files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/fe-message-ssot-nested-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
frontend/src/components/forms/CareerResumeForm.test.tsx (1)

69-69: ⚡ Quick win

Avoid hardcoded placeholder literal in the test selector.

getByPlaceholderText("例: 山田 太郎") reintroduces a direct message literal and makes the test fragile to copy-only changes. Prefer a non-literal selector (role/label/test-id) or a centralized constant.

As per coding guidelines, “In frontend code, follow message management rules from .claude/rules/frontend/messages.md - message literals are forbidden and must use Single Source of Truth (SSoT)”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/forms/CareerResumeForm.test.tsx` at line 69, The test
hardcodes the placeholder string in the selector
(fireEvent.change(screen.getByPlaceholderText("例: 山田 太郎"), ...)), which violates
message SSoT rules; replace that literal with a stable selector—either import
the placeholder constant from your messages/SSoT and use it, or better switch to
a non-literal selector such as getByLabelText or getByRole (or a data-testid
like getByTestId('career-resume-name')) that targets the input rendered by the
CareerResumeForm component so the test no longer depends on the placeholder
text.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@frontend/src/components/forms/CareerResumeForm.test.tsx`:
- Line 69: The test hardcodes the placeholder string in the selector
(fireEvent.change(screen.getByPlaceholderText("例: 山田 太郎"), ...)), which violates
message SSoT rules; replace that literal with a stable selector—either import
the placeholder constant from your messages/SSoT and use it, or better switch to
a non-literal selector such as getByLabelText or getByRole (or a data-testid
like getByTestId('career-resume-name')) that targets the input rendered by the
CareerResumeForm component so the test no longer depends on the placeholder
text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bb484513-bdd4-4ee5-b4f6-d4fed19f2dfd

📥 Commits

Reviewing files that changed from the base of the PR and between 5801b50 and 0d7aeb2.

📒 Files selected for processing (9)
  • frontend/src/components/ConfirmDialog.tsx
  • frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx
  • frontend/src/components/forms/CareerFormEditors/ClientEditor.tsx
  • frontend/src/components/forms/CareerResumeForm.test.tsx
  • frontend/src/components/forms/CareerResumeForm.tsx
  • frontend/src/constants/messages.ts
  • frontend/src/formTypes.ts
  • frontend/src/hooks/career/useCareerExperienceMutators.ts
  • frontend/src/hooks/useDocumentForm.ts

yusuke0610 and others added 2 commits June 10, 2026 12:06
レビュー指摘対応。getByPlaceholderText のリテラル結合を避け、
getByRole("textbox", { name: /氏名/ }) でラベル経由に特定して
プレースホルダ文言変更に強くする。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
concurrently 経由の推移依存 shell-quote@1.8.3 が GHSA-w7jw-789q-3m8p
(critical) に該当し npm audit --audit-level=high が CI で fail していた。
package.json の overrides で patch 版 1.8.4 を強制し、concurrently の
破壊的ダウングレード(--force)を避けて解消する。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@yusuke0610
yusuke0610 merged commit 4bb1d59 into main Jun 10, 2026
18 checks passed
@yusuke0610
yusuke0610 deleted the refactor/fe-message-ssot-nested-update branch June 10, 2026 05:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant