From 095e54f0444bb758bad802c26e3101e3abb65b3a Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Fri, 19 Jun 2026 19:46:44 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=E3=83=AB=E3=83=BC=E3=83=88=20front?= =?UTF-8?q?end/=20=E3=83=87=E3=82=A3=E3=83=AC=E3=82=AF=E3=83=88=E3=83=AA?= =?UTF-8?q?=E3=82=92=20web/=20=E3=81=AB=E3=83=AA=E3=83=8D=E3=83=BC?= =?UTF-8?q?=E3=83=A0=EF=BC=88=E3=83=97=E3=83=A9=E3=83=83=E3=83=88=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=BC=E3=83=A0=E5=88=A5=E6=A7=8B=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将来のプラットフォーム分割(web / mobile 等)を見据え、ルート直下の frontend/ を web/ にリネームし、関連する運用・ツーリングの命名も統一する。 PR #332(マルチプロバイダ LLM)マージ後の main を起点に再構築。 変更: - ディレクトリ: frontend/ → web/(git rename) - ビルド/CI: Makefile・ci.yml・.jscpd.json・.gitignore のパスを web/ に追従。 codegen 出力は web/src/api/generated.ts - make ターゲット: lint-frontend→lint-web, test-frontend→test-web, build-frontend→build-web 等(CI/docs の呼び出しも追従) - スクリプト: scripts/lint-frontend-messages.sh → lint-web-messages.sh - AI ツーリング: .claude/rules/frontend/ → .claude/rules/web/(paths frontmatter を web/**)、skills FE_refacter/FE_apply → WEB_refacter/WEB_apply - docs/CLAUDE/AGENTS/CONTRIBUTING/PR テンプレ・README: パス/ターゲット名を追従 - backend: ディレクトリを指すパス参照のみ追従(frontend/src → web/src の コメント、test_error_code_sync の参照パス) - web/package.json name: devforge-web 意図的に据え置いたもの: - ADR(docs/adr/*): 不変の意思決定記録。ファイル名・内容とも変更しない - backend のクライアントアプリ概念を指す識別子(frontend_url / get_frontend_origin / auth.frontend_origin_not_allowed / frontend_framework タクソノミ等): ディレクトリではなく「web クライアント」概念のため温存 検証: make ci green(backend lint+test 582、web lint+test 385、build) Co-Authored-By: Claude Opus 4.8 --- .claude/CLAUDE.md | 22 ++-- .claude/rules/backend/auth-security.md | 2 +- .claude/rules/common/duplication.md | 22 ++-- .claude/rules/infra/opentofu.md | 2 +- .claude/rules/infra/test.md | 2 +- .claude/rules/security.md | 4 +- .../rules/{frontend => web}/architecture.md | 4 +- .../{frontend => web}/component-design.md | 4 +- .claude/rules/{frontend => web}/messages.md | 38 +++---- .claude/rules/{frontend => web}/test.md | 18 +-- .claude/rules/{frontend => web}/typescript.md | 10 +- .claude/skills/SEC_apply/SKILL.md | 20 ++-- .claude/skills/SEC_review/SKILL.md | 22 ++-- .../skills/{FE_apply => WEB_apply}/SKILL.md | 62 +++++----- .../{FE_refacter => WEB_refacter}/SKILL.md | 42 +++---- .claude/skills/XR_apply/SKILL.md | 30 ++--- .claude/skills/XR_refacter/SKILL.md | 38 +++---- .github/PULL_REQUEST_TEMPLATE.md | 8 +- .github/workflows/ci.yml | 106 +++++++++--------- .gitignore | 20 ++-- .jscpd.json | 8 +- AGENTS.md | 10 +- Makefile | 96 ++++++++-------- backend/app/core/errors.py | 8 +- backend/app/schemas/billing.py | 2 +- backend/app/schemas/blog.py | 2 +- backend/app/schemas/master_data.py | 2 +- backend/app/schemas/resume.py | 2 +- backend/scripts/export_openapi.py | 2 +- backend/tests/test_error_code_sync.py | 4 +- docs/deployment.md | 4 +- docs/development.md | 18 +-- ...ntend-messages.sh => lint-web-messages.sh} | 10 +- {frontend => web}/.npmrc | 0 {frontend => web}/.prettierignore | 0 {frontend => web}/.prettierrc | 0 {frontend => web}/e2e/agent-chat.spec.ts | 0 {frontend => web}/e2e/auth.spec.ts | 0 {frontend => web}/e2e/billing.spec.ts | 0 .../e2e/career-dirty-indicator.spec.ts | 0 .../e2e/career-field-modal.spec.ts | 0 .../e2e/career-import-assist.spec.ts | 0 {frontend => web}/e2e/github-link.spec.ts | 0 {frontend => web}/e2e/helpers/auth.ts | 0 {frontend => web}/e2e/navigation.spec.ts | 0 {frontend => web}/e2e/notifications.spec.ts | 0 {frontend => web}/eslint.config.js | 0 {frontend => web}/functions/_middleware.ts | 0 {frontend => web}/index.html | 0 {frontend => web}/package-lock.json | 0 {frontend => web}/package.json | 2 +- {frontend => web}/playwright.config.d.ts | 0 {frontend => web}/playwright.config.js | 0 {frontend => web}/playwright.config.ts | 0 {frontend => web}/scripts/audit-check.mjs | 0 {frontend => web}/scripts/gen-redirects.mjs | 0 {frontend => web}/scripts/gen-types.mjs | 0 {frontend => web}/src/App.module.css | 0 {frontend => web}/src/App.tsx | 0 {frontend => web}/src/api/agent.ts | 0 {frontend => web}/src/api/ai-resume.ts | 0 {frontend => web}/src/api/auth.ts | 0 {frontend => web}/src/api/billing.ts | 0 {frontend => web}/src/api/blog.ts | 0 {frontend => web}/src/api/client.test.ts | 0 {frontend => web}/src/api/client.ts | 0 {frontend => web}/src/api/download.ts | 0 {frontend => web}/src/api/generated.ts | 0 {frontend => web}/src/api/githubLink.ts | 0 {frontend => web}/src/api/index.ts | 0 {frontend => web}/src/api/master-data.ts | 0 {frontend => web}/src/api/notifications.ts | 0 {frontend => web}/src/api/paths.ts | 0 {frontend => web}/src/api/resumes.ts | 0 {frontend => web}/src/api/types.ts | 0 .../components/CareerUnsavedGuard.test.tsx | 0 .../src/components/CareerUnsavedGuard.tsx | 0 .../src/components/ConfirmDialog.module.css | 0 .../src/components/ConfirmDialog.tsx | 0 .../src/components/ErrorBoundary.module.css | 0 .../src/components/ErrorBoundary.tsx | 0 .../src/components/LoadingOverlay.tsx | 0 .../components/NotificationBell.module.css | 0 .../src/components/NotificationBell.tsx | 0 .../src/components/SidebarLayout.tsx | 0 .../src/components/UserMenu.module.css | 0 {frontend => web}/src/components/UserMenu.tsx | 0 .../agent/AgentModelBadge.module.css | 0 .../src/components/agent/AgentModelBadge.tsx | 0 .../agent/ModelSelectModal.module.css | 0 .../agent/ModelSelectModal.test.tsx | 0 .../src/components/agent/ModelSelectModal.tsx | 0 .../src/components/auth/LoginForm.module.css | 0 .../src/components/auth/LoginForm.tsx | 0 .../auth/LoginPromptModal.module.css | 0 .../components/auth/LoginPromptModal.test.tsx | 0 .../src/components/auth/LoginPromptModal.tsx | 0 .../components/auth/LoginPromptProvider.tsx | 0 .../src/components/auth/loginPromptContext.ts | 0 .../components/billing/BillingView.module.css | 0 .../components/billing/BillingView.test.tsx | 0 .../src/components/billing/BillingView.tsx | 0 .../billing/CreditBalanceBadge.module.css | 0 .../components/billing/CreditBalanceBadge.tsx | 0 .../billing/CreditBalanceProvider.test.tsx | 0 .../billing/CreditBalanceProvider.tsx | 0 .../billing/CreditPurchaseForm.module.css | 0 .../billing/CreditPurchaseForm.test.tsx | 0 .../components/billing/CreditPurchaseForm.tsx | 0 .../billing/creditBalanceContext.ts | 0 .../src/components/blog/BlogArticleList.tsx | 0 .../src/components/blog/BlogPage.module.css | 0 .../src/components/blog/BlogPage.tsx | 0 .../src/components/blog/BlogPlatformList.tsx | 0 .../components/blog/BlogScoreCard.module.css | 0 .../src/components/blog/BlogScoreCard.tsx | 0 .../forms/AgentChatWidget.module.css | 0 .../components/forms/AgentChatWidget.test.tsx | 0 .../src/components/forms/AgentChatWidget.tsx | 0 .../forms/CareerDiffModal.module.css | 0 .../src/components/forms/CareerDiffModal.tsx | 0 .../CareerExperienceEditor.tsx | 0 .../forms/CareerFormEditors/ClientEditor.tsx | 0 .../forms/CareerResumeForm.module.css | 0 .../forms/CareerResumeForm.test.tsx | 0 .../src/components/forms/CareerResumeForm.tsx | 0 .../src/components/forms/Combobox.module.css | 0 .../src/components/forms/Combobox.tsx | 0 .../components/forms/MarkdownDocumentView.tsx | 0 .../components/forms/MarkdownFieldModal.tsx | 0 .../forms/MarkdownFieldTrigger.module.css | 0 .../components/forms/MarkdownFieldTrigger.tsx | 0 .../forms/MarkdownTextarea.module.css | 0 .../forms/MarkdownTextarea.test.tsx | 0 .../src/components/forms/MarkdownTextarea.tsx | 0 .../src/components/forms/PdfDocumentView.tsx | 0 .../forms/PdfPreviewModal.module.css | 0 .../src/components/forms/PdfPreviewModal.tsx | 0 .../components/forms/ProjectModal.module.css | 0 .../components/forms/ProjectModal.test.tsx | 0 .../src/components/forms/ProjectModal.tsx | 0 .../components/forms/ResumeForm.module.css | 0 .../forms/ResumeImportConfirmModal.module.css | 0 .../forms/ResumeSourceTracePanel.module.css | 0 .../forms/ResumeSourceTracePanel.tsx | 0 .../src/components/forms/documentView.ts | 0 .../forms/sections/CareerBasicInfoSection.tsx | 0 .../sections/CareerExperienceSection.tsx | 0 .../forms/sections/CareerFormToolbar.tsx | 0 .../sections/CareerQualificationsSection.tsx | 0 .../forms/sections/CareerSelfPrSection.tsx | 0 .../ContributionHeatmap.module.css | 0 .../github-link/ContributionHeatmap.test.tsx | 0 .../github-link/ContributionHeatmap.tsx | 0 .../GitHubLinkDashboard.module.css | 0 .../github-link/GitHubLinkDashboard.test.tsx | 0 .../github-link/GitHubLinkDashboard.tsx | 0 .../github-link/LanguageBar.module.css | 0 .../components/github-link/LanguageBar.tsx | 0 .../components/github-link/TechBar.module.css | 0 .../src/components/icons/BellIcon.tsx | 0 .../src/components/icons/ChevronDownIcon.tsx | 0 .../src/components/icons/EyeIcon.tsx | 0 .../src/components/icons/EyeSlashIcon.tsx | 0 .../src/components/icons/GitHubMarkIcon.tsx | 0 .../components/icons/MarkdownDownloadIcon.tsx | 0 .../src/components/icons/NoteIcon.tsx | 0 .../src/components/icons/PdfDownloadIcon.tsx | 0 .../src/components/icons/PlusIcon.tsx | 0 .../src/components/icons/QiitaIcon.tsx | 0 .../src/components/icons/SaveIcon.tsx | 0 .../src/components/icons/TrashIcon.tsx | 0 .../src/components/icons/ZennIcon.tsx | 0 .../components/ui/AsyncTaskLoading.module.css | 0 .../src/components/ui/AsyncTaskLoading.tsx | 0 .../src/components/ui/CharCount.module.css | 0 .../src/components/ui/CharCount.tsx | 0 .../src/components/ui/Collapsible.module.css | 0 .../src/components/ui/Collapsible.test.tsx | 0 .../src/components/ui/Collapsible.tsx | 0 .../components/ui/DeleteIconButton.module.css | 0 .../components/ui/DeleteIconButton.test.tsx | 0 .../src/components/ui/DeleteIconButton.tsx | 0 .../src/components/ui/DirtyDot.module.css | 0 .../src/components/ui/DirtyDot.tsx | 0 .../components/ui/InlineSpinner.module.css | 0 .../src/components/ui/InlineSpinner.tsx | 0 .../src/components/ui/ModalShell.module.css | 0 .../src/components/ui/ModalShell.tsx | 0 .../src/components/ui/Skeleton.module.css | 0 .../src/components/ui/Skeleton.tsx | 0 .../src/components/ui/toast/ToastItem.tsx | 0 .../ui/toast/ToastProvider.test.tsx | 0 .../src/components/ui/toast/ToastProvider.tsx | 0 .../src/components/ui/toast/ToastViewport.tsx | 0 .../src/components/ui/toast/index.ts | 0 .../src/components/ui/toast/toast.module.css | 0 .../src/components/ui/toast/toastContext.ts | 0 .../src/components/ui/toast/useToastBridge.ts | 0 {frontend => web}/src/constants.ts | 0 .../src/constants/agentModels.test.ts | 0 .../src/constants/agentModels.ts | 0 {frontend => web}/src/constants/errorCodes.ts | 0 .../src/constants/errorMessages.ts | 0 {frontend => web}/src/constants/messages.ts | 0 {frontend => web}/src/formMappers.ts | 0 {frontend => web}/src/formTypes.ts | 0 .../hooks/blog/useBlogAccountManager.test.ts | 0 .../src/hooks/blog/useBlogAccountManager.ts | 0 .../src/hooks/career/useAgentChat.test.ts | 0 .../src/hooks/career/useAgentChat.ts | 0 .../src/hooks/career/useCareerDirty.test.ts | 0 .../src/hooks/career/useCareerDirty.ts | 0 .../career/useCareerDraftRestore.test.ts | 0 .../src/hooks/career/useCareerDraftRestore.ts | 0 .../useCareerExperienceMutators.test.ts | 0 .../career/useCareerExperienceMutators.ts | 0 .../hooks/career/useCareerExportActions.ts | 0 .../hooks/career/useCareerFormModals.test.ts | 0 .../src/hooks/career/useCareerFormModals.ts | 0 .../hooks/career/useImportPanelLayout.test.ts | 0 .../src/hooks/career/useImportPanelLayout.ts | 0 .../src/hooks/career/usePhotoUpload.test.ts | 0 .../src/hooks/career/usePhotoUpload.ts | 0 .../hooks/career/useProjectModalForm.test.ts | 0 .../src/hooks/career/useProjectModalForm.ts | 0 .../hooks/career/useProjectModalState.test.ts | 0 .../src/hooks/career/useProjectModalState.ts | 0 .../hooks/career/useResumeDiffPreview.test.ts | 0 .../src/hooks/career/useResumeDiffPreview.ts | 0 .../career/useResumeImportAssist.test.ts | 0 .../src/hooks/career/useResumeImportAssist.ts | 0 .../src/hooks/useAgentUsageSummary.test.ts | 0 .../src/hooks/useAgentUsageSummary.ts | 0 .../src/hooks/useAsyncTaskPage.test.ts | 0 .../src/hooks/useAsyncTaskPage.ts | 0 .../src/hooks/useAuthSession.test.ts | 0 {frontend => web}/src/hooks/useAuthSession.ts | 0 .../src/hooks/useBillingPage.test.ts | 0 {frontend => web}/src/hooks/useBillingPage.ts | 0 .../src/hooks/useCreditBalance.test.ts | 0 .../src/hooks/useCreditBalance.ts | 0 .../src/hooks/useDocumentForm.test.ts | 0 .../src/hooks/useDocumentForm.ts | 0 .../src/hooks/useFocusOnMatch.test.ts | 0 .../src/hooks/useFocusOnMatch.ts | 0 {frontend => web}/src/hooks/useMasterData.ts | 0 .../src/hooks/useModelRates.test.ts | 0 {frontend => web}/src/hooks/useModelRates.ts | 0 .../src/hooks/useNotifications.test.ts | 0 .../src/hooks/useNotifications.ts | 0 .../src/hooks/usePdfActions.test.ts | 0 {frontend => web}/src/hooks/usePdfActions.ts | 0 .../src/hooks/useTaskPolling.test.ts | 0 {frontend => web}/src/hooks/useTaskPolling.ts | 0 {frontend => web}/src/hooks/useTheme.ts | 0 .../hooks/useUnsavedChangesWarning.test.ts | 0 .../src/hooks/useUnsavedChangesWarning.ts | 0 {frontend => web}/src/main.tsx | 0 {frontend => web}/src/pages/BillingPage.tsx | 0 {frontend => web}/src/pages/BlogPage.tsx | 0 {frontend => web}/src/pages/CareerPage.tsx | 0 .../src/pages/GitHubCallbackPage.tsx | 0 .../src/pages/GitHubLinkPage.tsx | 0 {frontend => web}/src/pages/LoginPage.tsx | 0 .../src/pages/NotFoundPage.module.css | 0 {frontend => web}/src/pages/NotFoundPage.tsx | 0 {frontend => web}/src/payloadBuilders.test.ts | 0 {frontend => web}/src/payloadBuilders.ts | 0 {frontend => web}/src/router/guards.test.tsx | 0 {frontend => web}/src/router/guards.tsx | 0 {frontend => web}/src/router/index.ts | 0 {frontend => web}/src/router/routes.tsx | 0 .../src/store/agentModelSlice.test.ts | 0 .../src/store/agentModelSlice.ts | 0 {frontend => web}/src/store/formCacheSlice.ts | 0 {frontend => web}/src/store/index.ts | 0 {frontend => web}/src/store/persistConfig.ts | 0 {frontend => web}/src/styles.css | 0 .../src/styles/shared.module.css | 0 {frontend => web}/src/test-setup.ts | 0 {frontend => web}/src/test/handlers.ts | 0 {frontend => web}/src/test/mswServer.ts | 0 .../src/test/renderWithProviders.tsx | 0 .../src/utils/agentOperations.test.ts | 0 .../src/utils/agentOperations.ts | 0 {frontend => web}/src/utils/appError.ts | 0 .../src/utils/careerDiff.test.ts | 0 {frontend => web}/src/utils/careerDiff.ts | 0 .../src/utils/careerDraft.test.ts | 0 {frontend => web}/src/utils/careerDraft.ts | 0 .../src/utils/careerReview.test.ts | 0 {frontend => web}/src/utils/careerReview.ts | 0 .../src/utils/creditEstimate.test.ts | 0 {frontend => web}/src/utils/creditEstimate.ts | 0 {frontend => web}/src/utils/deepEqual.ts | 0 .../src/utils/diffHighlight.test.ts | 0 {frontend => web}/src/utils/diffHighlight.ts | 0 {frontend => web}/src/utils/errorId.ts | 0 {frontend => web}/src/utils/markdown.test.ts | 0 {frontend => web}/src/utils/markdown.ts | 0 {frontend => web}/src/utils/pdfjs.ts | 0 {frontend => web}/src/utils/setAtPath.test.ts | 0 {frontend => web}/src/utils/setAtPath.ts | 0 {frontend => web}/src/utils/taskStatus.ts | 0 {frontend => web}/src/utils/text.test.ts | 0 {frontend => web}/src/utils/text.ts | 0 .../tests/payloadBuilders.test.cjs | 0 {frontend => web}/tsconfig.app.json | 0 {frontend => web}/tsconfig.json | 0 {frontend => web}/tsconfig.node.json | 0 {frontend => web}/vite.config.d.ts | 0 {frontend => web}/vite.config.ts | 0 {frontend => web}/wrangler.toml | 0 314 files changed, 323 insertions(+), 323 deletions(-) rename .claude/rules/{frontend => web}/architecture.md (99%) rename .claude/rules/{frontend => web}/component-design.md (95%) rename .claude/rules/{frontend => web}/messages.md (63%) rename .claude/rules/{frontend => web}/test.md (78%) rename .claude/rules/{frontend => web}/typescript.md (82%) rename .claude/skills/{FE_apply => WEB_apply}/SKILL.md (74%) rename .claude/skills/{FE_refacter => WEB_refacter}/SKILL.md (85%) rename scripts/{lint-frontend-messages.sh => lint-web-messages.sh} (84%) rename {frontend => web}/.npmrc (100%) rename {frontend => web}/.prettierignore (100%) rename {frontend => web}/.prettierrc (100%) rename {frontend => web}/e2e/agent-chat.spec.ts (100%) rename {frontend => web}/e2e/auth.spec.ts (100%) rename {frontend => web}/e2e/billing.spec.ts (100%) rename {frontend => web}/e2e/career-dirty-indicator.spec.ts (100%) rename {frontend => web}/e2e/career-field-modal.spec.ts (100%) rename {frontend => web}/e2e/career-import-assist.spec.ts (100%) rename {frontend => web}/e2e/github-link.spec.ts (100%) rename {frontend => web}/e2e/helpers/auth.ts (100%) rename {frontend => web}/e2e/navigation.spec.ts (100%) rename {frontend => web}/e2e/notifications.spec.ts (100%) rename {frontend => web}/eslint.config.js (100%) rename {frontend => web}/functions/_middleware.ts (100%) rename {frontend => web}/index.html (100%) rename {frontend => web}/package-lock.json (100%) rename {frontend => web}/package.json (98%) rename {frontend => web}/playwright.config.d.ts (100%) rename {frontend => web}/playwright.config.js (100%) rename {frontend => web}/playwright.config.ts (100%) rename {frontend => web}/scripts/audit-check.mjs (100%) rename {frontend => web}/scripts/gen-redirects.mjs (100%) rename {frontend => web}/scripts/gen-types.mjs (100%) rename {frontend => web}/src/App.module.css (100%) rename {frontend => web}/src/App.tsx (100%) rename {frontend => web}/src/api/agent.ts (100%) rename {frontend => web}/src/api/ai-resume.ts (100%) rename {frontend => web}/src/api/auth.ts (100%) rename {frontend => web}/src/api/billing.ts (100%) rename {frontend => web}/src/api/blog.ts (100%) rename {frontend => web}/src/api/client.test.ts (100%) rename {frontend => web}/src/api/client.ts (100%) rename {frontend => web}/src/api/download.ts (100%) rename {frontend => web}/src/api/generated.ts (100%) rename {frontend => web}/src/api/githubLink.ts (100%) rename {frontend => web}/src/api/index.ts (100%) rename {frontend => web}/src/api/master-data.ts (100%) rename {frontend => web}/src/api/notifications.ts (100%) rename {frontend => web}/src/api/paths.ts (100%) rename {frontend => web}/src/api/resumes.ts (100%) rename {frontend => web}/src/api/types.ts (100%) rename {frontend => web}/src/components/CareerUnsavedGuard.test.tsx (100%) rename {frontend => web}/src/components/CareerUnsavedGuard.tsx (100%) rename {frontend => web}/src/components/ConfirmDialog.module.css (100%) rename {frontend => web}/src/components/ConfirmDialog.tsx (100%) rename {frontend => web}/src/components/ErrorBoundary.module.css (100%) rename {frontend => web}/src/components/ErrorBoundary.tsx (100%) rename {frontend => web}/src/components/LoadingOverlay.tsx (100%) rename {frontend => web}/src/components/NotificationBell.module.css (100%) rename {frontend => web}/src/components/NotificationBell.tsx (100%) rename {frontend => web}/src/components/SidebarLayout.tsx (100%) rename {frontend => web}/src/components/UserMenu.module.css (100%) rename {frontend => web}/src/components/UserMenu.tsx (100%) rename {frontend => web}/src/components/agent/AgentModelBadge.module.css (100%) rename {frontend => web}/src/components/agent/AgentModelBadge.tsx (100%) rename {frontend => web}/src/components/agent/ModelSelectModal.module.css (100%) rename {frontend => web}/src/components/agent/ModelSelectModal.test.tsx (100%) rename {frontend => web}/src/components/agent/ModelSelectModal.tsx (100%) rename {frontend => web}/src/components/auth/LoginForm.module.css (100%) rename {frontend => web}/src/components/auth/LoginForm.tsx (100%) rename {frontend => web}/src/components/auth/LoginPromptModal.module.css (100%) rename {frontend => web}/src/components/auth/LoginPromptModal.test.tsx (100%) rename {frontend => web}/src/components/auth/LoginPromptModal.tsx (100%) rename {frontend => web}/src/components/auth/LoginPromptProvider.tsx (100%) rename {frontend => web}/src/components/auth/loginPromptContext.ts (100%) rename {frontend => web}/src/components/billing/BillingView.module.css (100%) rename {frontend => web}/src/components/billing/BillingView.test.tsx (100%) rename {frontend => web}/src/components/billing/BillingView.tsx (100%) rename {frontend => web}/src/components/billing/CreditBalanceBadge.module.css (100%) rename {frontend => web}/src/components/billing/CreditBalanceBadge.tsx (100%) rename {frontend => web}/src/components/billing/CreditBalanceProvider.test.tsx (100%) rename {frontend => web}/src/components/billing/CreditBalanceProvider.tsx (100%) rename {frontend => web}/src/components/billing/CreditPurchaseForm.module.css (100%) rename {frontend => web}/src/components/billing/CreditPurchaseForm.test.tsx (100%) rename {frontend => web}/src/components/billing/CreditPurchaseForm.tsx (100%) rename {frontend => web}/src/components/billing/creditBalanceContext.ts (100%) rename {frontend => web}/src/components/blog/BlogArticleList.tsx (100%) rename {frontend => web}/src/components/blog/BlogPage.module.css (100%) rename {frontend => web}/src/components/blog/BlogPage.tsx (100%) rename {frontend => web}/src/components/blog/BlogPlatformList.tsx (100%) rename {frontend => web}/src/components/blog/BlogScoreCard.module.css (100%) rename {frontend => web}/src/components/blog/BlogScoreCard.tsx (100%) rename {frontend => web}/src/components/forms/AgentChatWidget.module.css (100%) rename {frontend => web}/src/components/forms/AgentChatWidget.test.tsx (100%) rename {frontend => web}/src/components/forms/AgentChatWidget.tsx (100%) rename {frontend => web}/src/components/forms/CareerDiffModal.module.css (100%) rename {frontend => web}/src/components/forms/CareerDiffModal.tsx (100%) rename {frontend => web}/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx (100%) rename {frontend => web}/src/components/forms/CareerFormEditors/ClientEditor.tsx (100%) rename {frontend => web}/src/components/forms/CareerResumeForm.module.css (100%) rename {frontend => web}/src/components/forms/CareerResumeForm.test.tsx (100%) rename {frontend => web}/src/components/forms/CareerResumeForm.tsx (100%) rename {frontend => web}/src/components/forms/Combobox.module.css (100%) rename {frontend => web}/src/components/forms/Combobox.tsx (100%) rename {frontend => web}/src/components/forms/MarkdownDocumentView.tsx (100%) rename {frontend => web}/src/components/forms/MarkdownFieldModal.tsx (100%) rename {frontend => web}/src/components/forms/MarkdownFieldTrigger.module.css (100%) rename {frontend => web}/src/components/forms/MarkdownFieldTrigger.tsx (100%) rename {frontend => web}/src/components/forms/MarkdownTextarea.module.css (100%) rename {frontend => web}/src/components/forms/MarkdownTextarea.test.tsx (100%) rename {frontend => web}/src/components/forms/MarkdownTextarea.tsx (100%) rename {frontend => web}/src/components/forms/PdfDocumentView.tsx (100%) rename {frontend => web}/src/components/forms/PdfPreviewModal.module.css (100%) rename {frontend => web}/src/components/forms/PdfPreviewModal.tsx (100%) rename {frontend => web}/src/components/forms/ProjectModal.module.css (100%) rename {frontend => web}/src/components/forms/ProjectModal.test.tsx (100%) rename {frontend => web}/src/components/forms/ProjectModal.tsx (100%) rename {frontend => web}/src/components/forms/ResumeForm.module.css (100%) rename {frontend => web}/src/components/forms/ResumeImportConfirmModal.module.css (100%) rename {frontend => web}/src/components/forms/ResumeSourceTracePanel.module.css (100%) rename {frontend => web}/src/components/forms/ResumeSourceTracePanel.tsx (100%) rename {frontend => web}/src/components/forms/documentView.ts (100%) rename {frontend => web}/src/components/forms/sections/CareerBasicInfoSection.tsx (100%) rename {frontend => web}/src/components/forms/sections/CareerExperienceSection.tsx (100%) rename {frontend => web}/src/components/forms/sections/CareerFormToolbar.tsx (100%) rename {frontend => web}/src/components/forms/sections/CareerQualificationsSection.tsx (100%) rename {frontend => web}/src/components/forms/sections/CareerSelfPrSection.tsx (100%) rename {frontend => web}/src/components/github-link/ContributionHeatmap.module.css (100%) rename {frontend => web}/src/components/github-link/ContributionHeatmap.test.tsx (100%) rename {frontend => web}/src/components/github-link/ContributionHeatmap.tsx (100%) rename {frontend => web}/src/components/github-link/GitHubLinkDashboard.module.css (100%) rename {frontend => web}/src/components/github-link/GitHubLinkDashboard.test.tsx (100%) rename {frontend => web}/src/components/github-link/GitHubLinkDashboard.tsx (100%) rename {frontend => web}/src/components/github-link/LanguageBar.module.css (100%) rename {frontend => web}/src/components/github-link/LanguageBar.tsx (100%) rename {frontend => web}/src/components/github-link/TechBar.module.css (100%) rename {frontend => web}/src/components/icons/BellIcon.tsx (100%) rename {frontend => web}/src/components/icons/ChevronDownIcon.tsx (100%) rename {frontend => web}/src/components/icons/EyeIcon.tsx (100%) rename {frontend => web}/src/components/icons/EyeSlashIcon.tsx (100%) rename {frontend => web}/src/components/icons/GitHubMarkIcon.tsx (100%) rename {frontend => web}/src/components/icons/MarkdownDownloadIcon.tsx (100%) rename {frontend => web}/src/components/icons/NoteIcon.tsx (100%) rename {frontend => web}/src/components/icons/PdfDownloadIcon.tsx (100%) rename {frontend => web}/src/components/icons/PlusIcon.tsx (100%) rename {frontend => web}/src/components/icons/QiitaIcon.tsx (100%) rename {frontend => web}/src/components/icons/SaveIcon.tsx (100%) rename {frontend => web}/src/components/icons/TrashIcon.tsx (100%) rename {frontend => web}/src/components/icons/ZennIcon.tsx (100%) rename {frontend => web}/src/components/ui/AsyncTaskLoading.module.css (100%) rename {frontend => web}/src/components/ui/AsyncTaskLoading.tsx (100%) rename {frontend => web}/src/components/ui/CharCount.module.css (100%) rename {frontend => web}/src/components/ui/CharCount.tsx (100%) rename {frontend => web}/src/components/ui/Collapsible.module.css (100%) rename {frontend => web}/src/components/ui/Collapsible.test.tsx (100%) rename {frontend => web}/src/components/ui/Collapsible.tsx (100%) rename {frontend => web}/src/components/ui/DeleteIconButton.module.css (100%) rename {frontend => web}/src/components/ui/DeleteIconButton.test.tsx (100%) rename {frontend => web}/src/components/ui/DeleteIconButton.tsx (100%) rename {frontend => web}/src/components/ui/DirtyDot.module.css (100%) rename {frontend => web}/src/components/ui/DirtyDot.tsx (100%) rename {frontend => web}/src/components/ui/InlineSpinner.module.css (100%) rename {frontend => web}/src/components/ui/InlineSpinner.tsx (100%) rename {frontend => web}/src/components/ui/ModalShell.module.css (100%) rename {frontend => web}/src/components/ui/ModalShell.tsx (100%) rename {frontend => web}/src/components/ui/Skeleton.module.css (100%) rename {frontend => web}/src/components/ui/Skeleton.tsx (100%) rename {frontend => web}/src/components/ui/toast/ToastItem.tsx (100%) rename {frontend => web}/src/components/ui/toast/ToastProvider.test.tsx (100%) rename {frontend => web}/src/components/ui/toast/ToastProvider.tsx (100%) rename {frontend => web}/src/components/ui/toast/ToastViewport.tsx (100%) rename {frontend => web}/src/components/ui/toast/index.ts (100%) rename {frontend => web}/src/components/ui/toast/toast.module.css (100%) rename {frontend => web}/src/components/ui/toast/toastContext.ts (100%) rename {frontend => web}/src/components/ui/toast/useToastBridge.ts (100%) rename {frontend => web}/src/constants.ts (100%) rename {frontend => web}/src/constants/agentModels.test.ts (100%) rename {frontend => web}/src/constants/agentModels.ts (100%) rename {frontend => web}/src/constants/errorCodes.ts (100%) rename {frontend => web}/src/constants/errorMessages.ts (100%) rename {frontend => web}/src/constants/messages.ts (100%) rename {frontend => web}/src/formMappers.ts (100%) rename {frontend => web}/src/formTypes.ts (100%) rename {frontend => web}/src/hooks/blog/useBlogAccountManager.test.ts (100%) rename {frontend => web}/src/hooks/blog/useBlogAccountManager.ts (100%) rename {frontend => web}/src/hooks/career/useAgentChat.test.ts (100%) rename {frontend => web}/src/hooks/career/useAgentChat.ts (100%) rename {frontend => web}/src/hooks/career/useCareerDirty.test.ts (100%) rename {frontend => web}/src/hooks/career/useCareerDirty.ts (100%) rename {frontend => web}/src/hooks/career/useCareerDraftRestore.test.ts (100%) rename {frontend => web}/src/hooks/career/useCareerDraftRestore.ts (100%) rename {frontend => web}/src/hooks/career/useCareerExperienceMutators.test.ts (100%) rename {frontend => web}/src/hooks/career/useCareerExperienceMutators.ts (100%) rename {frontend => web}/src/hooks/career/useCareerExportActions.ts (100%) rename {frontend => web}/src/hooks/career/useCareerFormModals.test.ts (100%) rename {frontend => web}/src/hooks/career/useCareerFormModals.ts (100%) rename {frontend => web}/src/hooks/career/useImportPanelLayout.test.ts (100%) rename {frontend => web}/src/hooks/career/useImportPanelLayout.ts (100%) rename {frontend => web}/src/hooks/career/usePhotoUpload.test.ts (100%) rename {frontend => web}/src/hooks/career/usePhotoUpload.ts (100%) rename {frontend => web}/src/hooks/career/useProjectModalForm.test.ts (100%) rename {frontend => web}/src/hooks/career/useProjectModalForm.ts (100%) rename {frontend => web}/src/hooks/career/useProjectModalState.test.ts (100%) rename {frontend => web}/src/hooks/career/useProjectModalState.ts (100%) rename {frontend => web}/src/hooks/career/useResumeDiffPreview.test.ts (100%) rename {frontend => web}/src/hooks/career/useResumeDiffPreview.ts (100%) rename {frontend => web}/src/hooks/career/useResumeImportAssist.test.ts (100%) rename {frontend => web}/src/hooks/career/useResumeImportAssist.ts (100%) rename {frontend => web}/src/hooks/useAgentUsageSummary.test.ts (100%) rename {frontend => web}/src/hooks/useAgentUsageSummary.ts (100%) rename {frontend => web}/src/hooks/useAsyncTaskPage.test.ts (100%) rename {frontend => web}/src/hooks/useAsyncTaskPage.ts (100%) rename {frontend => web}/src/hooks/useAuthSession.test.ts (100%) rename {frontend => web}/src/hooks/useAuthSession.ts (100%) rename {frontend => web}/src/hooks/useBillingPage.test.ts (100%) rename {frontend => web}/src/hooks/useBillingPage.ts (100%) rename {frontend => web}/src/hooks/useCreditBalance.test.ts (100%) rename {frontend => web}/src/hooks/useCreditBalance.ts (100%) rename {frontend => web}/src/hooks/useDocumentForm.test.ts (100%) rename {frontend => web}/src/hooks/useDocumentForm.ts (100%) rename {frontend => web}/src/hooks/useFocusOnMatch.test.ts (100%) rename {frontend => web}/src/hooks/useFocusOnMatch.ts (100%) rename {frontend => web}/src/hooks/useMasterData.ts (100%) rename {frontend => web}/src/hooks/useModelRates.test.ts (100%) rename {frontend => web}/src/hooks/useModelRates.ts (100%) rename {frontend => web}/src/hooks/useNotifications.test.ts (100%) rename {frontend => web}/src/hooks/useNotifications.ts (100%) rename {frontend => web}/src/hooks/usePdfActions.test.ts (100%) rename {frontend => web}/src/hooks/usePdfActions.ts (100%) rename {frontend => web}/src/hooks/useTaskPolling.test.ts (100%) rename {frontend => web}/src/hooks/useTaskPolling.ts (100%) rename {frontend => web}/src/hooks/useTheme.ts (100%) rename {frontend => web}/src/hooks/useUnsavedChangesWarning.test.ts (100%) rename {frontend => web}/src/hooks/useUnsavedChangesWarning.ts (100%) rename {frontend => web}/src/main.tsx (100%) rename {frontend => web}/src/pages/BillingPage.tsx (100%) rename {frontend => web}/src/pages/BlogPage.tsx (100%) rename {frontend => web}/src/pages/CareerPage.tsx (100%) rename {frontend => web}/src/pages/GitHubCallbackPage.tsx (100%) rename {frontend => web}/src/pages/GitHubLinkPage.tsx (100%) rename {frontend => web}/src/pages/LoginPage.tsx (100%) rename {frontend => web}/src/pages/NotFoundPage.module.css (100%) rename {frontend => web}/src/pages/NotFoundPage.tsx (100%) rename {frontend => web}/src/payloadBuilders.test.ts (100%) rename {frontend => web}/src/payloadBuilders.ts (100%) rename {frontend => web}/src/router/guards.test.tsx (100%) rename {frontend => web}/src/router/guards.tsx (100%) rename {frontend => web}/src/router/index.ts (100%) rename {frontend => web}/src/router/routes.tsx (100%) rename {frontend => web}/src/store/agentModelSlice.test.ts (100%) rename {frontend => web}/src/store/agentModelSlice.ts (100%) rename {frontend => web}/src/store/formCacheSlice.ts (100%) rename {frontend => web}/src/store/index.ts (100%) rename {frontend => web}/src/store/persistConfig.ts (100%) rename {frontend => web}/src/styles.css (100%) rename {frontend => web}/src/styles/shared.module.css (100%) rename {frontend => web}/src/test-setup.ts (100%) rename {frontend => web}/src/test/handlers.ts (100%) rename {frontend => web}/src/test/mswServer.ts (100%) rename {frontend => web}/src/test/renderWithProviders.tsx (100%) rename {frontend => web}/src/utils/agentOperations.test.ts (100%) rename {frontend => web}/src/utils/agentOperations.ts (100%) rename {frontend => web}/src/utils/appError.ts (100%) rename {frontend => web}/src/utils/careerDiff.test.ts (100%) rename {frontend => web}/src/utils/careerDiff.ts (100%) rename {frontend => web}/src/utils/careerDraft.test.ts (100%) rename {frontend => web}/src/utils/careerDraft.ts (100%) rename {frontend => web}/src/utils/careerReview.test.ts (100%) rename {frontend => web}/src/utils/careerReview.ts (100%) rename {frontend => web}/src/utils/creditEstimate.test.ts (100%) rename {frontend => web}/src/utils/creditEstimate.ts (100%) rename {frontend => web}/src/utils/deepEqual.ts (100%) rename {frontend => web}/src/utils/diffHighlight.test.ts (100%) rename {frontend => web}/src/utils/diffHighlight.ts (100%) rename {frontend => web}/src/utils/errorId.ts (100%) rename {frontend => web}/src/utils/markdown.test.ts (100%) rename {frontend => web}/src/utils/markdown.ts (100%) rename {frontend => web}/src/utils/pdfjs.ts (100%) rename {frontend => web}/src/utils/setAtPath.test.ts (100%) rename {frontend => web}/src/utils/setAtPath.ts (100%) rename {frontend => web}/src/utils/taskStatus.ts (100%) rename {frontend => web}/src/utils/text.test.ts (100%) rename {frontend => web}/src/utils/text.ts (100%) rename {frontend => web}/tests/payloadBuilders.test.cjs (100%) rename {frontend => web}/tsconfig.app.json (100%) rename {frontend => web}/tsconfig.json (100%) rename {frontend => web}/tsconfig.node.json (100%) rename {frontend => web}/vite.config.d.ts (100%) rename {frontend => web}/vite.config.ts (100%) rename {frontend => web}/wrangler.toml (100%) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index cdc9f88a..1fa523f5 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -3,7 +3,7 @@ ## このファイルの読み方 - 本ファイルは全体ルールの索引。AI エージェント(Claude Code 含む)が最初に読むべき内容を集約している。 -- 領域固有ルール(backend / frontend / infra)は `.claude/rules//*.md` に分割済み。対象パスを編集する際に自動でロードされる。重複は避け、詳細は各 rule ファイルへ寄せる。 +- 領域固有ルール(backend / web / infra)は `.claude/rules//*.md` に分割済み。対象パスを編集する際に自動でロードされる。重複は避け、詳細は各 rule ファイルへ寄せる。 - **DevForge Agent(`backend/app/services/agent/` / `backend/app/prompts/agent_*.md` / `backend/app/schemas/agent.py`)を変更する場合は、作業前に必ず `.claude/rules/backend/agent.md` を読むこと。** 制約の責務分離(スキーマ vs プロンプト)・エラー契約・DB 非更新原則など、意図せず壊しやすい不変条件が集約されている。 ## AI エージェント実行方法 @@ -16,11 +16,11 @@ Makefile は `nix develop --command bash -c "..."` でラップ済み。AI は | 用途 | コマンド | |---|---| -| CI 相当一括 | `make ci` (= `lint + test + build-frontend`) | +| CI 相当一括 | `make ci` (= `lint + test + build-web`) | | Backend lint | `make lint-backend` | | Backend test | `make test-backend` | -| Frontend lint | `make lint-frontend` | -| Frontend test | `make test-frontend` | +| Frontend lint | `make lint-web` | +| Frontend test | `make test-web` | | Lint 自動修正 | `make lint-fix` | | マイグレーション | `make migrate` / `make migrate-create MSG="..."` | | インフラ validate | `make infra-validate` | @@ -35,7 +35,7 @@ make に無い操作(特定ファイルだけ ruff したい等)の場合の ```bash nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check app/services/tasks/handlers/blog_summarize.py" nix develop --command bash -c "cd backend && .venv/bin/python -m pytest tests/test_worker_extended.py -q" -nix develop --command bash -c "cd frontend && npm run test:e2e" +nix develop --command bash -c "cd web && npm run test:e2e" ``` ### 禁止: 生シェルでの直接実行 @@ -56,11 +56,11 @@ error: opening lock file "~/.cache/nix/fetcher-locks/...lock": Operation not per - **コメント・ドキュメント**: コード内コメント・docstring・JSDoc はすべて**日本語**で記述する。 - **エラーメッセージ**: HTTPException の `detail` 等、ユーザーに返すメッセージはすべて**日本語**。 -- **エラーメッセージのハードコード禁止**: ts/tsx でリテラル日本語を直接書かない(`throw new Error("...")` / `setError("...")` / `toast.error("...")` 等)。frontend 完結のメッセージは `frontend/src/constants/messages.ts` の定数を参照、API 経由のエラーは backend の `messages.json` 由来の `AppErrorResponse.message` を表示する。詳細: `.claude/rules/frontend/messages.md` +- **エラーメッセージのハードコード禁止**: ts/tsx でリテラル日本語を直接書かない(`throw new Error("...")` / `setError("...")` / `toast.error("...")` 等)。web 完結のメッセージは `web/src/constants/messages.ts` の定数を参照、API 経由のエラーは backend の `messages.json` 由来の `AppErrorResponse.message` を表示する。詳細: `.claude/rules/web/messages.md` - **例外の握りつぶし禁止**: `except SomeException: pass` は禁止。最低でも `logger.debug/warning/error` でログを残す。補助処理(通知生成など)で抑制する場合も `logger.warning` でログを出すこと。 - **過剰な抽象化を避ける**: PEP8 を守るな、PEP8 を理解した上で抽象化しろ。 -言語別の詳細ルールは `.claude/rules/{backend,frontend,infra}/` を参照。 +言語別の詳細ルールは `.claude/rules/{backend,web,infra}/` を参照。 領域横断の共通ルール(DRY / 重複検知)は `.claude/rules/common/duplication.md` を参照。 ## CI 確認ルール @@ -84,7 +84,7 @@ make ci - バックエンド API の追加・変更で、フロントエンドの UI フローに影響するもの ```bash -nix develop --command bash -c "cd frontend && npm run test:e2e" +nix develop --command bash -c "cd web && npm run test:e2e" ``` ### SSoT 生成物(codegen)のトリガー — 必須 @@ -93,10 +93,10 @@ nix develop --command bash -c "cd frontend && npm run test:e2e" | 正本(変更したら) | 再生成コマンド | コミットすべき生成物 | CI ジョブ | |---|---|---|---| -| backend の OpenAPI スキーマ(`app/schemas/` の Pydantic、router のシグネチャ・query/path パラメータ・**endpoint/schema の docstring**) | `make codegen-types` | `frontend/src/api/generated.ts`(`backend/openapi.json` は gitignore で対象外) | `codegen-drift`(ADR-0007) | +| backend の OpenAPI スキーマ(`app/schemas/` の Pydantic、router のシグネチャ・query/path パラメータ・**endpoint/schema の docstring**) | `make codegen-types` | `web/src/api/generated.ts`(`backend/openapi.json` は gitignore で対象外) | `codegen-drift`(ADR-0007) | - **判定基準**: 「OpenAPI スペックに出るものを変えたか」。エンドポイントの追加・削除、リクエスト/レスポンス型の変更、query/path パラメータの増減はもちろん、**docstring の文言変更だけでも description として spec に反映される**ため再生成が要る(今回の codegen-drift はこれで発生)。 -- backend の `app/schemas/` / `app/routers/` を触ったら、`make ci` 前に `make codegen-types` を回して `git diff frontend/src/api/generated.ts` を確認する。差分が出たら必ず同じ PR でコミットする。 +- backend の `app/schemas/` / `app/routers/` を触ったら、`make ci` 前に `make codegen-types` を回して `git diff web/src/api/generated.ts` を確認する。差分が出たら必ず同じ PR でコミットする。 - 新しい SSoT→生成物の系統を追加した場合は、本表に行を足して再発防止の対象に含める。 CI 定義: `.github/workflows/ci.yml` @@ -166,7 +166,7 @@ Claude Code は `/model` コマンドを自分では実行できないため、 - **lint 失敗時は当該ファイルだけ確認**: `make lint-backend` が他ファイルの I001 等で落ちる場合、自分の変更分は `nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check "` で個別検証してから進める(既存違反を巻き込まない)。 - **Router には「エンドポイント定義・依存性解決・HTTP 変換」のみ**: 外部 API 呼び出し・DB クエリ(`db.query(...)` 直書き)・ビジネスロジックを router に書かない。外部 API の例外は service 層で処理し、router では `raise_app_error` への変換のみ行う。詳細・Bad/Good 例: `.claude/rules/backend/layers.md` - **ORM model には「テーブル定義・リレーション」のみ**: ソート・フォーマット等の表示ロジックを `@property` として model に持たせない。`sort_utils` のような presentation 層ユーティリティを model に import しない。ソートは `relationship(order_by=...)` か service 層で行う。詳細: `.claude/rules/backend/layers.md` -- **300 行超のコンポーネント・500 行超のサービスモジュールは分割を検討する**: 行数は目安(強制閾値ではない)だが、超過したら責務が複数混在していないかを確認する。モーダル状態や更新ハンドラ群は専用フックに切り出す。詳細・Good パターン例: `.claude/rules/frontend/component-design.md` +- **300 行超のコンポーネント・500 行超のサービスモジュールは分割を検討する**: 行数は目安(強制閾値ではない)だが、超過したら責務が複数混在していないかを確認する。モーダル状態や更新ハンドラ群は専用フックに切り出す。詳細・Good パターン例: `.claude/rules/web/component-design.md` ## 命名規約 diff --git a/.claude/rules/backend/auth-security.md b/.claude/rules/backend/auth-security.md index d2c4a3fa..7c8baf4c 100644 --- a/.claude/rules/backend/auth-security.md +++ b/.claude/rules/backend/auth-security.md @@ -13,7 +13,7 @@ paths: - アクセストークン: 15 分(Cookie 名 `access_token`) - リフレッシュトークン: 7 日(Cookie 名 `refresh_token`) - 起動時に `validate_jwt_key_pair()` で秘密鍵と公開鍵の整合性を検証する -- GitHub OAuth の `state` は **backend 側 Cookie で検証**する。frontend だけで検証しないこと +- GitHub OAuth の `state` は **backend 側 Cookie で検証**する。web だけで検証しないこと - 認証 Cookie 属性は `COOKIE_SECURE` / `COOKIE_SAMESITE` で制御する ## 暗号化 diff --git a/.claude/rules/common/duplication.md b/.claude/rules/common/duplication.md index 0e7f33c4..3f841517 100644 --- a/.claude/rules/common/duplication.md +++ b/.claude/rules/common/duplication.md @@ -1,7 +1,7 @@ # コード重複 / DRY ポリシー(共通) -このルールは backend / frontend / infra すべての領域に適用される。 -領域別のコーディング規約(`.claude/rules/{backend,frontend,infra}/`)と併せて参照すること。 +このルールは backend / web / infra すべての領域に適用される。 +領域別のコーディング規約(`.claude/rules/{backend,web,infra}/`)と併せて参照すること。 ## 原則 @@ -30,7 +30,7 @@ CLAUDE.md にある通り「PEP8 を守るな、PEP8 を理解した上で抽象 - **エラーマッピング**: バックエンドのエラーコード ↔ ユーザー向けメッセージの対応表 - **API パス文字列**: `/api/v1/...` のリテラルが複数モジュールに散在 - **環境変数名のリテラル**: `os.environ["TURSO_DATABASE_URL"]` のような文字列を `settings.py` 以外で参照 -- **DTO / 型定義**: backend `app/schemas/` ↔ frontend `src/types.ts` の二重定義(同じフィールド構造を別言語で持つこと自体は許容、ただし片方の変更がもう片方の更新を忘れさせるなら検知できる仕組みが必要) +- **DTO / 型定義**: backend `app/schemas/` ↔ web `src/types.ts` の二重定義(同じフィールド構造を別言語で持つこと自体は許容、ただし片方の変更がもう片方の更新を忘れさせるなら検知できる仕組みが必要) ## 許容される類似(偶発的重複) @@ -59,12 +59,12 @@ CLAUDE.md にある通り「PEP8 を守るな、PEP8 を理解した上で抽象 ### Frontend (React + TypeScript) -1. **状態管理を含む共通ロジック** → `frontend/src/hooks/` の新規フック(`useDocumentForm`, `useTaskPolling` パターン) -2. **純粋関数 / 文字列変換 / 日付処理** → `frontend/src/utils/` -3. **API クライアントの共通パターン** → `frontend/src/api/client.ts` のラッパー追加 -4. **フォーム入出力変換** → `frontend/src/formMappers.ts` / `frontend/src/payloadBuilders.ts` -5. **共通 UI コンポーネント** → `frontend/src/components/ui/`(toast/, Skeleton 等の配置例) -6. **型定義** → `frontend/src/types.ts` / `frontend/src/formTypes.ts` +1. **状態管理を含む共通ロジック** → `web/src/hooks/` の新規フック(`useDocumentForm`, `useTaskPolling` パターン) +2. **純粋関数 / 文字列変換 / 日付処理** → `web/src/utils/` +3. **API クライアントの共通パターン** → `web/src/api/client.ts` のラッパー追加 +4. **フォーム入出力変換** → `web/src/formMappers.ts` / `web/src/payloadBuilders.ts` +5. **共通 UI コンポーネント** → `web/src/components/ui/`(toast/, Skeleton 等の配置例) +6. **型定義** → `web/src/types.ts` / `web/src/formTypes.ts` ### Infra (OpenTofu) @@ -76,7 +76,7 @@ CLAUDE.md にある通り「PEP8 を守るな、PEP8 を理解した上で抽象 ### 領域横断 (BE ↔ FE ↔ infra) -1. **エラーコード**: backend の `app/core/errors.py` を Single Source of Truth とし、frontend の `utils/appError.ts` は OpenAPI 経由で同期できないか検討する +1. **エラーコード**: backend の `app/core/errors.py` を Single Source of Truth とし、web の `utils/appError.ts` は OpenAPI 経由で同期できないか検討する 2. **環境変数名**: backend の `app/core/settings.py` で定義したフィールド名を、infra 側 (`infra/modules/cloud_run/main.tf` の `env` ブロック) と CI (`.github/workflows/ci.yml`) で参照する。リテラル文字列のコピペは避ける 3. **手順書 / README / docs**: 重複しがちな手順は `docs/` に正本を置き、README からはリンクで参照する @@ -100,7 +100,7 @@ PR 前に 1 度走らせて、新規重複が増えていないか確認する ### AI レビュー (refacter skill) -- 領域内: `BE_refacter` / `FE_refacter` / `INFRA_refacter` +- 領域内: `BE_refacter` / `WEB_refacter` / `INFRA_refacter` - 領域横断: `XR_refacter` 各 skill は `report/dupe/jscpd-*.json` を読み込んでから、「形だけ似ているのか / 本質的に重複しているのか」を判定する。 diff --git a/.claude/rules/infra/opentofu.md b/.claude/rules/infra/opentofu.md index 6cd4a5e9..3c980de7 100644 --- a/.claude/rules/infra/opentofu.md +++ b/.claude/rules/infra/opentofu.md @@ -12,7 +12,7 @@ infra/ ``` CLI: OpenTofu (`tofu`) を使用する。Nix で管理されており `nix develop` シェル内で利用可能。`.tf` の構文は Terraform と同一。`tofu init` / `plan` / `apply` の具体的な実行手順・GCS backend 認証は `docs/deployment.md`「OpenTofu」セクションを正本として参照(ここでは再掲しない)。 -デプロイ: GitHub Actions で `dev` ブランチ push 時に frontend → Cloudflare Pages、backend → Docker → Artifact Registry → Cloud Run。 +デプロイ: GitHub Actions で `dev` ブランチ push 時に web → Cloudflare Pages、backend → Docker → Artifact Registry → Cloud Run。 DB は Turso (libSQL) を使用。**DB 本体は OpenTofu の `infra/modules/turso/` で管理**(jpedroh/turso provider)。`module.turso.database_url` を cloud_run module の `turso_database_url` に渡す構成。auth token のみ state に乗せたくないため `turso CLI` で発行 → Secret Manager `-turso-auth-token` に手動投入する運用。詳細は `docs/data-model.md` の「Turso セットアップ」参照。 diff --git a/.claude/rules/infra/test.md b/.claude/rules/infra/test.md index 361d470f..a0f00a91 100644 --- a/.claude/rules/infra/test.md +++ b/.claude/rules/infra/test.md @@ -58,7 +58,7 @@ nix develop --command bash -c "tofu -chdir=infra/environments/dev plan" GitHub Actions が `dev` ブランチ push 時に自動実行: -1. frontend → Cloudflare Pages +1. web → Cloudflare Pages 2. backend → Docker → Artifact Registry → Cloud Run インフラ変更(`infra/**`)は別の GitHub Actions ワークフローまたは手動 `tofu apply` で適用する。 diff --git a/.claude/rules/security.md b/.claude/rules/security.md index 8ea388bd..eda6d3e5 100644 --- a/.claude/rules/security.md +++ b/.claude/rules/security.md @@ -1,13 +1,13 @@ --- paths: - backend/** - - frontend/** + - web/** - infra/** --- # セキュリティルール(全領域横断) -このファイルは backend / frontend / infra すべての領域に適用される横断セキュリティルール。 +このファイルは backend / web / infra すべての領域に適用される横断セキュリティルール。 認証・JWT・Cookie 属性・暗号化・Rate Limit の詳細は `.claude/rules/backend/auth-security.md` を参照し、ここでは重複させない。 --- diff --git a/.claude/rules/frontend/architecture.md b/.claude/rules/web/architecture.md similarity index 99% rename from .claude/rules/frontend/architecture.md rename to .claude/rules/web/architecture.md index aba4b5f1..3c306636 100644 --- a/.claude/rules/frontend/architecture.md +++ b/.claude/rules/web/architecture.md @@ -1,12 +1,12 @@ --- paths: - - frontend/** + - web/** --- # Frontend アーキテクチャ (React 18 + Vite + TypeScript) ``` -frontend/src/ +web/src/ ├── main.tsx # BrowserRouter + Redux Provider ラップ ├── App.tsx # 認証ステート管理 + ├── App.module.css diff --git a/.claude/rules/frontend/component-design.md b/.claude/rules/web/component-design.md similarity index 95% rename from .claude/rules/frontend/component-design.md rename to .claude/rules/web/component-design.md index 5ee0bfd8..83f6e8f8 100644 --- a/.claude/rules/frontend/component-design.md +++ b/.claude/rules/web/component-design.md @@ -1,11 +1,11 @@ --- paths: - - frontend/** + - web/** --- # Frontend コンポーネント設計ルール -`frontend/architecture.md` が「何があるか」を示すのに対し、このファイルは「コンポーネント設計の判断基準」を補完する。 +`web/architecture.md` が「何があるか」を示すのに対し、このファイルは「コンポーネント設計の判断基準」を補完する。 行数はあくまで目安(強制閾値ではない)。超過した場合に責務が複数混在していないかを確認するトリガーとして使う。 ## 行数の目安 diff --git a/.claude/rules/frontend/messages.md b/.claude/rules/web/messages.md similarity index 63% rename from .claude/rules/frontend/messages.md rename to .claude/rules/web/messages.md index 9912aeaf..b4ae1ae5 100644 --- a/.claude/rules/frontend/messages.md +++ b/.claude/rules/web/messages.md @@ -1,4 +1,4 @@ -# メッセージ管理ルール (frontend) +# メッセージ管理ルール (web) ts/tsx でユーザーに表示される文字列を**リテラルで直接書かない**。 必ず Single Source of Truth から取得すること。 @@ -7,14 +7,14 @@ ts/tsx でユーザーに表示される文字列を**リテラルで直接書 | メッセージの種類 | 正本 | 取得経路 | |---|---|---| -| **API 経由のエラー** (backend → frontend) | `backend/app/messages.json` | `AppErrorResponse.message` を `api/client.ts:buildApiError` 経由でそのまま表示 | -| **API レスポンスに message が無い時の補完** | `frontend/src/constants/errorMessages.ts` (`ERROR_CONFIG`) | `ErrorCode` を引いて補完(既存実装) | -| **frontend 完結のメッセージ** | `frontend/src/constants/messages.ts` | import して定数参照 | +| **API 経由のエラー** (backend → web) | `backend/app/messages.json` | `AppErrorResponse.message` を `api/client.ts:buildApiError` 経由でそのまま表示 | +| **API レスポンスに message が無い時の補完** | `web/src/constants/errorMessages.ts` (`ERROR_CONFIG`) | `ErrorCode` を引いて補完(既存実装) | +| **web 完結のメッセージ** | `web/src/constants/messages.ts` | import して定数参照 | -`messages.json` から frontend 用 TS 定数を build-time 生成する仕組みは**入っていない**。 +`messages.json` から web 用 TS 定数を build-time 生成する仕組みは**入っていない**。 `ERROR_CONFIG` は `backend/app/core/errors.py:ErrorCode` enum と**手動同期**する設計(型エラーで漏れを検出)。 -## frontend 完結のメッセージとは +## web 完結のメッセージとは backend を経由しない以下のような文言: @@ -24,7 +24,7 @@ backend を経由しない以下のような文言: - **JSX 直書きの UI 文言**: `ErrorBoundary` のタイトルなど - **開発者向け内部エラー**: `import_id が未設定です` のような状態管理エラー -これらは `frontend/src/constants/messages.ts` に集約する。カテゴリ別の定数: +これらは `web/src/constants/messages.ts` に集約する。カテゴリ別の定数: - `VALIDATION_MESSAGES` — 入力バリデーション - `NETWORK_MESSAGES` — ネットワーク / API クライアント層 @@ -39,24 +39,24 @@ backend を経由しない以下のような文言: 1. `backend/app/messages.json` の `error.` にキー追加 2. backend で `get_error("category.key", **kwargs)` または `raise_app_error(code=...)` で使う -3. frontend 側はとくに変更不要(`AppErrorResponse.message` が自動的に画面に出る) +3. web 側はとくに変更不要(`AppErrorResponse.message` が自動的に画面に出る) -### frontend 完結のメッセージ +### web 完結のメッセージ -1. `frontend/src/constants/messages.ts` の適切なカテゴリに定数追加 +1. `web/src/constants/messages.ts` の適切なカテゴリに定数追加 2. 使用箇所で import して参照 3. リテラルを書かない ## やってはいけないこと(再発防止対象) -以下は **ESLint または `make lint-frontend-messages` で自動検知され CI で fail する**: +以下は **ESLint または `make lint-web-messages` で自動検知され CI で fail する**: ```ts // ✗ ESLint で error throw new Error("入力してください"); throw new Error(`${field} を入力してください`); -// ✗ make lint-frontend-messages で error +// ✗ make lint-web-messages で error setError("失敗しました"); setErrorMessage("不正な値です"); setAccountError("取得に失敗"); @@ -85,17 +85,17 @@ setError(FALLBACK_MESSAGES.SAVE); ## 検証 ```bash -make lint-frontend # ESLint(no-restricted-syntax 含む) -make lint-frontend-messages # grep ベースの追加チェック +make lint-web # ESLint(no-restricted-syntax 含む) +make lint-web-messages # grep ベースの追加チェック ``` -両方を pass させることが「テスト OK」条件の前提(`.claude/rules/frontend/test.md` 参照)。 +両方を pass させることが「テスト OK」条件の前提(`.claude/rules/web/test.md` 参照)。 ## 参考 -- `frontend/src/constants/messages.ts` — frontend 完結メッセージの SSoT -- `frontend/src/constants/errorCodes.ts` / `errorMessages.ts` — backend ErrorCode 連携 +- `web/src/constants/messages.ts` — web 完結メッセージの SSoT +- `web/src/constants/errorCodes.ts` / `errorMessages.ts` — backend ErrorCode 連携 - `backend/app/messages.json` — backend のメッセージ正本 - `backend/app/core/errors.py` — ErrorCode enum -- `scripts/lint-frontend-messages.sh` — grep ベースの検知スクリプト -- `frontend/eslint.config.js` — no-restricted-syntax ルール定義 +- `scripts/lint-web-messages.sh` — grep ベースの検知スクリプト +- `web/eslint.config.js` — no-restricted-syntax ルール定義 diff --git a/.claude/rules/frontend/test.md b/.claude/rules/web/test.md similarity index 78% rename from .claude/rules/frontend/test.md rename to .claude/rules/web/test.md index aa9bcdd8..4bec7b62 100644 --- a/.claude/rules/frontend/test.md +++ b/.claude/rules/web/test.md @@ -1,6 +1,6 @@ --- paths: - - frontend/** + - web/** --- # Frontend テスト方針 @@ -22,27 +22,27 @@ paths: - 新しいページまたはルートの追加 - 認証・ナビゲーション・レイアウトコンポーネントの変更 - 通知ベル / サイドバー / `AuthenticatedLayout` の変更 -- バックエンド API の追加・変更で、frontend の UI フローに影響するもの +- バックエンド API の追加・変更で、web の UI フローに影響するもの ## 実行コマンド ```bash -make test-frontend # unit + vitest -nix develop --command bash -c "cd frontend && npm run test:e2e" # E2E(Playwright) +make test-web # unit + vitest +nix develop --command bash -c "cd web && npm run test:e2e" # E2E(Playwright) ``` 特定の vitest スイートだけ回す場合: ```bash -nix develop --command bash -c "cd frontend && npx vitest run src/hooks/useDocumentForm.test.ts" +nix develop --command bash -c "cd web && npx vitest run src/hooks/useDocumentForm.test.ts" ``` ## OK 基準(達成条件) 以下をすべて満たして初めて「テスト OK」と判定する: -1. **全 unit / vitest pass**: `make test-frontend` が exit 0 -2. **lint が pass**: `make lint-frontend` も同時に通ること -3. **build が通る**: `make build-frontend`(tsc + vite build)が通ること。TypeScript の型エラーが残っていないこと +1. **全 unit / vitest pass**: `make test-web` が exit 0 +2. **lint が pass**: `make lint-web` も同時に通ること +3. **build が通る**: `make build-web`(tsc + vite build)が通ること。TypeScript の型エラーが残っていないこと 4. **E2E トリガーに該当する場合は E2E pass**: 上記トリガーリストに該当する変更では `npm run test:e2e` を必ず実行し、全シナリオが green 5. **新規・変更コードに対応するテストが存在する**: - 新規フック → 主要分岐ごとに 1 ケース(最低 3 ケース) @@ -51,7 +51,7 @@ nix develop --command bash -c "cd frontend && npx vitest run src/hooks/useDocume ## E2E の注意点(重要 — 過去にハマった) -- **ルートモックは LIFO 登録**: キャッチオールを先、具体的モックを後(`.claude/rules/frontend/typescript.md` 参照) +- **ルートモックは LIFO 登録**: キャッチオールを先、具体的モックを後(`.claude/rules/web/typescript.md` 参照) - **`LoadingOverlay` 対策**: `waitForAuthenticatedLayout(page)` を必ず呼び出す。`position: fixed; z-index: 100` が要素クリックを邪魔する - **キャッチオールパターン**: `http://localhost:8000/**` を使う。`**/api/**` は Vite dev server のソースファイルにもマッチして壊れる diff --git a/.claude/rules/frontend/typescript.md b/.claude/rules/web/typescript.md similarity index 82% rename from .claude/rules/frontend/typescript.md rename to .claude/rules/web/typescript.md index e9a8db12..98c67e2b 100644 --- a/.claude/rules/frontend/typescript.md +++ b/.claude/rules/web/typescript.md @@ -1,23 +1,23 @@ --- paths: - - frontend/** + - web/** --- # TypeScript/React コーディング規約 - ESLint / Prettier の設定に従うこと -- リントは `make lint-frontend`、テストは `make test-frontend` を使う(Nix devshell 経由で解決される) -- 個別スクリプトを叩きたい場合は `nix develop --command bash -c "cd frontend && npm run